Add a Split Node (#167)

* Add a split node

* Repair damage to model settings schema

* cleanup

* Fix bug w warning msg in Split node

* Bug fix removing LLM meta key

* Rebuild react and package

* Add markdown parser package dependency
This commit is contained in:
ianarawjo 2023-11-19 10:59:37 -05:00 committed by GitHub
parent 5319f8b5a1
commit f7be853554
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 393 additions and 45 deletions

View File

@ -1,15 +1,15 @@
{
"files": {
"main.css": "/static/css/main.8665fcca.css",
"main.js": "/static/js/main.74fd3890.js",
"main.js": "/static/js/main.358435c9.js",
"static/js/787.4c72bb55.chunk.js": "/static/js/787.4c72bb55.chunk.js",
"index.html": "/index.html",
"main.8665fcca.css.map": "/static/css/main.8665fcca.css.map",
"main.74fd3890.js.map": "/static/js/main.74fd3890.js.map",
"main.358435c9.js.map": "/static/js/main.358435c9.js.map",
"787.4c72bb55.chunk.js.map": "/static/js/787.4c72bb55.chunk.js.map"
},
"entrypoints": [
"static/css/main.8665fcca.css",
"static/js/main.74fd3890.js"
"static/js/main.358435c9.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.74fd3890.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>
<!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.358435c9.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>

View File

@ -61,6 +61,7 @@
"mantine-react-table": "^1.0.0-beta.8",
"markdown-it": "^13.0.1",
"mathjs": "^11.8.2",
"mdast-util-from-markdown": "^2.0.0",
"net": "^1.0.2",
"net-browserify": "^0.2.4",
"node-fetch": "^2.6.11",

View File

@ -8,7 +8,7 @@ import ReactFlow, {
} from 'reactflow';
import { Button, Menu, LoadingOverlay, Text, Box, List, Loader, Tooltip } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { IconSettings, IconTextPlus, IconTerminal, IconCsv, IconSettingsAutomation, IconFileSymlink, IconRobot, IconRuler2, IconArrowMerge } from '@tabler/icons-react';
import { IconSettings, IconTextPlus, IconTerminal, IconCsv, IconSettingsAutomation, IconFileSymlink, IconRobot, IconRuler2, IconArrowMerge, IconArrowsSplit } from '@tabler/icons-react';
import RemoveEdge from './RemoveEdge';
import TextFieldsNode from './TextFieldsNode'; // Import a custom node
import PromptNode from './PromptNode';
@ -20,6 +20,7 @@ import AlertModal from './AlertModal';
import CsvNode from './CsvNode';
import TabularDataNode from './TabularDataNode';
import JoinNode from './JoinNode';
import SplitNode from './SplitNode';
import CommentNode from './CommentNode';
import GlobalSettingsModal from './GlobalSettingsModal';
import ExampleFlowsModal from './ExampleFlowsModal';
@ -89,6 +90,7 @@ const nodeTypes = {
table: TabularDataNode,
comment: CommentNode,
join: JoinNode,
split: SplitNode,
};
const edgeTypes = {
@ -232,6 +234,10 @@ const App = () => {
const { x, y } = getViewportCenter();
addNode({ id: 'join-'+Date.now(), type: 'join', data: {}, position: {x: x-200, y:y-100} });
};
const addSplitNode = () => {
const { x, y } = getViewportCenter();
addNode({ id: 'split-'+Date.now(), type: 'split', data: {}, position: {x: x-200, y:y-100} });
};
const onClickExamples = () => {
if (examplesModal && examplesModal.current)
@ -725,11 +731,12 @@ const App = () => {
<div id="custom-controls" style={{position: 'fixed', left: '10px', top: '10px', zIndex:8}}>
<Menu transitionProps={{ transition: 'pop-top-left' }}
position="top-start"
width={220}
closeOnClickOutside={true}
closeOnEscape
>
position="top-start"
width={220}
closeOnClickOutside={true}
closeOnEscape
styles={{item: { maxHeight: '28px' }}}
>
<Menu.Target>
<Button size="sm" variant="gradient" compact mr='sm'>Add Node +</Button>
</Menu.Target>
@ -779,6 +786,9 @@ const App = () => {
<MenuTooltip label="Concatenate responses or input data together before passing into later nodes, within or across variables and LLMs.">
<Menu.Item onClick={addJoinNode} icon={<IconArrowMerge size='14pt' />}> Join Node </Menu.Item>
</MenuTooltip>
<MenuTooltip label="Split responses or input data by some format. For instance, you can split a markdown list into separate items.">
<Menu.Item onClick={addSplitNode} icon={<IconArrowsSplit size='14pt' />}> Split Node </Menu.Item>
</MenuTooltip>
<Menu.Divider />
<Menu.Label>Misc</Menu.Label>
<MenuTooltip label="Make a comment about your flow.">

View File

@ -5,6 +5,7 @@ import NodeLabel from './NodeLabelComponent'
import { IconCsv } from '@tabler/icons-react';
import { Handle } from 'reactflow';
import BaseNode from './BaseNode';
import { processCSV } from "./backend/utils"
const CsvNode = ({ data, id }) => {
const setDataPropsForNode = useStore((state) => state.setDataPropsForNode);
@ -21,20 +22,10 @@ const CsvNode = ({ data, id }) => {
}
}, []);
const processCsv = (csv) => {
var matches = csv.match(/(\s*"[^"]+"\s*|\s*[^,]+|,)(?=,|$)/g);
if (!matches) return;
for (var n = 0; n < matches.length; ++n) {
matches[n] = matches[n].trim();
if (matches[n] == ',') matches[n] = '';
}
return matches.map(e => e.trim()).filter(e => e.length > 0);
}
// Handle a change in a text fields' input.
const handleInputChange = useCallback((event) => {
// Update the data for this text fields' id.
let new_data = { 'text': event.target.value, 'fields': processCsv(event.target.value) };
let new_data = { 'text': event.target.value, 'fields': processCSV(event.target.value) };
setDataPropsForNode(id, new_data);
pingOutputNodes(id);
}, [id, pingOutputNodes, setDataPropsForNode]);

View File

@ -17,19 +17,19 @@ const formattingOptions = [
{value: "[]", label:'["list", "of", "strings"]'}
];
const joinTexts = (texts, formatting) => {
const joinTexts = (texts, format) => {
const escaped_texts = texts.map(t => escapeBraces(t));
if (formatting === "\n\n" || formatting === "\n")
return escaped_texts.join(formatting);
else if (formatting === "-")
if (format === "\n\n" || format === "\n")
return escaped_texts.join(format);
else if (format === "-")
return escaped_texts.map((t) => ('- ' + t)).join("\n");
else if (formatting === "1.")
else if (format === "1.")
return escaped_texts.map((t, i) => (`${i+1}. ${t}`)).join("\n");
else if (formatting === '[]')
else if (format === '[]')
return JSON.stringify(escaped_texts);
console.error(`Could not join: Unknown formatting option: ${formatting}`);
console.error(`Could not join: Unknown formatting option: ${format}`);
return escaped_texts;
};
@ -72,7 +72,7 @@ const tagMetadataWithLLM = (input_data) => {
};
const extractLLMLookup = (input_data) => {
let llm_lookup = {};
Object.entries(input_data).forEach(([varname, resp_objs]) => {
Object.values(input_data).forEach((resp_objs) => {
resp_objs.forEach(r => {
if (typeof r === 'string' || !r?.llm?.key || r.llm.key in llm_lookup) return;
llm_lookup[r.llm.key] = r.llm;
@ -84,16 +84,17 @@ const removeLLMTagFromMetadata = (metavars) => {
if (!('__LLM_key' in metavars))
return metavars;
let mcopy = JSON.parse(JSON.stringify(metavars));
delete metavars['__LLM_key'];
delete mcopy['__LLM_key'];
return mcopy;
};
const truncStr = (s, maxLen) => {
if (s.length > maxLen) // Cut the name short if it's long
return s.substring(0, maxLen) + '...'
return s.substring(0, maxLen) + '...';
else
return s;
};
const groupResponsesBy = (responses, keyFunc) => {
let responses_by_key = {};
let unspecified_group = [];
@ -192,7 +193,7 @@ const JoinNode = ({ data, id }) => {
const handleOnConnect = useCallback(() => {
let input_data = pullInputData(["__input"], id);
if (!input_data?.__input) {
console.warn('Join Node: No input data detected.');
// soft fail
return;
}
@ -336,7 +337,7 @@ const JoinNode = ({ data, id }) => {
<BaseNode classNames="join-node" nodeId={id}>
<NodeLabel title={data.title || 'Join Node'}
nodeId={id}
icon={<IconArrowMerge size='14pt'/>}
icon={<IconArrowMerge size='12pt'/>}
customButtons={[
<JoinedTextsPopover key='joined-text-previews' textInfos={joinedTexts} onHover={handleOnConnect} onClick={openInfoModal} getColorForLLM={getColorForLLMAndSetIfNotFound} />
]} />

View File

@ -364,7 +364,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",
@ -636,9 +640,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": ["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",
@ -775,7 +789,7 @@ const AlephAlphaLuminousSettings = {
title: "Nickname",
description:
"Unique identifier to appear in ChainForge. Keep it short.",
default: "AA-Luminous",
default: "Luminous",
},
model: {
type: "string",
@ -791,6 +805,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",

313
chainforge/react-server/src/SplitNode.js vendored Normal file
View File

@ -0,0 +1,313 @@
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, IconArrowsSplit, IconList } from '@tabler/icons-react';
import { Divider, NativeSelect, Text, Popover, Tooltip, Center, Modal, Box } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { escapeBraces } from './backend/template';
import { processCSV } from "./backend/utils";
import { fromMarkdown } from "mdast-util-from-markdown";
const formattingOptions = [
{value: "list", label:"- list items"},
{value: "\n", label:"newline \\n"},
{value: "\n\n", label:"double newline \\n\\n"},
{value: ",", label:"commas (,)"},
{value: "code", label:"code blocks"},
{value: "paragraph", label:"paragraphs (md)"},
];
const deepcopy = (v) => JSON.parse(JSON.stringify(v));
const deepcopy_and_modify = (v, new_val_dict) => {
let new_v = deepcopy(v);
Object.entries(new_val_dict).forEach(([key, val]) => {
new_v[key] = val;
});
return new_v;
};
const excluding_key = (d, key) => {
if (!(key in d)) return d;
const copy_d = {...d};
delete copy_d[key];
return copy_d;
};
const truncStr = (s, maxLen) => {
if (s.length > maxLen) // Cut the name short if it's long
return s.substring(0, maxLen) + '...'
else
return s;
};
const tagMetadataWithLLM = (input_data) => {
let new_data = {};
Object.entries(input_data).forEach(([varname, resp_objs]) => {
new_data[varname] = resp_objs.map(r => {
if (!r || typeof r === 'string' || !r?.llm?.key) return r;
let r_copy = JSON.parse(JSON.stringify(r));
r_copy.metavars["__LLM_key"] = r.llm.key;
return r_copy;
});
});
return new_data;
};
const extractLLMLookup = (input_data) => {
let llm_lookup = {};
Object.entries(input_data).forEach(([varname, resp_objs]) => {
resp_objs.forEach(r => {
if (typeof r === 'string' || !r?.llm?.key || r.llm.key in llm_lookup) return;
llm_lookup[r.llm.key] = r.llm;
});
});
return llm_lookup;
};
const removeLLMTagFromMetadata = (metavars) => {
if (!('__LLM_key' in metavars))
return metavars;
let mcopy = JSON.parse(JSON.stringify(metavars));
delete mcopy['__LLM_key'];
return mcopy;
};
/** Flattens markdown AST as dict to text (string) */
function compileTextFromMdAST(md) {
if (md?.value !== undefined)
return md.value ?? "";
else if (md?.children?.length > 0)
return md.children.map(compileTextFromMdAST).join("");
return "";
}
const splitText = (s, format) => {
// If format is newline separators, we can just split:
if (format === "\n\n" || format === "\n")
return s.split(format).map(s => escapeBraces(s.trim())).filter(s => s.length > 0);
else if (format === ',')
return processCSV(s).map(s => escapeBraces(s)).filter(s => s.length > 0);
// Other formatting rules require markdown parsing:
// Parse string as markdown
const md = fromMarkdown(s);
let results = [];
const extract_md_blocks = (block_type) => {
if (md?.children.length > 0 && md.children.some(c => c.type === block_type)) {
// Find the relevant block(s) that appear in the markdown text, at the root level:
const md_blocks = md.children.filter(c => c.type === block_type);
for (const md_block of md_blocks) {
if (block_type === 'list') {
// Extract the list items, flattening the ASTs to text
const items = "children" in md_block ? md_block.children : [];
for (const item of items) {
const text = compileTextFromMdAST(item).trim();
results.push(text);
}
} else if (md_block?.children !== undefined) {
results.push(compileTextFromMdAST(md_block).trim());
}
if (md_block.value !== undefined)
results.push(md_block.value);
}
}
};
extract_md_blocks(format);
results = results.filter(s => s.length > 0).map(escapeBraces);
// NOTE: It is possible to have an empty [] results after split.
// This happens if the splitter is a markdown separator, and none were found in the input(s).
return results;
};
const displaySplitTexts = (textInfos, getColorForLLM) => {
const color_for_llm = (llm) => (getColorForLLM(llm) + '99');
return textInfos.map((info, idx) => {
const vars = info.fill_history;
let var_tags = vars === undefined ? [] : Object.keys(vars).map((varname) => {
const v = truncStr(vars[varname].trim(), 72);
return (<div key={varname} className="response-var-inline">
<span className="response-var-name">{varname}&nbsp;=&nbsp;</span><span className="response-var-value">{v}</span>
</div>);
});
const ps = (<pre className='small-response'>{info.text ?? info}</pre>);
return (
<div key={"r"+idx} className="response-box" style={{ backgroundColor: (info.llm ? color_for_llm(info.llm?.name) : '#ddd'), width: `100%`}}>
<div className="response-var-inline-container">
{var_tags}
</div>
{info.llm === undefined ?
ps
: (<div className="response-item-llm-name-wrapper">
<h1>{info.llm?.name}</h1>
{ps}
</div>)
}
</div>
);
});
};
const SplitTextsPopover = ({ textInfos, onHover, onClick, getColorForLLM }) => {
const [opened, { close, open }] = useDisclosure(false);
const _onHover = useCallback(() => {
onHover();
open();
}, [onHover, open]);
return (
<Popover position="right-start" withArrow withinPortal shadow="rgb(38, 57, 77) 0px 10px 30px -14px" key="query-info" opened={opened} styles={{dropdown: {maxHeight: '500px', maxWidth: '400px', overflowY: 'auto', backgroundColor: '#fff'}}}>
<Popover.Target>
<Tooltip label='Click to view all split inputs' withArrow>
<button className='custom-button' onMouseEnter={_onHover} onMouseLeave={close} onClick={onClick} style={{border:'none'}}>
<IconList size='12pt' color='gray' style={{marginBottom: '-4px'}} />
</button>
</Tooltip>
</Popover.Target>
<Popover.Dropdown sx={{ pointerEvents: 'none' }}>
<Center><Text size='xs' fw={500} color='#666'>Preview of split inputs ({textInfos?.length} total)</Text></Center>
{displaySplitTexts(textInfos, getColorForLLM)}
</Popover.Dropdown>
</Popover>
);
};
const SplitNode = ({ data, id }) => {
const [splitTexts, setSplitTexts] = useState([]);
// For an info pop-up that previews all the joined inputs
const [infoModalOpened, { open: openInfoModal, close: closeInfoModal }] = useDisclosure(false);
const [pastInputs, setPastInputs] = useState([]);
const pullInputData = useStore((state) => state.pullInputData);
const setDataPropsForNode = useStore((state) => state.setDataPropsForNode);
// Global lookup for what color to use per LLM
const getColorForLLMAndSetIfNotFound = useStore((state) => state.getColorForLLMAndSetIfNotFound);
const [splitOnFormat, setSplitOnFormat] = useState(data.splitFormat || 'list');
const handleOnConnect = useCallback(() => {
const formatting = splitOnFormat;
let input_data = pullInputData(["__input"], id);
if (!input_data?.__input) {
// soft fail if no inputs detected
return;
}
// Create lookup table for LLMs in input, indexed by llm key
const llm_lookup = extractLLMLookup(input_data);
// Tag all response objects in the input data with a metavar for their LLM (using the llm key as a uid)
input_data = tagMetadataWithLLM(input_data);
// Generate (flatten) the inputs, which could be recursively chained templates
// and a mix of LLM resp objects, templates, and strings.
// (We tagged each object with its LLM key so that we can use built-in features to keep track of the LLM associated with each response object)
fetch_from_backend('generatePrompts', {
prompt: "{__input}",
vars: input_data,
}).then(promptTemplates => {
// Convert the templates into response objects
let resp_objs = promptTemplates.map(p => ({
text: p.toString(),
fill_history: excluding_key(p.fill_history, "__input"),
llm: "__LLM_key" in p.metavars ? llm_lookup[p.metavars['__LLM_key']] : undefined,
metavars: removeLLMTagFromMetadata(p.metavars),
}));
// The naive splitter is just to look at every
// response object's text value, and split that into N objects
// that have the exact same properties except for their text values.
const split_objs = resp_objs.map(resp_obj => {
if (typeof resp_obj === "string") return splitText(resp_obj, formatting);
const texts = splitText(resp_obj?.text, formatting);
if (texts !== undefined && texts.length >= 1)
return texts.map(t => deepcopy_and_modify(resp_obj, {text: t}));
else if (texts?.length === 0)
return [];
else
return deepcopy(resp_obj);
}).flat(); // flatten the split response objects
setSplitTexts(split_objs);
setDataPropsForNode(id, { fields: split_objs });
});
}, [pullInputData, splitOnFormat, splitText, extractLLMLookup, tagMetadataWithLLM]);
if (data.input) {
// If there's a change in inputs...
if (data.input !== pastInputs) {
setPastInputs(data.input);
handleOnConnect();
}
}
// Refresh split output anytime the dropdown changes
useEffect(() => {
handleOnConnect();
}, [splitOnFormat])
useEffect(() => {
if (data.refresh && data.refresh === true) {
// Recreate the visualization:
setDataPropsForNode(id, { refresh: false });
handleOnConnect();
}
}, [data, id, handleOnConnect, setDataPropsForNode]);
return (
<BaseNode classNames="split-node" nodeId={id}>
<NodeLabel title={data.title || 'Split Node'}
nodeId={id}
icon={<IconArrowsSplit size='12pt'/>}
customButtons={[
<SplitTextsPopover key='split-text-previews' textInfos={splitTexts} onHover={handleOnConnect} onClick={openInfoModal} getColorForLLM={getColorForLLMAndSetIfNotFound} />
]} />
<Modal title={'List of split inputs (' + splitTexts.length + ' total)'} size='xl' opened={infoModalOpened} onClose={closeInfoModal} styles={{header: {backgroundColor: '#FFD700'}, root: {position: 'relative', left: '-5%'}}}>
<Box size={600} m='lg' mt='xl'>
{displaySplitTexts(splitTexts, getColorForLLMAndSetIfNotFound)}
</Box>
</Modal>
<NativeSelect onChange={(e) => {setSplitOnFormat(e.target.value); setDataPropsForNode(id, {splitFormat: e.target.value});}}
className='nowheel'
label={'Split on'}
data={formattingOptions}
size="xs"
value={splitOnFormat}
miw='80px'
mr='xs'
mt='-6px' />
{ !(splitOnFormat?.length <= 2) ?
<Text color='gray' size='8pt' mt='xs' maw='150px'>All other parts of the input text will be ignored.</Text>
: <></>
}
<Handle
type="target"
position="left"
id="__input"
className="grouped-handle"
style={{ top: "50%" }}
onConnect={handleOnConnect}
/>
<Handle
type="source"
position="right"
id="output"
className="grouped-handle"
style={{ top: "50%" }}
/>
</BaseNode>);
};
export default SplitNode;

View File

@ -892,4 +892,14 @@ export const filterDict = (dict: Dict, keyFilterFunc: (key: string) => boolean)
acc[key] = dict[key];
return acc;
}, {});
};
};
export const processCSV = (csv: string): string[] => {
var matches = csv.match(/(\s*"[^"]+"\s*|\s*[^,]+|,)(?=,|$)/g);
if (!matches) return;
for (var n = 0; n < matches.length; ++n) {
matches[n] = matches[n].trim();
if (matches[n] == ',') matches[n] = '';
}
return matches.map(e => e.trim()).filter(e => e.length > 0);
}

View File

@ -26,7 +26,7 @@ export const colorPalettes = {
var: varColorPalette,
}
const refreshableOutputNodeTypes = new Set(['evaluator', 'prompt', 'inspect', 'vis', 'llmeval', 'textfields', 'chat', 'simpleval', 'join']);
const refreshableOutputNodeTypes = new Set(['evaluator', 'prompt', 'inspect', 'vis', 'llmeval', 'textfields', 'chat', 'simpleval', 'join', 'split']);
export let initLLMProviders = [
{ name: "GPT3.5", emoji: "🤖", model: "gpt-3.5-turbo", base_model: "gpt-3.5-turbo", temp: 1.0 }, // The base_model designates what settings form will be used, and must be unique.

View File

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