Adding and removing LLM list items

This commit is contained in:
Ian Arawjo 2023-05-03 10:10:31 -04:00
parent ddade145f8
commit bf72a6f8a8
6 changed files with 101 additions and 79 deletions

View File

@ -39,6 +39,7 @@
"react-plotly.js": "^2.6.0",
"react-scripts": "5.0.1",
"styled-components": "^5.3.10",
"uuidv4": "^6.2.13",
"web-vitals": "^2.1.4",
"zustand": "^4.3.7"
}
@ -5295,6 +5296,11 @@
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.3.tgz",
"integrity": "sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g=="
},
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"node_modules/@types/ws": {
"version": "8.5.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.4.tgz",
@ -19990,6 +19996,15 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/uuidv4": {
"version": "6.2.13",
"resolved": "https://registry.npmjs.org/uuidv4/-/uuidv4-6.2.13.tgz",
"integrity": "sha512-AXyzMjazYB3ovL3q051VLH06Ixj//Knx7QnUSi1T//Ie3io6CpsPu9nVMOx5MoLWh6xV0B9J0hIaxungxXUbPQ==",
"dependencies": {
"@types/uuid": "8.3.4",
"uuid": "8.3.2"
}
},
"node_modules/v8-to-istanbul": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz",

View File

@ -34,6 +34,7 @@
"react-plotly.js": "^2.6.0",
"react-scripts": "5.0.1",
"styled-components": "^5.3.10",
"uuidv4": "^6.2.13",
"web-vitals": "^2.1.4",
"zustand": "^4.3.7"
},

View File

@ -2,7 +2,7 @@ import { useDisclosure } from '@mantine/hooks';
import { Modal, Button, Group } from '@mantine/core';
import { IconSettings, IconTrash } from '@tabler/icons-react';
export default function SettingsButton() {
export default function LLMItemButtonGroup( {onClickTrash, onClickSettings} ) {
const [opened, { open, close }] = useDisclosure(false);
return (
@ -12,8 +12,8 @@ export default function SettingsButton() {
</Modal>
<Group position="right" style={{float: 'right', height:'20px'}}>
<Button size="xs" variant="light" compact color="red" style={{padding: '0px'}} ><IconTrash size={"95%"} /></Button>
<Button size="xs" variant="light" compact onClick={open}>Settings&nbsp;<IconSettings size={"110%"} /></Button>
<Button onClick={onClickTrash} size="xs" variant="light" compact color="red" style={{padding: '0px'}} ><IconTrash size={"95%"} /></Button>
<Button onClick={onClickSettings} size="xs" variant="light" compact>Settings&nbsp;<IconSettings size={"110%"} /></Button>
</Group>
</div>
);

View File

@ -1,11 +1,16 @@
import { useState } from "react";
import { useState, useEffect, useCallback } from "react";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import ListItem, { DragItem, ListItemClone } from "./ListItemComponent";
import LLMListItem, { DragItem, LLMListItemClone } from "./LLMListItem";
import { StrictModeDroppable } from './StrictModeDroppable'
export default function LLMList({llms}) {
export default function LLMList({llms, onItemsChange}) {
const [items, setItems] = useState(llms);
const updateItems = useCallback((new_items) => {
setItems(new_items);
onItemsChange(new_items);
}, [onItemsChange]);
const onDragEnd = (result) => {
const { destination, source } = result;
if (!destination) return;
@ -21,35 +26,44 @@ export default function LLMList({llms}) {
setItems(newItems);
};
const removeItem = useCallback((item_key) => {
// Double-check that the item we want to remove is in the list of items...
if (!items.find(i => i.key === item_key)) {
console.error(`Could not remove model from LLM list: Could not find item with key ${item_key}.`);
return;
}
// Remove it
updateItems(items.filter(i => i.key !== item_key));
}, [items, updateItems]);
useEffect(() => {
// When LLMs list changes, we need to add new items
// while preserving the current order of 'items'.
// Check for new items and for each, add to end:
let new_items = Array.from(items);
llms.forEach(item => {
if (!items.find(i => i.key === item.key))
new_items.push(item);
});
updateItems(new_items);
}, [llms, updateItems]);
return (
<div className="list nowheel nodrag">
<div className="list nowheel nodrag">
<DragDropContext onDragEnd={onDragEnd}>
<StrictModeDroppable
droppableId="droppable"
droppableId="llm-list-droppable"
renderClone={(provided, snapshot, rubric) => (
// <DragItem
// {...provided.draggableProps}
// {...provided.dragHandleProps}
// ref={provided.innerRef}
// >
<ListItemClone
provided={provided}
snapshot={snapshot}
item={items[rubric.source.index]}
/>
// </DragItem>
<LLMListItemClone provided={provided} snapshot={snapshot} item={items[rubric.source.index]} />
)}
>
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{items.map((item, index) => (
<Draggable key={item.model} draggableId={item.model} index={index}>
<Draggable key={item.key} draggableId={item.key} index={index}>
{(provided, snapshot) => (
<ListItem
provided={provided}
snapshot={snapshot}
item={item}
/>
<LLMListItem provided={provided} snapshot={snapshot} item={item} removeCallback={removeItem} />
)}
</Draggable>
))}

View File

@ -1,7 +1,7 @@
/** Thanks to Kabir Haruna: https://codesandbox.io/s/i0rxsj */
import React from "react";
import styled from "styled-components";
import SettingsButton from "./SettingsButton"
import LLMItemButtonGroup from "./LLMItemButtonGroup"
const CardHeader = styled.div`
font-weight: 500;
@ -12,18 +12,6 @@ const CardHeader = styled.div`
margin-top: 1px;
`;
const Author = styled.div`
display: flex;
align-items: center;
`;
const CardFooter = styled.div`
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
`;
export const DragItem = styled.div`
padding: 10px;
border-radius: 6px;
@ -35,7 +23,7 @@ export const DragItem = styled.div`
flex-direction: column;
`;
const ListItem = ({ item, provided, snapshot }) => {
const LLMListItem = ({ item, provided, snapshot, removeCallback }) => {
return (
<DragItem
ref={provided.innerRef}
@ -44,15 +32,15 @@ const ListItem = ({ item, provided, snapshot }) => {
{...provided.dragHandleProps}
>
<div>
<CardHeader>{item.emoji}&nbsp;{item.model}</CardHeader>
<SettingsButton />
<CardHeader>{item.emoji}&nbsp;{item.name}</CardHeader>
<LLMItemButtonGroup onClickTrash={() => removeCallback(item.key)} />
</div>
</DragItem>
);
};
export const ListItemClone = ({ item, provided, snapshot }) => {
export const LLMListItemClone = ({ item, provided, snapshot }) => {
return (
<DragItem
ref={provided.innerRef}
@ -61,11 +49,11 @@ export const ListItemClone = ({ item, provided, snapshot }) => {
snapshot={snapshot}
>
<div>
<CardHeader>{item.emoji}&nbsp;{item.model}</CardHeader>
<SettingsButton />
<CardHeader>{item.emoji}&nbsp;{item.name}</CardHeader>
<LLMItemButtonGroup />
</div>
</DragItem>
);
};
export default ListItem;
export default LLMListItem;

View File

@ -1,6 +1,7 @@
import React, { useEffect, useState, useRef, useCallback } from 'react';
import { Handle } from 'react-flow-renderer';
import { Menu, Badge, useMantineTheme } from '@mantine/core';
import { Menu, Badge } from '@mantine/core';
import { v4 as uuid } from 'uuid';
import useStore from './store';
import StatusIndicator from './StatusIndicatorComponent'
import NodeLabel from './NodeLabelComponent'
@ -9,13 +10,14 @@ import LLMList from './LLMListComponent'
import AlertModal from './AlertModal'
// Available LLMs
const llmItems = [
{ model: "GPT3.5", emoji: "🙂", temp: 1.0 },
{ model: "GPT4", emoji: "🥵", temp: 1.0 },
{ model: "Alpaca 7B", emoji: "🦙", temp: 0.5 },
{ model: "Claude v1", emoji: "📚", temp: 0.5 },
{ model: "Ian Chatbot", emoji: "💩", temp: 0.5 }
const allLLMs = [
{ name: "GPT3.5", emoji: "🙂", model: "gpt3.5", temp: 1.0 },
{ name: "GPT4", emoji: "🥵", model: "gpt4", temp: 1.0 },
{ name: "Alpaca 7B", emoji: "🦙", model: "alpaca.7B", temp: 0.5 },
{ name: "Claude v1", emoji: "📚", model: "claude.v1", temp: 0.5 },
{ name: "Ian Chatbot", emoji: "💩", model: "test", temp: 0.5 }
];
const initLLMs = [allLLMs[0]];
// Helper funcs
const truncStr = (s, maxLen) => {
@ -43,7 +45,6 @@ const bucketResponsesByLLM = (responses) => {
};
const PromptNode = ({ data, id }) => {
const theme = useMantineTheme();
// Get state from the Zustand store:
const edges = useStore((state) => state.edges);
@ -55,13 +56,36 @@ const PromptNode = ({ data, id }) => {
const [templateVars, setTemplateVars] = useState(data.vars || []);
const [promptText, setPromptText] = useState(data.prompt);
const [promptTextOnLastRun, setPromptTextOnLastRun] = useState(null);
const [selectedLLMs, setSelectedLLMs] = useState(['gpt3.5']);
const [status, setStatus] = useState('none');
const [responsePreviews, setReponsePreviews] = useState([]);
const [numGenerations, setNumGenerations] = useState(data.n || 1);
// For displaying error messages to user
const alertModal = useRef(null);
// Selecting LLM models to prompt
const [llmItems, setLLMItems] = useState(initLLMs.map((i, idx) => ({key: uuid(), ...i})));
const [llmItemsCurrState, setLLMItemsCurrState] = useState([]);
const addModel = useCallback((model) => {
// Get the item for that model
let item = allLLMs.find(llm => llm.model === model);
if (!item) { // This should never trigger, but in case it does:
alertModal.current.trigger(`Could not find model named '${model}' in list of available LLMs.`);
return;
}
// Give it a uid as a unique key (this is needed for the draggable list to support multiple same-model items; keys must be unique)
item = {key: uuid(), ...item};
// Add model to LLM list (regardless of it's present already or not).
setLLMItems(llmItemsCurrState.concat([item]))
}, [llmItemsCurrState]);
const onLLMListItemsChange = useCallback((new_items) => {
setLLMItemsCurrState(new_items);
}, [setLLMItemsCurrState]);
const handleMouseEnter = () => {
setHovered(true);
@ -112,7 +136,7 @@ const PromptNode = ({ data, id }) => {
console.log('Connected!');
// Check that there is at least one LLM selected:
if (selectedLLMs.length === 0) {
if (llmItems.length === 0) {
alert('Please select at least one LLM to prompt.')
return;
}
@ -167,7 +191,7 @@ const PromptNode = ({ data, id }) => {
headers: {'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*'},
body: JSON.stringify({
id: id,
llm: selectedLLMs,
llm: llmItems.map(item => item.model),
prompt: py_prompt_template,
vars: pulled_data,
params: {
@ -239,22 +263,6 @@ const PromptNode = ({ data, id }) => {
}
}
const handleLLMChecked = (event) => {
console.log(event.target.value, event.target.checked);
if (event.target.checked) {
if (!selectedLLMs.includes(event.target.value)) {
// Add the selected LLM to the list:
setSelectedLLMs(selectedLLMs.concat([event.target.value]))
}
} else {
if (selectedLLMs.includes(event.target.value)) {
// Remove the LLM from the selected list:
const removeByIndex = (array, index) => array.filter((_, i) => i !== index);
setSelectedLLMs(removeByIndex(selectedLLMs, selectedLLMs.indexOf(event.target.value)));
}
}
}
const handleNumGenChange = (event) => {
let n = event.target.value;
if (!isNaN(n) && n.length > 0 && /^\d+$/.test(n)) {
@ -273,10 +281,6 @@ const PromptNode = ({ data, id }) => {
? '1px solid #222'
: '1px solid #999';
const selectedModel = (name) => {
console.log(name);
};
return (
<div
className="prompt-node"
@ -314,7 +318,7 @@ const PromptNode = ({ data, id }) => {
<label htmlFor="num-generations" style={{fontSize: '10pt'}}>Num responses per prompt:&nbsp;</label>
<input id="num-generations" name="num-generations" type="number" min={1} max={50} defaultValue={data.n || 1} onChange={handleNumGenChange} className="nodrag"></input>
</div>
<div id="llms-list" className="nowheel" style={{backgroundColor: '#eee', padding: '8px', overflowY: 'auto', maxHeight: '175px'}}>
<div id="llms-list" className="nowheel" style={{backgroundColor: '#eee', borderRadius: '4px', padding: '8px', overflowY: 'auto', maxHeight: '175px'}}>
<div style={{marginTop: '6px', marginBottom: '6px', marginLeft: '6px', paddingBottom: '4px', textAlign: 'left', fontSize: '10pt', color: '#777'}}>
Models to query:
<div className="add-llm-model-btn nodrag">
@ -327,14 +331,14 @@ const PromptNode = ({ data, id }) => {
<button>Add +</button>
</Menu.Target>
<Menu.Dropdown>
{llmItems.map(item => (<Menu.Item key={item.model} onClick={() => selectedModel(item.model)} icon={item.emoji}>{item.model}</Menu.Item>))}
{allLLMs.map(item => (<Menu.Item key={item.model} onClick={() => addModel(item.model)} icon={item.emoji}>{item.name}</Menu.Item>))}
</Menu.Dropdown>
</Menu>
</div>
</div>
<div className="nodrag">
<LLMList llms={llmItems} />
<LLMList llms={llmItems} onItemsChange={onLLMListItemsChange} />
{/* <input type="checkbox" id="gpt3.5" name="gpt3.5" value="gpt3.5" defaultChecked={true} onChange={handleLLMChecked} />
<label htmlFor="gpt3.5">GPT3.5 </label>
<input type="checkbox" id="gpt4" name="gpt4" value="gpt4" defaultChecked={false} onChange={handleLLMChecked} />