Merge branch 'dev' into python_custom_node_component

This commit is contained in:
gustavoschaedler 2023-07-12 16:34:21 +01:00
commit a6fe4091ac
78 changed files with 1415 additions and 1669 deletions

View file

@ -5,7 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="/favicon.ico" />
<script src="node_modules/ace-builds/src-min-noconflict/ace.js" type="text/javascript"></script>
<script src="/node_modules/ace-builds/src-min-noconflict/ace.js" type="text/javascript"></script>
<title>Langflow</title>
</head>
<body id='body' style="width: 100%; height:100%">

View file

@ -12,10 +12,12 @@ import IntComponent from "../../../../components/intComponent";
import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { MAX_LENGTH_TO_SCROLL_TOOLTIP } from "../../../../constants";
import { PopUpContext } from "../../../../contexts/popUpContext";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
import { cleanEdges } from "../../../../util/reactflowUtils";
import {
classNames,
getRandomKeyByssmm,
@ -41,6 +43,7 @@ export default function ParameterComponent({
}: ParameterComponentType) {
const ref = useRef(null);
const refHtml = useRef(null);
const refNumberComponents = useRef(0);
const infoHtml = useRef(null);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
@ -58,10 +61,6 @@ export default function ParameterComponent({
updateNodeInternals(data.id);
}, [data.id, position, updateNodeInternals]);
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
);
useEffect(() => {}, [closePopUp, data.node.template]);
const { reactFlowInstance } = useContext(typesContext);
@ -99,6 +98,8 @@ export default function ParameterComponent({
useEffect(() => {
const groupedObj = groupByFamily(myData, tooltipTitle, left, data.type);
refNumberComponents.current = groupedObj[0]?.type?.length;
refHtml.current = groupedObj.map((item, i) => {
const Icon: any = nodeIconsLucide[item.family];
@ -127,7 +128,7 @@ export default function ParameterComponent({
{nodeNames[item.family] ?? ""}{" "}
<span className="text-xs">
{" "}
{item.type == "" ? "" : " - "}
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, i) => (
<React.Fragment key={el + i}>
@ -160,7 +161,7 @@ export default function ParameterComponent({
}
>
{title}
<span className="text-destructive">{required ? " *" : ""}</span>
<span className="text-status-red">{required ? " *" : ""}</span>
<div className="">
{info !== "" && (
<ShadTooltip content={infoHtml.current}>
@ -181,7 +182,11 @@ export default function ParameterComponent({
<></>
) : (
<ShadTooltip
style="max-w-[30vw] max-h-[20vh] overflow-auto custom-scroll"
styleClasses={
refNumberComponents.current > MAX_LENGTH_TO_SCROLL_TOOLTIP
? "tooltip-fixed-width custom-scroll overflow-y-scroll nowheel"
: "tooltip-fixed-width"
}
delayDuration={0}
content={refHtml.current}
side={left ? "left" : "right"}
@ -222,7 +227,7 @@ export default function ParameterComponent({
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={false}
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
@ -240,10 +245,9 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<ToggleShadComponent
disabled={disabled}
enabled={enabled}
enabled={data.node.template[name].value}
setEnabled={(t) => {
handleOnNewValue(t);
setEnabled(t);
}}
size="large"
/>
@ -309,6 +313,15 @@ export default function ParameterComponent({
field_name={name}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
if (reactFlowInstance) {
cleanEdges({
flow: {
edges: reactFlowInstance.getEdges(),
nodes: reactFlowInstance.getNodes(),
},
updateEdge: (edge) => reactFlowInstance.setEdges(edge),
});
}
}}
nodeClass={data.node}
disabled={disabled}

View file

@ -114,7 +114,9 @@ export default function GenericNode({
<div>
<Tooltip
title={
!validationStatus ? (
isBuilding ? (
<span>Building...</span>
) : !validationStatus ? (
<span className="flex">
Build{" "}
<Zap

View file

@ -24,7 +24,7 @@ export default function AlertDropdown({}: AlertDropdownType) {
return (
<div
ref={componentRef}
className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-background px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-muted px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
>
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
Notifications

View file

@ -20,6 +20,7 @@ export default function ErrorAlert({
}, 5000);
}
}, [id, removeAlert, show]);
return (
<Transition
className="relative"
@ -50,7 +51,8 @@ export default function ErrorAlert({
</div>
<div className="ml-3">
<h3 className="error-build-foreground">{title}</h3>
{list.length !== 0 ? (
{list?.length !== 0 &&
list?.some((item) => item !== null && item !== undefined) ? (
<div className="error-build-message-div">
<ul className="error-build-message-list">
{list.map((item, index) => (

View file

@ -13,7 +13,7 @@ export default function AccordionComponent({
open = [],
}: AccordionComponentType) {
const [value, setValue] = useState(
open.length == 0 ? "" : getOpenAccordion()
open.length === 0 ? "" : getOpenAccordion()
);
function getOpenAccordion() {

View file

@ -0,0 +1,21 @@
import DOMPurify from "dompurify";
const SanitizedHTMLWrapper = ({
className,
content,
onClick,
suppressWarning = false,
}) => {
const sanitizedHTML = DOMPurify.sanitize(content);
return (
<div
className={className}
dangerouslySetInnerHTML={{ __html: sanitizedHTML }}
suppressContentEditableWarning={suppressWarning}
onClick={onClick}
/>
);
};
export default SanitizedHTMLWrapper;

View file

@ -1,33 +1,26 @@
import { ShadToolTipType } from "../../types/components";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "../ui/tooltip";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
export default function ShadTooltip({
content,
side,
asChild = true,
children,
style,
styleClasses,
delayDuration = 500,
}: ShadToolTipType) {
return (
<TooltipProvider>
<Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
<TooltipContent
className={style}
side={side}
avoidCollisions={false}
sticky="always"
>
{content}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<TooltipContent
className={styleClasses}
side={side}
avoidCollisions={false}
sticky="always"
>
{content}
</TooltipContent>
</Tooltip>
);
}

View file

@ -72,7 +72,7 @@ export default function Chat({ flow }: ChatType) {
}
prevNodesRef.current = currentNodes;
}, [tabsState]);
}, [tabsState, flow.id]);
return (
<>
@ -83,9 +83,17 @@ export default function Chat({ flow }: ChatType) {
setIsBuilt={setIsBuilt}
isBuilt={isBuilt}
/>
{isBuilt && canOpen && (
<FormModal key={flow.id} flow={flow} open={open} setOpen={setOpen} />
)}
{isBuilt &&
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
canOpen && (
<FormModal
key={flow.id}
flow={flow}
open={open}
setOpen={setOpen}
/>
)}
<ChatTrigger
canOpen={canOpen}
open={open}

View file

@ -63,8 +63,8 @@ export default function Dropdown({
<Listbox.Options
className={classNames(
editNode
? "dropdown-component-true-options nowheel"
: "dropdown-component-false-options nowheel",
? "dropdown-component-true-options nowheel custom-scroll"
: "dropdown-component-false-options nowheel custom-scroll",
apiModal ? "mb-2 w-[250px]" : "absolute"
)}
>

View file

@ -14,7 +14,6 @@ import {
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "../../../ui/dropdown-menu";
@ -52,13 +51,11 @@ export const MenuBar = ({ flows, tabId }) => {
<div className="header-menu-bar">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="header-menu-bar-display"
variant="primary"
size="sm"
>
<div className="header-menu-flow-name">{current_flow.name}</div>
<ChevronDown className="h-4 w-4" />
<Button asChild variant="primary" size="sm">
<div className="header-menu-bar-display">
<div className="header-menu-flow-name">{current_flow.name}</div>
<ChevronDown className="h-4 w-4" />
</div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-44">
@ -99,7 +96,7 @@ export const MenuBar = ({ flows, tabId }) => {
<Redo className="header-menu-options " />
Redo
</DropdownMenuItem>
<DropdownMenuSeparator />
{/* <DropdownMenuSeparator /> */}
{/* <DropdownMenuLabel>Projects</DropdownMenuLabel> */}
{/* <DropdownMenuRadioGroup className="max-h-full overflow-scroll"
value={tabId}

View file

@ -121,7 +121,7 @@ export default function Header() {
<>
<div
className="absolute z-10"
style={{ top: top + 34, left: left - AlertWidth }}
style={{ top: top + 40, left: left - AlertWidth }}
>
<AlertDropdown />
</div>

View file

@ -39,11 +39,13 @@ export default function InputComponent({
if (disableCopyPaste) setDisableCopyPaste(false);
}}
className={classNames(
" pr-9 ",
disabled ? " input-disable " : "",
password && !pwdVisible && myValue !== "" ? "password" : "",
password && !pwdVisible && myValue !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : " input-primary ",
password && editNode ? "pr-8" : "pr-3"
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : ""
)}
placeholder={password && editNode ? "Key" : "Type something..."}
onChange={(e) => {

View file

@ -98,10 +98,10 @@ export default function InputFileComponent({
onClick={handleButtonClick}
className={
editNode
? " input-edit-node " + " input-dialog "
: (disabled ? " input-disable " : "") +
" input-primary " +
" input-dialog "
? "input-edit-node input-dialog text-muted-foreground"
: disabled
? "input-disable input-dialog input-primary"
: "input-dialog input-primary text-muted-foreground"
}
>
{myValue !== "" ? myValue : "No file"}

View file

@ -30,7 +30,7 @@ export default function IntComponent({
<div
className={
"w-full " +
(disabled ? "pointer-events-none w-full cursor-not-allowed" : "w-full")
(disabled ? "pointer-events-none w-full cursor-not-allowed" : "")
}
>
<input
@ -70,7 +70,7 @@ export default function IntComponent({
className={
editNode
? " input-edit-node "
: " input-primary " + (disabled ? " input-disable " : "")
: " input-primary " + (disabled ? " input-disable" : "")
}
placeholder={editNode ? "Integer number" : "Type an integer number"}
onChange={(e) => {

View file

@ -4,7 +4,6 @@ import GenericModal from "../../modals/genericModal";
import { TextAreaComponentType } from "../../types/components";
import { TypeModal } from "../../utils";
import * as _ from "lodash";
import { ExternalLink } from "lucide-react";
import { typesContext } from "../../contexts/typesContext";
import { postValidatePrompt } from "../../controllers/API";
@ -18,7 +17,7 @@ export default function PromptAreaComponent({
disabled,
editNode = false,
}: TextAreaComponentType) {
const [myValue, setMyValue] = useState("");
const [myValue, setMyValue] = useState(value);
const { openPopUp } = useContext(PopUpContext);
const { reactFlowInstance } = useContext(typesContext);
useEffect(() => {
@ -29,84 +28,93 @@ export default function PromptAreaComponent({
}, [disabled, onChange]);
useEffect(() => {
if (value !== "" && myValue !== value && reactFlowInstance) {
// only executed once
setMyValue(value);
postValidatePrompt(field_name, 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) => {});
setMyValue(value);
if (value !== "" && !editNode) {
postValidatePrompt(field_name, value, nodeClass).then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
// need to update reactFlowInstance to re-render the nodes.
}
});
}
}, [reactFlowInstance, field_name]);
}, [value, reactFlowInstance]);
// useEffect(() => {
// if (value !== "" && myValue !== value && reactFlowInstance) {
// // only executed once
// setMyValue(value);
// postValidatePrompt(field_name, 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, field_name, myValue, nodeClass, setNodeClass, value]);
return (
<div className={disabled ? "cursor-not-allowed" : ""}>
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className="flex w-full items-center">
<span
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
className={
editNode
? " input-edit-node " + " input-dialog "
: (disabled ? " input-disable " : "") +
" input-primary " +
" input-dialog "
}
>
{myValue !== "" ? myValue : "Type your prompt here"}
</span>
<button
onClick={() => {
openPopUp(
<GenericModal
field_name={field_name}
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
>
{!editNode && (
<ExternalLink
strokeWidth={1.5}
className={
"icons-parameters-comp" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className="flex w-full items-center">
<span
onClick={() => {
openPopUp(
<GenericModal
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
)}
</button>
</div>
);
}}
className={
editNode
? "input-edit-node input-dialog"
: (disabled ? " input-disable text-ring " : "") +
" input-primary text-muted-foreground "
}
>
{myValue !== "" ? myValue : "Type your prompt here"}
</span>
<button
onClick={() => {
openPopUp(
<GenericModal
field_name={field_name}
type={TypeModal.PROMPT}
value={myValue}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
setMyValue(t);
onChange(t);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
/>
);
}}
>
{!editNode && (
<ExternalLink
strokeWidth={1.5}
className={
"icons-parameters-comp" +
(disabled ? " text-ring" : " hover:text-accent-foreground")
}
/>
)}
</button>
</div>
</div>
);

View file

@ -5,6 +5,7 @@ import { TextAreaComponentType } from "../../types/components";
import { TypeModal } from "../../utils";
import { ExternalLink } from "lucide-react";
import { TabsContext } from "../../contexts/tabsContext";
export default function TextAreaComponent({
value,
@ -14,6 +15,7 @@ export default function TextAreaComponent({
}: TextAreaComponentType) {
const [myValue, setMyValue] = useState(value);
const { openPopUp, closePopUp } = useContext(PopUpContext);
const { setDisableCopyPaste } = useContext(TabsContext);
useEffect(() => {
if (disabled) {
@ -27,19 +29,20 @@ export default function TextAreaComponent({
}, [closePopUp]);
return (
<div className={disabled ? "cursor-not-allowed" : ""}>
<div
className={
(editNode ? "relative top-2 w-full" : "flex w-full items-center") +
(disabled ? " pointer-events-none" : "")
}
>
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
<div className="flex w-full items-center">
<input
value={myValue}
onFocus={() => {
setDisableCopyPaste(true);
}}
onBlur={() => {
setDisableCopyPaste(false);
}}
className={
editNode
? "input-edit-node"
: "input-primary" + (disabled ? " input-disable " : "")
? " input-edit-node "
: " input-primary " + (disabled ? " input-disable" : "")
}
placeholder={"Type something..."}
onChange={(e) => {

View file

@ -17,7 +17,7 @@ const Separator = React.forwardRef<
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
"shrink-0 bg-ring/40",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}

View file

@ -14,15 +14,17 @@ const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}
/>
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
className
)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

View file

@ -5,6 +5,12 @@ import { FlowType } from "./types/flow";
import { TabsState } from "./types/tabs";
import { buildInputs, buildTweaks } from "./utils";
/**
* Number maximum of components to scroll on tooltips
* @constant
*/
export const MAX_LENGTH_TO_SCROLL_TOOLTIP = 200;
/**
* The base text for subtitle of Export Dialog (Toolbar)
* @constant
@ -545,10 +551,3 @@ export const NOUNS: string[] = [
*
*/
export const USER_PROJECTS_HEADER = "My Collection";
/**
* CSS for highlight HTML
* @constant
*
*/
export const HIGHLIGH_CSS =
"block pl-3 pr-14 py-2 w-full h-full text-sm outline-0 border-0 break-all overflow-y-hidden max-w-[75vw]";

View file

@ -1,5 +1,6 @@
import { ReactNode } from "react";
import { ReactFlowProvider } from "reactflow";
import { TooltipProvider } from "../components/ui/tooltip";
import { SSEProvider } from "./SSEContext";
import { AlertProvider } from "./alertContext";
import { DarkProvider } from "./darkContext";
@ -13,23 +14,25 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
//element to wrap all context
return (
<>
<ReactFlowProvider>
<DarkProvider>
<TypesProvider>
<LocationProvider>
<AlertProvider>
<SSEProvider>
<TabsProvider>
<UndoRedoProvider>
<PopUpProvider>{children}</PopUpProvider>
</UndoRedoProvider>
</TabsProvider>
</SSEProvider>
</AlertProvider>
</LocationProvider>
</TypesProvider>
</DarkProvider>
</ReactFlowProvider>
<TooltipProvider>
<ReactFlowProvider>
<DarkProvider>
<TypesProvider>
<LocationProvider>
<AlertProvider>
<SSEProvider>
<TabsProvider>
<UndoRedoProvider>
<PopUpProvider>{children}</PopUpProvider>
</UndoRedoProvider>
</TabsProvider>
</SSEProvider>
</AlertProvider>
</LocationProvider>
</TypesProvider>
</DarkProvider>
</ReactFlowProvider>
</TooltipProvider>
</>
);
}

View file

@ -54,7 +54,7 @@ const TabsContextInitialValue: TabsContextType = {
setTabsState: (state: TabsState) => {},
getNodeId: (nodeType: string) => "",
setTweak: (tweak: any) => {},
getTweak: {},
getTweak: [],
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
@ -75,7 +75,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const { templates, reactFlowInstance } = useContext(typesContext);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
const [tabsState, setTabsState] = useState<TabsState>({});
const [getTweak, setTweak] = useState({});
const [getTweak, setTweak] = useState([]);
const newNodeId = useRef(uid());
function incrementNodeId() {
@ -539,11 +539,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const updateEdges = (edges) => {
edges.forEach((edge) => {
edge.style = { stroke: "inherit" };
edge.className =
edge.targetHandle.split("|")[0] === "Text"
(edge.targetHandle.split("|")[0] === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ";
: "stroke-gray-900 ") + " stroke-connection";
edge.animated = edge.targetHandle.split("|")[0] === "Text";
});
};

View file

@ -76,7 +76,7 @@ export async function postValidatePrompt(
*/
export async function getExamples(): Promise<FlowType[]> {
const url =
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples";
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples?ref=fix_examples";
const response = await axios.get(url);
const jsonFiles = response.data.filter((file: any) => {

View file

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 48 48"><defs><path id="a" d="M44.5 20H24v8.5h11.8C34.7 33.9 30.1 37 24 37c-7.2 0-13-5.8-13-13s5.8-13 13-13c3.1 0 5.9 1.1 8.1 2.9l6.4-6.4C34.6 4.1 29.6 2 24 2 11.8 2 2 11.8 2 24s9.8 22 22 22c11 0 21-8 21-22 0-1.3-.2-2.7-.5-4z"/></defs><clipPath id="b"><use xlink:href="#a" overflow="visible"/></clipPath><path clip-path="url(#b)" fill="#FBBC05" d="M0 37V11l17 13z"/><path clip-path="url(#b)" fill="#EA4335" d="M0 11l17 13 7-6.1L48 14V0H0z"/><path clip-path="url(#b)" fill="#34A853" d="M0 37l30-23 7.9 1L48 0v48H0z"/><path clip-path="url(#b)" fill="#4285F4" d="M48 48L17 24l-4-3 35-10z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="2443" height="2500" preserveAspectRatio="xMidYMid" viewBox="0 0 256 262" id="google"><path fill="#4285F4" d="M255.878 133.451c0-10.734-.871-18.567-2.756-26.69H130.55v48.448h71.947c-1.45 12.04-9.283 30.172-26.69 42.356l-.244 1.622 38.755 30.023 2.685.268c24.659-22.774 38.875-56.282 38.875-96.027"></path><path fill="#34A853" d="M130.55 261.1c35.248 0 64.839-11.605 86.453-31.622l-41.196-31.913c-11.024 7.688-25.82 13.055-45.257 13.055-34.523 0-63.824-22.773-74.269-54.25l-1.531.13-40.298 31.187-.527 1.465C35.393 231.798 79.49 261.1 130.55 261.1"></path><path fill="#FBBC05" d="M56.281 156.37c-2.756-8.123-4.351-16.827-4.351-25.82 0-8.994 1.595-17.697 4.206-25.82l-.073-1.73L15.26 71.312l-1.335.635C5.077 89.644 0 109.517 0 130.55s5.077 40.905 13.925 58.602l42.356-32.782"></path><path fill="#EB4335" d="M130.55 50.479c24.514 0 41.05 10.589 50.479 19.438l36.844-35.974C195.245 12.91 165.798 0 130.55 0 79.49 0 35.393 29.301 13.925 71.947l42.211 32.783c10.59-31.477 39.891-54.251 74.414-54.251"></path></svg>

Before

Width:  |  Height:  |  Size: 688 B

After

Width:  |  Height:  |  Size: 1 KiB

Before After
Before After

View file

@ -1,7 +1,7 @@
import React, { forwardRef } from "react";
import { ReactComponent as HugginFaceSVG } from "./hf-logo.svg";
export const HugginFaceIcon = forwardRef<
export const HuggingFaceIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {

View file

@ -6,8 +6,8 @@
/* TODO: Confirm that all colors here are found in tailwind config */
@layer base {
:root {
:root {
--background: 0 0% 100%; /* hsl(0 0% 100%) */
--foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--muted: 210 40% 98%; /* hsl(210 40% 98%) */
@ -27,36 +27,40 @@
--destructive: 0 100% 50%; /* hsl(0 100% 50%) */
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--radius: 0.5rem;
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--round-btn-shadow: #00000063;
--error-background: #fef2f2;
--error-foreground: #991b1b;
--success-background: #f0fdf4;
--success-foreground: #14532d;
--info-background: #f0f4fd;
--info-foreground: #141653;
--high-indigo: #4338ca;
--medium-indigo: #6366f1;
--chat-bot-icon: #afe6ef;
--chat-user-icon: #aface9;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923de;
--build-trigger: #dc735b;
--chat-trigger: #5c8be1;
--chat-trigger-disabled: #9db7e8;
--chat-trigger-disabled: #b4c3da;
--status-red: #ef4444;
--status-yellow: #eab308;
--status-green: #4ade80;
--status-blue:#2563eb;
--connection: #555;
}
.dark {
--background: 224 35% 7.5%; /* hsl(224 40% 10%) */
--foreground: 213 31% 85%; /* hsl(213 31% 91%) */
--foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--muted: 223 27% 11%; /* hsl(223 27% 11%) */
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */
@ -70,13 +74,13 @@
--border: 216 24% 17%; /* hsl(216 34% 17%) */
--input: 216 24% 17%; /* hsl(216 34% 17%) */
--primary: 210 20% 80%; /* hsl(210 40% 98%) */
--primary: 210 20% 80%; /* hsl(210 20% 80%) */
--primary-foreground: 222.2 27.4% 1.2%; /* hsl(222 47% 1%) */
--secondary: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
--secondary-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
--secondary: 222.2 37.4% 7.2%; /* hsl(222 47% 11%) */
--secondary-foreground: 210 40% 80%; /* hsl(210 40% 80%) */
--accent: 216 24% 17%; /* hsl(216 34% 17%) */
--accent: 216 24% 20%; /* hsl(216 34% 17%) */
--accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */
@ -87,7 +91,7 @@
--radius: 0.5rem;
--round-btn-shadow: #00000063;
--success-background: #022c22;
--success-foreground: #ecfdf5;
@ -105,11 +109,13 @@
--blur-shared: #151923d2;
--build-trigger: #dc735b;
--chat-trigger: #5c8be1;
--chat-trigger-disabled: #2b4470;
--chat-trigger-disabled: #2d3b54;
--status-red: #ef4444;
--status-yellow: #eab308;
--status-green: #4ade80;
--status-blue: #2563eb;
--connection: #555;
}}
@layer base {
@ -205,6 +211,7 @@ The cursor: default; property value restores the browser's default cursor style
.input-primary {
@apply bg-background block border-ring form-input px-3 placeholder:text-muted-foreground rounded-md shadow-sm sm:text-sm truncate w-full;
}
.input-edit-node{
@apply input-primary placeholder:text-center pt-0.5 pb-0.5 text-center
}
@ -212,7 +219,7 @@ The cursor: default; property value restores the browser's default cursor style
@apply input-primary pr-7 mx-2
}
.input-disable{
@apply bg-input
@apply bg-border placeholder:text-ring border-transparent
}
.input-dialog{
@apply text-ring cursor-pointer bg-transparent
@ -545,7 +552,7 @@ The cursor: default; property value restores the browser's default cursor style
@apply flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium
}
.header-menu-bar-display {
@apply flex max-w-[200px] items-center gap-2
@apply flex max-w-[200px] items-center gap-2 cursor-pointer
}
.header-menu-flow-name {
@apply flex-1 truncate
@ -913,5 +920,172 @@ The cursor: default; property value restores the browser's default cursor style
@apply ml-3 h-6 w-6
}
.form-modal-lock-true {
@apply bg-input text-primary
}
.form-modal-no-input {
@apply bg-input text-center text-primary dark:bg-gray-700 dark:text-gray-300
}
.form-modal-lock-false {
@apply bg-white text-primary
}
.code-highlight{
@apply block px-3 py-2 w-full h-full text-sm outline-0 border-0 break-all overflow-y-hidden
}
.form-modal-lockchat {
@apply form-input focus:ring-ring focus:border-ring block w-full rounded-md border-border p-4 pr-16 custom-scroll sm:text-sm
}
.form-modal-send-icon-position {
@apply absolute bottom-2 right-4
}
.form-modal-send-button {
@apply rounded-md p-2 px-1 transition-all duration-300
}
.form-modal-lock-icon {
@apply ml-1 mr-1 h-5 w-5 animate-pulse
}
.form-modal-send-icon {
@apply mr-2 h-5 w-5 rotate-[44deg]
}
.form-modal-play-icon {
@apply h-5 w-5 mx-1
}
.form-modal-chat-position {
@apply flex-max-width px-2 py-6 pl-4 pr-9
}
.form-modal-chatbot-icon {
@apply mb-3 ml-3 mr-6 mt-1
}
.form-modal-chat-image {
@apply flex flex-col items-center gap-1
}
.form-modal-chat-img-box {
@apply relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl
}
.form-modal-chat-bot-icon {
@apply form-modal-chat-img-box bg-chat-bot-icon
}
.form-modal-chat-user-icon {
@apply form-modal-chat-img-box bg-chat-user-icon
}
.form-modal-chat-icon-img {
@apply absolute scale-[60%]
}
.form-modal-chat-text-position {
@apply flex w-full flex-1 text-start
}
.form-modal-chat-text {
@apply relative flex w-full flex-col text-start text-sm font-normal text-muted-foreground
}
.form-modal-chat-icon-div {
@apply absolute -left-6 -top-3 cursor-pointer
}
.form-modal-chat-icon {
@apply h-4 w-4 animate-bounce
}
.form-modal-chat-thought-border {
@apply rounded-md border border-ring/60
}
.form-modal-chat-thought-size {
@apply inline-block h-full w-[95%]
}
.form-modal-chat-thought {
@apply cursor-pointer overflow-scroll bg-background text-start text-primary scrollbar-hide form-modal-chat-thought-border form-modal-chat-thought-size py-2 px-2
}
.form-modal-markdown-span {
@apply mt-1 animate-pulse cursor-default
}
.form-modal-initial-prompt-btn {
@apply mb-2 flex items-center gap-2 rounded-md border border-border bg-background shadow-sm px-4 py-2 text-sm font-semibold
}
.form-modal-iv-box {
@apply mt-2 flex-max-width h-[80vh]
}
.form-modal-iv-size {
@apply mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide
}
.file-component-arrangement {
@apply flex items-center py-2
}
.file-component-variable {
@apply -ml-px mr-1 h-4 w-4 text-primary
}
.file-component-variables-span {
@apply font-semibold text-primary
}
.file-component-variables-title {
@apply flex items-center justify-between pt-2
}
.file-component-variables-div {
@apply mr-2.5 flex items-center
}
.file-component-variables-title-txt {
@apply text-sm font-medium text-primary
}
.file-component-accordion-div {
@apply flex items-start gap-3
}
.file-component-badge-div {
@apply flex-max-width items-center justify-between
}
.file-component-tab-column {
@apply flex flex-col gap-2 p-1
}
.tab-accordion-badge-div {
@apply flex flex-1 items-center justify-between py-4 text-sm font-normal text-muted-foreground transition-all
}
.eraser-column-arrangement {
@apply flex-max-width flex-1 flex-col
}
.eraser-size {
@apply relative flex h-full w-full flex-col rounded-md border bg-muted
}
.eraser-position {
@apply absolute right-3 top-3 z-50
}
.chat-message-div {
@apply flex-max-width h-full flex-col items-center overflow-scroll scrollbar-hide
}
.chat-alert-box {
@apply flex-max-width h-full flex-col items-center justify-center text-center align-middle
}
.langflow-chat-span {
@apply text-lg text-foreground
}
.langflow-chat-desc {
@apply w-2/4 rounded-md border border-border bg-muted px-6 py-8
}
.langflow-chat-desc-span {
@apply text-base text-muted-foreground
}
.langflow-chat-input-div {
@apply flex-max-width flex-col items-center justify-between px-8 pb-6
}
.langflow-chat-input {
@apply relative w-full rounded-md shadow-sm
}
}
.tooltip-fixed-width{
@apply max-w-[30vw] max-h-[20vh] overflow-auto
}
.ace-editor-arrangement {
@apply flex-max-width h-full flex-col transition-all
}
.ace-editor {
@apply h-full w-full rounded-lg border-[1px] border-border custom-scroll
}
.ace-editor-save-btn {
@apply flex-max-width h-fit justify-end
}
.export-modal-save-api {
@apply font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70
}
.chat-message-highlight {
@apply px-0.5 rounded-md bg-indigo-100 dark:bg-indigo-900
}
}

View file

@ -142,7 +142,8 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
}
function startTweaks() {
tweak?.current?.push(buildTweaks(flow));
const t = buildTweaks(flow);
tweak?.current?.push(t);
}
function filterNodes() {
@ -266,7 +267,7 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="h-[80vh] lg:max-w-[80vw]">
<DialogContent className="h-[80vh] md:max-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Code</span>
@ -382,10 +383,10 @@ export default function ApiModal({ flow }: { flow: FlowType }) {
key={i}
className="h-10 dark:border-b-muted"
>
<TableCell className="p-0 text-center text-sm text-gray-900">
<TableCell className="p-0 text-center text-sm text-foreground">
{n}
</TableCell>
<TableCell className="p-0 text-center text-xs text-gray-900 dark:text-gray-300">
<TableCell className="p-0 text-xs text-foreground">
<div className="m-auto w-[250px]">
{t.data.node.template[n]
.type === "str" &&

View file

@ -1,5 +1,5 @@
import { Variable } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react";
import { useContext, useRef, useState } from "react";
import CodeAreaComponent from "../../components/codeAreaComponent";
import Dropdown from "../../components/dropdownComponent";
import FloatComponent from "../../components/floatComponent";
@ -30,6 +30,7 @@ import {
TableRow,
} from "../../components/ui/table";
import { PopUpContext } from "../../contexts/popUpContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import { NodeDataType } from "../../types/flow";
import { classNames, limitScrollFieldsModal } from "../../utils";
@ -54,7 +55,12 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
const { closePopUp } = useContext(PopUpContext);
const { types } = useContext(typesContext);
const ref = useRef();
const [enabled, setEnabled] = useState(null);
const { setTabsState, tabId } = useContext(TabsContext);
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === data.id) ??
false;
if (nodeLength == 0) {
closePopUp();
}
@ -66,18 +72,30 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
}
}
useEffect(() => {}, [closePopUp, data.node.template]);
function changeAdvanced(node): void {
Object.keys(data.node.template).filter((n, i) => {
function changeAdvanced(node) {
Object.keys(data.node.template).map((n, i) => {
if (n === node.name) {
data.node.template[n].advanced = !data.node.template[n].advanced;
}
return true;
return n;
});
setNodeValue(!nodeValue);
}
const handleOnNewValue = (newValue: any, name) => {
data.node.template[name].value = newValue;
// Set state to pending
setTabsState((prev) => {
return {
...prev,
[tabId]: {
...prev[tabId],
isPending: true,
},
};
});
};
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger asChild></DialogTrigger>
@ -87,11 +105,13 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<span className="pr-2">{data.type}</span>
<Badge variant="secondary">ID: {data.id}</Badge>
</DialogTitle>
<DialogDescription>
{data.node?.description}
<div className="flex pt-3">
<Variable className="edit-node-modal-variable "></Variable>
<span className="edit-node-modal-span">Parameters</span>
<DialogDescription asChild>
<div>
{data.node?.description}
<div className="flex pt-3">
<Variable className="edit-node-modal-variable "></Variable>
<span className="edit-node-modal-span">Parameters</span>
</div>
</div>
</DialogDescription>
</DialogHeader>
@ -145,7 +165,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
{data.node.template[n].list ? (
<InputListComponent
editNode={true}
disabled={false}
disabled={disabled}
value={
!data.node.template[n].value ||
data.node.template[n].value === ""
@ -153,28 +173,28 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
: data.node.template[n].value
}
onChange={(t: string[]) => {
data.node.template[n].value = t;
handleOnNewValue(t, n);
}}
/>
) : data.node.template[n].multiline ? (
<TextAreaComponent
disabled={false}
disabled={disabled}
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
data.node.template[n].value = t;
handleOnNewValue(t, n);
}}
/>
) : (
<InputComponent
editNode={true}
disabled={false}
disabled={disabled}
password={
data.node.template[n].password ?? false
}
value={data.node.template[n].value ?? ""}
onChange={(t) => {
data.node.template[n].value = t;
handleOnNewValue(t, n);
}}
/>
)}
@ -183,19 +203,18 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<div className="ml-auto">
{" "}
<ToggleShadComponent
disabled={disabled}
enabled={data.node.template[n].value}
setEnabled={(e) => {
data.node.template[n].value = e;
setEnabled(e);
setEnabled={(t) => {
handleOnNewValue(t, n);
}}
size="small"
disabled={false}
/>
</div>
) : data.node.template[n].type === "float" ? (
<div className="mx-auto">
<FloatComponent
disabled={false}
disabled={disabled}
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t) => {
@ -210,9 +229,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
numberOfOptions={nodeLength}
editNode={true}
options={data.node.template[n].options}
onSelect={(newValue) =>
(data.node.template[n].value = newValue)
}
onSelect={(t) => handleOnNewValue(t, n)}
value={
data.node.template[n].value ??
"Choose an option"
@ -222,11 +239,11 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
) : data.node.template[n].type === "int" ? (
<div className="mx-auto">
<IntComponent
disabled={false}
disabled={disabled}
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t) => {
data.node.template[n].value = t;
handleOnNewValue(t, n);
}}
/>
</div>
@ -234,15 +251,15 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<div className="mx-auto">
<InputFileComponent
editNode={true}
disabled={false}
disabled={disabled}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
data.node.template[n].value = t;
handleOnNewValue(t, n);
}}
fileTypes={data.node.template[n].fileTypes}
suffixes={data.node.template[n].suffixes}
onFileChange={(t: string) => {
data.node.template[n].content = t;
handleOnNewValue(t, n);
}}
></InputFileComponent>
</div>
@ -251,10 +268,14 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
<PromptAreaComponent
field_name={n}
editNode={true}
disabled={false}
disabled={disabled}
nodeClass={data.node}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
data.node.template[n].value = t;
handleOnNewValue(t, n);
}}
/>
</div>
@ -272,7 +293,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
editNode={true}
value={data.node.template[n].value ?? ""}
onChange={(t: string) => {
data.node.template[n].value = t;
handleOnNewValue(t, n);
}}
/>
</div>
@ -289,7 +310,7 @@ export default function EditNodeModal({ data }: { data: NodeDataType }) {
setEnabled={(e) =>
changeAdvanced(data.node.template[n])
}
disabled={false}
disabled={disabled}
size="small"
/>
</div>

View file

@ -52,7 +52,7 @@ export default function ModalField({
{display && (
<div>
<span className="mx-2">{title}</span>
<span className="text-destructive">{required ? " *" : ""}</span>
<span className="text-status-red">{required ? " *" : ""}</span>
</div>
)}

View file

@ -54,7 +54,7 @@ function BaseModal({ open, setOpen, children }: BaseModalProps) {
);
//UPDATE COLORS AND STYLE CLASSSES
return (
<Dialog open={open} onOpenChange={setModalOpen}>
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger className="hidden"></DialogTrigger>
<DialogContent className="min-w-[80vw]">
{headerChild}

View file

@ -1,157 +0,0 @@
import Convert from "ansi-to-html";
import DOMPurify from "dompurify";
import { MessageCircle, User2 } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import AiIcon from "../../../assets/Gooey Ring-5s-271px.svg";
import AiIconStill from "../../../assets/froze-flow.png";
import { ChatMessageType } from "../../../types/chat";
import { classNames } from "../../../utils";
import FileCard from "../fileComponent";
import { CodeBlock } from "./codeBlock";
export default function ChatMessage({
chat,
lockChat,
lastMessage,
}: {
chat: ChatMessageType;
lockChat: boolean;
lastMessage: boolean;
}) {
const convert = new Convert({ newline: true });
const [message, setMessage] = useState("");
const imgRef = useRef(null);
useEffect(() => {
setMessage(chat.message);
}, [chat.message]);
const [hidden, setHidden] = useState(true);
return (
<div
className={classNames(
"chat-message-modal",
chat.isSend ? "bg-background " : "bg-input"
)}
>
<div className={classNames("chat-message-modal-div")}>
{!chat.isSend && (
<div className="relative h-8 w-8">
<img
className={
"chat-message-modal-img " +
(lockChat ? "opacity-100" : "opacity-0")
}
src={lastMessage ? AiIcon : AiIconStill}
/>
<img
className={
"chat-message-modal-img " +
(lockChat ? "opacity-0" : "opacity-100")
}
src={AiIconStill}
/>
</div>
)}
{chat.isSend && <User2 className="-mb-1 h-6 w-6 text-primary " />}
</div>
{!chat.isSend ? (
<div className="chat-message-modal-display">
<div className="chat-message-modal-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
className="chat-message-modal-icon-div"
>
<MessageCircle className="h-5 w-5 animate-bounce " />
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<div
onClick={() => setHidden((prev) => !prev)}
className=" chat-message-modal-thought"
dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(convert.toHtml(chat.thought)),
}}
></div>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="chat-message-modal-markdown">
<div className="w-full">
<div className="w-full">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose text-muted-foreground "
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == "▍") {
return (
<span className="chat-message-modal-markdown-span">
▍
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{message}
</ReactMarkdown>
</div>
{chat.files && (
<div className="my-2 w-full">
{chat.files.map((file, index) => {
return (
<div key={index} className="my-2 w-full">
<FileCard
fileName={"Generated File"}
fileType={file.data_type}
content={file.data}
/>
</div>
);
})}
</div>
)}
</div>
</div>
</div>
</div>
) : (
<div className="flex w-full items-center">
<div className="chat-message-modal-alert ">
{message.split("\n").map((line, index) => (
<span key={index} className="text-muted-foreground ">
{line}
<br />
</span>
))}
</div>
</div>
)}
</div>
);
}

View file

@ -1,415 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import { Eraser, MessagesSquare, X } from "lucide-react";
import { Fragment, useContext, useEffect, useRef, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { typesContext } from "../../contexts/typesContext";
import { sendAllProps } from "../../types/api";
import { ChatMessageType } from "../../types/chat";
import { FlowType } from "../../types/flow";
import { validateNodes } from "../../utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import _ from "lodash";
import { getHealth } from "../../controllers/API";
export default function ChatModal({
flow,
open,
setOpen,
}: {
open: boolean;
setOpen: Function;
flow: FlowType;
}) {
const [chatValue, setChatValue] = useState("");
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext);
const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
const messagesRef = useRef(null);
const id = useRef(flow.id);
useEffect(() => {
if (messagesRef.current) {
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
}
}, [chatHistory]);
useEffect(() => {
isOpen.current = open;
}, [open]);
useEffect(() => {
id.current = flow.id;
}, [flow.id]);
var isStream = false;
const addChatHistory = (
message: string,
isSend: boolean,
thought?: string,
files?: Array<any>
) => {
setChatHistory((old) => {
let newChat = _.cloneDeep(old);
if (files) {
newChat.push({ message, isSend, files, thought });
} else if (thought) {
newChat.push({ message, isSend, thought });
} else {
newChat.push({ message, isSend });
}
return newChat;
});
};
//add proper type signature for function
function updateLastMessage({
str,
thought,
end = false,
files,
}: {
str?: string;
thought?: string;
// end param default is false
end?: boolean;
files?: Array<any>;
}) {
setChatHistory((old) => {
let newChat = [...old];
if (str) {
if (end) {
newChat[newChat.length - 1].message = str;
} else {
newChat[newChat.length - 1].message =
newChat[newChat.length - 1].message + str;
}
}
if (thought) {
newChat[newChat.length - 1].thought = thought;
}
if (files) {
newChat[newChat.length - 1].files = files;
}
return newChat;
});
}
function handleOnClose(event: CloseEvent) {
if (isOpen.current) {
setErrorData({ title: event.reason });
setTimeout(() => {
connectWS();
setLockChat(false);
}, 1000);
}
}
function getWebSocketUrl(chatId, isDevelopment = false) {
const isSecureProtocol = window.location.protocol === "https:";
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
const host = isDevelopment ? "localhost:7860" : window.location.host;
const chatEndpoint = `/api/v1/chat/${chatId}`;
return `${
isDevelopment ? "ws" : webSocketProtocol
}://${host}${chatEndpoint}`;
}
function handleWsMessage(data: any) {
if (Array.isArray(data)) {
//set chat history
setChatHistory((_) => {
let newChatHistory: ChatMessageType[] = [];
data.forEach(
(chatItem: {
intermediate_steps?: "string";
is_bot: boolean;
message: string;
type: string;
files?: Array<any>;
}) => {
if (chatItem.message) {
newChatHistory.push(
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
files: chatItem.files,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.message,
thought: chatItem.intermediate_steps,
}
);
}
}
);
return newChatHistory;
});
}
if (data.type === "start") {
addChatHistory("", false);
isStream = true;
}
if (data.type === "end") {
if (data.message) {
updateLastMessage({ str: data.message, end: true });
}
if (data.intermediate_steps) {
updateLastMessage({
str: data.message,
thought: data.intermediate_steps,
end: true,
});
}
if (data.files) {
updateLastMessage({
end: true,
files: data.files,
});
}
setLockChat(false);
isStream = false;
}
if (data.type === "stream" && isStream) {
updateLastMessage({ str: data.message });
}
}
function connectWS() {
try {
const urlWs = getWebSocketUrl(
id.current,
process.env.NODE_ENV === "development"
);
const newWs = new WebSocket(urlWs);
newWs.onopen = () => {
console.log("WebSocket connection established!");
};
newWs.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log("Received data:", data);
handleWsMessage(data);
//get chat history
};
newWs.onclose = (event) => {
handleOnClose(event);
};
newWs.onerror = (ev) => {
getHealth()
.then((res) => {
if (res.status === 200) {
connectWS();
}
})
.catch((err) => {
setErrorData({
// message when the backend failed
title: "The backend is not responding. Please try again later.",
// possible solution list
list: [
"Check your internet connection.",
"Check if the backend is running.",
],
});
});
};
ws.current = newWs;
} catch (error) {
connectWS();
console.log(error);
}
}
useEffect(() => {
connectWS();
return () => {
console.log("unmount");
console.log(ws);
if (ws.current) {
ws.current.close();
}
};
}, []);
useEffect(() => {
if (
ws.current &&
(ws.current.readyState === ws.current.CLOSED ||
ws.current.readyState === ws.current.CLOSING)
) {
connectWS();
setLockChat(false);
}
}, [lockChat]);
async function sendAll(data: sendAllProps) {
try {
if (ws) {
ws.current.send(JSON.stringify(data));
}
} catch (error) {
setErrorData({
title: "There was an error sending the message",
list: [error.message],
});
setChatValue(data.message);
connectWS();
}
}
useEffect(() => {
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]);
const ref = useRef(null);
useEffect(() => {
if (open && ref.current) {
ref.current.focus();
}
}, [open]);
function sendMessage() {
if (chatValue !== "") {
let nodeValidationErrors = validateNodes(reactFlowInstance);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
let message = chatValue;
setChatValue("");
addChatHistory(message, true);
sendAll({
...reactFlowInstance.toObject(),
message,
chatHistory,
name: flow.name,
description: flow.description,
});
} else {
setErrorData({
title: "Oops! Looks like you missed some required information:",
list: nodeValidationErrors,
});
}
} else {
setErrorData({
title: "Error sending message",
list: ["The message cannot be empty."],
});
}
}
function clearChat() {
setChatHistory([]);
ws.current.send(JSON.stringify({ clear_history: true }));
if (lockChat) setLockChat(false);
}
function setModalOpen(x: boolean) {
setOpen(x);
}
return (
<Transition.Root show={open} appear={open} 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="send-message-modal-transition" />
</Transition.Child>
<div className="chat-modal-box">
<div className="chat-modal-box-div">
<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=" chat-modal-dialog-panel">
<div className="chat-modal-dialog-panel-div">
<button
onClick={() => clearChat()}
className="chat-modal-dialog-trash-panel"
>
<Eraser className="h-4 w-4" />
</button>
<button
onClick={() => setModalOpen(false)}
className="chat-modal-dialog-x-panel"
>
<X className="h-5 w-5" />
</button>
</div>
<div ref={messagesRef} className="chat-modal-dialog-history">
{chatHistory.length > 0 ? (
chatHistory.map((c, i) => (
<ChatMessage
lockChat={lockChat}
chat={c}
lastMessage={chatHistory.length - 1 == i ? true : false}
key={i}
/>
))
) : (
<div className="chat-modal-dialog-span-box">
<span>
👋{" "}
<span className="text-lg text-muted-foreground">
Langflow Chat
</span>
</span>
<br />
<div className="chat-modal-dialog-desc">
<span className="text-base text-ring">
Start a conversation and click the agent’s thoughts{" "}
<span>
<MessagesSquare className="mx-1 inline h-5 w-5 animate-bounce " />
</span>{" "}
to inspect the chaining process.
</span>
</div>
</div>
)}
<div ref={ref}></div>
</div>
<div className="chat-modal-input-div">
<div className="chat-modal-input">
<ChatInput
chatValue={chatValue}
lockChat={lockChat}
sendMessage={sendMessage}
setChatValue={setChatValue}
inputRef={ref}
/>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -10,16 +10,9 @@ import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/src-noconflict/ace";
// import "ace-builds/webpack-resolver";
import { TerminalSquare } from "lucide-react";
import { useContext, useState } from "react";
import AceEditor from "react-ace";
import { Button } from "../../components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { CODE_PROMPT_DIALOG_SUBTITLE } from "../../constants";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
@ -45,9 +38,7 @@ export default function CodeAreaModal({
setNodeClass: (Class: APIClassType) => void;
dynamic?: boolean;
}) {
const [open, setOpen] = useState(true);
const [code, setCode] = useState(value);
const [loading, setLoading] = useState(false);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData } = useContext(alertContext);
const [activeTab, setActiveTab] = useState("0");
@ -55,14 +46,12 @@ export default function CodeAreaModal({
detail: { error: string; traceback: string };
}>(null);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
const { setErrorData, setSuccessData } = useContext(alertContext);
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
setCloseEdit("editcode");
closePopUp();
}, 300);
setCloseEdit("codearea");
closePopUp();
}
}
console.log(dynamic);

View file

@ -71,10 +71,7 @@ export default function ExportModal() {
setChecked(event);
}}
/>
<label
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
<label htmlFor="terms" className="export-modal-save-api text-sm">
Save with my API keys
</label>
</div>

View file

@ -1,4 +1,4 @@
import { Lock, LucideSend } from "lucide-react";
import { Lock, LucideSend, Sparkles } from "lucide-react";
import { useEffect } from "react";
import { classNames } from "../../../utils";
@ -8,6 +8,7 @@ export default function ChatInput({
sendMessage,
setChatValue,
inputRef,
noInput,
}) {
useEffect(() => {
if (!lockChat && inputRef.current) {
@ -32,7 +33,7 @@ export default function ChatInput({
}}
rows={1}
ref={inputRef}
disabled={lockChat}
disabled={lockChat || noInput}
style={{
resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`,
@ -49,31 +50,38 @@ export default function ChatInput({
}}
className={classNames(
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",
"form-input block w-full rounded-md border-gray-300 p-4 pr-16 custom-scroll dark:border-gray-600 sm:text-sm"
? " form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
: " form-modal-lock-false bg-background",
"form-modal-lockchat"
)}
placeholder={"Send a message..."}
placeholder={
noInput
? "No chat input variables found. Click to run your flow."
: "Send a message..."
}
/>
<div className="absolute bottom-2 right-4">
<div className="form-modal-send-icon-position">
<button
className={classNames(
"rounded-md p-2 px-1 transition-all duration-300",
chatValue == "" ? "text-primary" : " bg-indigo-600 text-background"
"form-modal-send-button",
noInput
? "bg-indigo-600 text-background"
: chatValue === ""
? "text-primary"
: "bg-emerald-600 text-background"
)}
disabled={lockChat}
onClick={() => sendMessage()}
>
{lockChat ? (
<Lock
className="ml-1 mr-1 h-5 w-5 animate-pulse"
aria-hidden="true"
/>
<Lock className="form-modal-lock-icon" aria-hidden="true" />
) : noInput ? (
<Sparkles className="form-modal-play-icon" aria-hidden="true" />
) : (
<LucideSend
className="mr-2 h-5 w-5 rotate-[44deg] "
aria-hidden="true"
/>
<LucideSend className="form-modal-send-icon " aria-hidden="true" />
)}
</button>
</div>

View file

@ -1,5 +1,5 @@
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-react";
import { FC, memo, useState } from "react";
import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { programmingLanguages } from "../../../../utils";
@ -9,7 +9,7 @@ interface Props {
value: string;
}
export const CodeBlock: FC<Props> = memo(({ language, value }) => {
export function CodeBlock({ language, value }) {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const copyToClipboard = () => {
@ -63,7 +63,7 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
</div>
<SyntaxHighlighter
className=" w-[47vw]"
className="overflow-auto"
language={language}
style={oneDark}
customStyle={{ margin: 0 }}
@ -72,5 +72,5 @@ export const CodeBlock: FC<Props> = memo(({ language, value }) => {
</SyntaxHighlighter>
</div>
);
});
}
CodeBlock.displayName = "CodeBlock";

View file

@ -1,18 +1,18 @@
import Convert from "ansi-to-html";
import { ChevronDown } from "lucide-react";
import { useState } from "react";
import { useMemo, useState } from "react";
import ReactMarkdown from "react-markdown";
import rehypeMathjax from "rehype-mathjax";
import remarkGfm from "remark-gfm";
import remarkMath from "remark-math";
import MaleTechnology from "../../../assets/male-technologist.png";
import Robot from "../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
import { THOUGHTS_ICON } from "../../../constants";
import { ChatMessageType } from "../../../types/chat";
import { classNames } from "../../../utils";
import FileCard from "../fileComponent";
import { CodeBlock } from "./codeBlock";
export default function ChatMessage({
chat,
lockChat,
@ -24,96 +24,111 @@ export default function ChatMessage({
}) {
const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true);
const [template, setTemplate] = useState(chat.template);
const template = chat.template;
const [promptOpen, setPromptOpen] = useState(false);
return (
<div
className={classNames(
"flex w-full px-2 py-6 pl-4 pr-9",
chat.isSend ? "" : " "
)}
className={classNames("form-modal-chat-position", chat.isSend ? "" : " ")}
>
<div className={classNames("mb-3 ml-3 mr-6 mt-1 ")}>
<div className={classNames("form-modal-chatbot-icon ")}>
{!chat.isSend ? (
<div className="flex flex-col items-center gap-1">
<div className="relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#afe6ef] p-5 text-2xl ">
<img src={Robot} className="absolute scale-[60%]" />
<div className="form-modal-chat-image">
<div className="form-modal-chat-bot-icon ">
<img
src={Robot}
className="form-modal-chat-icon-img"
alt="robot_image"
/>
</div>
</div>
) : (
<div className="flex flex-col items-center gap-1">
<div className="relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md bg-[#aface9] p-5 text-2xl ">
<img src={MaleTechnology} className="absolute scale-[60%]" />
<div className="form-modal-chat-image">
<div className="form-modal-chat-user-icon ">
<img
src={MaleTechnology}
className="form-modal-chat-icon-img"
alt="male_technology"
/>
</div>
</div>
)}
</div>
{!chat.isSend ? (
<div className="flex w-full flex-1 text-start">
<div className="relative inline-block w-full text-start text-sm font-normal text-muted-foreground">
<div className="form-modal-chat-text-position">
<div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
className="absolute -left-8 -top-3 cursor-pointer"
className="form-modal-chat-icon-div"
>
<THOUGHTS_ICON className="h-4 w-4 animate-bounce dark:text-white" />
<THOUGHTS_ICON className="form-modal-chat-icon" />
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<div
<SanitizedHTMLWrapper
className=" form-modal-chat-thought"
content={convert.toHtml(chat.thought)}
onClick={() => setHidden((prev) => !prev)}
className=" ml-3 inline-block h-full w-[95%] cursor-pointer overflow-scroll rounded-md border
border-gray-300 bg-muted px-2 text-start text-primary scrollbar-hide dark:border-gray-500 dark:bg-gray-800"
dangerouslySetInnerHTML={{
__html: convert.toHtml(chat.thought),
}}
></div>
/>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full">
<div className="w-full dark:text-white">
<div className="w-full">
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose inline-block break-words text-primary
dark:prose-invert"
components={{
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == "▍") {
return (
<span className="mt-1 animate-pulse cursor-default">
▍
</span>
{useMemo(
() => (
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose inline-block break-words text-primary
dark:prose-invert sm:max-w-[30vw] lg:max-w-[40vw]"
components={{
code: ({
node,
inline,
className,
children,
...props
}) => {
if (children.length) {
if (children[0] === "▍") {
return (
<span className="form-modal-markdown-span">
▍
</span>
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(
className || ""
);
}
children[0] = (children[0] as string).replace(
"`▍`",
"▍"
);
}
const match = /language-(\w+)/.exec(className || "");
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{chat.message.toString()}
</ReactMarkdown>
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ""}
value={String(children).replace(/\n$/, "")}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{chat.message.toString()}
</ReactMarkdown>
),
[chat.message, chat.message.toString()]
)}
</div>
{chat.files && (
<div className="my-2 w-full">
@ -136,51 +151,57 @@ export default function ChatMessage({
</div>
) : (
<div>
<button
className="mb-2 flex items-center gap-4 rounded-md border border-ring/60 bg-background px-4 py-2 text-base font-semibold"
onClick={() => {
setPromptOpen((old) => !old);
}}
>
Initial Prompt
<ChevronDown
className={
"h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "")
}
/>
</button>
<span className="prose inline-block break-words text-primary dark:prose-invert">
{promptOpen
? template.split("\n").map((line, index) => {
const regex = /{([^}]+)}/g;
let match;
let parts = [];
let lastIndex = 0;
while ((match = regex.exec(line)) !== null) {
// Push text up to the match
if (match.index !== lastIndex) {
parts.push(line.substring(lastIndex, match.index));
}
// Push div with matched text
if (chat.message[match[1]]) {
parts.push(
<span className="my-1 rounded-md bg-indigo-100">
{chat.message[match[1]]}
</span>
);
}
{template ? (
<>
<button
className="form-modal-initial-prompt-btn"
onClick={() => {
setPromptOpen((old) => !old);
}}
>
Display Prompt
<ChevronDown
className={
"h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "")
}
/>
</button>
<span className="prose inline-block break-words text-primary dark:prose-invert">
{promptOpen
? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g;
let match;
let parts = [];
let lastIndex = 0;
while ((match = regex.exec(line)) !== null) {
// Push text up to the match
if (match.index !== lastIndex) {
parts.push(line.substring(lastIndex, match.index));
}
// Push div with matched text
if (chat.message[match[1]]) {
parts.push(
<span className="chat-message-highlight">
{chat.message[match[1]]}
</span>
);
}
// Update last index
lastIndex = regex.lastIndex;
}
// Push text after the last match
if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex));
}
return <p>{parts}</p>;
})
: chat.message[chat.chatKey]}
</span>
// Update last index
lastIndex = regex.lastIndex;
}
// Push text after the last match
if (lastIndex !== line.length) {
parts.push(line.substring(lastIndex));
}
return <p>{parts}</p>;
})
: chat.message[chat.chatKey]}
</span>
</>
) : (
<span>{chat.message[chat.chatKey]}</span>
)}
</div>
)}
</div>

View file

@ -50,7 +50,7 @@ export default function FormModal({
const handleKeys = formKeysData.handle_keys;
const keyToUse = Object.keys(inputKeys).find(
(k) => !handleKeys.some((j) => j === k)
(k) => !handleKeys.some((j) => j === k) && inputKeys[k] === ""
);
return inputKeys[keyToUse];
@ -63,15 +63,19 @@ export default function FormModal({
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const { reactFlowInstance } = useContext(typesContext);
const { setErrorData, setNoticeData } = useContext(alertContext);
const { setErrorData } = useContext(alertContext);
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 tabsStateFlowId = tabsState[flow.id];
const tabsStateFlowIdFormKeysData = tabsStateFlowId.formKeysData;
const [chatKey, setChatKey] = useState(
Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
(k) => !tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k)
(k) =>
!tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k) &&
tabsState[flow.id].formKeysData.input_keys[k] === ""
)
);
@ -86,7 +90,7 @@ export default function FormModal({
}, [open]);
useEffect(() => {
id.current = flow.id;
}, [flow.id, tabsState[flow.id], tabsState[flow.id].formKeysData]);
}, [flow.id, tabsStateFlowId, tabsStateFlowIdFormKeysData]);
var isStream = false;
@ -290,6 +294,7 @@ export default function FormModal({
ws.current.close();
}
};
// do not add connectWS on dependencies array
}, []);
useEffect(() => {
@ -301,6 +306,7 @@ export default function FormModal({
connectWS();
setLockChat(false);
}
// do not add connectWS on dependencies array
}, [lockChat]);
async function sendAll(data: sendAllProps) {
@ -331,41 +337,35 @@ export default function FormModal({
}, [open]);
function sendMessage() {
if (chatValue !== "") {
let nodeValidationErrors = validateNodes(reactFlowInstance);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
let inputs = tabsState[id.current].formKeysData.input_keys;
setChatValue("");
const message = inputs;
addChatHistory(
message,
true,
chatKey,
tabsState[flow.id].formKeysData.template
);
sendAll({
...reactFlowInstance.toObject(),
inputs: inputs,
chatHistory,
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:",
list: nodeValidationErrors,
});
}
let nodeValidationErrors = validateNodes(reactFlowInstance);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
let inputs = tabsState[id.current].formKeysData.input_keys;
setChatValue("");
const message = inputs;
addChatHistory(
message,
true,
chatKey,
tabsState[flow.id].formKeysData.template
);
sendAll({
...reactFlowInstance.toObject(),
inputs: inputs,
chatHistory,
name: flow.name,
description: flow.description,
});
setTabsState((old) => {
if (!chatKey) return old;
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
return newTabsState;
});
} else {
setErrorData({
title: "Error sending message",
list: ["The message cannot be empty."],
title: "Oops! Looks like you missed some required information:",
list: nodeValidationErrors,
});
}
}
@ -383,6 +383,9 @@ export default function FormModal({
if (checked === true) {
setChatKey(i);
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
} else {
setChatKey(null);
setChatValue("");
}
}
return (
@ -401,19 +404,19 @@ export default function FormModal({
<DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<div className="mt-2 flex h-[80vh] w-full ">
<div className="mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide">
<div className="flex items-center py-2">
<Variable className=" -ml-px mr-1 h-4 w-4 text-primary"></Variable>
<span className="text-md font-semibold text-primary">
<div className="form-modal-iv-box ">
<div className="form-modal-iv-size">
<div className="file-component-arrangement">
<Variable className=" file-component-variable"></Variable>
<span className="file-component-variables-span text-md">
Input Variables
</span>
</div>
<div className="flex items-center justify-between pt-2">
<div className="mr-2.5 flex items-center">
<div className="file-component-variables-title">
<div className="file-component-variables-div">
<span className="text-sm font-medium text-primary">Name</span>
</div>
<div className="mr-2.5 flex items-center">
<div className="file-component-variables-div">
<span className="text-sm font-medium text-primary">
Chat Input
</span>
@ -422,10 +425,10 @@ export default function FormModal({
<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}>
<div className="file-component-accordion-div" key={k}>
<AccordionItem className="w-full" key={k} value={i}>
<AccordionTrigger className="flex gap-2">
<div className="flex w-full items-center justify-between">
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{i}
</Badge>
@ -450,7 +453,7 @@ export default function FormModal({
</div>
</AccordionTrigger>
<AccordionContent>
<div className="flex flex-col gap-2 p-1">
<div className="file-component-tab-column">
{tabsState[
id.current
].formKeysData.handle_keys.some((t) => t === i) && (
@ -482,21 +485,21 @@ export default function FormModal({
)}
{tabsState[id.current].formKeysData.memory_keys.map((i, k) => (
<AccordionItem key={k} value={i}>
<div className="group flex flex-1 items-center justify-between py-4 text-sm font-normal text-muted-foreground transition-all">
<div className="tab-accordion-badge-div group">
<div className="group-hover:underline">
<Badge size="md" variant="gray">
{i}
</Badge>
</div>
Used as Memory Key
Used as memory key
</div>
</AccordionItem>
))}
</Accordion>
</div>
<div className="flex w-full flex-1 flex-col">
<div className="relative flex h-full w-full flex-col rounded-md border bg-muted">
<div className="absolute right-3 top-3 z-50">
<div className="eraser-column-arrangement">
<div className="eraser-size">
<div className="eraser-position">
<button disabled={lockChat} onClick={() => clearChat()}>
<Eraser
className={classNames(
@ -509,30 +512,29 @@ export default function FormModal({
/>
</button>
</div>
<div
ref={messagesRef}
className="flex h-full w-full flex-col items-center overflow-scroll scrollbar-hide"
>
<div ref={messagesRef} className="chat-message-div">
{chatHistory.length > 0 ? (
chatHistory.map((c, i) => (
<ChatMessage
lockChat={lockChat}
chat={c}
lastMessage={chatHistory.length - 1 == i ? true : false}
lastMessage={
chatHistory.length - 1 === i ? true : false
}
key={i}
/>
))
) : (
<div className="flex h-full w-full flex-col items-center justify-center text-center align-middle">
<div className="chat-alert-box">
<span>
👋{" "}
<span className="text-lg text-gray-600 dark:text-gray-300">
<span className="langflow-chat-span">
LangFlow Chat
</span>
</span>
<br />
<div className="w-2/4 rounded-md border border-gray-200 bg-muted px-6 py-8 dark:border-gray-700 dark:bg-gray-900">
<span className="text-base text-gray-500">
<div className="langflow-chat-desc">
<span className="langflow-chat-desc-span">
Start a conversation and click the agent's thoughts{" "}
<span>
<THOUGHTS_ICON className="mx-1 inline h-5 w-5 animate-bounce " />
@ -544,10 +546,11 @@ export default function FormModal({
)}
<div ref={ref}></div>
</div>
<div className="flex w-full flex-col items-center justify-between px-8 pb-6">
<div className="relative w-full rounded-md shadow-sm">
<div className="langflow-chat-input-div">
<div className="langflow-chat-input">
<ChatInput
chatValue={chatValue}
noInput={!chatKey}
lockChat={lockChat}
sendMessage={sendMessage}
setChatValue={(value) => {

View file

@ -1,24 +1,12 @@
import DOMPurify from "dompurify";
import { FileText, Variable } from "lucide-react";
import { useContext, useEffect, useRef, useState } from "react";
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
import ShadTooltip from "../../components/ShadTooltipComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { DialogTitle } from "../../components/ui/dialog";
import { Textarea } from "../../components/ui/textarea";
import {
HIGHLIGH_CSS,
PROMPT_DIALOG_SUBTITLE,
TEXT_DIALOG_SUBTITLE,
} from "../../constants";
import { PROMPT_DIALOG_SUBTITLE, TEXT_DIALOG_SUBTITLE } from "../../constants";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
@ -32,6 +20,7 @@ import {
regexHighlight,
varHighlightHTML,
} from "../../utils";
import BaseModal from "../baseModal";
export default function GenericModal({
field_name = "",
@ -57,7 +46,6 @@ export default function GenericModal({
const [myModalType] = useState(type);
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlightInvalid, setWordsHighlightInvalid] = useState([]);
const [wordsHighlight, setWordsHighlight] = useState([]);
const { dark } = useContext(darkContext);
const { setErrorData, setSuccessData, setNoticeData } =
@ -102,15 +90,14 @@ export default function GenericModal({
(word) => !invalid_chars.includes(word)
);
setWordsHighlightInvalid(invalid_chars);
setWordsHighlight(filteredWordsHighlight);
}
useEffect(() => {
if (type == TypeModal.PROMPT && inputValue && inputValue != "") {
if (type === TypeModal.PROMPT && inputValue && inputValue != "") {
checkVariables(inputValue);
}
}, []);
}, [inputValue, type]);
const coloredContent = (inputValue || "")
.replace(/</g, "&lt;")
@ -120,13 +107,13 @@ export default function GenericModal({
const TextAreaContentView = () => {
return (
<div
className={HIGHLIGH_CSS}
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(coloredContent) }}
suppressContentEditableWarning={true}
<SanitizedHTMLWrapper
className="code-highlight"
content={coloredContent}
onClick={() => {
setIsEdit(true);
}}
suppressWarning={true}
/>
);
};
@ -135,10 +122,9 @@ export default function GenericModal({
postValidatePrompt(field_name, inputValue, nodeClass)
.then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
let inputVariables = apiReturn.data.input_variables;
if (inputVariables.length === 0) {
setNodeClass(apiReturn.data?.frontend_node);
let inputVariables = apiReturn.data.input_variables ?? [];
if (inputVariables && inputVariables.length === 0) {
setIsEdit(true);
setNoticeData({
title: "Your template does not have any variables.",
@ -159,143 +145,147 @@ export default function GenericModal({
}
})
.catch((error) => {
console.log(error);
setIsEdit(true);
return setErrorData({
title: "There is something wrong with this prompt, please review it",
list: [error.response.data.detail],
list: [error?.response?.data?.detail],
});
});
}
return (
<Dialog open={true} onOpenChange={setModalOpen}>
<DialogTrigger></DialogTrigger>
<DialogContent className="min-w-[80vw] gap-2">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">{myModalTitle}</span>
<FileText
strokeWidth={1.5}
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>
{(() => {
switch (myModalTitle) {
case "Edit Text":
return TEXT_DIALOG_SUBTITLE;
<BaseModal open={true} setOpen={setModalOpen}>
<BaseModal.Header
description={(() => {
switch (myModalTitle) {
case "Edit Text":
return TEXT_DIALOG_SUBTITLE;
case "Edit Prompt":
return PROMPT_DIALOG_SUBTITLE;
case "Edit Prompt":
return PROMPT_DIALOG_SUBTITLE;
default:
return null;
}
})()}
</DialogDescription>
</DialogHeader>
<div
className={classNames(
!isEdit ? "rounded-lg border" : "",
"flex h-[55vh] w-full"
)}
>
{type == TypeModal.PROMPT && isEdit ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 custom-scroll focus-visible:ring-1 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={inputValue}
onBlur={() => {
blur();
setIsEdit(false);
}}
autoFocus
onChange={(e) => {
setInputValue(e.target.value);
checkVariables(e.target.value);
}}
placeholder="Type message here."
/>
) : type == TypeModal.PROMPT && !isEdit ? (
<TextAreaContentView />
) : type != TypeModal.PROMPT ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg border-gray-300 focus-visible:ring-1 dark:border-gray-700 dark:bg-gray-900 dark:text-white"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
placeholder="Type message here."
/>
) : (
<></>
)}
</div>
{type == TypeModal.PROMPT && (
<>
<div className="sm:6/6 mr-28 mt-3 h-[60px] overflow-y-auto custom-scroll">
<div className="flex flex-wrap items-center">
<Variable className=" -ml-px mr-1 flex h-4 w-4 text-primary"></Variable>
<span className="text-md font-semibold text-primary">
Input Variables:
</span>
{wordsHighlight.map((word, index) => (
<ShadTooltip
key={getRandomKeyByssmm() + index}
content={word.replace(/[{}]/g, "")}
asChild={false}
>
<Badge
key={index}
variant="gray"
size="md"
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) + "..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
</div>
</>
)}
<DialogFooter>
<Button
className="mt-3"
onClick={() => {
switch (myModalType) {
case 1:
setValue(inputValue);
setModalOpen(false);
break;
case 2:
!inputValue || inputValue == ""
? setModalOpen(false)
: validatePrompt(false);
break;
default:
break;
}
}}
type="submit"
default:
return null;
}
})()}
>
<DialogTitle className="flex items-center">
<span className="pr-2">{myModalTitle}</span>
<FileText
strokeWidth={1.5}
className="h-6 w-6 pl-1 text-primary "
aria-hidden="true"
/>
</DialogTitle>
</BaseModal.Header>
<BaseModal.Content>
<div className="flex h-full flex-col">
<div
className={classNames(
!isEdit ? "rounded-lg border" : "",
"flex h-full w-full"
)}
>
{myButtonText}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{type === TypeModal.PROMPT && isEdit ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg custom-scroll focus-visible:ring-1"
value={inputValue}
onBlur={() => {
setIsEdit(false);
}}
autoFocus
onChange={(e) => {
setInputValue(e.target.value);
checkVariables(e.target.value);
}}
placeholder="Type message here."
/>
) : type === TypeModal.PROMPT && !isEdit ? (
<TextAreaContentView />
) : type !== TypeModal.PROMPT ? (
<Textarea
ref={ref}
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
placeholder="Type message here."
/>
) : (
<></>
)}
</div>
<div className="mt-6 flex h-fit w-full items-end justify-between">
<div className="mb-auto flex-1">
{type === TypeModal.PROMPT && (
<div className=" mr-2">
<div className="max-h-20 overflow-y-auto custom-scroll">
<div className="flex flex-wrap items-center">
<Variable className=" -ml-px mr-1 flex h-4 w-4 text-primary"></Variable>
<span className="text-md font-semibold text-primary">
Prompt Variables:
</span>
{wordsHighlight.map((word, index) => (
<ShadTooltip
key={getRandomKeyByssmm() + index}
content={word.replace(/[{}]/g, "")}
asChild={false}
>
<Badge
key={index}
variant="gray"
size="md"
className="m-1 max-w-[40vw] cursor-default truncate p-2.5 text-sm"
>
<div className="relative bottom-[1px]">
<span>
{word.replace(/[{}]/g, "").length > 59
? word.replace(/[{}]/g, "").slice(0, 56) +
"..."
: word.replace(/[{}]/g, "")}
</span>
</div>
</Badge>
</ShadTooltip>
))}
</div>
</div>
<span className="mt-1 text-xs text-muted-foreground">
Prompt variables can be created with any chosen name inside
curly brackets, e.g. {"{variable_name}"}
</span>
</div>
)}
</div>
<Button
onClick={() => {
switch (myModalType) {
case 1:
setValue(inputValue);
setModalOpen(false);
break;
case 2:
!inputValue || inputValue === ""
? setModalOpen(false)
: validatePrompt(false);
break;
default:
break;
}
}}
type="submit"
>
{myButtonText}
</Button>
</div>
</div>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -1,151 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import { DocumentTextIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { PopUpContext } from "../../contexts/popUpContext";
import { checkPrompt } from "../../controllers/API";
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, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
setCloseEdit("prompt");
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="node-modal-div" />
</Transition.Child>
<div className="node-modal-dialog-arrangement">
<div className="node-modal-dialog-div">
<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="node-modal-dialog-panel">
<div className=" node-modal-dialog-panel-div ">
<button
type="button"
className="node-modal-dialog-button"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="node-modal-dialog-icon-div">
<div className="node-modal-icon-arrangement">
<div className="prompt-modal-icon-box">
<DocumentTextIcon
className="prompt-modal-icon"
aria-hidden="true"
/>
</div>
<div className="node-modal-title-div ">
<Dialog.Title as="h3" className="node-modal-title">
Edit Prompt
</Dialog.Title>
</div>
</div>
<div className="prompt-modal-txtarea-arrangement">
<div className="flex-max-width h-full">
<div className="prompt-modal-txtarea-box">
<textarea
ref={ref}
className="prompt-modal-txtarea"
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
/>
</div>
</div>
</div>
<div className="node-modal-button-box">
<button
type="button"
className="node-modal-button"
onClick={() => {
checkPrompt(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

@ -1,121 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import {
ClipboardDocumentListIcon,
XMarkIcon,
} from "@heroicons/react/24/outline";
import { Fragment, useContext, useRef, useState } from "react";
import { PopUpContext } from "../../contexts/popUpContext";
export default function TextAreaModal({
value,
setValue,
}: {
setValue: (value: string) => void;
value: string | string[];
}) {
const [open, setOpen] = useState(true);
const [myValue, setMyValue] = useState(value);
const { closePopUp, setCloseEdit } = useContext(PopUpContext);
const ref = useRef();
function setModalOpen(x: boolean) {
setOpen(x);
if (x === false) {
setTimeout(() => {
setCloseEdit("textarea");
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="node-modal-div" />
</Transition.Child>
<div className="node-modal-dialog-arrangement">
<div className="node-modal-dialog-div">
<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="node-modal-dialog-panel">
<div className=" node-modal-dialog-panel-div">
<button
type="button"
className="node-modal-dialog-button"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
<div className="node-modal-dialog-icon-div">
<div className="node-modal-icon-arrangement">
<div className="prompt-modal-icon-box">
<ClipboardDocumentListIcon
className="prompt-modal-icon"
aria-hidden="true"
/>
</div>
<div className="node-modal-title-div">
<Dialog.Title as="h3" className="node-modal-title">
Edit text
</Dialog.Title>
</div>
</div>
<div className="txtarea-modal-arrangement">
<div className="flex h-full w-full">
<div className="txtarea-modal-box">
<textarea
ref={ref}
className="txtarea-modal-input"
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
setValue(e.target.value);
}}
/>
</div>
</div>
</div>
<div className="node-modal-button-box">
<button
type="button"
className="node-modal-button"
onClick={() => {
setModalOpen(false);
}}
>
Finish editing
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
);
}

View file

@ -12,9 +12,8 @@ const ConnectionLineComponent = ({
<path
fill="none"
// ! Replace hash # colors here
stroke="#222"
strokeWidth={1.5}
className="animated "
className="animated stroke-connection "
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
style={connectionLineStyle}
/>

View file

@ -184,11 +184,11 @@ export default function Page({ flow }: { flow: FlowType }) {
addEdge(
{
...params,
style: { stroke: "inherit" },
style: { stroke: "#555" },
className:
params.targetHandle.split("|")[0] === "Text"
(params.targetHandle.split("|")[0] === "Text"
? "stroke-foreground "
: "stroke-foreground ",
: "stroke-foreground ") + " stroke-connection",
animated: params.targetHandle.split("|")[0] === "Text",
},
eds
@ -288,7 +288,7 @@ export default function Page({ flow }: { flow: FlowType }) {
}
},
// Specify dependencies for useCallback
[getNodeId, reactFlowInstance, setErrorData, setNodes, takeSnapshot]
[getNodeId, reactFlowInstance, setNodes, takeSnapshot]
);
useEffect(() => {
@ -322,7 +322,7 @@ export default function Page({ flow }: { flow: FlowType }) {
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
},
[]
[reactFlowInstance, setEdges]
);
const onEdgeUpdateEnd = useCallback((_, edge) => {
@ -338,7 +338,8 @@ export default function Page({ flow }: { flow: FlowType }) {
const onSelectionEnd = useCallback(() => {
setSelectionEnded(true);
}, []);
const onSelectionStart = useCallback(() => {
const onSelectionStart = useCallback((event) => {
event.preventDefault();
setSelectionEnded(false);
}, []);
@ -371,10 +372,11 @@ export default function Page({ flow }: { flow: FlowType }) {
<ReactFlow
nodes={nodes}
onMove={() => {
updateFlow({
...flow,
data: reactFlowInstance.toObject(),
});
if (reactFlowInstance)
updateFlow({
...flow,
data: reactFlowInstance.toObject(),
});
}}
edges={edges}
onPaneClick={() => {
@ -409,7 +411,6 @@ export default function Page({ flow }: { flow: FlowType }) {
nodesDraggable={!disableCopyPaste}
panOnDrag={!disableCopyPaste}
zoomOnDoubleClick={!disableCopyPaste}
selectNodesOnDrag={false}
className="theme-attribution"
minZoom={0.01}
maxZoom={8}

View file

@ -162,7 +162,9 @@ export default function ExtraSidebar() {
<div key={k} data-tooltip-id={t}>
<div
draggable
className={"side-bar-components-border"}
className={
"side-bar-components-border bg-background"
}
style={{
borderLeftColor:
nodeColors[d] ?? nodeColors.unknown,

View file

@ -148,7 +148,7 @@ export type ShadToolTipType = {
asChild?: boolean;
children?: ReactElement;
delayDuration?: number;
style?: string;
styleClasses?: string;
};
export type TextHighlightType = {

View file

@ -27,3 +27,11 @@ export type FlowStyleType = {
color: string;
flow_id: string;
};
export type TweaksType = Array<
{
[key: string]: {
output_key?: string;
};
} & FlowStyleType
>;

View file

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from "react";
import { FlowType } from "../flow";
import { FlowType, TweaksType } from "../flow";
export type TabsContextType = {
saveFlow: (flow: FlowType) => Promise<void>;
@ -32,8 +32,8 @@ export type TabsContextType = {
) => void;
lastCopiedSelection: { nodes: any; edges: any };
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
setTweak: (tweak: any) => void;
getTweak: any;
setTweak: (tweak: TweaksType) => void;
getTweak: TweaksType[];
};
export type TabsState = {

View file

@ -0,0 +1,10 @@
import { Edge } from "reactflow";
import { NodeType } from "../flow";
export type cleanEdgesType = {
flow: {
edges: Edge[];
nodes: NodeType[];
};
updateEdge: (edge: Edge[]) => void;
};

View file

@ -0,0 +1,46 @@
import _ from "lodash";
import { cleanEdgesType } from "./../types/utils/reactflowUtils";
export function cleanEdges({
flow: { edges, nodes },
updateEdge,
}: cleanEdgesType) {
let newEdges = _.cloneDeep(edges);
edges.forEach((edge) => {
// check if the source and target node still exists
const sourceNode = nodes.find((node) => node.id === edge.source);
const targetNode = nodes.find((node) => node.id === edge.target);
if (!sourceNode || !targetNode) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
// check if the source and target handle still exists
if (sourceNode && targetNode) {
const sourceHandle = edge.sourceHandle; //right
const targetHandle = edge.targetHandle; //left
if (targetHandle) {
const field = targetHandle.split("|")[1];
const id =
(targetNode.data.node.template[field]?.input_types?.join(";") ??
targetNode.data.node.template[field]?.type) +
"|" +
field +
"|" +
targetNode.data.id;
if (id !== targetHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
}
if (sourceHandle) {
const id = [
sourceNode.data.type,
sourceNode.data.id,
...sourceNode.data.node.base_classes,
].join("|");
if (id !== sourceHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
}
}
}
});
updateEdge(newEdges);
}

View file

@ -35,7 +35,7 @@ import { EvernoteIcon } from "./icons/Evernote";
import { FBIcon } from "./icons/FacebookMessenger";
import { GitBookIcon } from "./icons/GitBook";
import { GoogleIcon } from "./icons/Google";
import { HugginFaceIcon } from "./icons/HuggingFace";
import { HuggingFaceIcon } from "./icons/HuggingFace";
import { IFixIcon } from "./icons/IFixIt";
import { MetaIcon } from "./icons/Meta";
import { MidjourneyIcon } from "./icons/Midjorney";
@ -206,10 +206,10 @@ export const nodeIconsLucide: {
HNLoader: HackerNewsIcon as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>>
>,
HuggingFaceHub: HugginFaceIcon as React.ForwardRefExoticComponent<
HuggingFaceHub: HuggingFaceIcon as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>>
>,
HuggingFaceEmbeddings: HugginFaceIcon as React.ForwardRefExoticComponent<
HuggingFaceEmbeddings: HuggingFaceIcon as React.ForwardRefExoticComponent<
ComponentType<SVGProps<SVGSVGElement>>
>,
IFixitLoader: IFixIcon as React.ForwardRefExoticComponent<
@ -608,7 +608,7 @@ export function isValidConnection(
) ||
targetHandle.split("|")[0] === "str"
) {
let targetNode = reactFlowInstance.getNode(target).data.node;
let targetNode = reactFlowInstance?.getNode(target)?.data?.node;
if (!targetNode) {
if (
!reactFlowInstance
@ -853,7 +853,7 @@ export function groupByFamily(data, baseClasses, left, type) {
});
});
if (left == false) {
if (left === false) {
let groupedBy = arrOfType.filter((object, index, self) => {
const foundIndex = self.findIndex(
(o) => o.family === object.family && o.type === object.type
@ -876,7 +876,7 @@ export function groupByFamily(data, baseClasses, left, type) {
});
}
if (left == false) {
if (left === false) {
let resFil = result.filter((group) => group.family === parentOutput);
result = resFil;
}
@ -903,8 +903,8 @@ export function groupByFamily(data, baseClasses, left, type) {
}
groupedArray.forEach((object, index, self) => {
const findObj = arrOfLength.find((x) => x.type == object.family);
if (object.component.length == findObj.length) {
const findObj = arrOfLength.find((x) => x.type === object.family);
if (object.component.length === findObj.length) {
self[index]["type"] = "";
} else {
self[index]["type"] = object.component.join(", ");
@ -1048,10 +1048,6 @@ export const INVALID_CHARACTERS = [
export const regexHighlight = /\{([^}]+)\}/g;
export const varHighlightHTML = ({ name }: IVarHighlightType) => {
const html = `<div class="inline-flex items-center justify-center rounded-md font-medium text-primary bg-muted pb-1">
<span class='opacity-60 pl-1'>{</span>
<span>${name}</span>
<span class='opacity-60 pr-1'>}</span>
</div>`;
const html = `<span class="font-semibold chat-message-highlight">{${name}}</span>`;
return html;
};

View file

@ -43,6 +43,7 @@ module.exports = {
"accordion-up": "accordion-up 0.2s ease-out",
},
colors: {
connection: "var(--connection)",
"almost-dark-gray": "var(--almost-dark-gray)",
"almost-light-blue": "var(--almost-light-blue)",
"almost-medium-blue": "var(--almost-medium-blue)",
@ -82,6 +83,8 @@ module.exports = {
"status-yellow": "var(--status-yellow)",
"success-background": "var(--success-background)",
"success-foreground": "var(--success-foreground)",
"chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)",
white: "var(--white)",
border: "hsl(var(--border))",
@ -127,18 +130,18 @@ module.exports = {
sans: ["var(--font-sans)", ...fontFamily.sans],
},
keyframes: {
"accordion-down": {
slideDown: {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
to: { height: 100 },
},
"accordion-up": {
slideUp: {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
"accordion-down": "slideDown 300ms ease-out",
"accordion-up": "slideUp 300ms ease-in",
},
},
},