Merge remote-tracking branch 'origin/form_io' into python_custom_node_component

This commit is contained in:
gustavoschaedler 2023-06-30 16:44:11 +01:00
commit 6416c43ff2
37 changed files with 669 additions and 423 deletions

View file

@ -25,6 +25,7 @@ import { nodeColors } from "../../../../utils";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import { PopUpContext } from "../../../../contexts/popUpContext";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import * as _ from "lodash";
export default function ParameterComponent({
left,
@ -36,6 +37,7 @@ export default function ParameterComponent({
type,
name = "",
required = false,
optionalHandle = null,
}: ParameterComponentType) {
const ref = useRef(null);
const refHtml = useRef(null);
@ -73,6 +75,7 @@ export default function ParameterComponent({
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
formKeysData: prev[tabId].formKeysData,
},
@ -85,7 +88,7 @@ export default function ParameterComponent({
refHtml.current = groupedObj.map((item, i) => (
<span
key={getRandomKeyByssmm()}
key={getRandomKeyByssmm() + item.family + i}
className={classNames(
i > 0 ? "items-center flex mt-3" : "items-center flex"
)}
@ -132,13 +135,14 @@ export default function ParameterComponent({
<span className="text-red-600">{required ? " *" : ""}</span>
</div>
{left &&
(type === "str" ||
((type === "str" ||
type === "bool" ||
type === "float" ||
type === "code" ||
type === "prompt" ||
type === "file" ||
type === "int") ? (
type === "int") && !optionalHandle
) ? (
<></>
) : (
<ShadTooltip

View file

@ -102,7 +102,7 @@ export default function GenericNode({
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
}}
/>
<div className="ml-2 truncate">
<div className="ml-2 truncate flex-1">
<ShadTooltip
delayDuration={1500}
content={data.node.display_name}
@ -204,6 +204,7 @@ export default function GenericNode({
data={data}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors[data.node.template[t].type] ??
nodeColors.unknown
}
title={
@ -214,11 +215,12 @@ export default function GenericNode({
: toTitleCase(t)
}
name={t}
tooltipTitle={data.node.template[t].type}
tooltipTitle={data.node.template[t].input_types?.join("\n") ?? data.node.template[t].type}
required={data.node.template[t].required}
id={data.node.template[t].type + "|" + t + "|" + data.id}
id={(data.node.template[t].input_types?.join(";") ?? data.node.template[t].type) + "|" + t + "|" + data.id}
left={true}
type={data.node.template[t].type}
optionalHandle={data.node.template[t].input_types}
/>
) : (
<></>

View file

@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import { useNodes } from "reactflow";
import { ChatType } from "../../types/chat";
import ChatTrigger from "./chatTrigger";
@ -7,11 +7,13 @@ import BuildTrigger from "./buildTrigger";
import { getBuildStatus } from "../../controllers/API";
import { NodeType } from "../../types/flow";
import FormModal from "../../modals/formModal";
import { TabsContext } from "../../contexts/tabsContext";
import * as _ from "lodash";
export default function Chat({ flow }: ChatType) {
const [open, setOpen] = useState(false);
const [openForm, setOpenForm] = useState(false);
const [isBuilt, setIsBuilt] = useState(false);
const {tabsState} = useContext(TabsContext);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@ -44,21 +46,19 @@ export default function Chat({ flow }: ChatType) {
const prevNodesRef = useRef<any[] | undefined>();
const nodes = useNodes();
useEffect(() => {
const prevNodes = prevNodesRef.current;
const currentNodes = nodes.map(
(node: NodeType) => node.data.node.template
(node: NodeType) => _.cloneDeep(node.data.node.template)
);
if (
prevNodes &&
JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
tabsState && tabsState[flow.id] && tabsState[flow.id].isPending
&& JSON.stringify(prevNodes) !== JSON.stringify(currentNodes)
) {
setIsBuilt(false);
}
prevNodesRef.current = currentNodes;
}, [nodes]);
}, [tabsState]);
return (
<>

View file

@ -5,6 +5,9 @@ import GenericModal from "../../modals/genericModal";
import { TypeModal } from "../../utils";
import { INPUT_STYLE } from "../../constants";
import { ExternalLink } from "lucide-react";
import { postValidatePrompt } from "../../controllers/API";
import { typesContext } from "../../contexts/typesContext";
import * as _ from "lodash";
export default function PromptAreaComponent({
setNodeClass,
@ -14,8 +17,10 @@ export default function PromptAreaComponent({
disabled,
editNode = false,
}: TextAreaComponentType) {
const [myValue, setMyValue] = useState(value);
const [myValue, setMyValue] = useState("");
const { openPopUp } = useContext(PopUpContext);
const { reactFlowInstance } = useContext(typesContext);
useEffect(() => {
if (disabled) {
setMyValue("");
@ -24,8 +29,19 @@ export default function PromptAreaComponent({
}, [disabled, onChange]);
useEffect(() => {
setMyValue(value);
}, [value]);
if (value !== "" && myValue !== value && reactFlowInstance) { // only executed once
setMyValue(value);
postValidatePrompt(value, nodeClass)
.then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
// need to update reactFlowInstance to re-render the nodes.
reactFlowInstance.setEdges(_.cloneDeep(reactFlowInstance.getEdges()));
}
})
.catch((error) => {});
}
}, [reactFlowInstance]);
return (
<div

View file

@ -8,11 +8,6 @@ export default function ToggleShadComponent({
disabled,
size,
}: ToggleComponentType) {
useEffect(() => {
if (disabled) {
setEnabled(false);
}
}, [disabled, setEnabled]);
let scaleX, scaleY;
switch (size) {
case "small":
@ -32,11 +27,12 @@ export default function ToggleShadComponent({
scaleY = 1;
}
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<div className={disabled ? "pointer-events-none cursor-not-allowed " : ""}>
<Switch
style={{
transform: `scaleX(${scaleX}) scaleY(${scaleY})`,
}}
disabled={disabled}
className="data-[state=unchecked]:bg-slate-500"
checked={enabled}
onCheckedChange={(x: boolean) => {

View file

@ -173,7 +173,7 @@ export const DESCRIPTIONS: string[] = [
"Your Hub for Text Generation.",
"Promptly Ingenious!",
"Building Linguistic Labyrinths.",
"LangFlow: Create, Chain, Communicate.",
"Create, Chain, Communicate.",
"Connect the Dots, Craft Language.",
"Interactive Language Weaving.",
"Generate, Innovate, Communicate.",
@ -183,6 +183,46 @@ export const DESCRIPTIONS: string[] = [
"Nurture NLP Nodes Here.",
"Conversational Cartography Unlocked.",
"Design, Develop, Dialogize.",
"Unleashing Linguistic Creativity.",
"Graph Your Way to Great Conversations.",
"The Power of Language at Your Fingertips.",
"Sculpting Language with Precision.",
"Where Language Meets Logic.",
"Building Intelligent Interactions.",
"Your Passport to Linguistic Landscapes.",
"Create, Curate, Communicate with LangFlow.",
"Flow into the Future of Language.",
"Mapping Meaningful Conversations.",
"Unravel the Art of Articulation.",
"Language Engineering Excellence.",
"Navigate the Networks of Conversation.",
"Crafting Conversations, One Node at a Time.",
"The Pinnacle of Prompt Generation.",
"Language Models, Mapped and Mastered.",
"Powerful Prompts, Perfectly Positioned.",
"Innovation in Interaction with LangFlow.",
"Your Toolkit for Text Generation.",
"Unfolding Linguistic Possibilities.",
"Building Powerful Solutions with Language Models.",
"Uncover Business Opportunities with NLP.",
"Harness the Power of Conversational AI.",
"Transform Your Business with Smart Dialogues.",
"Craft Meaningful Interactions, Generate Value.",
"Unleashing Business Potential through Language Engineering.",
"Empowering Enterprises with Intelligent Interactions.",
"Driving Innovation in Business Communication.",
"Catalyzing Business Growth through Conversational AI.",
"Text Generation Meets Business Transformation.",
"Navigate the Linguistic Landscape, Discover Opportunities.",
"Create Powerful Connections, Boost Business Value.",
"Empowering Communication, Enabling Opportunities.",
"Advanced NLP for Groundbreaking Business Solutions.",
"Innovation in Interaction, Revolution in Revenue.",
"Maximize Impact with Intelligent Conversations.",
"Beyond Text Generation - Unleashing Business Opportunities.",
"Unlock the Power of AI in Your Business Conversations.",
"Crafting Dialogues that Drive Business Success.",
"Engineered for Excellence, Built for Business.",
];
/**
@ -256,8 +296,49 @@ export const ADJECTIVES: string[] = [
"thirsty",
"tiny",
"trusting",
"bubbly",
"charming",
"cheerful",
"comical",
"dazzling",
"delighted",
"dynamic",
"effervescent",
"enthusiastic",
"exuberant",
"fluffy",
"friendly",
"funky",
"giddy",
"giggly",
"gleeful",
"goofy",
"graceful",
"grinning",
"hilarious",
"inquisitive",
"joyous",
"jubilant",
"lively",
"mirthful",
"mischievous",
"optimistic",
"peppy",
"perky",
"playful",
"quirky",
"radiant",
"sassy",
"silly",
"spirited",
"sprightly",
"twinkly",
"upbeat",
"vibrant",
"witty",
"zany",
"zealous",
];
/**
* Nouns for the name of the flow
* @constant
@ -406,6 +487,38 @@ export const NOUNS: string[] = [
"wright",
"yalow",
"yonath",
"coulomb",
"degrasse",
"dewey",
"edison",
"eratosthenes",
"faraday",
"galton",
"gauss",
"herschel",
"hubble",
"joule",
"kaku",
"kepler",
"khayyam",
"lavoisier",
"maxwell",
"mendel",
"mendeleev",
"ohm",
"pascal",
"planck",
"riemann",
"schrodinger",
"sagan",
"tesla",
"tyson",
"volta",
"watt",
"weber",
"wien",
"zoBell",
"zuse",
];
/**

View file

@ -189,26 +189,28 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
function processFlowEdges(flow) {
if(!flow.data || !flow.data.edges) return;
if (!flow.data || !flow.data.edges) return;
flow.data.edges.forEach((edge) => {
edge.className = "";
edge.style = { stroke: "#555555" };
});
}
function updateDisplay_name(node:NodeType,template:APIClassType) {
node.data.node.display_name = template["display_name"]?template["display_name"]:node.data.type;
function updateDisplay_name(node: NodeType, template: APIClassType) {
node.data.node.display_name = template["display_name"]
? template["display_name"]
: node.data.type;
}
function processFlowNodes(flow) {
if(!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node:NodeType) => {
if (!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node: NodeType) => {
const template = templates[node.data.type];
if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` });
return;
}
if (Object.keys(template["template"]).length > 0) {
updateDisplay_name(node,template);
updateDisplay_name(node, template);
updateNodeBaseClasses(node, template);
updateNodeEdges(flow, node, template);
updateNodeDescription(node, template);
@ -217,11 +219,15 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
function updateNodeBaseClasses(node:NodeType,template:APIClassType) {
function updateNodeBaseClasses(node: NodeType, template: APIClassType) {
node.data.node.base_classes = template["base_classes"];
}
function updateNodeEdges(flow:FlowType, node:NodeType,template:APIClassType) {
function updateNodeEdges(
flow: FlowType,
node: NodeType,
template: APIClassType
) {
flow.data.edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
@ -233,11 +239,11 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
function updateNodeDescription(node:NodeType,template:APIClassType) {
function updateNodeDescription(node: NodeType, template: APIClassType) {
node.data.node.description = template["description"];
}
function updateNodeTemplate(node:NodeType,template:APIClassType) {
function updateNodeTemplate(node: NodeType, template: APIClassType) {
node.data.node.template = updateTemplate(
template["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType
@ -302,28 +308,39 @@ export function TabsProvider({ children }: { children: ReactNode }) {
* If the file type is application/json, the file is read and parsed into a JSON object.
* The resulting JSON object is passed to the addFlow function.
*/
function uploadFlow(newProject?: boolean) {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = (e: Event) => {
// check if the file type is application/json
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
// get the file from the file input
const file = (e.target as HTMLInputElement).files[0];
// read the file as text
file.text().then((text) => {
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
function uploadFlow(newProject?: boolean, file?: File) {
if (file) {
file.text().then((text) => {
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
addFlow(flow, newProject);
});
}
};
// trigger the file input click event to open the file dialog
input.click();
addFlow(flow, newProject);
});
} else {
// create a file input
const input = document.createElement("input");
input.type = "file";
input.accept = ".json";
// add a change event listener to the file input
input.onchange = (e: Event) => {
// check if the file type is application/json
if (
(e.target as HTMLInputElement).files[0].type === "application/json"
) {
// get the file from the file input
const currentfile = (e.target as HTMLInputElement).files[0];
// read the file as text
currentfile.text().then((text) => {
// parse the text into a JSON object
let flow: FlowType = JSON.parse(text);
addFlow(flow, newProject);
});
}
};
// trigger the file input click event to open the file dialog
input.click();
}
}
function uploadFlows() {

View file

@ -53,7 +53,7 @@ export default function ChatInput({
lockChat
? " bg-input text-black dark:bg-gray-700 dark:text-gray-300"
: " bg-white-200 text-black dark:bg-gray-900 dark:text-gray-300",
"p-4 form-input block w-full custom-scroll rounded-md border-gray-300 dark:border-gray-600 pr-12 sm:text-sm" +
"p-4 form-input block w-full custom-scroll rounded-md border-gray-300 dark:border-gray-600 pr-16 sm:text-sm" +
INPUT_STYLE
)}
placeholder={"Send a message..."}
@ -62,7 +62,7 @@ export default function ChatInput({
<button className={classNames("p-2 px-1 transition-all duration-300 rounded-md",chatValue == "" ? "text-primary" : " bg-indigo-600 text-background")} disabled={lockChat} onClick={() => sendMessage()}>
{lockChat ? (
<Lock
className="h-5 w-5 ml-1 mr-11 animate-pulse"
className="h-5 w-5 ml-1 mr-1 animate-pulse"
aria-hidden="true"
/>
) : (

View file

@ -69,7 +69,7 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
</div>
<SyntaxHighlighter
className=" w-[570px]"
className=" w-[47vw]"
language={language}
style={oneDark}
customStyle={{ margin: 0 }}

View file

@ -92,7 +92,7 @@ export default function ChatMessage({
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose dark:prose-invert text-primary"
className="markdown prose dark:prose-invert text-primary max-w-full"
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {

View file

@ -4,7 +4,13 @@ import { alertContext } from "../../contexts/alertContext";
import { classNames, validateNodes } from "../../utils";
import { typesContext } from "../../contexts/typesContext";
import ChatMessage from "./chatMessage";
import { TerminalSquare, MessageSquare, Variable, Eraser } from "lucide-react";
import {
TerminalSquare,
MessageSquare,
Variable,
Eraser,
MessageSquarePlus,
} from "lucide-react";
import { sendAllProps } from "../../types/api";
import { ChatMessageType } from "../../types/chat";
import ChatInput from "./chatInput";
@ -30,6 +36,14 @@ import {
import { Textarea } from "../../components/ui/textarea";
import { Badge } from "../../components/ui/badge";
import ToggleShadComponent from "../../components/toggleShadComponent";
import Dropdown from "../../components/dropdownComponent";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "../../components/ui/dropdown-menu";
import { Button } from "../../components/ui/button";
export default function FormModal({
flow,
@ -40,18 +54,27 @@ export default function FormModal({
setOpen: Function;
flow: FlowType;
}) {
const [chatValue, setChatValue] = useState("");
const [keysValue, setKeysValue] = useState([]);
const { tabsState, setTabsState } = useContext(TabsContext);
const [chatValue, setChatValue] = useState(
tabsState[flow.id].formKeysData.input_keys[
Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
(k) => !tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k)
)
]
);
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext);
const { tabsState } = useContext(TabsContext);
const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
const messagesRef = useRef(null);
const id = useRef(flow.id);
const [chatKey, setChatKey] = useState(0);
const [chatKey, setChatKey] = useState(
Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
(k) => !tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k)
)
);
useEffect(() => {
if (messagesRef.current) {
@ -64,12 +87,6 @@ export default function FormModal({
}, [open]);
useEffect(() => {
id.current = flow.id;
if (tabsState[flow.id].formKeysData) {
setKeysValue(
Array(tabsState[flow.id].formKeysData.input_keys.length).fill("")
);
}
}, [flow.id, tabsState[flow.id], tabsState[flow.id].formKeysData]);
var isStream = false;
@ -166,13 +183,13 @@ export default function FormModal({
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.is_bot ? chatItem.message : formatMessage(chatItem.message),
message: formatMessage(chatItem.message),
thought: chatItem.intermediate_steps,
files: chatItem.files,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.is_bot ? chatItem.message : formatMessage(chatItem.message),
message: formatMessage(chatItem.message),
thought: chatItem.intermediate_steps,
}
);
@ -305,6 +322,7 @@ export default function FormModal({
}, [open]);
function formatMessage(inputs: any): string {
if (inputs) {
if (typeof inputs == "string") return inputs;
// inputs is a object with the keys and values being input_keys and keysValue
// so the formated message is a string with the keys and values separated by ": "
let message = "";
@ -325,13 +343,7 @@ export default function FormModal({
let nodeValidationErrors = validateNodes(reactFlowInstance);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
// Message variable makes a object with the keys being the names from tabsState[flow.id].formKeysData.input_keys and the values being the keysValue of the correspondent index
let keys = tabsState[flow.id].formKeysData.input_keys; // array of keys
let values = keysValue.map((k, i) => (i == chatKey ? chatValue : k)); // array of values
let inputs = keys.reduce((object, key, index) => {
object[key] = values[index];
return object;
}, {});
let inputs = tabsState[id.current].formKeysData.input_keys;
setChatValue("");
const message = formatMessage(inputs);
addChatHistory(message, true);
@ -342,6 +354,11 @@ export default function FormModal({
name: flow.name,
description: flow.description,
});
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
return newTabsState;
});
} else {
setErrorData({
title: "Oops! Looks like you missed some required information:",
@ -365,13 +382,12 @@ export default function FormModal({
setOpen(x);
}
function handleOnCheckedChange(checked: boolean, index: number) {
function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) {
setChatKey(index);
setChatKey(i);
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
}
}
console.log(chatHistory)
return (
<Dialog open={open} onOpenChange={setModalOpen}>
<DialogTrigger className="hidden"></DialogTrigger>
@ -388,47 +404,90 @@ export default function FormModal({
<DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<div className="flex h-[80vh] w-full mt-2">
<div className="w-1/4 h-full flex flex-col justify-start mr-6">
<div className="flex py-2">
<Variable className="w-6 h-6 pe-1 text-gray-700 stroke-2 dark:text-slate-200"></Variable>
<span className="text-md font-semibold text-gray-800 dark:text-white">
Input Variables
</span>
<div className="flex h-[80vh] w-full mt-2 ">
<div className="w-2/5 h-full overflow-auto scrollbar-hide flex flex-col justify-start mr-6">
<div className="flex justify-between items-center">
<div className="flex py-2 items-center">
<Variable className="w-4 h-4 mr-1 text-primary"></Variable>
<span className="text-sm font-semibold text-primary">
Input Variables
</span>
</div>
<div className="flex mr-2.5 py-2 items-center">
<span className="text-sm font-semibold text-primary">
Chat Input
</span>
</div>
</div>
<Accordion type="single" collapsible className="w-full">
{tabsState[id.current].formKeysData.input_keys.map((i, k) => (
<AccordionItem key={k} value={i}>
<AccordionTrigger><Badge variant="gray" size="md">{i}</Badge></AccordionTrigger>
<AccordionContent>
<div className="p-1 flex flex-col gap-2">
<div className="flex items-center space-x-2">
<Label htmlFor="airplane-mode" className="-mt-1">From Chat</Label>
<ToggleShadComponent
enabled={chatKey === k}
setEnabled={(value) =>
handleOnCheckedChange(value, k)
}
size="small"
disabled={false}
/>
</div>
<Textarea
value={keysValue[k]}
onChange={(e) =>
setKeysValue((old) => [...old.slice(0, k), e.target.value, ...old.slice(k + 1)])
}
disabled={chatKey === k}
placeholder="Enter text..."
></Textarea>
</div>
</AccordionContent>
</AccordionItem>
))}
<Accordion type="multiple" className="w-full">
{Object.keys(tabsState[id.current].formKeysData.input_keys).map(
(i, k) => (
<div className="flex items-start gap-3" key={k}>
<AccordionItem className="w-full" key={k} value={i}>
<AccordionTrigger className="flex gap-2">
<div className="flex items-center w-full justify-between">
<Badge variant="gray" size="md">
{i}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<ToggleShadComponent
enabled={chatKey === i}
setEnabled={(value) =>
handleOnCheckedChange(value, i)
}
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys.some((t) => t === i)}
/>
</div>
</div>
</AccordionTrigger>
<AccordionContent>
<div className="p-1 flex flex-col gap-2">
{tabsState[
id.current
].formKeysData.handle_keys.some((t) => t === i) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
value={
tabsState[id.current].formKeysData.input_keys[i]
}
onChange={(e) => {
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[
id.current
].formKeysData.input_keys[i] = e.target.value;
return newTabsState;
});
}}
disabled={chatKey === i}
placeholder="Enter text..."
></Textarea>
</div>
</AccordionContent>
</AccordionItem>
</div>
)
)}
{tabsState[id.current].formKeysData.memory_keys.map((i, k) => (
<AccordionItem key={k} value={i}>
<div className="flex flex-1 items-center justify-between py-4 font-normal transition-all group text-muted-foreground text-sm">
<div className="group-hover:underline"><Badge size="md" variant="gray">{i}</Badge></div>
<div className="group-hover:underline">
<Badge size="md" variant="gray">
{i}
</Badge>
</div>
Used as Memory Key
</div>
</AccordionItem>
@ -437,14 +496,19 @@ export default function FormModal({
</div>
<div className="w-full">
<div className="flex flex-col rounded-md border bg-muted w-full h-full relative">
<div className="absolute right-3 top-3 z-50">
<button disabled={lockChat} onClick={() => clearChat()}>
<Eraser
className={classNames("h-5 w-5", lockChat ? "text-primary animate-pulse" : "text-primary hover:text-gray-600")}
aria-hidden="true"
/>
</button>
</div>
<div className="absolute right-3 top-3 z-50">
<button disabled={lockChat} onClick={() => clearChat()}>
<Eraser
className={classNames(
"h-5 w-5",
lockChat
? "text-primary animate-pulse"
: "text-primary hover:text-gray-600"
)}
aria-hidden="true"
/>
</button>
</div>
<div
ref={messagesRef}
className="w-full h-full flex-col flex items-center overflow-scroll scrollbar-hide"
@ -486,7 +550,16 @@ export default function FormModal({
chatValue={chatValue}
lockChat={lockChat}
sendMessage={sendMessage}
setChatValue={setChatValue}
setChatValue={(value) => {
setChatValue(value);
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[
chatKey
] = chatValue;
return newTabsState;
});
}}
inputRef={ref}
/>
</div>

View file

@ -1,153 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon, DocumentTextIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
import { darkContext } from "../../contexts/darkContext";
import { postValidatePrompt } from "../../controllers/API";
import { alertContext } from "../../contexts/alertContext";
export default function PromptAreaModal({
value,
setValue,
}: {
setValue: (value: string) => void;
value: string;
}) {
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const { closePopUp } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
closePopUp();
}, 300);
}
}
return (
<Transition.Root show={open} appear={true} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-ring dark:bg-gray-600 dark:bg-opacity-75 bg-opacity-75 transition-opacity" />
</Transition.Child>
<div className="fixed inset-0 z-10 overflow-y-auto">
<div className="flex h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative flex flex-col justify-between transform h-[600px] overflow-hidden rounded-lg bg-white dark:bg-gray-800 text-left shadow-xl transition-all sm:my-8 w-[700px]">
<div className=" z-50 absolute top-0 right-0 hidden pt-4 pr-4 sm:block">
<button
type="button"
className="rounded-md text-gray-400 hover:text-gray-500"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="h-full w-full flex flex-col justify-center items-center">
<div className="flex w-full pb-4 z-10 justify-center shadow-sm">
<div className="mx-auto mt-4 flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-blue-100 dark:bg-gray-900 sm:mx-0 sm:h-10 sm:w-10">
<DocumentTextIcon
className="h-6 w-6 text-blue-600"
aria-hidden="true"
/>
</div>
<div className="mt-4 text-center sm:ml-4 sm:text-left">
<Dialog.Title
as="h3"
className="text-lg font-medium dark:text-white leading-10 text-gray-900"
>
Edit Prompt
</Dialog.Title>
</div>
</div>
<div className="h-full w-full bg-accent overflow-auto dark:bg-gray-900 p-4 gap-4 flex flex-row justify-center items-center">
<div className="flex h-full w-full">
<div className="overflow-hidden px-4 py-5 sm:p-6 w-full h-full rounded-lg bg-white dark:bg-gray-800 shadow">
<textarea
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
/>
</div>
</div>
</div>
<div className="bg-gray-200 dark:bg-gray-900 w-full pb-3 flex flex-row-reverse px-4">
<button
type="button"
className="inline-flex w-full justify-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-ring focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:ring-offset-1 sm:ml-3 sm:w-auto sm:text-sm"
onClick={() => {
postValidatePrompt(myValue)
.then((apiReturn) => {
if (apiReturn.data) {
let inputVariables =
apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setErrorData({
title:
"The template you are attempting to use does not contain any variables for data entry.",
});
} else {
setSuccessData({
title: "Prompt is ready",
});
setModalOpen(false);
setValue(myValue);
}
} else {
setErrorData({
title: "Something went wrong, please try again",
});
}
})
.catch((error) => {
return setErrorData({
title:
"There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
});
});
}}
>
Check & Save
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -37,6 +37,7 @@ const nodeTypes = {
export default function Page({ flow }: { flow: FlowType }) {
let {
updateFlow,
uploadFlow,
disableCopyPaste,
addFlow,
getNodeId,
@ -151,6 +152,7 @@ export default function Page({ flow }: { flow: FlowType }) {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
@ -166,6 +168,7 @@ export default function Page({ flow }: { flow: FlowType }) {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
@ -217,62 +220,72 @@ export default function Page({ flow }: { flow: FlowType }) {
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
event.dataTransfer.dropEffect = "move";
} else {
event.dataTransfer.dropEffect = "copy";
}
}, []);
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
takeSnapshot();
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
takeSnapshot();
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds = reactFlowWrapper.current.getBoundingClientRect();
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds =
reactFlowWrapper.current.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("json")
);
// Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse(
event.dataTransfer.getData("nodedata")
);
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
});
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
});
// Generate a unique node ID
let { type } = data;
let newId = getNodeId(type);
let newNode: NodeType;
// Generate a unique node ID
let { type } = data;
let newId = getNodeId(type);
let newNode: NodeType;
if (data.type !== "groupNode") {
// Create a new node object
newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
if (data.type !== "groupNode") {
// Create a new node object
newNode = {
id: newId,
value: null,
},
};
} else {
// Create a new node object
newNode = {
id: newId,
type: "genericNode",
position,
data: {
...data,
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
} else {
// Create a new node object
newNode = {
id: newId,
value: null,
},
};
type: "genericNode",
position,
data: {
...data,
id: newId,
value: null,
},
};
// Add the new node to the list of nodes in state
// Add the new node to the list of nodes in state
}
setNodes((nds) => nds.concat(newNode));
} else if (event.dataTransfer.types.some((t) => t === "Files")) {
takeSnapshot();
uploadFlow(false, event.dataTransfer.files.item(0));
}
setNodes((nds) => nds.concat(newNode));
},
// Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]

View file

@ -40,7 +40,7 @@ export default function ExtraSidebar() {
crt.classList.add("cursor-grabbing");
document.body.appendChild(crt);
event.dataTransfer.setDragImage(crt, 0, 0);
event.dataTransfer.setData("json", JSON.stringify(data));
event.dataTransfer.setData("nodedata", JSON.stringify(data));
}
function handleSearchInput(e: string) {

View file

@ -12,6 +12,7 @@ export type APIClassType = {
description: string;
template: APITemplateType;
display_name: string;
input_types?: Array<string>;
[key: string]: Array<string> | string | APITemplateType;
};

View file

@ -36,6 +36,7 @@ export type ParameterComponentType = {
name?: string;
tooltipTitle: string;
dataContext?: typesContextType;
optionalHandle?: Array<String>;
};
export type InputListComponentType = {
value: string[];

View file

@ -14,7 +14,7 @@ export type TabsContextType = {
downloadFlow: (flow: FlowType) => void;
downloadFlows: () => void;
uploadFlows: () => void;
uploadFlow: (newFlow?: boolean) => void;
uploadFlow: (newFlow?: boolean, file?: File) => void;
hardReset: () => void;
//disable CopyPaste
disableCopyPaste: boolean;
@ -33,6 +33,6 @@ export type TabsContextType = {
export type TabsState = {
[key: string]: {
isPending: boolean;
formKeysData: {input_keys?: Array<string>, memory_keys?: Array<string>};
formKeysData: {input_keys?: Object, memory_keys?: Array<string>, handle_keys?: Array<string>};
};
};

View file

@ -45,6 +45,7 @@ import { twMerge } from "tailwind-merge";
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "./constants";
import { ComponentType, SVGProps } from "react";
import {
Compass,
Cpu,
Fingerprint,
Gift,
@ -139,6 +140,8 @@ export const nodeColors: { [char: string]: string } = {
toolkits: "#DB2C2C",
wrappers: "#E6277A",
utilities: "#31A3CC",
output_parsers: "#E6A627",
str: "#049524",
unknown: "#9CA3AF",
};
@ -158,6 +161,7 @@ export const nodeNames: { [char: string]: string } = {
wrappers: "Wrappers",
textsplitters: "Text Splitters",
utilities: "Utilities",
output_parsers: "Output Parsers",
unknown: "Unknown",
};
@ -363,6 +367,9 @@ export const nodeIconsLucide: {
utilities: Wand2 as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>>
>,
output_parsers: Compass as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>>
>,
unknown: HelpCircle as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>>
>,
@ -625,11 +632,11 @@ export function isValidConnection(
reactFlowInstance: ReactFlowInstance
) {
if (
sourceHandle.split("|")[0] === targetHandle.split("|")[0] ||
targetHandle.split("|")[0].split(";").some((n) => n === sourceHandle.split("|")[0]) ||
sourceHandle
.split("|")
.slice(2)
.some((t) => t === targetHandle.split("|")[0]) ||
.some((t) => targetHandle.split("|")[0].split(";").some((n) => n === t)) ||
targetHandle.split("|")[0] === "str"
) {
let targetNode = reactFlowInstance.getNode(target).data.node;