mirror of
https://github.com/ianarawjo/ChainForge.git
synced 2025-03-14 08:16:37 +00:00
Add prompt preview tooltip, add ability to disable textfields selectively, bug fixes (#97)
* Add tooltip to prompt preview button * Focus scrollwheel on textfields textareas * Replace escaped { and } with their bare versions * Escape braces in tabular data by default. Ignore empty rows. * Add ability to disable fields on textfields * Make sure deleting a field deletes its fields_visibility * Add withinPortal to Tooltips on side-buttons in text fields * Add Anthropic model Claude-2.
This commit is contained in:
parent
318f81e1df
commit
3657609c32
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,15 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.26e6dbb2.css",
|
||||
"main.js": "/static/js/main.0ddb49f0.js",
|
||||
"main.css": "/static/css/main.d97bf957.css",
|
||||
"main.js": "/static/js/main.44a6025f.js",
|
||||
"static/js/787.4c72bb55.chunk.js": "/static/js/787.4c72bb55.chunk.js",
|
||||
"index.html": "/index.html",
|
||||
"main.26e6dbb2.css.map": "/static/css/main.26e6dbb2.css.map",
|
||||
"main.0ddb49f0.js.map": "/static/js/main.0ddb49f0.js.map",
|
||||
"main.d97bf957.css.map": "/static/css/main.d97bf957.css.map",
|
||||
"main.44a6025f.js.map": "/static/js/main.44a6025f.js.map",
|
||||
"787.4c72bb55.chunk.js.map": "/static/js/787.4c72bb55.chunk.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.26e6dbb2.css",
|
||||
"static/js/main.0ddb49f0.js"
|
||||
"static/css/main.d97bf957.css",
|
||||
"static/js/main.44a6025f.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.0ddb49f0.js"></script><link href="/static/css/main.26e6dbb2.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.44a6025f.js"></script><link href="/static/css/main.d97bf957.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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7
chainforge/react-server/src/EvaluatorNode.js
vendored
7
chainforge/react-server/src/EvaluatorNode.js
vendored
@ -258,9 +258,10 @@ const EvaluatorNode = ({ data, id }) => {
|
||||
runButtonTooltip="Run evaluator over inputs"
|
||||
customButtons={[
|
||||
<Tooltip label='Info' key="eval-info">
|
||||
<button onClick={openInfoModal} className='custom-button' style={{border:'none'}}>
|
||||
<IconInfoCircle size='12pt' color='gray' style={{marginBottom: '-4px'}} />
|
||||
</button></Tooltip>]}
|
||||
<button onClick={openInfoModal} className='custom-button' style={{border:'none'}}>
|
||||
<IconInfoCircle size='12pt' color='gray' style={{marginBottom: '-4px'}} />
|
||||
</button>
|
||||
</Tooltip>]}
|
||||
/>
|
||||
<LLMResponseInspectorModal ref={inspectModal} jsonResponses={lastResponses} />
|
||||
<Modal title={default_header} size='60%' opened={infoModalOpened} onClose={closeInfoModal} styles={{header: {backgroundColor: '#FFD700'}, root: {position: 'relative', left: '-80px'}}}>
|
||||
|
@ -9,6 +9,7 @@ import { Collapse, Flex, MultiSelect, NativeSelect } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import * as XLSX from 'xlsx';
|
||||
import useStore from './store';
|
||||
import { filterDict } from './backend/utils';
|
||||
|
||||
// Helper funcs
|
||||
const truncStr = (s, maxLen) => {
|
||||
@ -17,13 +18,6 @@ const truncStr = (s, maxLen) => {
|
||||
else
|
||||
return s;
|
||||
};
|
||||
const filterDict = (dict, keyFilterFunc) => {
|
||||
return Object.keys(dict).reduce((acc, key) => {
|
||||
if (keyFilterFunc(key) === true)
|
||||
acc[key] = dict[key];
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
const groupResponsesBy = (responses, keyFunc) => {
|
||||
let responses_by_key = {};
|
||||
let unspecified_group = [];
|
||||
|
@ -11,12 +11,13 @@
|
||||
*/
|
||||
|
||||
import { APP_IS_RUNNING_LOCALLY } from "./backend/utils";
|
||||
import { filterDict } from './backend/utils';
|
||||
|
||||
// Available LLMs in ChainForge, in the format expected by LLMListItems.
|
||||
export let AvailableLLMs = [
|
||||
{ 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.
|
||||
{ name: "GPT4", emoji: "🥵", model: "gpt-4", base_model: "gpt-4", temp: 1.0 },
|
||||
{ name: "Claude", emoji: "📚", model: "claude-v1", base_model: "claude-v1", temp: 0.5 },
|
||||
{ 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 },
|
||||
@ -25,14 +26,6 @@ if (APP_IS_RUNNING_LOCALLY()) {
|
||||
AvailableLLMs.push({ name: "Dalai (Alpaca.7B)", emoji: "🦙", model: "alpaca.7B", base_model: "dalai", temp: 0.5 });
|
||||
}
|
||||
|
||||
const filterDict = (dict, keyFilterFunc) => {
|
||||
return Object.keys(dict).reduce((acc, key) => {
|
||||
if (keyFilterFunc(key) === true)
|
||||
acc[key] = dict[key];
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const ChatGPTSettings = {
|
||||
fullName: "GPT-3.5+ (OpenAI)",
|
||||
schema: {
|
||||
@ -249,9 +242,9 @@ const ClaudeSettings = {
|
||||
"type": "string",
|
||||
"title": "Model Version",
|
||||
"description": "Select a version of Claude to query. For more details on the differences, see the Anthropic API documentation.",
|
||||
"enum": ["claude-v1", "claude-v1-100k", "claude-instant-v1", "claude-instant-v1-100k", "claude-v1.3",
|
||||
"enum": ["claude-2", "claude-2.0", "claude-instant-1", "claude-instant-1.1", "claude-v1", "claude-v1-100k", "claude-instant-v1", "claude-instant-v1-100k", "claude-v1.3",
|
||||
"claude-v1.3-100k", "claude-v1.2", "claude-v1.0", "claude-instant-v1.1", "claude-instant-v1.1-100k", "claude-instant-v1.0"],
|
||||
"default": "claude-v1"
|
||||
"default": "claude-2"
|
||||
},
|
||||
"temperature": {
|
||||
"type": "number",
|
||||
@ -314,7 +307,7 @@ const ClaudeSettings = {
|
||||
"ui:autofocus": true
|
||||
},
|
||||
"model": {
|
||||
"ui:help": "Defaults to claude-v1."
|
||||
"ui:help": "Defaults to claude-2. Note that Anthropic models in particular are subject to change. Model names prior to Claude 2, including 100k context window, are no longer listed on the Anthropic site, so they may or may not work."
|
||||
},
|
||||
"temperature": {
|
||||
"ui:help": "Defaults to 1.0.",
|
||||
|
12
chainforge/react-server/src/PromptNode.js
vendored
12
chainforge/react-server/src/PromptNode.js
vendored
@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState, useRef, useCallback } from 'react';
|
||||
import { Handle } from 'react-flow-renderer';
|
||||
import { Menu, Button, Progress, Textarea, Text, Popover, Center, Modal, Box } from '@mantine/core';
|
||||
import { Menu, Button, Progress, Textarea, Text, Popover, Center, Modal, Box, Tooltip } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { IconSearch, IconList } from '@tabler/icons-react';
|
||||
@ -69,11 +69,13 @@ const PromptListPopover = ({ promptInfos, onHover, onClick }) => {
|
||||
}, [onHover, open]);
|
||||
|
||||
return (
|
||||
<Popover width={400} position="right-start" withArrow withinPortal shadow="rgb(38, 57, 77) 0px 10px 30px -14px" key="query-info" opened={opened} styles={{dropdown: {maxHeight: '500px', overflowY: 'auto', backgroundColor: '#fff'}}}>
|
||||
<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>
|
||||
<button className='custom-button' onMouseEnter={_onHover} onMouseLeave={close} onClick={onClick} style={{border:'none'}}>
|
||||
<IconList size='12pt' color='gray' style={{marginBottom: '-4px'}} />
|
||||
</button>
|
||||
<Tooltip label='Click to view all prompts' 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 generated prompts ({promptInfos.length} total)</Text></Center>
|
||||
|
43
chainforge/react-server/src/TextFieldsNode.js
vendored
43
chainforge/react-server/src/TextFieldsNode.js
vendored
@ -1,7 +1,7 @@
|
||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { Handle } from 'react-flow-renderer';
|
||||
import { Textarea } from '@mantine/core';
|
||||
import { IconTextPlus } from '@tabler/icons-react';
|
||||
import { Textarea, Tooltip } from '@mantine/core';
|
||||
import { IconTextPlus, IconEye, IconEyeOff } from '@tabler/icons-react';
|
||||
import useStore from './store';
|
||||
import NodeLabel from './NodeLabelComponent';
|
||||
import TemplateHooks, { extractBracketedSubstrings } from './TemplateHooksComponent';
|
||||
@ -26,13 +26,16 @@ const setsAreEqual = (setA, setB) => {
|
||||
return equal;
|
||||
}
|
||||
|
||||
const delButtonId = 'del-';
|
||||
const visibleButtonId = 'eye-';
|
||||
|
||||
const TextFieldsNode = ({ data, id }) => {
|
||||
|
||||
const [templateVars, setTemplateVars] = useState(data.vars || []);
|
||||
const setDataPropsForNode = useStore((state) => state.setDataPropsForNode);
|
||||
const delButtonId = 'del-';
|
||||
|
||||
const [textfieldsValues, setTextfieldsValues] = useState(data.fields || {});
|
||||
const [fieldVisibility, setFieldVisibility] = useState(data.fields_visibility || {});
|
||||
|
||||
const getUID = useCallback(() => {
|
||||
if (textfieldsValues) {
|
||||
@ -48,15 +51,18 @@ const TextFieldsNode = ({ data, id }) => {
|
||||
const handleDelete = useCallback((event) => {
|
||||
// Update the data for this text field's id.
|
||||
let new_fields = { ...textfieldsValues };
|
||||
let new_vis = { ...fieldVisibility };
|
||||
var item_id = event.target.id.substring(delButtonId.length);
|
||||
delete new_fields[item_id];
|
||||
delete new_vis[item_id];
|
||||
// if the new_data is empty, initialize it with one empty field
|
||||
if (Object.keys(new_fields).length === 0) {
|
||||
new_fields[getUID()] = "";
|
||||
}
|
||||
setTextfieldsValues(new_fields);
|
||||
setDataPropsForNode(id, {fields: new_fields});
|
||||
}, [textfieldsValues, id, delButtonId, setDataPropsForNode]);
|
||||
setFieldVisibility(new_vis);
|
||||
setDataPropsForNode(id, {fields: new_fields, fields_visibility: new_vis});
|
||||
}, [textfieldsValues, fieldVisibility, id, delButtonId, setDataPropsForNode]);
|
||||
|
||||
// Initialize fields (run once at init)
|
||||
useEffect(() => {
|
||||
@ -76,6 +82,14 @@ const TextFieldsNode = ({ data, id }) => {
|
||||
setDataPropsForNode(id, { fields: new_fields });
|
||||
}, [textfieldsValues, id, setDataPropsForNode]);
|
||||
|
||||
// Disable/hide a text field temporarily
|
||||
const handleDisableField = useCallback((field_id) => {
|
||||
let vis = {...fieldVisibility};
|
||||
vis[field_id] = fieldVisibility[field_id] === false; // toggles it
|
||||
setFieldVisibility(vis);
|
||||
setDataPropsForNode(id, { fields_visibility: vis });
|
||||
}, [fieldVisibility, setDataPropsForNode]);
|
||||
|
||||
// Save the state of a textfield when it changes and update hooks
|
||||
const handleTextFieldChange = useCallback((field_id, val) => {
|
||||
|
||||
@ -145,11 +159,26 @@ const TextFieldsNode = ({ data, id }) => {
|
||||
{Object.keys(textfieldsValues).map(i => (
|
||||
<div className="input-field" key={i}>
|
||||
<Textarea id={i} name={i}
|
||||
className="text-field-fixed nodrag"
|
||||
className="text-field-fixed nodrag nowheel"
|
||||
minRows="2"
|
||||
value={textfieldsValues[i]}
|
||||
disabled={fieldVisibility[i] === false}
|
||||
onChange={(event) => handleTextFieldChange(i, event.currentTarget.value)} />
|
||||
{Object.keys(textfieldsValues).length > 1 ? (<button id={delButtonId + i} className="remove-text-field-btn nodrag" onClick={handleDelete}>X</button>) : <></>}
|
||||
{Object.keys(textfieldsValues).length > 1 ? (
|
||||
<div style={{display: 'flex', flexDirection: 'column'}}>
|
||||
<Tooltip label='remove field' position='right' withArrow arrowSize={10} withinPortal>
|
||||
<button id={delButtonId + i} className="remove-text-field-btn nodrag" onClick={handleDelete} style={{flex: 1}}>X</button>
|
||||
</Tooltip>
|
||||
<Tooltip label={(fieldVisibility[i] === false ? 'enable' : 'disable') + ' field'} position='right' withArrow arrowSize={10} withinPortal>
|
||||
<button id={visibleButtonId + i} className="remove-text-field-btn nodrag" onClick={() => handleDisableField(i)} style={{flex: 1}}>
|
||||
{fieldVisibility[i] === false ?
|
||||
<IconEyeOff size='14pt' pointerEvents='none' />
|
||||
: <IconEye size='14pt' pointerEvents='none' />
|
||||
}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
) : <></>}
|
||||
</div>))}
|
||||
</div>
|
||||
<Handle
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { StringTemplate, PromptTemplate, PromptPermutationGenerator } from '../template';
|
||||
import { StringTemplate, PromptTemplate, PromptPermutationGenerator, escapeBraces } from '../template';
|
||||
import { expect, test } from '@jest/globals';
|
||||
|
||||
test('string template', () => {
|
||||
@ -93,4 +93,14 @@ test('carry together vars', () => {
|
||||
num_prompts += 1;
|
||||
}
|
||||
expect(num_prompts).toBe(3*3);
|
||||
});
|
||||
|
||||
test('escaped braces', () => {
|
||||
// Escaped braces \{ and \} should not be treated as real variables, one, and two, should be
|
||||
// removed when calling the 'toString' method on a PromptTemplate:
|
||||
let promptTemplate = new PromptTemplate('For what show did \\{person\\} get a Netflix deal?');
|
||||
let filledTemplate = promptTemplate.fill({'person': 'Meghan Markle'});
|
||||
expect(promptTemplate.toString()).toBe('For what show did {person} get a Netflix deal?');
|
||||
expect(promptTemplate.toString()).toEqual(filledTemplate.toString());
|
||||
expect(escapeBraces('Why is the set {0, 1, 2} of size 3?')).toEqual('Why is the set \\{0, 1, 2\\} of size 3?');
|
||||
});
|
@ -31,6 +31,10 @@ export enum LLM {
|
||||
Dalai_Llama_65B = "llama.65B",
|
||||
|
||||
// Anthropic
|
||||
Claude_v2 = "claude-2",
|
||||
Claude_v2_0 = "claude-2.0",
|
||||
Claude_1_instant = "claude-instant-1",
|
||||
Claude_1_instant_1 = "claude-instant-1.1",
|
||||
Claude_v1 = "claude-v1",
|
||||
Claude_v1_0 = "claude-v1.0",
|
||||
Claude_v1_2 = "claude-v1.2",
|
||||
|
@ -16,6 +16,14 @@ function isDict(o: any): boolean {
|
||||
return typeof o === 'object' && !Array.isArray(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a string, returns the same string with braces { and } escaped, \{ and \}. Does nothing else.
|
||||
* @param str The string to transform
|
||||
*/
|
||||
export function escapeBraces(str: string): string {
|
||||
return str.replace(/[{}]/g, '\\$&');
|
||||
}
|
||||
|
||||
export class StringTemplate {
|
||||
val: string;
|
||||
/**
|
||||
@ -143,8 +151,9 @@ export class PromptTemplate {
|
||||
this.metavars = {};
|
||||
}
|
||||
|
||||
/** Returns the value of this.template, with any escaped braces \{ and \} with the escape \ char removed. */
|
||||
toString(): string {
|
||||
return this.template;
|
||||
return this.template.replaceAll('\\{', '{').replaceAll('\\}', '}');
|
||||
}
|
||||
|
||||
toValue(): string {
|
||||
|
@ -692,3 +692,11 @@ export function merge_response_objs(resp_obj_A: LLMResponseObject | undefined, r
|
||||
metavars: resp_obj_B.metavars,
|
||||
};
|
||||
}
|
||||
|
||||
export const filterDict = (dict: Dict, keyFilterFunc: (key: string) => boolean) => {
|
||||
return Object.keys(dict).reduce((acc, key) => {
|
||||
if (keyFilterFunc(key) === true)
|
||||
acc[key] = dict[key];
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
27
chainforge/react-server/src/store.js
vendored
27
chainforge/react-server/src/store.js
vendored
@ -4,6 +4,8 @@ import {
|
||||
applyNodeChanges,
|
||||
applyEdgeChanges,
|
||||
} from 'react-flow-renderer';
|
||||
import { escapeBraces } from './backend/template';
|
||||
import { filterDict } from './backend/utils';
|
||||
|
||||
// Initial project settings
|
||||
const initialAPIKeys = {};
|
||||
@ -124,17 +126,25 @@ const useStore = create((set, get) => ({
|
||||
|
||||
// Extract all the data for every row of the source column, appending the other values as 'meta-vars':
|
||||
return rows.map(row => {
|
||||
const row_keys = Object.keys(row);
|
||||
|
||||
// Check if this is an 'empty' row (with all empty strings); if so, skip it:
|
||||
if (row_keys.every(key => key === '__uid' || !row[key] || row[key].trim() === ""))
|
||||
return undefined;
|
||||
|
||||
const row_excluding_col = {};
|
||||
Object.keys(row).forEach(key => {
|
||||
row_keys.forEach(key => {
|
||||
if (key !== src_col.key && key !== '__uid')
|
||||
row_excluding_col[col_header_lookup[key]] = row[key];
|
||||
});
|
||||
return {
|
||||
text: ((src_col.key in row) ? row[src_col.key] : ""),
|
||||
// We escape any braces in the source text before they're passed downstream.
|
||||
// This is a special property of tabular data nodes: we don't want their text to be treated as prompt templates.
|
||||
text: escapeBraces((src_col.key in row) ? row[src_col.key] : ""),
|
||||
metavars: row_excluding_col,
|
||||
associate_id: row.__uid, // this is used by the backend to 'carry' certain values together
|
||||
}
|
||||
});
|
||||
}).filter(r => r !== undefined);
|
||||
} else {
|
||||
console.error(`Could not find table column with source handle name ${sourceHandleKey}`);
|
||||
return null;
|
||||
@ -145,8 +155,15 @@ const useStore = create((set, get) => ({
|
||||
if ("fields" in src_node.data) {
|
||||
if (Array.isArray(src_node.data["fields"]))
|
||||
return src_node.data["fields"];
|
||||
else
|
||||
return Object.values(src_node.data["fields"]);
|
||||
else {
|
||||
// We have to filter over a special 'fields_visibility' prop, which
|
||||
// can select what fields get output:
|
||||
if ("fields_visibility" in src_node.data)
|
||||
return Object.values(filterDict(src_node.data["fields"],
|
||||
fid => src_node.data["fields_visibility"][fid] !== false));
|
||||
else // return all field values
|
||||
return Object.values(src_node.data["fields"]);
|
||||
}
|
||||
}
|
||||
// NOTE: This assumes it's on the 'data' prop, with the same id as the handle:
|
||||
else return src_node.data[sourceHandleKey];
|
||||
|
@ -32,6 +32,7 @@
|
||||
text-align: left;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
pointer-events: none;
|
||||
|
||||
/* Position the tooltip */
|
||||
position: absolute;
|
||||
|
Loading…
x
Reference in New Issue
Block a user