Right-click to Duplicate (v0.2.6.7) (#150)

* Auto-change shortname upon model settings edit

* Adding context menu to nodes on right-click

* Add BaseNode and subclass all CF nodes with BaseNode component

* Add BaseNode to CSVNode

* Move HF and Aleph up in LLM list.

* Rebuild react
This commit is contained in:
ianarawjo 2023-11-08 16:11:01 -08:00 committed by GitHub
parent 136fe70458
commit e456ce6f1c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 239 additions and 72 deletions

View File

@ -1,15 +1,15 @@
{
"files": {
"main.css": "/static/css/main.60127273.css",
"main.js": "/static/js/main.ee990fc8.js",
"main.css": "/static/css/main.8665fcca.css",
"main.js": "/static/js/main.3027e2a4.js",
"static/js/787.4c72bb55.chunk.js": "/static/js/787.4c72bb55.chunk.js",
"index.html": "/index.html",
"main.60127273.css.map": "/static/css/main.60127273.css.map",
"main.ee990fc8.js.map": "/static/js/main.ee990fc8.js.map",
"main.8665fcca.css.map": "/static/css/main.8665fcca.css.map",
"main.3027e2a4.js.map": "/static/js/main.3027e2a4.js.map",
"787.4c72bb55.chunk.js.map": "/static/js/787.4c72bb55.chunk.js.map"
},
"entrypoints": [
"static/css/main.60127273.css",
"static/js/main.ee990fc8.js"
"static/css/main.8665fcca.css",
"static/js/main.3027e2a4.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.ee990fc8.js"></script><link href="/static/css/main.60127273.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.3027e2a4.js"></script><link href="/static/css/main.8665fcca.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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

75
chainforge/react-server/src/BaseNode.js vendored Normal file
View File

@ -0,0 +1,75 @@
/**
* The base class for every node in ChainForge.
* Used to wrap common behavior like right-click context menu.
*/
import { useCallback, useMemo, useState, useRef } from "react";
import { Menu } from '@mantine/core';
import { IconCopy, IconX } from '@tabler/icons-react';
import AreYouSureModal from "./AreYouSureModal";
import useStore from './store';
export const BaseNode = ({children, classNames, nodeId, style}) => {
const removeNode = useStore((state) => state.removeNode);
const duplicateNode = useStore((state) => state.duplicateNode);
const [contextMenuStyle, setContextMenuStyle] = useState({left: -100, top:0});
const [contextMenuOpened, setContextMenuOpened] = useState(false);
// For 'delete node' confirmation popup
const deleteConfirmModal = useRef(null);
// Class styles for ChainForge nodes
const classes = useMemo(() => {
return "cfnode " + (classNames ?? "");
}, [classNames]);
// Duplicate the node
const handleDuplicateNode = useCallback(() => {
duplicateNode(nodeId, { x: 28, y: 28 });
}, [nodeId, duplicateNode]);
// Remove the node, after user confirmation dialog
const handleRemoveNode = useCallback(() => {
// Open the 'are you sure' modal:
if (deleteConfirmModal && deleteConfirmModal.current)
deleteConfirmModal.current.trigger();
}, [deleteConfirmModal]);
const handleOpenContextMenu = (e) => {
// Ignore all right-clicked elements that aren't divs:
// (for instance, textfields should still have normal right-click)
if (e.target.localName !== "div")
return;
e.preventDefault();
setContextMenuStyle({
dropdown: {
position: 'absolute',
left: e.pageX + 'px !important',
top: e.pageY + 'px !important',
boxShadow: '2px 2px 4px #ccc',
}
});
setContextMenuOpened(true);
};
// A BaseNode is just a div with "cfnode" as a class, and optional other className(s) for the specific node.
// It adds a context menu to all nodes upon right-click of the node itself (the div), to duplicate or delete the node.
return (<div className={classes} onPointerDown={() => setContextMenuOpened(false)} onContextMenu={handleOpenContextMenu} style={style}>
<AreYouSureModal ref={deleteConfirmModal}
title="Delete node"
message="Are you sure you want to delete this node? This action is irreversible."
onConfirm={() => removeNode(nodeId)} />
<Menu opened={contextMenuOpened} withinPortal={true} onChange={setContextMenuOpened} styles={contextMenuStyle}>
{children}
<Menu.Dropdown>
<Menu.Item key='duplicate' onClick={handleDuplicateNode}><IconCopy size='10pt' />&nbsp;Duplicate Node</Menu.Item>
<Menu.Item key='delete' onClick={handleRemoveNode}><IconX size='10pt' />&nbsp;Delete Node</Menu.Item>
</Menu.Dropdown>
</Menu>
</div>);
};
export default BaseNode;

View File

@ -1,6 +1,7 @@
import React, { useState } from 'react';
import useStore from './store';
import NodeLabel from './NodeLabelComponent'
import NodeLabel from './NodeLabelComponent';
import BaseNode from './BaseNode';
import { Textarea } from '@mantine/core';
/**
@ -19,7 +20,7 @@ const CommentNode = ({ data, id }) => {
};
return (
<div className="cfnode" style={{backgroundColor: '#eee'}}>
<BaseNode nodeId={id} style={{backgroundColor: '#eee'}}>
<NodeLabel title={data.title || 'Comment'}
nodeId={id}
icon={'✏️'} />
@ -31,7 +32,7 @@ const CommentNode = ({ data, id }) => {
w={'260px'}
minRows={2}
styles={{input: { border: 'none', backgroundColor: '#eee' }}} />
</div>
</BaseNode>
);
};

View File

@ -4,6 +4,7 @@ import useStore from './store';
import NodeLabel from './NodeLabelComponent'
import { IconCsv } from '@tabler/icons-react';
import { Handle } from 'reactflow';
import BaseNode from './BaseNode';
const CsvNode = ({ data, id }) => {
const setDataPropsForNode = useStore((state) => state.setDataPropsForNode);
@ -108,20 +109,19 @@ const CsvNode = ({ data, id }) => {
}, [id, data.text]);
return (
<div className="text-fields-node cfnode">
<NodeLabel title={data.title || 'CSV Node'} nodeId={id} icon={<IconCsv size="16px" />} />
{csvInput}
{contentDiv}
{countText ? countText : <></>}
<Handle
type="source"
position="right"
id="output"
className="grouped-handle"
style={{ top: "50%" }}
/>
</div>
);
<BaseNode classNames="text-fields-node" nodeId={id}>
<NodeLabel title={data.title || 'CSV Node'} nodeId={id} icon={<IconCsv size="16px" />} />
{csvInput}
{contentDiv}
{countText ? countText : <></>}
<Handle
type="source"
position="right"
id="output"
className="grouped-handle"
style={{ top: "50%" }}
/>
</BaseNode>);
};
export default CsvNode;

View File

@ -4,8 +4,9 @@ import { Button, Code, Modal, Tooltip, Box, Text } from '@mantine/core';
import { Prism } from '@mantine/prism';
import { useDisclosure } from '@mantine/hooks';
import useStore from './store';
import NodeLabel from './NodeLabelComponent'
import { IconTerminal, IconSearch, IconInfoCircle } from '@tabler/icons-react'
import BaseNode from "./BaseNode";
import NodeLabel from './NodeLabelComponent';
import { IconTerminal, IconSearch, IconInfoCircle } from '@tabler/icons-react';
import LLMResponseInspectorModal from './LLMResponseInspectorModal';
// Ace code editor
@ -255,7 +256,7 @@ const EvaluatorNode = ({ data, id }) => {
const node_header = data.title || default_header;
return (
<div className="evaluator-node cfnode">
<BaseNode classNames="evaluator-node" nodeId={id}>
<NodeLabel title={node_header}
nodeId={id}
onEdit={hideStatusIndicator}
@ -353,7 +354,7 @@ const EvaluatorNode = ({ data, id }) => {
onClick={showResponseInspector}
showNotificationDot={uninspectedResponses} />
) : <></>}
</div>
</BaseNode>
);
};

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect } from 'react';
import { Handle } from 'reactflow';
import useStore from './store';
import BaseNode from './BaseNode';
import NodeLabel from './NodeLabelComponent';
import LLMResponseInspector, { exportToExcel } from './LLMResponseInspector';
import fetch_from_backend from './fetch_from_backend';
@ -55,7 +56,7 @@ const InspectorNode = ({ data, id }) => {
}, [data, id, handleOnConnect, setDataPropsForNode]);
return (
<div className="inspector-node cfnode">
<BaseNode classNames="inspector-node" nodeId={id}>
<NodeLabel title={data.title || 'Inspect Node'}
nodeId={id}
icon={'🔍'}
@ -73,7 +74,7 @@ const InspectorNode = ({ data, id }) => {
style={{ top: "50%" }}
onConnect={handleOnConnect}
/>
</div>
</BaseNode>
);
};

View File

@ -1,6 +1,7 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Handle } from 'reactflow';
import useStore from './store';
import BaseNode from './BaseNode';
import NodeLabel from './NodeLabelComponent';
import fetch_from_backend from './fetch_from_backend';
import { IconArrowMerge, IconList } from '@tabler/icons-react';
@ -332,7 +333,7 @@ const JoinNode = ({ data, id }) => {
}, [data, id, handleOnConnect, setDataPropsForNode]);
return (
<div className="join-node cfnode">
<BaseNode classNames="join-node" nodeId={id}>
<NodeLabel title={data.title || 'Join Node'}
nodeId={id}
icon={<IconArrowMerge size='14pt'/>}
@ -389,7 +390,7 @@ const JoinNode = ({ data, id }) => {
className="grouped-handle"
style={{ top: "50%" }}
/>
</div>);
</BaseNode>);
};
export default JoinNode;

View File

@ -4,6 +4,7 @@ import { Alert, Progress, Textarea } from '@mantine/core';
import { IconAlertTriangle, IconRobot, IconSearch } from "@tabler/icons-react";
import { v4 as uuid } from 'uuid';
import useStore from './store';
import BaseNode from './BaseNode';
import NodeLabel from './NodeLabelComponent';
import fetch_from_backend from './fetch_from_backend';
import { getDefaultModelSettings } from './ModelSettingSchemas';
@ -139,7 +140,7 @@ const LLMEvaluatorNode = ({ data, id }) => {
}, [data]);
return (
<div className="evaluator-node cfnode">
<BaseNode classNames="evaluator-node" nodeId={id}>
<NodeLabel title={data.title || 'LLM Scorer'}
nodeId={id}
icon={<IconRobot size="16px" />}
@ -200,7 +201,7 @@ const LLMEvaluatorNode = ({ data, id }) => {
onClick={showResponseInspector}
showNotificationDot={uninspectedResponses}
/>) : <></>}
</div>
</BaseNode>
);
};

View File

@ -342,7 +342,11 @@ const PaLM2Settings = {
"title": "Model",
"description": "Select a PaLM model to query. For more details on the differences, see the Google PaLM API documentation.",
"enum": ["text-bison-001", "chat-bison-001"],
"default": "chat-bison-001"
"default": "chat-bison-001",
"shortname_map": {
"text-bison-001": "PaLM2-text",
"chat-bison-001": "PaLM2-chat",
}
},
"temperature": {
"type": "number",
@ -614,9 +618,19 @@ const HuggingFaceTextInferenceSettings = {
"model": {
"type": "string",
"title": "Model",
"description": "Select a suggested HuggingFace-hosted model to query using the Inference API. For more details, check out https://huggingface.co/inference-api",
"enum": ["mistralai/Mistral-7B-Instruct-v0.1", "HuggingFaceH4/zephyr-7b-beta", "tiiuae/falcon-7b-instruct", "microsoft/DialoGPT-large", "bigscience/bloom-560m", "gpt2", "bigcode/santacoder", "bigcode/starcoder", "Other (HuggingFace)"],
"default": "tiiuae/falcon-7b-instruct",
"description": "Select a suggested HuggingFace-hosted model to query using the Inference API. For more details, check out https://huggingface.co/inference-api",
"enum": ["mistralai/Mistral-7B-Instruct-v0.1", "HuggingFaceH4/zephyr-7b-beta", "tiiuae/falcon-7b-instruct", "microsoft/DialoGPT-large", "bigscience/bloom-560m", "gpt2", "bigcode/santacoder", "bigcode/starcoder", "Other (HuggingFace)"],
"default": "tiiuae/falcon-7b-instruct",
"shortname_map": {
"mistralai/Mistral-7B-Instruct-v0.1": "Mistral-7B",
"HuggingFaceH4/zephyr-7b-beta": "Zephyr-7B",
"tiiuae/falcon-7b-instruct": "Falcon-7B",
"microsoft/DialoGPT-large": "DialoGPT",
"bigscience/bloom-560m": "Bloom560M",
"gpt2": "GPT-2",
"bigcode/santacoder": "santacoder",
"bigcode/starcoder": "starcoder"
}
},
"custom_model": {
"type": "string",
@ -769,6 +783,14 @@ const AlephAlphaLuminousSettings = {
"luminous-supreme-control",
],
default: "luminous-base",
shortname_map: {
"luminous-extended": "luminous-ext",
"luminous-extended-control": "luminous-ext-ctrl",
"luminous-base-control": "luminous-base-ctrl",
"luminous-base": "luminous-base",
"luminous-supreme": "luminous-supr",
"luminous-supreme-control": "luminous-supr-ctrl",
}
},
temperature: {
type: "number",
@ -1075,7 +1097,7 @@ export const getTemperatureSpecForModel = (modelName) => {
};
export const postProcessFormData = (settingsSpec, formData) => {
// Strip all 'model' and 'shortname' props in the submitted form, as these are passed elsewhere or unecessary for the backend
// Strip all 'model' and 'shortname' props in the submitted form, as these are passed elsewhere or unnecessary for the backend
const skip_keys = {'model': true, 'shortname': true};
let new_data = {};

View File

@ -19,11 +19,14 @@ const ModelSettingsModal = forwardRef((props, ref) => {
const [schema, setSchema] = useState({'type': 'object', 'description': 'No model info object was passed to settings modal.'});
const [uiSchema, setUISchema] = useState({});
const [modelName, setModelName] = useState("(unknown)");
const [baseModelName, setBaseModelName] = useState("(unknown)");
const [initShortname, setInitShortname] = useState(undefined);
const [initModelName, setInitModelName] = useState(undefined);
// Totally necessary emoji picker
const [modelEmoji, setModelEmoji] = useState('');
const [emojiPickerOpen, setEmojiPickerOpen] = useState(false);
const [emojiPickerOpen, setEmojiPickerOpen] = useState(null);
useEffect(() => {
if (props.model && props.model.base_model) {
@ -31,22 +34,26 @@ const ModelSettingsModal = forwardRef((props, ref) => {
if (!(props.model.base_model in ModelSettings)) {
setSchema({'type': 'object', 'description': `Did not find settings schema for base model ${props.model.base_model}. Maybe you are missing importing a custom provider script?`});
setUISchema({});
setModelName(props.model.base_model);
setBaseModelName(props.model.base_model);
return;
}
const settingsSpec = ModelSettings[props.model.base_model];
const schema = settingsSpec.schema;
setSchema(schema);
setUISchema(settingsSpec.uiSchema);
setModelName(settingsSpec.fullName);
setBaseModelName(settingsSpec.fullName);
if (props.model.formData) {
setFormData(props.model.formData);
setInitShortname(props.model.formData.shortname);
setInitModelName(props.model.formData.model);
} else {
// Create settings from schema
let default_settings = {};
Object.keys(schema.properties).forEach(key => {
default_settings[key] = 'default' in schema.properties[key] ? schema.properties[key]['default'] : undefined;
});
setInitShortname(default_settings.shortname);
setInitModelName(default_settings.model);
setFormData(getDefaultModelFormData(settingsSpec));
}
}
@ -58,7 +65,6 @@ const ModelSettingsModal = forwardRef((props, ref) => {
}, [props.model]);
const saveFormState = useCallback((fdata) => {
// For some reason react-json-form-schema returns 'undefined' on empty strings.
// We need to (1) detect undefined values for keys in formData and (2) if they are of type string, replace with "",
// if that property is marked with a special "allow_empty_str" property.
@ -82,9 +88,24 @@ const ModelSettingsModal = forwardRef((props, ref) => {
saveFormState(submitInfo.formData);
}, [saveFormState]);
// On every edit to the form...
const onFormDataChange = (state) => {
if (state && state.formData)
if (state && state.formData) {
// This checks if the model name has changed, but the shortname wasn't edited (in this window).
// In this case, we auto-change the shortname, to save user's time and nickname models appropriately.
if (state.formData.shortname === initShortname && state.formData.model !== initModelName) {
const shortname_map = schema.properties?.model?.shortname_map;
if (shortname_map && state.formData.model in shortname_map)
state.formData.shortname = shortname_map[state.formData.model];
else
state.formData.shortname = state.formData.model;
setInitShortname(state.formData.shortname);
setInitModelName(state.formData.model);
}
setFormData(state.formData);
}
};
const onClickSubmit = useCallback(() => {
@ -103,7 +124,7 @@ const ModelSettingsModal = forwardRef((props, ref) => {
// This gives the parent access to triggering the modal
const trigger = useCallback(() => {
open();
}, [schema, uiSchema, modelName, open]);
}, [schema, uiSchema, baseModelName, open]);
useImperativeHandle(ref, () => ({
trigger,
}));
@ -118,10 +139,9 @@ return (
<Popover.Dropdown>
<Picker data={emojidata} onEmojiSelect={onEmojiSelect} theme="light" />
</Popover.Dropdown>
</Popover><span>{`Model Settings: ${modelName}`}</span>
</Popover><span>{`Model Settings: ${baseModelName}`}</span>
</div>
} closeOnClickOutside={false} style={{position: 'relative', 'left': '-5%'}}>
<Form schema={schema} uiSchema={uiSchema} formData={formData} validator={validator} onChange={onFormDataChange} onSubmit={onSubmit} style={{width: '100%'}}>
<Button title='Submit' onClick={onClickSubmit} style={{float: 'right', marginRight: '30px'}}>Submit</Button>
<div style={{height: '50px'}}></div>

View File

@ -90,6 +90,5 @@ export default function NodeLabel({ title, nodeId, icon, onEdit, onSave, editabl
</div>
{/* <button className="AmitSahoo45-button-3 nodrag" onClick={handleRunClick}><div className="play-button"></div></button> */}
</div>
</>);
}

View File

@ -4,7 +4,8 @@ import { Switch, Progress, Textarea, Text, Popover, Center, Modal, Box, Tooltip
import { useDisclosure } from '@mantine/hooks';
import { IconList } from '@tabler/icons-react';
import useStore from './store';
import NodeLabel from './NodeLabelComponent'
import BaseNode from './BaseNode';
import NodeLabel from './NodeLabelComponent';
import TemplateHooks, { extractBracketedSubstrings } from './TemplateHooksComponent'
import { LLMListContainer } from './LLMListComponent'
import LLMResponseInspectorModal from './LLMResponseInspectorModal';
@ -640,7 +641,7 @@ const PromptNode = ({ data, id, type: node_type }) => {
}, [textAreaRef]);
return (
<div className="prompt-node cfnode">
<BaseNode classNames="prompt-node" nodeId={id}>
<NodeLabel title={data.title || node_default_title}
nodeId={id}
onEdit={hideStatusIndicator}
@ -741,7 +742,7 @@ const PromptNode = ({ data, id, type: node_type }) => {
) : <></>
}
</div>
</div>
</BaseNode>
);
};

View File

@ -1,5 +1,6 @@
import React, { useState, useEffect, useCallback } from 'react';
import useStore from './store';
import BaseNode from './BaseNode';
import NodeLabel from './NodeLabelComponent';
import { IconSettingsAutomation } from '@tabler/icons-react';
@ -66,7 +67,7 @@ const ScriptNode = ({ data, id }) => {
}, [data, id, setDataPropsForNode]);
return (
<div className="script-node cfnode">
<BaseNode classNames="script-node" nodeId={id}>
<NodeLabel title={data.title || 'Global Python Scripts'} nodeId={id} editable={false} icon={<IconSettingsAutomation size="16px" />}/>
<label htmlFor="num-generations" style={{fontSize: '10pt'}}>Enter folder paths for external modules you wish to import.</label> <br/><br/>
<div>
@ -75,7 +76,7 @@ const ScriptNode = ({ data, id }) => {
<div className="add-text-field-btn">
<button onClick={handleAddField}>+</button>
</div>
</div>
</BaseNode>
);
};

View File

@ -2,6 +2,7 @@ import { useState, useCallback, useEffect, useRef } from "react";
import { Handle } from "reactflow";
import { NativeSelect, TextInput, Flex, Text, Box, Select, ActionIcon, Menu, Tooltip } from "@mantine/core";
import { IconCaretDown, IconHash, IconRuler2, IconSearch, IconX } from "@tabler/icons-react";
import BaseNode from "./BaseNode";
import NodeLabel from "./NodeLabelComponent";
import InspectFooter from "./InspectFooter";
import LLMResponseInspectorModal from "./LLMResponseInspectorModal";
@ -196,7 +197,7 @@ const SimpleEvalNode = ({data, id}) => {
}, [data]);
return (
<div className="evaluator-node cfnode">
<BaseNode classNames="evaluator-node" nodeId={id}>
<NodeLabel title={data.title || 'Simple Evaluator'}
nodeId={id}
icon={<IconRuler2 size="16px" />}
@ -295,7 +296,7 @@ const SimpleEvalNode = ({data, id}) => {
onClick={showResponseInspector}
showNotificationDot={uninspectedResponses}
/>) : <></>}
</div>
</BaseNode>
);
};

View File

@ -6,6 +6,7 @@ import Papa from 'papaparse';
import { v4 as uuidv4 } from 'uuid';
import { IconX, IconArrowBarToUp, IconArrowBarToDown } from '@tabler/icons-react';
import TemplateHooks from './TemplateHooksComponent';
import BaseNode from './BaseNode';
import NodeLabel from './NodeLabelComponent';
import AlertModal from './AlertModal';
import RenameValueModal from './RenameValueModal';
@ -389,7 +390,7 @@ const TabularDataNode = ({ data, id }) => {
}, [ref]);
return (
<div className="tabular-data-node cfnode" onPointerDown={() => setContextMenuOpened(false)}>
<BaseNode classNames="tabular-data-node" nodeId={id} onPointerDown={() => setContextMenuOpened(false)}>
<NodeLabel title={data.title || 'Tabular Data Node'}
nodeId={id}
icon={'🗂️'}
@ -434,7 +435,7 @@ const TabularDataNode = ({ data, id }) => {
<TemplateHooks vars={tableColumns.map(col => col.header)} nodeId={id} startY={hooksY} position='right' />
</div>
</div>
</BaseNode>
);
};

View File

@ -5,6 +5,7 @@ import { IconTextPlus, IconEye, IconEyeOff } from '@tabler/icons-react';
import useStore from './store';
import NodeLabel from './NodeLabelComponent';
import TemplateHooks, { extractBracketedSubstrings } from './TemplateHooksComponent';
import BaseNode from './BaseNode';
// Helper funcs
const union = (setA, setB) => {
@ -164,7 +165,7 @@ const TextFieldsNode = ({ data, id }) => {
}, [data, id, pingOutputNodes]);
return (
<div className="text-fields-node cfnode">
<BaseNode classNames="text-fields-node" nodeId={id}>
<NodeLabel title={data.title || 'TextFields Node'} nodeId={id} icon={<IconTextPlus size="16px" />} />
<div ref={setRef}>
{Object.keys(textfieldsValues).map(i => (
@ -205,7 +206,7 @@ const TextFieldsNode = ({ data, id }) => {
<div className="add-text-field-btn">
<button onClick={handleAddField}>+</button>
</div>
</div>
</BaseNode>
);
};

View File

@ -3,6 +3,7 @@ import { Handle } from 'reactflow';
import { NativeSelect } from '@mantine/core';
import useStore, { colorPalettes } from './store';
import Plot from 'react-plotly.js';
import BaseNode from './BaseNode';
import NodeLabel from './NodeLabelComponent';
import PlotLegend from './PlotLegend';
import fetch_from_backend from './fetch_from_backend';
@ -728,7 +729,7 @@ const VisNode = ({ data, id }) => {
}, [plotDivRef, plotlySpec]);
return (
<div className="vis-node cfnode">
<BaseNode classNames="vis-node" nodeId={id}>
<NodeLabel title={data.title || 'Vis Node'}
nodeId={id}
status={status}
@ -786,7 +787,7 @@ const VisNode = ({ data, id }) => {
style={{ top: '50%' }}
onConnect={handleOnConnect}
/>
</div>
</BaseNode>
);
};

View File

@ -33,9 +33,9 @@ export let initLLMProviders = [
{ name: "GPT4", emoji: "🥵", model: "gpt-4", base_model: "gpt-4", temp: 1.0 },
{ name: "Claude", emoji: "📚", model: "claude-2", base_model: "claude-v1", temp: 0.5 },
{ name: "PaLM2", emoji: "🦬", model: "chat-bison-001", base_model: "palm2-bison", temp: 0.7 },
{ name: "Azure OpenAI", emoji: "🔷", model: "azure-openai", base_model: "azure-openai", temp: 1.0 },
{ name: "HuggingFace", emoji: "🤗", model: "tiiuae/falcon-7b-instruct", base_model: "hf", temp: 1.0 },
{ name: "Aleph Alpha", emoji: "💡", model: "luminous-base", base_model: "luminous-base", temp: 0.0 }
{ name: "Aleph Alpha", emoji: "💡", model: "luminous-base", base_model: "luminous-base", temp: 0.0 },
{ name: "Azure OpenAI", emoji: "🔷", model: "azure-openai", base_model: "azure-openai", temp: 1.0 },
];
if (APP_IS_RUNNING_LOCALLY()) {
initLLMProviders.push({ name: "Dalai (Alpaca.7B)", emoji: "🦙", model: "alpaca.7B", base_model: "dalai", temp: 0.5 });
@ -270,6 +270,12 @@ const useStore = create((set, get) => ({
},
getNode: (id) => get().nodes.find(n => n.id === id),
addNode: (newnode) => {
// Make sure we select the added node.
// This will float it to the top.
get().deselectAllNodes();
newnode.selected = true;
// Add the node to the internal state
set({
nodes: get().nodes.concat(newnode)
});
@ -279,6 +285,37 @@ const useStore = create((set, get) => ({
nodes: get().nodes.filter(n => n.id !== id)
});
},
deselectAllNodes: () => {
// Deselect all nodes
set({
nodes: get().nodes.map((n) => {
n.selected = false;
return n;
})
});
},
duplicateNode: (id, offset) => {
const nodes = get().nodes;
const node = nodes.find(n => n.id === id);
if (!node) {
console.error(`Could not duplicate node: No node found with id ${id}`);
return undefined;
}
// Deep copy node data
let dup = JSON.parse(JSON.stringify(node));
// Shift position
dup.position.x += offset && offset.x !== undefined ? offset.x : 0;
dup.position.y += offset && offset.y !== undefined ? offset.y : 0;
// Change id to new unique id
dup.id = `${dup.type}-${Date.now()}`;
// Select it (floats it to top)
dup.selected = true;
// Deselect all previous nodes
get().deselectAllNodes();
// Declare new node with copied data, at the shifted position
get().addNode(dup);
return dup;
},
setNodes: (newnodes) => {
set({
nodes: newnodes

View File

@ -171,6 +171,9 @@
margin-bottom: 12px;
padding-bottom: 4px;
}
.node-header:hover {
border-bottom-color: #888;
}
.prompt-node {
background-color: #fff;

View File

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