Formatting project

This commit is contained in:
Cristhian Zanforlin Lousa 2023-06-14 21:22:45 -03:00
commit 86eb9af794
21 changed files with 470 additions and 492 deletions

View file

@ -59,8 +59,7 @@ export default function ParameterComponent({
data.node.template[name]?.value ?? false data.node.template[name]?.value ?? false
); );
useEffect(() => { useEffect(() => {}, [closePopUp, data.node.template]);
}, [closePopUp, data.node.template]);
const { reactFlowInstance } = useContext(typesContext); const { reactFlowInstance } = useContext(typesContext);
let disabled = let disabled =
@ -69,216 +68,209 @@ export default function ParameterComponent({
const [myData, setMyData] = useState(useContext(typesContext).data); const [myData, setMyData] = useState(useContext(typesContext).data);
useEffect(() => { useEffect(() => {
const groupedObj = groupByFamily(myData, tooltipTitle); const groupedObj = groupByFamily(myData, tooltipTitle);
refHtml.current = groupedObj.map((item, i) => ( refHtml.current = groupedObj.map((item, i) => (
<span <span
key={i} key={i}
className={classNames( className={classNames(
i > 0 ? "items-center flex mt-3" : "items-center flex" i > 0 ? "items-center flex mt-3" : "items-center flex"
)} )}
>
<div
className="h-5 w-5"
style={{
color: nodeColors[item.family],
}}
> >
<div {React.createElement(nodeIcons[item.family])}
className="h-5 w-5" </div>
style={{ <span className="ps-2 text-gray-950">
color: nodeColors[item.family], {nodeNames[item.family] ?? ""}{" "}
}} <span className={classNames(left ? "hidden" : "")}>
> {" "}
{React.createElement(nodeIcons[item.family])} -&nbsp;
</div> {item.type.split(", ").length > 2
<span className="ps-2 text-gray-950"> ? item.type.split(", ").map((el, i) => (
{nodeNames[item.family] ?? ""}{" "} <>
<span className={classNames(left ? "hidden" : "")}> <span key={i}>
{" "} {i == item.type.split(", ").length - 1
-&nbsp; ? el
{item.type.split(", ").length > 2 : (el += `, `)}
? item.type.split(", ").map((el, i) => ( </span>
<> {i % 2 == 0 && i > 0 && <br></br>}
<span key={i}> </>
{i == item.type.split(", ").length - 1 ))
? el : item.type}
: (el += `, `)}
</span>
{i % 2 == 0 && i > 0 && <br></br>}
</>
))
: item.type}
</span>
</span> </span>
</span> </span>
)); </span>
));
}, [tooltipTitle]); }, [tooltipTitle]);
return ( return (
<> <>
<div <div
ref={ref} ref={ref}
className="w-full flex flex-wrap justify-between items-center bg-muted dark:bg-gray-800 dark:text-white mt-1 px-5 py-2" className="w-full flex flex-wrap justify-between items-center bg-muted dark:bg-gray-800 dark:text-white mt-1 px-5 py-2"
> >
<> <>
<div className={"text-sm truncate w-full " + (left ? "" : "text-end")}> <div
{title} className={"text-sm truncate w-full " + (left ? "" : "text-end")}
<span className="text-red-600">{required ? " *" : ""}</span>
</div>
{left &&
(type === "str" ||
type === "bool" ||
type === "float" ||
type === "code" ||
type === "prompt" ||
type === "file" ||
type === "int") ? (
<></>
) : (
<ShadTooltip
delayDuration={0}
content={refHtml.current}
side={left ? "left" : "right"}
open={refHtml?.current?.length > 0}
> >
<Handle {title}
type={left ? "target" : "source"} <span className="text-red-600">{required ? " *" : ""}</span>
position={left ? Position.Left : Position.Right} </div>
id={id} {left &&
isValidConnection={(connection) => (type === "str" ||
isValidConnection(connection, reactFlowInstance) type === "bool" ||
} type === "float" ||
className={classNames( type === "code" ||
left ? "-ml-0.5 " : "-mr-0.5 ", type === "prompt" ||
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800" type === "file" ||
)} type === "int") ? (
style={{ <></>
borderColor: color, ) : (
top: position, <ShadTooltip
}} delayDuration={0}
></Handle> content={refHtml.current}
</ShadTooltip> side={left ? "left" : "right"}
)} open={refHtml?.current?.length > 0}
>
{left === true && <Handle
type === "str" && type={left ? "target" : "source"}
!data.node.template[name].options ? ( position={left ? Position.Left : Position.Right}
<div className="mt-2 w-full"> id={id}
{data.node.template[name].list ? ( isValidConnection={(connection) =>
<InputListComponent isValidConnection(connection, reactFlowInstance)
disabled={disabled}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
} }
onChange={(t: string[]) => { className={classNames(
data.node.template[name].value = t; left ? "-ml-0.5 " : "-mr-0.5 ",
"w-3 h-3 rounded-full border-2 bg-white dark:bg-gray-800"
)}
style={{
borderColor: color,
top: position,
}} }}
/> ></Handle>
) : data.node.template[name].multiline ? ( </ShadTooltip>
<TextAreaComponent )}
{left === true &&
type === "str" &&
!data.node.template[name].options ? (
<div className="mt-2 w-full">
{data.node.template[name].list ? (
<InputListComponent
disabled={disabled}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={(t: string[]) => {
data.node.template[name].value = t;
}}
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<InputComponent
disabled={disabled}
disableCopyPaste={true}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
)}
</div>
) : left === true && type === "bool" ? (
<div className="mt-2">
<ToggleComponent
disabled={disabled} disabled={disabled}
value={data.node.template[name].value ?? ""} enabled={enabled}
onChange={(t: string) => { setEnabled={(t) => {
data.node.template[name].value = t; data.node.template[name].value = t;
setEnabled(t);
}} }}
/> />
) : ( </div>
<InputComponent ) : left === true && type === "float" ? (
<div className="mt-2 w-full">
<FloatComponent
disabled={disabled} disabled={disabled}
disableCopyPaste={true} disableCopyPaste={true}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
)} </div>
</div> ) : left === true &&
) : left === true && type === "bool" ? ( type === "str" &&
<div className="mt-2"> data.node.template[name].options ? (
<ToggleComponent <div className="w-full">
<Dropdown
options={data.node.template[name].options}
onSelect={(newValue) =>
(data.node.template[name].value = newValue)
}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
</div>
) : left === true && type === "code" ? (
<CodeAreaComponent
disabled={disabled} disabled={disabled}
enabled={enabled} value={data.node.template[name].value ?? ""}
setEnabled={(t) => { onChange={(t: string) => {
data.node.template[name].value = t; data.node.template[name].value = t;
setEnabled(t);
}} }}
/> />
</div> ) : left === true && type === "file" ? (
) : left === true && type === "float" ? ( <InputFileComponent
<div className="mt-2 w-full">
<FloatComponent
disabled={disabled} disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t) => { onChange={(t: string) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].content = t;
}}
></InputFileComponent>
) : left === true && type === "int" ? (
<div className="mt-2 w-full">
<IntComponent
disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}} }}
/> />
</div> </div>
) : left === true && ) : left === true && type === "prompt" ? (
type === "str" && <PromptAreaComponent
data.node.template[name].options ? (
<div className="w-full">
<Dropdown
options={data.node.template[name].options}
onSelect={(newValue) =>
(data.node.template[name].value = newValue)
}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
</div>
) : left === true && type === "code" ? (
<CodeAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : left === true && type === "file" ? (
<InputFileComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].content = t;
}}
></InputFileComponent>
) : left === true && type === "int" ? (
<div className="mt-2 w-full">
<IntComponent
disabled={disabled} disabled={disabled}
disableCopyPaste={true}
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t) => { onChange={(t: string) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
</div> ) : (
) : left === true && type === "prompt" ? ( <></>
<PromptAreaComponent )}
disabled={disabled} </>
value={data.node.template[name].value ?? ""} </div>
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<></>
)}
</>
</div>
</> </>
); );
} }

View file

@ -101,8 +101,7 @@ export default function GenericNode({
return; return;
} }
useEffect(() => { useEffect(() => {}, [closePopUp, data.node.template]);
}, [closePopUp, data.node.template]);
return ( return (
<> <>

View file

@ -11,14 +11,13 @@ const ShadTooltip = (props) => {
<Tooltip delayDuration={props.delayDuration}> <Tooltip delayDuration={props.delayDuration}>
<TooltipTrigger asChild>{props.children}</TooltipTrigger> <TooltipTrigger asChild>{props.children}</TooltipTrigger>
<TooltipContent <TooltipContent
side={props.side} side={props.side}
avoidCollisions={false} avoidCollisions={false}
sticky="always" sticky="always"
> >
{props.content} {props.content}
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
); );

View file

@ -4,10 +4,25 @@ import { Link } from "react-router-dom";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { FlowType } from "../../types/flow"; import { FlowType } from "../../types/flow";
import { gradients } from "../../utils"; import { gradients } from "../../utils";
import { CardTitle, CardDescription, CardFooter, Card, CardHeader } from "../ui/card"; import {
CardTitle,
CardDescription,
CardFooter,
Card,
CardHeader,
} from "../ui/card";
export const CardComponent = ({
export const CardComponent = ({ flow, id, onDelete, button }: { flow: FlowType; id: string, onDelete?: () => void, button?: JSX.Element }) => { flow,
id,
onDelete,
button,
}: {
flow: FlowType;
id: string;
onDelete?: () => void;
button?: JSX.Element;
}) => {
const { removeFlow } = useContext(TabsContext); const { removeFlow } = useContext(TabsContext);
return ( return (
@ -20,19 +35,14 @@ export const CardComponent = ({ flow, id, onDelete, button }: { flow: FlowType;
"rounded-full w-8 h-8 flex items-center justify-center text-2xl " + "rounded-full w-8 h-8 flex items-center justify-center text-2xl " +
gradients[parseInt(flow.id.slice(0, 6), 16) % gradients.length] gradients[parseInt(flow.id.slice(0, 6), 16) % gradients.length]
} }
> ></span>
</span> <span className="flex-1 truncate-doubleline">{flow.name}</span>
<span className="flex-1 truncate-doubleline">
{flow.name}
</span>
</div> </div>
{onDelete && {onDelete && (
<button <button onClick={onDelete}>
onClick={onDelete} <Trash2 className="w-5 text-primary opacity-0 group-hover:opacity-100 transition-all" />
> </button>
<Trash2 className="w-5 text-primary opacity-0 group-hover:opacity-100 transition-all" /> )}
</button>
}
</CardTitle> </CardTitle>
<CardDescription className="pt-2 pb-2"> <CardDescription className="pt-2 pb-2">
<div className="truncate-doubleline"> <div className="truncate-doubleline">
@ -53,10 +63,7 @@ export const CardComponent = ({ flow, id, onDelete, button }: { flow: FlowType;
<span className="text-base">&nbsp;</span>OpenAI+ <span className="text-base">&nbsp;</span>OpenAI+
</Badge> */} </Badge> */}
</div> </div>
{button && {button && button}
button
}
</div> </div>
</CardFooter> </CardFooter>
</Card> </Card>

View file

@ -29,7 +29,6 @@ export default function Dropdown({
<> <>
<div className={editNode ? "mt-1" : "relative mt-1"}> <div className={editNode ? "mt-1" : "relative mt-1"}>
<Listbox.Button <Listbox.Button
className={ className={
editNode editNode
? "relative pr-8 placeholder:text-center block w-full pt-0.5 pb-0.5 form-input dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 rounded-md shadow-sm sm:text-sm border-gray-300 border-1" + ? "relative pr-8 placeholder:text-center block w-full pt-0.5 pb-0.5 form-input dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 rounded-md shadow-sm sm:text-sm border-gray-300 border-1" +
@ -38,12 +37,10 @@ export default function Dropdown({
INPUT_STYLE INPUT_STYLE
} }
> >
<span className="block truncate w-full">{internalValue}</span> <span className="block truncate w-full">{internalValue}</span>
<span <span
className={ className={
"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
"pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
} }
> >
<ChevronUpDownIcon <ChevronUpDownIcon

View file

@ -49,15 +49,11 @@ export const MenuBar = ({ flows, tabId }) => {
</Link> </Link>
<div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5"> <div className="flex items-center font-medium text-sm rounded-md py-1 px-1.5 gap-0.5">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger > <DropdownMenuTrigger>
<Button <Button className="gap-2 flex items-center" variant="primary">
className="gap-2 flex items-center" {current_flow.name}
variant="primary" <ChevronDown className="w-4 h-4" />
> </Button>
{current_flow.name}
<ChevronDown className="w-4 h-4" />
</Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-44"> <DropdownMenuContent className="w-44">
<DropdownMenuLabel>Edit</DropdownMenuLabel> <DropdownMenuLabel>Edit</DropdownMenuLabel>

View file

@ -28,10 +28,7 @@ export default function Header() {
<span className="text-2xl ml-4">⛓️</span> <span className="text-2xl ml-4">⛓️</span>
</Link> </Link>
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && ( {flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
<MenuBar <MenuBar flows={flows} tabId={tabId} />
flows={flows}
tabId={tabId}
/>
)} )}
</div> </div>
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">

View file

@ -15,8 +15,7 @@ const buttonVariants = cva(
"border border-input hover:bg-accent hover:text-accent-foreground", "border border-input hover:bg-accent hover:text-accent-foreground",
primary: primary:
"border bg-background text-secondary-foreground hover:bg-background/80 hover:shadow-sm", "border bg-background text-secondary-foreground hover:bg-background/80 hover:shadow-sm",
secondary: secondary: "bg-muted text-secondary-foreground hover:bg-secondary/80",
"bg-muted text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-accent hover:text-accent-foreground",
link: "underline-offset-4 hover:underline text-primary", link: "underline-offset-4 hover:underline text-primary",
}, },

View file

@ -129,4 +129,3 @@ export const EXPORT_CODE_DIALOG =
*/ */
export const INPUT_STYLE = export const INPUT_STYLE =
" focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none "; " focus:ring-1 focus:ring-offset-1 focus:ring-ring focus:outline-none ";

View file

@ -14,17 +14,17 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
<> <>
<ReactFlowProvider> <ReactFlowProvider>
<DarkProvider> <DarkProvider>
<TypesProvider> <TypesProvider>
<LocationProvider> <LocationProvider>
<AlertProvider> <AlertProvider>
<TabsProvider> <TabsProvider>
<UndoRedoProvider> <UndoRedoProvider>
<PopUpProvider>{children}</PopUpProvider> <PopUpProvider>{children}</PopUpProvider>
</UndoRedoProvider> </UndoRedoProvider>
</TabsProvider> </TabsProvider>
</AlertProvider> </AlertProvider>
</LocationProvider> </LocationProvider>
</TypesProvider> </TypesProvider>
</DarkProvider> </DarkProvider>
</ReactFlowProvider> </ReactFlowProvider>
</> </>

View file

@ -549,7 +549,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
uploadFlows, uploadFlows,
uploadFlow, uploadFlow,
getNodeId, getNodeId,
paste paste,
}} }}
> >
{children} {children}

View file

@ -1,4 +1,10 @@
import { createContext, useCallback, useContext, useEffect, useState } from "react"; import {
createContext,
useCallback,
useContext,
useEffect,
useState,
} from "react";
import { Edge, Node, useReactFlow } from "reactflow"; import { Edge, Node, useReactFlow } from "reactflow";
import { cloneDeep } from "lodash"; import { cloneDeep } from "lodash";
import { TabsContext } from "./tabsContext"; import { TabsContext } from "./tabsContext";
@ -10,22 +16,22 @@ type undoRedoContextType = {
}; };
type UseUndoRedoOptions = { type UseUndoRedoOptions = {
maxHistorySize: number; maxHistorySize: number;
enableShortcuts: boolean; enableShortcuts: boolean;
}; };
type UseUndoRedo = (options?: UseUndoRedoOptions) => { type UseUndoRedo = (options?: UseUndoRedoOptions) => {
undo: () => void; undo: () => void;
redo: () => void; redo: () => void;
takeSnapshot: () => void; takeSnapshot: () => void;
canUndo: boolean; canUndo: boolean;
canRedo: boolean; canRedo: boolean;
}; };
type HistoryItem = { type HistoryItem = {
nodes: Node[]; nodes: Node[];
edges: Edge[]; edges: Edge[];
}; };
const initialValue = { const initialValue = {
undo: () => {}, undo: () => {},
@ -34,152 +40,144 @@ const initialValue = {
}; };
const defaultOptions: UseUndoRedoOptions = { const defaultOptions: UseUndoRedoOptions = {
maxHistorySize: 100, maxHistorySize: 100,
enableShortcuts: true, enableShortcuts: true,
}; };
export const undoRedoContext = createContext<undoRedoContextType>(initialValue); export const undoRedoContext = createContext<undoRedoContextType>(initialValue);
export function UndoRedoProvider({ children }) { export function UndoRedoProvider({ children }) {
const { tabId, flows } = useContext(TabsContext); const { tabId, flows } = useContext(TabsContext);
const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => [])); const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => [])); const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
const [tabIndex, setTabIndex] = useState(flows.findIndex((f) => f.id === tabId)); const [tabIndex, setTabIndex] = useState(
flows.findIndex((f) => f.id === tabId)
);
useEffect(() => { useEffect(() => {
// whenever the flows variable changes, we need to add one array to the past and future states // whenever the flows variable changes, we need to add one array to the past and future states
setPast((old) => flows.map((f, i) => (old[i] ? old[i] : []))); setPast((old) => flows.map((f, i) => (old[i] ? old[i] : [])));
setFuture((old) => flows.map((f, i) => (old[i] ? old[i] : []))); setFuture((old) => flows.map((f, i) => (old[i] ? old[i] : [])));
setTabIndex(flows.findIndex((f) => f.id === tabId)); setTabIndex(flows.findIndex((f) => f.id === tabId));
}, [flows, tabId]);
}, [flows, tabId]); const { setNodes, setEdges, getNodes, getEdges } = useReactFlow();
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow(); const takeSnapshot = useCallback(() => {
// push the current graph to the past state
console.log(past);
console.log(tabIndex);
setPast((old) => {
let newPast = cloneDeep(old);
newPast[tabIndex] = old[tabIndex].slice(
old[tabIndex].length - defaultOptions.maxHistorySize + 1,
old[tabIndex].length
);
newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
return newPast;
});
const takeSnapshot = useCallback(() => { // whenever we take a new snapshot, the redo operations need to be cleared to avoid state mismatches
// push the current graph to the past state setFuture((old) => {
console.log(past); let newFuture = cloneDeep(old);
console.log(tabIndex); newFuture[tabIndex] = [];
return newFuture;
});
}, [getNodes, getEdges, past, future, flows, tabId, setPast, setFuture]);
const undo = useCallback(() => {
// get the last state that we want to go back to
const pastState = past[tabIndex][past[tabIndex].length - 1];
if (pastState) {
// first we remove the state from the history
setPast((old) => { setPast((old) => {
let newPast = cloneDeep(old); let newPast = cloneDeep(old);
newPast[tabIndex] = old[tabIndex].slice( newPast[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
old[tabIndex].length - defaultOptions.maxHistorySize + 1, return newPast;
old[tabIndex].length });
); // we store the current graph for the redo operation
setFuture((old) => {
let newFuture = cloneDeep(old);
newFuture[tabIndex] = old[tabIndex];
newFuture[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
return newFuture;
});
// now we can set the graph to the past state
setNodes(pastState.nodes);
setEdges(pastState.edges);
}
}, [
setNodes,
setEdges,
getNodes,
getEdges,
future,
past,
setFuture,
setPast,
tabIndex,
]);
const redo = useCallback(() => {
const futureState = future[tabIndex][future[tabIndex].length - 1];
if (futureState) {
setFuture((old) => {
let newFuture = cloneDeep(old);
newFuture[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1);
return newFuture;
});
setPast((old) => {
let newPast = cloneDeep(old);
newPast[tabIndex] = old[tabIndex];
newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() }); newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
return newPast; return newPast;
}); });
setNodes(futureState.nodes);
setEdges(futureState.edges);
}
}, [
future,
past,
setFuture,
setPast,
setNodes,
setEdges,
getNodes,
getEdges,
future,
tabIndex,
]);
// whenever we take a new snapshot, the redo operations need to be cleared to avoid state mismatches useEffect(() => {
setFuture((old) => { // this effect is used to attach the global event handlers
let newFuture = cloneDeep(old); if (!defaultOptions.enableShortcuts) {
newFuture[tabIndex] = []; return;
return newFuture; }
});
}, [
getNodes,
getEdges,
past,
future,
flows,
tabId,
setPast,
setFuture,
]);
const undo = useCallback(() => { const keyDownHandler = (event: KeyboardEvent) => {
// get the last state that we want to go back to if (
const pastState = past[tabIndex][past[tabIndex].length - 1]; event.key === "z" &&
(event.ctrlKey || event.metaKey) &&
if (pastState) { event.shiftKey
// first we remove the state from the history ) {
setPast((old) => { redo();
let newPast = cloneDeep(old); } else if (event.key === "y" && (event.ctrlKey || event.metaKey)) {
newPast[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1); event.preventDefault(); // prevent the default action
return newPast; redo();
}); } else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
// we store the current graph for the redo operation undo();
setFuture((old) => {
let newFuture = cloneDeep(old);
newFuture[tabIndex] = old[tabIndex];
newFuture[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
return newFuture;
});
// now we can set the graph to the past state
setNodes(pastState.nodes);
setEdges(pastState.edges);
} }
}, [ };
setNodes,
setEdges,
getNodes,
getEdges,
future,
past,
setFuture,
setPast,
tabIndex,
]);
const redo = useCallback(() => { document.addEventListener("keydown", keyDownHandler);
const futureState = future[tabIndex][future[tabIndex].length - 1];
if (futureState) { return () => {
setFuture((old) => { document.removeEventListener("keydown", keyDownHandler);
let newFuture = cloneDeep(old); };
newFuture[tabIndex] = old[tabIndex].slice(0, old[tabIndex].length - 1); }, [undo, redo]);
return newFuture;
});
setPast((old) => {
let newPast = cloneDeep(old);
newPast[tabIndex] = old[tabIndex];
newPast[tabIndex].push({ nodes: getNodes(), edges: getEdges() });
return newPast;
});
setNodes(futureState.nodes);
setEdges(futureState.edges);
}
}, [
future,
past,
setFuture,
setPast,
setNodes,
setEdges,
getNodes,
getEdges,
future,
tabIndex,
]);
useEffect(() => {
// this effect is used to attach the global event handlers
if (!defaultOptions.enableShortcuts) {
return;
}
const keyDownHandler = (event: KeyboardEvent) => {
if (
event.key === "z" &&
(event.ctrlKey || event.metaKey) &&
event.shiftKey
) {
redo();
} else if (event.key === "y" && (event.ctrlKey || event.metaKey)) {
event.preventDefault(); // prevent the default action
redo();
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
undo();
}
};
document.addEventListener("keydown", keyDownHandler);
return () => {
document.removeEventListener("keydown", keyDownHandler);
};
}, [undo, redo]);
return ( return (
<undoRedoContext.Provider <undoRedoContext.Provider
value={{ value={{

View file

@ -172,7 +172,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
} }
onChange={(t: string[]) => { onChange={(t: string[]) => {
data.node.template[n].value = t; data.node.template[n].value = t;
}} }}
/> />
) : data.node.template[n].multiline ? ( ) : data.node.template[n].multiline ? (
@ -182,7 +181,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[n].value = t; data.node.template[n].value = t;
}} }}
/> />
) : ( ) : (
@ -195,7 +193,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[n].value = t; data.node.template[n].value = t;
}} }}
/> />
)} )}
@ -208,7 +205,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
setEnabled={(e) => { setEnabled={(e) => {
data.node.template[n].value = e; data.node.template[n].value = e;
setEnabled(e); setEnabled(e);
}} }}
disabled={false} disabled={false}
/> />
@ -221,7 +217,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[n].value = t; data.node.template[n].value = t;
}} }}
/> />
</div> </div>
@ -249,7 +244,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[n].value = t; data.node.template[n].value = t;
}} }}
/> />
</div> </div>
@ -266,7 +260,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
suffixes={data.node.template[n].suffixes} suffixes={data.node.template[n].suffixes}
onFileChange={(t: string) => { onFileChange={(t: string) => {
data.node.template[n].content = t; data.node.template[n].content = t;
}} }}
></InputFileComponent> ></InputFileComponent>
</div> </div>
@ -278,7 +271,6 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
value={data.node.template[n].value ?? ""} value={data.node.template[n].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[n].value = t; data.node.template[n].value = t;
}} }}
/> />
</div> </div>

View file

@ -70,7 +70,6 @@ export default function ModalField({
} }
onChange={(t: string[]) => { onChange={(t: string[]) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
) : data.node.template[name].multiline ? ( ) : data.node.template[name].multiline ? (
@ -79,7 +78,6 @@ export default function ModalField({
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
) : ( ) : (
@ -89,7 +87,6 @@ export default function ModalField({
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
)} )}
@ -103,7 +100,6 @@ export default function ModalField({
setEnabled={(t) => { setEnabled={(t) => {
data.node.template[name].value = t; data.node.template[name].value = t;
setEnabled(t); setEnabled(t);
}} }}
/> />
</div> </div>
@ -114,7 +110,6 @@ export default function ModalField({
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
</div> </div>
@ -133,7 +128,6 @@ export default function ModalField({
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t) => { onChange={(t) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
</div> </div>
@ -149,7 +143,6 @@ export default function ModalField({
suffixes={data.node.template[name].suffixes} suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => { onFileChange={(t: string) => {
data.node.template[name].content = t; data.node.template[name].content = t;
}} }}
></InputFileComponent> ></InputFileComponent>
</div> </div>
@ -160,7 +153,6 @@ export default function ModalField({
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
</div> </div>
@ -171,7 +163,6 @@ export default function ModalField({
value={data.node.template[name].value ?? ""} value={data.node.template[name].value ?? ""}
onChange={(t: string) => { onChange={(t: string) => {
data.node.template[name].value = t; data.node.template[name].value = t;
}} }}
/> />
</div> </div>

View file

@ -20,6 +20,7 @@ import EditFlowSettings from "../../components/nameInputComponent";
import { Label } from "../../components/ui/label"; import { Label } from "../../components/ui/label";
import { Input } from "../../components/ui/input"; import { Input } from "../../components/ui/input";
import { Textarea } from "../../components/ui/textarea"; import { Textarea } from "../../components/ui/textarea";
import { Download } from "lucide-react";
export default function ExportModal() { export default function ExportModal() {
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
@ -48,7 +49,7 @@ export default function ExportModal() {
<DialogHeader> <DialogHeader>
<DialogTitle className="flex items-center"> <DialogTitle className="flex items-center">
<span className="pr-2">Export</span> <span className="pr-2">Export</span>
<ArrowDownTrayIcon <Download
className="h-6 w-6 text-gray-800 pl-1 dark:text-white" className="h-6 w-6 text-gray-800 pl-1 dark:text-white"
aria-hidden="true" aria-hidden="true"
/> />

View file

@ -85,24 +85,33 @@ export default function CommunityPage() {
</div> </div>
</div> </div>
<span className="flex pb-8 px-6 w-[70%] text-muted-foreground"> <span className="flex pb-8 px-6 w-[70%] text-muted-foreground">
Discover and learn from shared examples by the LangFlow community. We welcome new example contributions that can help our community explore new and powerful features. Discover and learn from shared examples by the LangFlow community. We
welcome new example contributions that can help our community explore
new and powerful features.
</span> </span>
<div className="w-full p-4 grid gap-4 md:grid-cols-2 lg:grid-cols-4"> <div className="w-full p-4 grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{!loadingExamples && {!loadingExamples &&
examples.map((flow, idx) => ( examples.map((flow, idx) => (
<CardComponent key={idx} flow={flow} id={flow.id} button={<Button <CardComponent
variant="outline" key={idx}
size="sm" flow={flow}
className="whitespace-nowrap " id={flow.id}
onClick={() => { button={
addFlow(flow, true).then((id) => { <Button
navigate("/flow/" + id); variant="outline"
}); size="sm"
}} className="whitespace-nowrap "
> onClick={() => {
<GitFork className="w-4 mr-2" /> addFlow(flow, true).then((id) => {
Fork Example navigate("/flow/" + id);
</Button>}/> });
}}
>
<GitFork className="w-4 mr-2" />
Fork Example
</Button>
}
/>
))} ))}
</div> </div>
</div> </div>

View file

@ -317,53 +317,53 @@ export default function Page({ flow }: { flow: FlowType }) {
<div className="w-full h-full" ref={reactFlowWrapper}> <div className="w-full h-full" ref={reactFlowWrapper}>
{Object.keys(templates).length > 0 && {Object.keys(templates).length > 0 &&
Object.keys(types).length > 0 ? ( Object.keys(types).length > 0 ? (
<div className="w-full h-full"> <div className="w-full h-full">
<ReactFlow <ReactFlow
nodes={nodes} nodes={nodes}
onMove={() => { onMove={() => {
updateFlow({ updateFlow({
...flow, ...flow,
data: reactFlowInstance.toObject(), data: reactFlowInstance.toObject(),
}); });
}} }}
edges={edges} edges={edges}
onPaneClick={() => { onPaneClick={() => {
setDisableCopyPaste(false); setDisableCopyPaste(false);
}} }}
onPaneMouseLeave={() => { onPaneMouseLeave={() => {
setDisableCopyPaste(true); setDisableCopyPaste(true);
}} }}
onNodesChange={onNodesChange} onNodesChange={onNodesChange}
onEdgesChange={onEdgesChangeMod} onEdgesChange={onEdgesChangeMod}
onConnect={onConnect} onConnect={onConnect}
disableKeyboardA11y={true} disableKeyboardA11y={true}
onLoad={setReactFlowInstance} onLoad={setReactFlowInstance}
onInit={setReactFlowInstance} onInit={setReactFlowInstance}
nodeTypes={nodeTypes} nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate} onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart} onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd} onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart} onNodeDragStart={onNodeDragStart}
onSelectionDragStart={onSelectionDragStart} onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd} onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart} onSelectionStart={onSelectionStart}
onEdgesDelete={onEdgesDelete} onEdgesDelete={onEdgesDelete}
connectionLineComponent={ConnectionLineComponent} connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver} onDragOver={onDragOver}
onDrop={onDrop} onDrop={onDrop}
onNodesDelete={onDelete} onNodesDelete={onDelete}
onSelectionChange={onSelectionChange} onSelectionChange={onSelectionChange}
nodesDraggable={!disableCopyPaste} nodesDraggable={!disableCopyPaste}
panOnDrag={!disableCopyPaste} panOnDrag={!disableCopyPaste}
zoomOnDoubleClick={!disableCopyPaste} zoomOnDoubleClick={!disableCopyPaste}
selectNodesOnDrag={false} selectNodesOnDrag={false}
className="theme-attribution" className="theme-attribution"
> >
<Background className="dark:bg-gray-900" /> <Background className="dark:bg-gray-900" />
<Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls> <Controls className="[&>button]:text-black [&>button]:dark:bg-gray-800 hover:[&>button]:dark:bg-gray-700 [&>button]:dark:text-gray-400 [&>button]:dark:fill-gray-400 [&>button]:dark:border-gray-600"></Controls>
</ReactFlow> </ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} /> <Chat flow={flow} reactFlowInstance={reactFlowInstance} />
</div> </div>
) : ( ) : (
<></> <></>
)} )}

View file

@ -3,17 +3,19 @@ import Page from "./components/PageComponent";
import { TabsContext } from "../../contexts/tabsContext"; import { TabsContext } from "../../contexts/tabsContext";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
export default function FlowPage(){ export default function FlowPage() {
const { flows, tabId, setTabId } = useContext(TabsContext); const { flows, tabId, setTabId } = useContext(TabsContext);
const {id} = useParams(); const { id } = useParams();
useEffect(() => { useEffect(() => {
setTabId(id); setTabId(id);
}, [id]) }, [id]);
return ( return (
<div className="h-full w-full overflow-hidden"> <div className="h-full w-full overflow-hidden">
{flows.length > 0 && tabId !== "" && flows.findIndex(flow => flow.id === tabId) !== -1 && {flows.length > 0 &&
<Page flow={flows.find(flow => flow.id === tabId)} /> tabId !== "" &&
} flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page flow={flows.find((flow) => flow.id === tabId)} />
)}
</div> </div>
) );
} }

View file

@ -1,18 +1,18 @@
import { Route, Routes } from "react-router-dom" import { Route, Routes } from "react-router-dom";
import HomePage from "./pages/MainPage"; import HomePage from "./pages/MainPage";
import FlowPage from "./pages/FlowPage"; import FlowPage from "./pages/FlowPage";
import CommunityPage from "./pages/CommunityPage"; import CommunityPage from "./pages/CommunityPage";
const Router = () => { const Router = () => {
return( return (
<Routes> <Routes>
<Route path="/" element={<HomePage/>}/> <Route path="/" element={<HomePage />} />
<Route path="/community" element={<CommunityPage/>}/> <Route path="/community" element={<CommunityPage />} />
<Route path="/flow/:id/"> <Route path="/flow/:id/">
<Route path="" element={<FlowPage/>}/> <Route path="" element={<FlowPage />} />
</Route> </Route>
</Routes> </Routes>
) );
} };
export default Router; export default Router;