merge fix zustand

This commit is contained in:
cristhianzl 2024-04-01 16:38:01 -03:00
commit 6a8ca8db47
160 changed files with 9418 additions and 6208 deletions

View file

@ -0,0 +1,29 @@
{
"extends": ["eslint:recommended", "plugin:node/recommended"],
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"no-console": "warn",
"no-self-assign": "warn",
"no-self-compare": "warn",
"complexity": ["error", { "max": 15 }],
"indent": ["error", 2, { "SwitchCase": 1 }],
"no-dupe-keys": "error",
"no-invalid-regexp": "error",
"no-undef": "error",
"no-return-assign": "error",
"no-redeclare": "error",
"no-empty": "error",
"no-await-in-loop": "error",
"node/exports-style": ["error", "module.exports"],
"node/file-extension-in-import": ["error", "always"],
"node/prefer-global/buffer": ["error", "always"],
"node/prefer-global/console": ["error", "always"],
"node/prefer-global/process": ["error", "always"],
"node/prefer-global/url-search-params": ["error", "always"],
"node/prefer-global/url": ["error", "always"],
"node/prefer-promises/dns": "error",
"node/prefer-promises/fs": "error"
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,12 +3,8 @@
"version": "0.1.2",
"private": true,
"dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@headlessui/react": "^1.7.17",
"@heroicons/react": "^2.0.18",
"@mui/material": "^5.14.7",
"@preact/signals-react": "^2.0.0",
"@million/lint": "^0.0.73",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
@ -19,7 +15,7 @@
"@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
@ -29,9 +25,7 @@
"@tailwindcss/forms": "^0.5.6",
"@tailwindcss/line-clamp": "^0.4.4",
"@types/axios": "^0.14.0",
"accordion": "^3.0.2",
"ace-builds": "^1.24.1",
"add": "^2.0.6",
"ansi-to-html": "^0.7.2",
"axios": "^1.5.0",
"base64-js": "^1.5.1",
@ -43,6 +37,7 @@
"framer-motion": "^11.0.6",
"lodash": "^4.17.21",
"lucide-react": "^0.331.0",
"million": "^3.0.6",
"moment": "^2.29.4",
"playwright": "^1.42.0",
"react": "^18.2.0",
@ -55,8 +50,6 @@
"react-markdown": "^8.0.7",
"react-router-dom": "^6.15.0",
"react-syntax-highlighter": "^15.5.0",
"react-tabs": "^6.0.2",
"react-tooltip": "^5.21.1",
"react18-json-view": "^0.2.3",
"reactflow": "^11.9.2",
"rehype-mathjax": "^4.0.3",
@ -64,8 +57,6 @@
"remark-math": "^5.1.1",
"shadcn-ui": "^0.2.3",
"short-unique-id": "^4.4.4",
"switch": "^0.0.0",
"table": "^6.8.1",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7",
"uuid": "^9.0.0",
@ -117,6 +108,8 @@
"@vitejs/plugin-react-swc": "^3.3.2",
"autoprefixer": "^10.4.15",
"daisyui": "^4.0.4",
"eslint": "^8.57.0",
"eslint-plugin-node": "^11.1.0",
"postcss": "^8.4.29",
"prettier": "^2.8.8",
"prettier-plugin-organize-imports": "^3.2.3",

View file

@ -27,11 +27,15 @@ import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType, ResponseErrorTypeAPI } from "../../../../types/api";
import {
APIClassType,
ResponseErrorDetailAPI,
ResponseErrorTypeAPI,
} from "../../../../types/api";
import { ParameterComponentType } from "../../../../types/components";
import {
debouncedHandleUpdateValues,
handleUpdateValues,
throttledHandleUpdateValues,
} from "../../../../utils/parameterUtils";
import {
convertObjToArray,
@ -104,10 +108,11 @@ export default function ParameterComponent({
});
}
} catch (error) {
let responseError = error as ResponseErrorTypeAPI;
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail.error ?? "Unknown error"],
list: [responseError.response.data.detail ?? "Unknown error"],
});
}
setIsLoading(false);
@ -136,10 +141,11 @@ export default function ParameterComponent({
});
}
} catch (error) {
let responseError = error as ResponseErrorTypeAPI;
let responseError = error as ResponseErrorDetailAPI;
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail.error ?? "Unknown error"],
list: [responseError.response.data.detail ?? "Unknown error"],
});
}
setIsLoading(false);
@ -164,7 +170,7 @@ export default function ParameterComponent({
if (shouldUpdate) {
setIsLoading(true);
try {
newTemplate = await throttledHandleUpdateValues(name, data);
newTemplate = await debouncedHandleUpdateValues(name, data);
} catch (error) {
let responseError = error as ResponseErrorTypeAPI;
setErrorData({
@ -377,7 +383,7 @@ export default function ParameterComponent({
!showNode ? "mt-0" : ""
)}
style={{
borderColor: color,
borderColor: color ?? nodeColors.unknown,
}}
onClick={() => {
setFilterEdge(groupedEdge.current);
@ -406,7 +412,7 @@ export default function ParameterComponent({
}
>
{!left && data.node?.frozen && (
<div>
<div className="pr-1">
<IconComponent className="h-5 w-5 text-ice" name={"Snowflake"} />
</div>
)}
@ -421,7 +427,7 @@ export default function ParameterComponent({
{title}
</span>
)}
<span className={(info === "" ? "" : "ml-1 ") + " text-status-red pl-1"}>
<span className={(required ? "ml-2 " : "") + "text-status-red"}>
{required ? "*" : ""}
</span>
<div className="">
@ -445,7 +451,7 @@ export default function ParameterComponent({
<div className="flex">
<ShadTooltip
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
delayDuration={0}
delayDuration={1000}
content={refHtml.current}
side={left ? "left" : "right"}
>
@ -473,7 +479,7 @@ export default function ParameterComponent({
"h-3 w-3 rounded-full border-2 bg-background"
)}
style={{
borderColor: color,
borderColor: color ?? nodeColors.unknown,
}}
onClick={() => {
setFilterEdge(groupedEdge.current);
@ -679,6 +685,7 @@ export default function ParameterComponent({
) : left === true && type === "int" ? (
<div className="mt-2 w-full">
<IntComponent
rangeSpec={data.node?.template[name].rangeSpec}
disabled={disabled}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}

View file

@ -1,5 +1,5 @@
import { cloneDeep } from "lodash";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { NodeToolbar, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
@ -86,7 +86,11 @@ export default function GenericNode({
if (!thisNodeTemplate.code) return;
const currentCode = thisNodeTemplate.code?.value;
const thisNodesCode = data.node!.template?.code?.value;
if (currentCode !== thisNodesCode) {
const componentsToIgnore = ["Custom Component", "Prompt"];
if (
currentCode !== thisNodesCode &&
!componentsToIgnore.includes(data.node!.display_name)
) {
setIsOutdated(true);
} else {
setIsOutdated(false);
@ -293,7 +297,6 @@ export default function GenericNode({
);
}
};
const getSpecificClassFromBuildStatus = (
buildStatus: BuildStatus | undefined,
validationStatus: validationStatusType | null
@ -342,17 +345,16 @@ export default function GenericNode({
const getNodeSizeClass = (showNode) =>
showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full";
return (
<>
const memoizedNodeToolbarComponent = useMemo(() => {
return (
<NodeToolbar>
<NodeToolbarComponent
position={{ x: xPos, y: yPos }}
data={data}
deleteNode={(id) => {
takeSnapshot();
deleteNode(id);
}}
setShowNode={(show: boolean) => {
setShowNode={(show) => {
setNode(data.id, (old) => ({
...old,
data: { ...old.data, showNode: show },
@ -366,8 +368,25 @@ export default function GenericNode({
updateNodeCode={updateNodeCode}
isOutdated={isOutdated}
selected={selected}
></NodeToolbarComponent>
/>
</NodeToolbar>
);
}, [
data,
deleteNode,
takeSnapshot,
setNode,
setShowNode,
handles,
showNode,
updateNodeCode,
isOutdated,
selected,
]);
return (
<>
{memoizedNodeToolbarComponent}
<div
className={getNodeBorderClassName(
selected,

View file

@ -1,40 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="271px" height="271px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<defs>
<filter id="ldio-978hsxudfzl-filter" x="-100%" y="-100%" width="300%" height="300%" color-interpolation-filters="sRGB">
<feGaussianBlur in="SourceGraphic" stdDeviation="3.6"></feGaussianBlur>
<feComponentTransfer result="cutoff">
<feFuncA type="table" tableValues="0 0 0 0 0 0 1 1 1 1 1"></feFuncA>
</feComponentTransfer>
</filter>
</defs>
<g filter="url(#ldio-978hsxudfzl-filter)"><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#2edbb5">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="5s" repeatCount="indefinite" begin="-0.2s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="5s" repeatCount="indefinite" begin="0s"></animateTransform>
</g>
</g><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#1d99ff">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="2.5s" repeatCount="indefinite" begin="-0.15000000000000002s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="2.5s" repeatCount="indefinite" begin="-0.05s"></animateTransform>
</g>
</g><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#4f41ff">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.6666666666666665s" repeatCount="indefinite" begin="-0.1s"></animateTransform>
</g>
</g><g transform="translate(50 50)">
<g>
<circle cx="8" cy="0" r="5" fill="#8400ff">
<animate attributeName="r" keyTimes="0;0.5;1" values="5.3999999999999995;12.6;5.3999999999999995" dur="1.25s" repeatCount="indefinite" begin="-0.05s"></animate>
</circle>
<animateTransform attributeName="transform" type="rotate" keyTimes="0;1" values="0;360" dur="1.25s" repeatCount="indefinite" begin="-0.15000000000000002s"></animateTransform>
</g>
</g></g>
<!-- [ldio] generated by https://loading.io/ --></svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -12,6 +12,7 @@ export default function AccordionComponent({
children,
open = [],
keyValue,
sideBar,
}: AccordionComponentType): JSX.Element {
const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion()
@ -45,7 +46,9 @@ export default function AccordionComponent({
onClick={() => {
handleClick();
}}
className="ml-3"
className={
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3"
}
>
{trigger}
</AccordionTrigger>

View file

@ -74,7 +74,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onChange={handleDescriptionChange}
value={description!}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
className="mt-2 max-h-[100px] resize-none font-normal"
rows={3}
onDoubleClickCapture={(event) => {
handleFocus(event);

View file

@ -1,69 +0,0 @@
import { cloneDeep } from "lodash";
import useFlowStore from "../../stores/flowStore";
import { IOInputProps } from "../../types/components";
import IOFileInput from "../IOInputs/FileInput";
import { Textarea } from "../ui/textarea";
export default function IOInputField({
inputType,
inputId,
left,
}: IOInputProps): JSX.Element | undefined {
const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const node = nodes.find((node) => node.id === inputId);
function handleInputType() {
if (!node) return <>"No node found!"</>;
switch (inputType) {
case "TextInput":
return (
<Textarea
className={`w-full ${left ? "" : " h-full"}`}
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"].value}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
case "FileLoader":
return (
<IOFileInput
field={node.data.node!.template["file_path"]["value"]}
updateValue={(e) => {
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["file_path"].value = e;
setNode(node.id, newNode);
}
}}
/>
);
default:
return (
<Textarea
className="w-full custom-scroll"
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"]}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
}
}
return handleInputType();
}

View file

@ -1,47 +0,0 @@
import useFlowStore from "../../stores/flowStore";
import { IOOutputProps } from "../../types/components";
import { Textarea } from "../ui/textarea";
export default function IOOutputView({
outputType,
outputId,
left,
}: IOOutputProps): JSX.Element | undefined {
const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const flowPool = useFlowStore((state) => state.flowPool);
const node = nodes.find((node) => node.id === outputId);
function handleOutputType() {
if (!node) return <>"No node found!"</>;
switch (outputType) {
case "TextOutput":
return (
<Textarea
className={`w-full custom-scroll ${left ? "" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[(flowPool[node.id]?.length ?? 1) - 1]
?.params ?? ""
}
readOnly
/>
);
default:
return (
<Textarea
className={`w-full custom-scroll ${left ? "" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[(flowPool[node.id]?.length ?? 1) - 1]
?.params ?? ""
}
readOnly
/>
);
}
}
return handleOutputType();
}

View file

@ -1,17 +0,0 @@
import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
import { styled } from "@mui/material/styles";
export const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))(({ theme }) => ({
[`& .${tooltipClasses.tooltip}`]: {
backgroundColor: theme.palette.common.white,
color: "rgba(0, 0, 0, 0.87)",
boxShadow: theme.shadows[2],
fontSize: 14,
},
[`& .${tooltipClasses.arrow}:before`]: {
color: theme.palette.common.white,
boxShadow: theme.shadows[1],
},
}));

View file

@ -1,3 +0,0 @@
export default function LoadingSpinner({}) {
return <></>;
}

View file

@ -1,33 +0,0 @@
import { useNavigate } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { cn } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
import { Card, CardContent } from "../ui/card";
export default function NewFlowCardComponent({}: {}) {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
return (
<Card
className={cn(
"group relative flex h-48 w-2/6 flex-col justify-between overflow-hidden transition-all hover:shadow-md"
)}
>
<CardContent className="flex h-full w-full items-center justify-center align-middle">
<button
onClick={() => {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
className={cn("h-12 w-12 text-muted-foreground")}
name="PlusCircle"
/>
</button>
</CardContent>
</Card>
);
}

View file

@ -1,18 +0,0 @@
import { RadialProgressType } from "../../types/components";
export default function RadialProgressComponent({
value,
color,
}: RadialProgressType): JSX.Element {
const style = {
"--value": value! * 100,
"--size": "1.5rem",
"--thickness": "2px",
} as React.CSSProperties;
return (
<div className={"radial-progress " + color} style={style}>
<strong className="text-[8px]">{Math.trunc(value! * 100)}%</strong>
</div>
);
}

View file

@ -1,45 +0,0 @@
"use client";
import type { FC } from "react";
import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import "react-tooltip/dist/react-tooltip.css";
import { TooltipProps } from "../../types/components";
import { classNames } from "../../utils/utils";
const TooltipReact: FC<TooltipProps> = ({
selector,
content,
disabled,
position = "top",
children,
htmlContent,
className,
clickable,
delayShow,
}: TooltipProps): JSX.Element => {
return (
<div className="tooltip-container">
{React.cloneElement(children as React.ReactElement, {
"data-tooltip-id": selector,
})}
<ReactTooltip
id={selector}
content={content}
className={classNames(
"z-[9999] !bg-white !text-xs !font-normal !text-foreground !opacity-100 !shadow-md",
className!
)}
place={position}
clickable={clickable}
isOpen={disabled ? false : undefined}
delayShow={delayShow}
positionStrategy="absolute"
float={true}
>
{htmlContent && htmlContent}
</ReactTooltip>
</div>
);
};
export default TooltipReact;

View file

@ -1,14 +0,0 @@
import { TooltipComponentType } from "../../types/components";
import { LightTooltip } from "../LightTooltipComponent";
export default function Tooltip({
children,
title,
placement,
}: TooltipComponentType): JSX.Element {
return (
<LightTooltip placement={placement} title={title} arrow>
{children}
</LightTooltip>
);
}

View file

@ -1,121 +0,0 @@
import { Transition } from "@headlessui/react";
import { useState } from "react";
import Loading from "../../../components/ui/loading";
import { FlowType } from "../../../types/flow";
import { MISSED_ERROR_ALERT } from "../../../constants/alerts_constants";
import { BuildStatus } from "../../../constants/enums";
import useAlertStore from "../../../stores/alertStore";
import useFlowStore from "../../../stores/flowStore";
import { validateNodes } from "../../../utils/reactflowUtils";
import RadialProgressComponent from "../../RadialProgress";
import IconComponent from "../../genericIconComponent";
export default function BuildTrigger({
open,
flow,
}: {
open: boolean;
flow: FlowType;
}): JSX.Element {
const isBuilding = useFlowStore((state) => state.isBuilding);
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
const buildFlow = useFlowStore((state) => state.buildFlow);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const updateBuildStatus = useFlowStore((state) => state.updateBuildStatus);
const setErrorData = useAlertStore((state) => state.setErrorData);
const eventClick = isBuilding ? "pointer-events-none" : "";
const [progress, setProgress] = useState(0);
async function handleBuild(flow: FlowType): Promise<void> {
try {
if (isBuilding) {
return;
}
const errorsObjs = validateNodes(nodes, edges);
const errors = errorsObjs.flatMap((errorObj) => errorObj.errors);
if (errors.length > 0) {
setErrorData({
title: MISSED_ERROR_ALERT,
list: errors,
});
const ids = errorsObjs.map((errorObj) => errorObj.id);
updateBuildStatus(ids, BuildStatus.ERROR);
return;
}
const minimumLoadingTime = 200; // in milliseconds
const startTime = Date.now();
setIsBuilding(true);
await enforceMinimumLoadingTime(startTime, minimumLoadingTime);
await buildFlow({});
} catch (error) {
console.error("Error:", error);
} finally {
setIsBuilding(false);
}
}
const hasIO = useFlowStore((state) => state.hasIO);
async function enforceMinimumLoadingTime(
startTime: number,
minimumLoadingTime: number
) {
const elapsedTime = Date.now() - startTime;
const remainingTime = minimumLoadingTime - elapsedTime;
if (remainingTime > 0) {
return new Promise((resolve) => setTimeout(resolve, remainingTime));
}
}
return (
<Transition
show={!open}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<div
className={hasIO ? "fixed bottom-20 right-4" : "fixed bottom-4 right-4"}
>
<div
className={`${eventClick} round-button-form`}
onClick={() => {
handleBuild(flow);
}}
>
<button>
<div className="round-button-div">
{isBuilding && progress < 1 ? (
// Render your loading animation here when isBuilding is true
<RadialProgressComponent
// ! confirm below works
color={"text-build-trigger"}
value={progress}
></RadialProgressComponent>
) : isBuilding ? (
<Loading
strokeWidth={1.5}
className="build-trigger-loading-icon"
/>
) : (
<IconComponent
name="Zap"
className="sh-6 w-6 fill-build-trigger stroke-build-trigger stroke-1"
/>
)}
</div>
</button>
</div>
</div>
</Transition>
);
}

View file

@ -1,71 +0,0 @@
import { Transition } from "@headlessui/react";
import {
CHAT_CANNOT_OPEN_DESCRIPTION,
CHAT_CANNOT_OPEN_TITLE,
FLOW_NOT_BUILT_DESCRIPTION,
FLOW_NOT_BUILT_TITLE,
} from "../../../constants/constants";
import useAlertStore from "../../../stores/alertStore";
import { chatTriggerPropType } from "../../../types/components";
import IconComponent from "../../genericIconComponent";
export default function ChatTrigger({
open,
setOpen,
isBuilt,
canOpen,
}: chatTriggerPropType): JSX.Element {
const setErrorData = useAlertStore((state) => state.setErrorData);
function handleClick(): void {
if (isBuilt) {
if (canOpen) {
setOpen(true);
} else {
setErrorData({
title: CHAT_CANNOT_OPEN_TITLE,
list: [CHAT_CANNOT_OPEN_DESCRIPTION],
});
}
} else {
setErrorData({
title: FLOW_NOT_BUILT_TITLE,
list: [FLOW_NOT_BUILT_DESCRIPTION],
});
}
}
return (
<Transition
show={!open}
appear={true}
enter="transition ease-out duration-300"
enterFrom="translate-y-96"
enterTo="translate-y-0"
leave="transition ease-in duration-300"
leaveFrom="translate-y-0"
leaveTo="translate-y-96"
>
<button
onClick={handleClick}
className={
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button " +
(!isBuilt || !canOpen ? "cursor-not-allowed" : "cursor-pointer")
}
>
<div className="flex gap-3">
<IconComponent
name="MessagesSquare"
className={
"h-6 w-6 transition-all " +
(isBuilt && canOpen
? "message-button-icon"
: "disabled-message-button-icon")
}
/>
</div>
</button>
</Transition>
);
}

View file

@ -1,20 +1,17 @@
import { Transition } from "@headlessui/react";
import { useEffect, useMemo, useRef, useState } from "react";
import ApiModal from "../../modals/ApiModal";
import IOModal from "../../modals/IOModal";
import ShareModal from "../../modals/shareModal";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useStoreStore } from "../../stores/storeStore";
import { ChatType } from "../../types/chat";
import { classNames } from "../../utils/utils";
import IOView from "../IOview";
import ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator";
export default function FlowToolbar({ flow }: ChatType): JSX.Element {
export default function FlowToolbar(): JSX.Element {
const [open, setOpen] = useState(false);
const flowState = useFlowStore((state) => state.flowState);
const nodes = useFlowStore((state) => state.nodes);
const hasIO = useFlowStore((state) => state.hasIO);
const hasStore = useStoreStore((state) => state.hasStore);
const validApiKey = useStoreStore((state) => state.validApiKey);
@ -92,15 +89,15 @@ export default function FlowToolbar({ flow }: ChatType): JSX.Element {
<div className="flex">
<div className="flex h-full w-full gap-1 rounded-sm text-medium-indigo transition-all">
{hasIO ? (
<IOView open={open} setOpen={setOpen} disable={!hasIO}>
<div className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-medium-indigo transition-all transition-all duration-150 ease-in-out ease-in-out hover:bg-hover">
<IOModal open={open} setOpen={setOpen} disable={!hasIO}>
<div className="relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-medium-indigo transition-all transition-all duration-500 ease-in-out ease-in-out hover:bg-hover">
<ForwardedIconComponent
name="Zap"
className={"message-button-icon h-5 w-5 transition-all"}
/>
Run
</div>
</IOView>
</IOModal>
) : (
<div
className={`relative inline-flex w-full cursor-not-allowed items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-muted-foreground transition-all duration-150 ease-in-out ease-in-out`}

View file

@ -569,6 +569,11 @@ export default function CodeTabsComponent({
<IntComponent
disabled={false}
editNode={true}
rangeSpec={
node.data.node.template[
templateField
].rangeSpec
}
value={
!node.data.node.template[
templateField

View file

@ -2,6 +2,8 @@ import dynamicIconImports from "lucide-react/dynamicIconImports";
import { Suspense, forwardRef, lazy, memo } from "react";
import { IconComponentProps } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import { cn } from "../../utils/utils";
import Loading from "../ui/loading";
const ForwardedIconComponent = memo(
forwardRef(
@ -34,7 +36,9 @@ const ForwardedIconComponent = memo(
return null; // Render nothing until the icon is loaded
}
const fallback = (
<div style={{ background: "#ddd", width: 24, height: 24 }} />
<div className={cn(className, "flex items-center justify-center")}>
<Loading />
</div>
);
return (
<Suspense fallback={fallback}>

View file

@ -193,29 +193,31 @@ export const MenuBar = ({
setOpen={setOpenSettings}
></FlowSettingsModal>
</div>
<ShadTooltip
content={
SAVED_HOVER +
new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", {
hour: "numeric",
minute: "numeric",
second: "numeric",
})
}
side="bottom"
styleClasses="cursor-default"
>
<div className="flex cursor-default items-center gap-1.5 text-sm text-muted-foreground">
<IconComponent
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
className={cn(
"h-4 w-4",
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
)}
/>
{printByBuildStatus()}
</div>
</ShadTooltip>
{(currentFlow.updated_at || saveLoading) && (
<ShadTooltip
content={
SAVED_HOVER +
new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", {
hour: "numeric",
minute: "numeric",
second: "numeric",
})
}
side="bottom"
styleClasses="cursor-default"
>
<div className="flex cursor-default items-center gap-1.5 text-sm text-muted-foreground">
<IconComponent
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
className={cn(
"h-4 w-4",
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
)}
/>
{printByBuildStatus()}
</div>
</ShadTooltip>
)}
</div>
) : (
<></>

View file

@ -119,7 +119,7 @@ export default function InputComponent({
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && setSelectedOption ? "pr-16" : "",
password && setSelectedOption ? "pr-[62.9px]" : "",
(!password && setSelectedOption) ||
(password && !setSelectedOption)
? "pr-8"

View file

@ -9,11 +9,12 @@ import { Input } from "../ui/input";
export default function IntComponent({
value,
onChange,
rangeSpec,
disabled,
editNode = false,
id = "",
}: IntComponentType): JSX.Element {
const min = 0;
const min = -Infinity;
// Clear component state
useEffect(() => {
@ -31,8 +32,9 @@ export default function IntComponent({
handleKeyDown(event, value, "");
}}
type="number"
step="1"
min={0}
step={rangeSpec?.step ?? 1}
min={rangeSpec?.min ?? min}
max={rangeSpec?.max ?? undefined}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (Number(event.target.value) < min) {
event.target.value = min.toString();

View file

@ -1,72 +0,0 @@
import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-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 "../../../../constants/constants";
import { Props } from "../../../../types/components";
export function CodeBlock({ language, value }: Props): JSX.Element {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const copyToClipboard = (): void => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
navigator.clipboard.writeText(value).then(() => {
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 2000);
});
};
const downloadAsFile = (): void => {
const fileExtension = programmingLanguages[language] || ".file";
const suggestedFileName = `${"generated-code"}${fileExtension}`;
const fileName = window.prompt("enter file name", suggestedFileName);
if (!fileName) {
// user pressed cancel on prompt
return;
}
const blob = new Blob([value], { type: "text/plain" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = fileName;
link.href = url;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
return (
<div className="codeblock font-sans text-[16px]">
<div className="code-block-modal">
<span className="code-block-modal-span">{language}</span>
<div className="flex items-center">
<button className="code-block-modal-button" onClick={copyToClipboard}>
{isCopied ? <IconCheck size={18} /> : <IconClipboard size={18} />}
{isCopied ? "Copied!" : "Copy Code"}
</button>
<button className="code-block-modal-button" onClick={downloadAsFile}>
<IconDownload size={18} />
</button>
</div>
</div>
<SyntaxHighlighter
className="overflow-auto"
language={language}
style={oneDark}
customStyle={{ margin: 0 }}
>
{value}
</SyntaxHighlighter>
</div>
);
}
CodeBlock.displayName = "CodeBlock";

View file

@ -1,85 +0,0 @@
import * as base64js from "base64-js";
import { useState } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../types/components";
export default function FileCard({
fileName,
content,
fileType,
}: fileCardPropsType): JSX.Element {
const handleDownload = (): void => {
const byteArray = new Uint8Array(base64js.toByteArray(content));
const blob = new Blob([byteArray], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = fileName + ".png";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
const [isHovered, setIsHovered] = useState(false);
function handleMouseEnter(): void {
setIsHovered(true);
}
function handleMouseLeave(): void {
setIsHovered(false);
}
if (fileType === "image") {
return (
<div
className="relative h-1/4 w-1/4"
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<img
src={`data:image/png;base64,${content}`}
alt="generated image"
className="h-full w-full rounded-lg"
/>
{isHovered && (
<div className={`file-card-modal-image-div `}>
<button
className="file-card-modal-image-button "
onClick={handleDownload}
>
<IconComponent
name="DownloadCloud"
className="h-5 w-5 text-current hover:scale-110"
/>
</button>
</div>
)}
</div>
);
}
return (
<button onClick={handleDownload} className="file-card-modal-button">
<div className="file-card-modal-div">
ooooooooooooooo{" "}
{fileType === "image" ? (
<img
src={`data:image/png;base64,${content}`}
alt=""
className="h-8 w-8"
/>
) : (
<IconComponent name="File" className="h-8 w-8" />
)}
<div className="file-card-modal-footer">
{" "}
<div className="file-card-modal-name">{fileName}</div>
<div className="file-card-modal-type">{fileType}</div>
</div>
<IconComponent
name="DownloadCloud"
className="ml-auto h-6 w-6 text-current"
/>
</div>
</button>
);
}

View file

@ -1,30 +0,0 @@
import React, { ReactNode } from "react";
interface ElementStackProps {
children: ReactNode[];
}
const ElementStack: React.FC<ElementStackProps> = ({ children }) => {
return (
<div
className={`grid grid-cols-1`}
style={{ display: "grid", gridAutoFlow: "row" }}
>
{children.map((child, index) => (
<div
key={index}
style={{
gridColumn: 1,
gridRow: 1,
transform: `translateX(${index * 0.1}rem)`,
zIndex: children.length - index,
}}
>
{child}
</div>
))}
</div>
);
};
export default ElementStack;

View file

@ -1,59 +0,0 @@
import { Switch } from "@headlessui/react";
import { useEffect } from "react";
import { ToggleComponentType } from "../../types/components";
import { classNames } from "../../utils/utils";
export default function ToggleComponent({
enabled,
setEnabled,
disabled,
}: ToggleComponentType): JSX.Element {
// set component state as disabled
useEffect(() => {
if (disabled) {
setEnabled(false);
}
}, [disabled, setEnabled]);
return (
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<Switch
checked={enabled}
onChange={(isEnabled: boolean) => {
setEnabled(isEnabled);
}}
className={classNames(
enabled ? "bg-primary" : "bg-input",
"toggle-component-switch "
)}
>
<span className="sr-only">Use setting</span>
<span
className={classNames(
enabled ? "translate-x-5" : "translate-x-0",
"toggle-component-span",
disabled ? "bg-input " : "bg-background"
)}
>
<span
className={classNames(
enabled
? "opacity-0 duration-100 ease-out"
: "opacity-100 duration-200 ease-in",
"toggle-component-second-span"
)}
aria-hidden="true"
></span>
<span
className={classNames(
enabled
? "opacity-100 duration-200 ease-in"
: "opacity-0 duration-100 ease-out",
"toggle-component-second-span"
)}
aria-hidden="true"
></span>
</span>
</Switch>
</div>
);
}

View file

@ -32,7 +32,7 @@ const AccordionTrigger = React.forwardRef<
)}
>
{children}
<ChevronDownIcon className="h-4 w-4 text-muted-foreground transition-transform duration-200" />
<ChevronDownIcon className="h-4 w-4 font-bold text-primary transition-transform duration-200" />
</div>
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>

View file

@ -43,17 +43,17 @@ export interface ButtonProps
function toTitleCase(text: string) {
return text
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false,children, ...props }, ref) => {
({ className, variant, size, asChild = false, children, ...props }, ref) => {
const Comp = asChild ? Slot : "button";
let newChildren = children;
if (typeof(children)==="string"){
newChildren = toTitleCase(children)
if (typeof children === "string") {
newChildren = toTitleCase(children);
}
return (
<Comp
@ -61,7 +61,6 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
ref={ref}
children={newChildren}
{...props}
/>
);
}

View file

@ -753,4 +753,4 @@ export const NATIVE_CATEGORIES = [
"agents",
];
export const SAVE_DEBOUNCE_TIME = 500;
export const SAVE_DEBOUNCE_TIME = 300;

View file

@ -14,3 +14,8 @@ export enum BuildStatus {
INACTIVE = "INACTIVE",
ERROR = "ERROR",
}
export enum InputOutput {
INPUT = "input",
OUTPUT = "output",
}

View file

@ -83,7 +83,7 @@ export function AuthProvider({ children }): React.ReactElement {
useFlowsManagerStore.setState({ isLoading: false });
}
});
}, [setUserData, setLoading, autoLogin, setIsAdmin]);
}, [autoLogin]);
function getUser() {
getLoggedUser()

View file

@ -0,0 +1 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M16 8.016A8.522 8.522 0 008.016 16h-.032A8.521 8.521 0 000 8.016v-.032A8.521 8.521 0 007.984 0h.032A8.522 8.522 0 0016 7.984v.032z" fill="url(#prefix__paint0_radial_980_20147)"/><defs><radialGradient id="prefix__paint0_radial_980_20147" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(16.1326 5.4553 -43.70045 129.2322 1.588 6.503)"><stop offset=".067" stop-color="#9168C0"/><stop offset=".343" stop-color="#5684D1"/><stop offset=".672" stop-color="#1BA1E3"/></radialGradient></defs></svg>

After

Width:  |  Height:  |  Size: 599 B

View file

@ -0,0 +1,10 @@
const SvgGoogleGenerativeAI = (props) => (<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" {...props}>
<path d="M16 8.016A8.522 8.522 0 008.016 16h-.032A8.521 8.521 0 000 8.016v-.032A8.521 8.521 0 007.984 0h.032A8.522 8.522 0 0016 7.984v.032z" fill="url(#prefix__paint0_radial_980_20147)"/>
<defs>
<radialGradient id="prefix__paint0_radial_980_20147" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(16.1326 5.4553 -43.70045 129.2322 1.588 6.503)">
<stop offset=".067" stop-color="#9168C0"/><stop offset=".343" stop-color="#5684D1"/><stop offset=".672" stop-color="#1BA1E3"/>
</radialGradient>
</defs>
</svg>
);
export default SvgGoogleGenerativeAI;

View file

@ -0,0 +1,9 @@
import React, { forwardRef } from "react";
import SvgGoogleGenerativeAI from "./GoogleGemini";
export const GoogleGenerativeAIIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <SvgGoogleGenerativeAI ref={ref} {...props} />;
});

View file

@ -8,14 +8,17 @@ import "./style/index.css";
// @ts-ignore
import "./style/applies.css";
// @ts-ignore
import { StrictMode } from "react";
import "./style/classes.css";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<ContextWrapper>
<App />
</ContextWrapper>
<StrictMode>
<ContextWrapper>
<App />
</ContextWrapper>
</StrictMode>
);
reportWebVitals();

View file

@ -60,10 +60,6 @@ const EditNodeModal = forwardRef(
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
const setNoticeData = useAlertStore((state) => state.setNoticeData);
const globalVariablesEntries = useGlobalVariablesStore(
(state) => state.globalVariablesEntries
);
function changeAdvanced(n) {
setMyData((old) => {

View file

@ -1,11 +1,11 @@
import { Button } from "../../ui/button";
import { Button } from "../../../../../../components/ui/button";
import { useEffect, useState } from "react";
import { BASE_URL_API } from "../../../constants/constants";
import { uploadFile } from "../../../controllers/API";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { IOFileInputProps } from "../../../types/components";
import IconComponent from "../../genericIconComponent";
import IconComponent from "../../../../../../components/genericIconComponent";
import { BASE_URL_API } from "../../../../../../constants/constants";
import { uploadFile } from "../../../../../../controllers/API";
import useFlowsManagerStore from "../../../../../../stores/flowsManagerStore";
import { IOFileInputProps } from "../../../../../../types/components";
export default function IOFileInput({ field, updateValue }: IOFileInputProps) {
//component to handle file upload from chatIO

View file

@ -0,0 +1,109 @@
import { cloneDeep } from "lodash";
import { Textarea } from "../../../../components/ui/textarea";
import { InputOutput } from "../../../../constants/enums";
import useFlowStore from "../../../../stores/flowStore";
import { IOFieldViewProps } from "../../../../types/components";
import IOFileInput from "./components/FileInput";
export default function IOFieldView({
type,
fieldType,
fieldId,
left,
}: IOFieldViewProps): JSX.Element | undefined {
const nodes = useFlowStore((state) => state.nodes);
const setNode = useFlowStore((state) => state.setNode);
const flowPool = useFlowStore((state) => state.flowPool);
const node = nodes.find((node) => node.id === fieldId);
function handleOutputType() {
if (!node) return <>"No node found!"</>;
switch (type) {
case InputOutput.INPUT:
switch (fieldType) {
case "TextInput":
return (
<Textarea
className={`w-full custom-scroll ${left ? " min-h-32" : " h-full"}`}
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"].value}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
case "FileLoader":
return (
<IOFileInput
field={node.data.node!.template["file_path"]["value"]}
updateValue={(e) => {
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["file_path"].value = e;
setNode(node.id, newNode);
}
}}
/>
);
default:
return (
<Textarea
className={`w-full custom-scroll ${left ? " min-h-32" : " h-full"}`}
placeholder={"Enter text..."}
value={node.data.node!.template["input_value"]}
onChange={(e) => {
e.target.value;
if (node) {
let newNode = cloneDeep(node);
newNode.data.node!.template["input_value"].value =
e.target.value;
setNode(node.id, newNode);
}
}}
/>
);
}
case InputOutput.OUTPUT:
switch (fieldType) {
case "TextOutput":
return (
<Textarea
className={`w-full custom-scroll ${left ? " min-h-32" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[
(flowPool[node.id]?.length ?? 1) - 1
]?.params ?? ""
}
readOnly
/>
);
default:
return (
<Textarea
className={`w-full custom-scroll ${left ? " min-h-32" : " h-full"}`}
placeholder={"Empty"}
// update to real value on flowPool
value={
(flowPool[node.id] ?? [])[
(flowPool[node.id]?.length ?? 1) - 1
]?.params ?? ""
}
readOnly
/>
);
}
default:
break;
}
}
return handleOutputType();
}

View file

@ -1,13 +1,13 @@
import { useEffect, useState } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { Textarea } from "../../../components/ui/textarea";
import IconComponent from "../../../../../components/genericIconComponent";
import { Textarea } from "../../../../../components/ui/textarea";
import {
CHAT_INPUT_PLACEHOLDER,
CHAT_INPUT_PLACEHOLDER_SEND,
} from "../../../constants/constants";
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
import { chatInputType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
} from "../../../../../constants/constants";
import useFlowsManagerStore from "../../../../../stores/flowsManagerStore";
import { chatInputType } from "../../../../../types/components";
import { classNames } from "../../../../../utils/utils";
export default function ChatInput({
lockChat,
@ -25,17 +25,8 @@ export default function ChatInput({
}
}, [lockChat, inputRef]);
/* function handleChange(value: number) {
console.log(value);
if (value > 0) {
setRepeat(value);
} else {
setRepeat(1);
}
} */
useEffect(() => {
if (inputRef.current) {
if (inputRef.current && inputRef.current.scrollHeight !== 0) {
inputRef.current.style.height = "inherit"; // Reset the height
inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; // Set it to the scrollHeight
}
@ -50,7 +41,8 @@ export default function ChatInput({
event.key === "Enter" &&
!lockChat &&
!saveLoading &&
!event.shiftKey
!event.shiftKey &&
!event.nativeEvent.isComposing
) {
sendMessage(repeat);
}
@ -108,7 +100,7 @@ export default function ChatInput({
/>
) : noInput ? (
<IconComponent
name="Play"
name="Zap"
className="form-modal-play-icon"
aria-hidden="true"
/>

View file

@ -2,8 +2,8 @@ import { IconCheck, IconClipboard, IconDownload } from "@tabler/icons-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 "../../../../constants/constants";
import { Props } from "../../../../types/components";
import { programmingLanguages } from "../../../../../../constants/constants";
import { Props } from "../../../../../../types/components";
export function CodeBlock({ language, value }: Props): JSX.Element {
const [isCopied, setIsCopied] = useState<Boolean>(false);

View file

@ -4,15 +4,14 @@ import Markdown 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 CodeTabsComponent from "../../../components/codeTabsComponent";
import IconComponent from "../../../components/genericIconComponent";
import useAlertStore from "../../../stores/alertStore";
import useFlowStore from "../../../stores/flowStore";
import { chatMessagePropsType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
import MaleTechnology from "../../../../../assets/male-technologist.png";
import Robot from "../../../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../../../components/SanitizedHTMLWrapper";
import CodeTabsComponent from "../../../../../components/codeTabsComponent";
import IconComponent from "../../../../../components/genericIconComponent";
import useFlowStore from "../../../../../stores/flowStore";
import { chatMessagePropsType } from "../../../../../types/components";
import { classNames, cn } from "../../../../../utils/utils";
import FileCard from "../fileComponent";
export default function ChatMessage({
@ -34,7 +33,6 @@ export default function ChatMessage({
const [isStreaming, setIsStreaming] = useState(false);
const eventSource = useRef<EventSource | undefined>(undefined);
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
const setErrorData = useAlertStore((state) => state.setErrorData);
const chatMessageRef = useRef(chatMessage);
// Sync ref with state
@ -59,17 +57,7 @@ export default function ChatMessage({
setIsStreaming(false);
eventSource.current?.close();
setStreamUrl(undefined);
// property data is not available in the event object
// so check if the event object has a data property
if (event.data) {
let parsedData = JSON.parse(event.data);
if (parsedData.error) {
reject(new Error(parsedData.error));
} else
reject(new Error("An error occurred while streaming the output"));
} else {
reject(new Error("An error occurred while streaming the output"));
}
reject(new Error("Streaming failed"));
};
eventSource.current.addEventListener("close", (event) => {
setStreamUrl(undefined); // Update state to reflect the stream is closed
@ -91,10 +79,7 @@ export default function ChatMessage({
}
})
.catch((error) => {
setErrorData({
title: "Streaming Error",
list: [error.message],
});
console.error(error);
setLockChat(false);
});
}
@ -123,30 +108,28 @@ export default function ChatMessage({
chat.isSend ? "" : " "
)}
>
<div className={classNames("form-modal-chatbot-icon")}>
{!chat.isSend ? (
<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>
<span className="truncate text-xs">{chat.sender_name}</span>
</div>
) : (
<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>
<span className="truncate text-xs">{chat.sender_name}</span>
</div>
<div
className={classNames(
"mr-3 mt-1 flex max-w-16 flex-col items-center gap-1 overflow-hidden px-3 pb-3"
)}
>
<div className="flex flex-col items-center gap-1">
<div
className={cn(
"relative flex h-8 w-8 items-center justify-center overflow-hidden rounded-md p-5 text-2xl",
!chat.isSend ? "bg-chat-bot-icon" : "bg-chat-user-icon"
)}
>
<img
src={!chat.isSend ? Robot : MaleTechnology}
className="absolute scale-[60%]"
alt={!chat.isSend ? "robot_image" : "male_technology"}
/>
</div>
<span className="max-w-16 truncate text-xs">
{chat.sender_name}
</span>
</div>
</div>
{!chat.isSend ? (
<div className="form-modal-chat-text-position min-w-96 flex-grow">

View file

@ -1,7 +1,7 @@
import * as base64js from "base64-js";
import { useState } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../types/components";
import IconComponent from "../../../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../../../types/components";
export default function FileCard({
fileName,

View file

@ -1,31 +1,32 @@
import { useEffect, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { NOCHATOUTPUT_NOTICE_ALERT } from "../../constants/alerts_constants";
import IconComponent from "../../../../components/genericIconComponent";
import { NOCHATOUTPUT_NOTICE_ALERT } from "../../../../constants/alerts_constants";
import {
CHAT_FIRST_INITIAL_TEXT,
CHAT_SECOND_INITIAL_TEXT,
} from "../../constants/constants";
import { deleteFlowPool } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { sendAllProps } from "../../types/api";
} from "../../../../constants/constants";
import { deleteFlowPool } from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { sendAllProps } from "../../../../types/api";
import {
ChatMessageType,
ChatOutputType,
FlowPoolObjectType,
} from "../../types/chat";
import { classNames } from "../../utils/utils";
} from "../../../../types/chat";
import { chatViewProps } from "../../../../types/components";
import { classNames } from "../../../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
export default function NewChatView({
export default function ChatView({
sendMessage,
chatValue,
setChatValue,
lockChat,
setLockChat,
}): JSX.Element {
}: chatViewProps): JSX.Element {
const { flowPool, outputs, inputs, CleanFlowPool } = useFlowStore();
const { setNoticeData } = useAlertStore();
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
@ -38,11 +39,11 @@ export default function NewChatView({
const outputTypes = outputs.map((obj) => obj.type);
const updateFlowPool = useFlowStore((state) => state.updateFlowPool);
useEffect(() => {
if (!outputTypes.includes("ChatOutput")) {
setNoticeData({ title: NOCHATOUTPUT_NOTICE_ALERT });
}
}, []);
// useEffect(() => {
// if (!outputTypes.includes("ChatOutput")) {
// setNoticeData({ title: NOCHATOUTPUT_NOTICE_ALERT });
// }
// }, []);
//build chat history
useEffect(() => {

View file

@ -1,36 +1,38 @@
import { useEffect, useState } from "react";
import AccordionComponent from "../../components/AccordionComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import { Badge } from "../../components/ui/badge";
import { Button } from "../../components/ui/button";
import {
Tabs,
TabsContent,
TabsList,
TabsTrigger,
} from "../../components/ui/tabs";
import {
CHAT_FORM_DIALOG_SUBTITLE,
OUTPUTS_MODAL_TITLE,
TEXT_INPUT_MODAL_TITLE,
} from "../../constants/constants";
import BaseModal from "../../modals/baseModal";
import { InputOutput } from "../../constants/enums";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { IOModalPropsType } from "../../types/components";
import { NodeType } from "../../types/flow";
import { updateVerticesOrder } from "../../utils/buildUtils";
import { cn } from "../../utils/utils";
import AccordionComponent from "../AccordionComponent";
import IOInputField from "../IOInputField";
import IOOutputView from "../IOOutputView";
import ShadTooltip from "../ShadTooltipComponent";
import IconComponent from "../genericIconComponent";
import NewChatView from "../newChatView";
import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
import BaseModal from "../baseModal";
import IOFieldView from "./components/IOFieldView";
import ChatView from "./components/chatView";
export default function IOView({
export default function IOModal({
children,
open,
setOpen,
disable,
}: {
children: JSX.Element;
open: boolean;
setOpen: (open: boolean) => void;
disable?: boolean;
}): JSX.Element {
}: IOModalPropsType): JSX.Element {
const allNodes = useFlowStore((state) => state.nodes);
const inputs = useFlowStore((state) => state.inputs).filter(
(input) => input.type !== "ChatInput"
);
@ -52,9 +54,24 @@ export default function IOView({
const [selectedTab, setSelectedTab] = useState(
inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0
);
function startView() {
if (!chatInput && !chatOutput) {
if (inputs.length > 0) {
return inputs[0];
}
else {
return outputs[0];
}
}
else {
return undefined
}
}
const [selectedViewField, setSelectedViewField] = useState<
{ type: string; id: string } | undefined
>(undefined);
>(startView());
const buildFlow = useFlowStore((state) => state.buildFlow);
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
@ -99,19 +116,13 @@ export default function IOView({
}
useEffect(() => {
setSelectedViewField(undefined);
setSelectedViewField(startView());
setSelectedTab(inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0);
}, [inputs.length, outputs.length]);
}, [allNodes]);
return (
<BaseModal
size={
haveChat || selectedViewField
? selectedTab === 0
? "large-thin"
: "large"
: "small"
}
size={selectedTab === 0 ? "large-thin" : "large"}
open={open}
setOpen={setOpen}
disable={disable}
@ -134,8 +145,7 @@ export default function IOView({
{selectedTab !== 0 && (
<div
className={cn(
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start transition-all duration-300",
haveChat || selectedViewField ? "w-2/6" : "w-full"
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start transition-all duration-300"
)}
>
<Tabs
@ -212,10 +222,11 @@ export default function IOView({
<div className="file-component-tab-column">
<div className="">
{input && (
<IOInputField
<IOFieldView
type={InputOutput.INPUT}
left={true}
inputType={input.type}
inputId={input.id}
fieldType={input.type}
fieldId={input.id}
/>
)}
</div>
@ -230,7 +241,7 @@ export default function IOView({
className="api-modal-tabs-content mt-4"
>
<div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold">
<IconComponent className="h-4 w-4" name={"FileType2"} />
<IconComponent className="h-4 w-4" name={"Type"} />
{OUTPUTS_MODAL_TITLE}
</div>
{nodes
@ -279,10 +290,11 @@ export default function IOView({
<div className="file-component-tab-column">
<div className="">
{output && (
<IOOutputView
<IOFieldView
type={InputOutput.OUTPUT}
left={true}
outputType={output.type}
outputId={output.id}
fieldType={output.type}
fieldId={output.id}
/>
)}
</div>
@ -296,74 +308,81 @@ export default function IOView({
</div>
)}
{haveChat || selectedViewField ? (
<div className="flex h-full min-w-96 flex-grow">
{selectedViewField && (
<div
className={cn(
"flex h-full w-full flex-col items-start gap-4 pt-4",
!selectedViewField ? "hidden" : ""
)}
>
<div className="font-xl flex items-center justify-center gap-3 font-semibold">
<button onClick={() => setSelectedViewField(undefined)}>
<IconComponent
name={"ArrowLeft"}
className="h-6 w-6"
></IconComponent>
</button>
{selectedViewField.type}
</div>
<div className="h-full w-full">
{inputs.some(
(input) => input.id === selectedViewField.id
) ? (
<IOInputField
left={false}
inputType={selectedViewField.type!}
inputId={selectedViewField.id!}
/>
) : (
<IOOutputView
left={false}
outputType={selectedViewField.type!}
outputId={selectedViewField.id!}
/>
)}
</div>
</div>
)}
<div className="flex h-full min-w-96 flex-grow">
{selectedViewField && (
<div
className={cn(
"flex h-full w-full",
selectedViewField ? "hidden" : ""
"flex h-full w-full flex-col items-start gap-4 pt-4",
!selectedViewField ? "hidden" : ""
)}
>
<NewChatView
<div className="font-xl flex items-center justify-center gap-3 font-semibold">
{(haveChat && <button onClick={() => setSelectedViewField(undefined)}>
<IconComponent
name={"ArrowLeft"}
className="h-6 w-6"
></IconComponent>
</button>)}
{
nodes.find((node) => node.id === selectedViewField.id)
?.data.node.display_name
}
</div>
<div className="h-full w-full">
{inputs.some(
(input) => input.id === selectedViewField.id
) ? (
<IOFieldView
type={InputOutput.INPUT}
left={false}
fieldType={selectedViewField.type!}
fieldId={selectedViewField.id!}
/>
) : (
<IOFieldView
type={InputOutput.OUTPUT}
left={false}
fieldType={selectedViewField.type!}
fieldId={selectedViewField.id!}
/>
)}
</div>
</div>
)}
<div
className={cn(
"flex h-full w-full",
selectedViewField ? "hidden" : ""
)}
>
{haveChat ? (
<ChatView
sendMessage={sendMessage}
chatValue={chatValue}
setChatValue={setChatValue}
lockChat={lockChat}
setLockChat={setLockChat}
/>
</div>
) : (
<span className="flex h-full w-full items-center justify-center font-thin text-muted-foreground">
Select an IO component to view
</span>
)}
</div>
) : (
<div className="absolute bottom-8 right-8"></div>
)}
</div>
</div>
</div>
</BaseModal.Content>
<BaseModal.Footer>
{!haveChat && (
<div className="flex w-full justify-end pt-2">
<div className="flex w-full justify-end pt-2">
<Button
variant={"outline"}
className="flex gap-2 px-3"
onClick={() => sendMessage(1)}
>
<IconComponent
name={isBuilding ? "Loader2" : "Play"}
name={isBuilding ? "Loader2" : "Zap"}
className={cn(
"h-4 w-4",
isBuilding

View file

@ -1,6 +1,11 @@
import { useNavigate } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
import {
Card,
CardContent,
CardDescription,
CardTitle,
} from "../../../../components/ui/card";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
export default function NewFlowCardComponent() {
const addFlow = useFlowsManagerStore((state) => state.addFlow);

View file

@ -1,21 +1,28 @@
import { useNavigate } from "react-router-dom";
/// <reference types="vite-plugin-svgr/client" />
//@ts-ignore
import { ReactComponent as TransferFiles } from "../../assets/undraw_transfer_files_re_a2a9.svg";
import { ReactComponent as TransferFiles } from "../../../../assets/undraw_transfer_files_re_a2a9.svg";
//@ts-ignore
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg";
import { ReactComponent as BasicPrompt } from "../../../../assets/undraw_design_components_9vy6.svg";
//@ts-ignore
import { ReactComponent as ChatWithHistory } from "../../assets/undraw_mobile_messages_re_yx8w.svg";
import { ReactComponent as ChatWithHistory } from "../../../../assets/undraw_mobile_messages_re_yx8w.svg";
//@ts-ignore
import { ReactComponent as Assistant } from "../../assets/undraw_team_collaboration_re_ow29.svg";
import { ReactComponent as Assistant } from "../../../../assets/undraw_team_collaboration_re_ow29.svg";
//@ts-ignore
import { ReactComponent as APIRequest } from "../../assets/undraw_real_time_analytics_re_yliv.svg";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowType } from "../../types/flow";
import { updateIds } from "../../utils/reactflowUtils";
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
import { ReactComponent as APIRequest } from "../../../../assets/undraw_real_time_analytics_re_yliv.svg";
import {
Card,
CardContent,
CardDescription,
CardTitle,
} from "../../../../components/ui/card";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { UndrawCardComponentProps } from "../../../../types/components";
import { updateIds } from "../../../../utils/reactflowUtils";
export default function UndrawCardComponent({ flow }: { flow: FlowType }) {
export default function UndrawCardComponent({
flow,
}: UndrawCardComponentProps): JSX.Element {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();

View file

@ -0,0 +1,35 @@
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { newFlowModalPropsType } from "../../types/components";
import BaseModal from "../baseModal";
import NewFlowCardComponent from "./components/NewFlowCardComponent";
import UndrawCardComponent from "./components/undrawCards";
export default function NewFlowModal({
open,
setOpen,
}: newFlowModalPropsType): JSX.Element {
const examples = useFlowsManagerStore((state) => state.examples);
return (
<BaseModal size="three-cards" open={open} setOpen={setOpen}>
<BaseModal.Header description={"Select a template below"}>
<span className="pr-2" data-testid="modal-title">
Get Started
</span>
{/* <IconComponent
name="Group"
className="h-6 w-6 stroke-2 text-primary "
aria-hidden="true"
/> */}
</BaseModal.Header>
<BaseModal.Content>
<div className=" grid h-full w-full grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
<NewFlowCardComponent />
{examples.map((example, idx) => {
return <UndrawCardComponent key={idx} flow={example} />;
})}
</div>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -1,113 +0,0 @@
import { useEffect } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { Textarea } from "../../../components/ui/textarea";
import {
CHAT_INPUT_PLACEHOLDER,
CHAT_INPUT_PLACEHOLDER_SEND,
} from "../../../constants/constants";
import { chatInputType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
export default function ChatInput({
lockChat,
chatValue,
sendMessage,
setChatValue,
inputRef,
noInput,
}: chatInputType): JSX.Element {
useEffect(() => {
if (!lockChat && inputRef.current) {
inputRef.current.focus();
}
}, [lockChat, inputRef]);
useEffect(() => {
if (inputRef.current) {
inputRef.current.style.height = "inherit"; // Reset the height
inputRef.current.style.height = `${inputRef.current.scrollHeight}px`; // Set it to the scrollHeight
}
}, [chatValue]);
return (
<div className="relative">
<Textarea
onKeyDown={(event) => {
if (event.key === "Enter" && !event.nativeEvent.isComposing && !lockChat && !event.shiftKey) {
sendMessage();
}
}}
rows={1}
ref={inputRef}
disabled={lockChat || noInput}
style={{
resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`,
maxHeight: "150px",
overflow: `${
inputRef.current && inputRef.current.scrollHeight > 150
? "auto"
: "hidden"
}`,
}}
value={
lockChat
? "Thinking..."
: typeof chatValue === "object" &&
Object.keys(chatValue)?.length === 0
? CHAT_INPUT_PLACEHOLDER
: chatValue
}
onChange={(event): void => {
setChatValue(event.target.value);
}}
className={classNames(
lockChat
? " form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
: " form-modal-lock-false bg-background",
"form-modal-lockchat"
)}
placeholder={
noInput ? CHAT_INPUT_PLACEHOLDER : CHAT_INPUT_PLACEHOLDER_SEND
}
/>
<div className="form-modal-send-icon-position">
<button
className={classNames(
"form-modal-send-button",
noInput
? "bg-high-indigo text-background"
: chatValue === ""
? "text-primary"
: "bg-chat-send text-background"
)}
disabled={lockChat}
onClick={(): void => sendMessage()}
>
{lockChat ? (
<IconComponent
name="Lock"
className="form-modal-lock-icon"
aria-hidden="true"
/>
) : noInput ? (
<IconComponent
name="Sparkles"
className="form-modal-play-icon"
aria-hidden="true"
/>
) : (
<IconComponent
name="LucideSend"
className="form-modal-send-icon "
aria-hidden="true"
/>
)}
</button>
</div>
</div>
);
}

View file

@ -1,221 +0,0 @@
import Convert from "ansi-to-html";
import { 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 CodeTabsComponent from "../../../components/codeTabsComponent";
import IconComponent from "../../../components/genericIconComponent";
import { chatMessagePropsType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
import FileCard from "../fileComponent";
export default function ChatMessage({
chat,
lockChat,
lastMessage,
}: chatMessagePropsType): JSX.Element {
const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true);
const template = chat.template;
const [promptOpen, setPromptOpen] = useState(false);
return (
<div
className={classNames("form-modal-chat-position", chat.isSend ? "" : " ")}
>
<div className={classNames("form-modal-chatbot-icon ")}>
{!chat.isSend ? (
<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="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="form-modal-chat-text-position">
<div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={(): void => setHidden((prev) => !prev)}
className="form-modal-chat-icon-div"
>
<IconComponent
name="MessageSquare"
className="form-modal-chat-icon"
/>
</div>
)}
{chat.thought && chat.thought !== "" && !hidden && (
<SanitizedHTMLWrapper
className=" form-modal-chat-thought"
content={convert.toHtml(chat.thought)}
onClick={() => setHidden((prev) => !prev)}
/>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full">
<div className="w-full dark:text-white">
<div className="w-full">
{chat.message.toString() === "" && lockChat ? (
<IconComponent
name="MoreHorizontal"
className="h-8 w-8 animate-pulse"
/>
) : (
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose min-w-full text-primary word-break-break-word
dark:prose-invert"
components={{
pre({ node, ...props }) {
return <>{props.children}</>;
},
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 || "");
return !inline ? (
<CodeTabsComponent
isMessage
tabs={[
{
name: (match && match[1]) || "",
mode: (match && match[1]) || "",
image:
"https://curl.se/logo/curl-symbol-transparent.png",
language: (match && match[1]) || "",
code: String(children).replace(/\n$/, ""),
},
]}
activeTab={"0"}
setActiveTab={() => {}}
/>
) : (
<code className={className} {...props}>
{children}
</code>
);
},
}}
>
{chat.message.toString()}
</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>
{template ? (
<>
<button
className="form-modal-initial-prompt-btn"
onClick={() => {
setPromptOpen((old) => !old);
}}
>
Display Prompt
<IconComponent
name="ChevronDown"
className={
"h-3 w-3 transition-all " + (promptOpen ? "rotate-180" : "")
}
/>
</button>
<span className="prose text-primary word-break-break-word dark:prose-invert">
{promptOpen
? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g;
let match;
let parts: Array<JSX.Element | string> = [];
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>
</>
) : (
<span>{chat.message[chat.chatKey]}</span>
)}
</div>
)}
</div>
);
}

View file

@ -1,650 +0,0 @@
import { useContext, useEffect, useRef, useState } from "react";
import { sendAllProps } from "../../types/api";
import { ChatMessageType } from "../../types/chat";
import { FlowType } from "../../types/flow";
import { classNames } from "../../utils/utils";
import ChatInput from "./chatInput";
import ChatMessage from "./chatMessage";
import _, { cloneDeep } from "lodash";
import AccordionComponent from "../../components/AccordionComponent";
import IconComponent from "../../components/genericIconComponent";
import ToggleShadComponent from "../../components/toggleShadComponent";
import { Badge } from "../../components/ui/badge";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { Textarea } from "../../components/ui/textarea";
import {
CHAT_ERROR_ALERT,
INFO_MISSING_ALERT,
MSG_ERROR_ALERT,
} from "../../constants/alerts_constants";
import {
CHAT_FIRST_INITIAL_TEXT,
CHAT_FORM_DIALOG_SUBTITLE,
CHAT_SECOND_INITIAL_TEXT,
LANGFLOW_CHAT_TITLE,
} from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import { AuthContext } from "../../contexts/authContext";
import { getBuildStatus } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import useFlowStore from "../../stores/flowStore";
import { FlowState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils";
export default function FormModal({
flow,
open,
setOpen,
}: {
open: boolean;
setOpen: (open: boolean) => void;
flow: FlowType;
}): JSX.Element {
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const updateBuildStatus = useFlowStore((state) => state.updateBuildStatus);
const flowState = useFlowStore((state) => state.flowState);
const setFlowState = useFlowStore((state) => state.setFlowState);
const [chatValue, setChatValue] = useState(() => {
try {
if (!flowState) {
throw new Error("flowState is undefined");
}
const inputKeys = flowState.input_keys;
const handleKeys = flowState.handle_keys;
const keyToUse = Object.keys(inputKeys!).find(
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
);
return inputKeys![keyToUse!];
} catch (error) {
console.error(error);
// return a sensible default or `undefined` if no default is possible
return undefined;
}
});
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
const template = useRef(flowState?.template ?? undefined);
const { accessToken } = useContext(AuthContext);
const setErrorData = useAlertStore((state) => state.setErrorData);
const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
const messagesRef = useRef<HTMLDivElement | null>(null);
const [chatKey, setChatKey] = useState(() => {
if (flowState?.input_keys) {
return Object.keys(flowState.input_keys!).find(
(key) =>
!flowState.handle_keys!.some((j) => j === key) &&
flowState.input_keys![key] === ""
);
}
// TODO: return a sensible default
return "";
});
useEffect(() => {
if (messagesRef.current) {
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
}
}, [chatHistory]);
useEffect(() => {
isOpen.current = open;
}, [open]);
var isStream = false;
const addChatHistory = (
message: string | Object,
isSend: boolean,
chatKey: string,
template?: string,
thought?: string,
files?: Array<any>
) => {
setChatHistory((old) => {
let newChat = _.cloneDeep(old);
if (files) {
newChat.push({ message, isSend, files, thought, chatKey });
} else if (thought) {
newChat.push({ message, isSend, thought, chatKey });
} else if (template) {
newChat.push({ message, isSend, chatKey, template });
} else {
newChat.push({ message, isSend, chatKey });
}
return newChat;
});
};
//add proper type signature for function
function updateLastMessage({
str,
thought,
prompt,
end = false,
files,
}: {
str?: string;
thought?: string;
prompt?: string;
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) {
newChat[newChat.length - 1].thought = thought;
}
if (files && newChat[newChat.length - 1]?.files) {
newChat[newChat.length - 1].files = files;
}
if (prompt && newChat[newChat.length - 2]?.template) {
newChat[newChat.length - 2].template = prompt;
}
return newChat;
});
}
function handleOnClose(event: CloseEvent): void {
if (isOpen.current) {
//check if the user has been logged out, if so close the chat when the user is redirected to the login page
if (window.location.href.includes("login")) {
setOpen(false);
ws.current?.close();
return;
}
getBuildStatus(flow.id)
.then((response) => {
if (response.data.built) {
connectWS();
} else {
setErrorData({
title: CHAT_ERROR_ALERT,
});
}
})
.catch((error) => {
setErrorData({
title: error.data?.detail ? error.data.detail : error.message,
});
});
setErrorData({ title: event.reason });
setTimeout(() => {
setLockChat(false);
}, 1000);
}
}
//TODO improve check of user authentication
function getWebSocketUrl(
chatId: string,
isDevelopment: boolean = false
): string {
const isSecureProtocol =
window.location.protocol === "https:" || window.location.port === "443";
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}?token=${encodeURIComponent(accessToken!)}`;
}
function handleWsMessage(data: any) {
if (Array.isArray(data) && data.length > 0) {
//set chat history
setChatHistory((_) => {
let newChatHistory: ChatMessageType[] = [];
for (let i = 0; i < data.length; i++) {
if (data[i].type === "prompt" && data[i].prompt) {
if (data[i - 1] && !data[i - 1].is_bot) {
data[i - 1].prompt = data[i].prompt;
template.current = data[i].prompt;
}
}
}
data = data.filter((item: any) => item.type !== "prompt");
data.forEach(
(chatItem: {
intermediate_steps?: string;
is_bot: boolean;
message: string;
prompt?: string;
type: string;
chatKey: string;
files?: Array<any>;
}) => {
if (chatItem.message) {
newChatHistory.push(
chatItem.files
? {
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.prompt,
thought: chatItem.intermediate_steps,
files: chatItem.files,
chatKey: chatItem.chatKey,
}
: {
isSend: !chatItem.is_bot,
message: chatItem.message,
template: chatItem.prompt,
thought: chatItem.intermediate_steps,
chatKey: chatItem.chatKey,
}
);
}
}
);
return newChatHistory;
});
}
if (data.type === "start") {
addChatHistory("", false, chatKey!);
isStream = true;
}
if (data.type === "end") {
if (data.message) {
updateLastMessage({
str: data.message,
end: true,
prompt: template.current,
});
}
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 == "prompt" && data.prompt) {
template.current = data.prompt;
}
if (data.type === "stream" && isStream) {
updateLastMessage({ str: data.message });
}
}
function connectWS(): void {
try {
const urlWs = getWebSocketUrl(
flow.id,
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);
handleWsMessage(data);
//get chat history
};
newWs.onclose = (event) => {
handleOnClose(event);
};
newWs.onerror = (ev) => {
console.log(ev);
connectWS();
};
ws.current = newWs;
} catch (error) {
if (flow.id === "") {
connectWS();
}
console.log(error);
}
}
useEffect(() => {
connectWS();
return () => {
console.log(ws);
if (ws.current) {
ws.current.close();
}
};
// do not add connectWS on dependencies array
}, [open]);
useEffect(() => {
return () => {
if (ws.current) {
console.log("closing ws");
ws.current.close();
}
};
}, []);
useEffect(() => {
if (
ws.current &&
(ws.current.readyState === ws.current.CLOSED ||
ws.current.readyState === ws.current.CLOSING)
) {
connectWS();
setLockChat(false);
}
// do not add connectWS on dependencies array
}, [lockChat]);
async function sendAll(data: sendAllProps): Promise<void> {
try {
if (ws) {
ws.current?.send(JSON.stringify(data));
}
} catch (error) {
setErrorData({
title: MSG_ERROR_ALERT,
list: [(error as { message: string }).message],
});
setChatValue(data.inputs);
connectWS();
}
}
useEffect(() => {
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]);
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (open && ref.current) {
ref.current.focus();
}
}, [open]);
function sendMessage(): void {
let nodeValidationErrors = validateNodes(nodes, edges);
const errors = nodeValidationErrors.flatMap((error) => error.errors);
if (errors.length === 0) {
setLockChat(true);
let inputs = flowState?.input_keys;
setChatValue("");
const message = inputs;
addChatHistory(message!, true, chatKey!, template.current);
sendAll({
...flow.data!,
inputs: inputs!,
chatHistory,
name: flow.name,
description: flow.description,
chatKey: chatKey!,
});
if (flowState && chatKey) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![chatKey] = "";
return newFlowState;
});
}
} else {
setErrorData({
title: INFO_MISSING_ALERT,
list: errors,
});
const ids = nodeValidationErrors.map((error) => error.id);
updateBuildStatus(ids, BuildStatus.ERROR);
}
}
function clearChat(): void {
setChatHistory([]);
template.current = flowState?.template;
ws.current?.send(JSON.stringify({ clear_history: true }));
if (lockChat) setLockChat(false);
}
function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) {
setChatKey(i);
setChatValue(flowState?.input_keys![i] ?? "");
} else {
setChatKey(null!);
setChatValue("");
}
}
return (
<Dialog open={open} onOpenChange={setOpen}>
<DialogTrigger hidden></DialogTrigger>
{flowState && flowState && (
<DialogContent className="min-w-[80vw]">
<DialogHeader>
<DialogTitle className="flex items-center">
<span className="pr-2">Chat</span>
<IconComponent
name="prompts"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</DialogTitle>
<DialogDescription>{CHAT_FORM_DIALOG_SUBTITLE}</DialogDescription>
</DialogHeader>
<div className="flex-max-width mt-2 h-[80vh]">
<div className="form-modal-iv-size">
<div className="file-component-arrangement">
<IconComponent
name="Variable"
className=" file-component-variable"
/>
<span className="file-component-variables-span text-md">
Input Variables
</span>
</div>
<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="file-component-variables-div">
<span className="text-sm font-medium text-primary">
Chat Input
</span>
</div>
</div>
{flowState?.input_keys
? Object.keys(flowState?.input_keys!).map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={(value) =>
handleOnCheckedChange(value, key)
}
size="small"
disabled={flowState.handle_keys!.some(
(t) => t === key
)}
/>
</div>
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
{flowState?.handle_keys!.some((t) => t === key) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
className="custom-scroll"
value={flowState?.input_keys![key]}
onChange={(e) => {
if (flowState) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![key] =
e.target.value;
return newFlowState;
});
}
}}
disabled={chatKey === key}
placeholder="Enter text..."
></Textarea>
</div>
</AccordionComponent>
</div>
))
: null}
{flowState?.memory_keys!.map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={() => {}}
size="small"
disabled={true}
/>
</div>
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
<div className="font-normal text-muted-foreground ">
Source: Memory
</div>
</div>
</AccordionComponent>
</div>
))}
</div>
<div className="eraser-column-arrangement">
<div className="eraser-size">
<div className="eraser-position">
<button disabled={lockChat} onClick={() => clearChat()}>
<IconComponent
name="Eraser"
className={classNames(
"h-5 w-5",
lockChat
? "animate-pulse text-primary"
: "text-primary hover:text-gray-600"
)}
aria-hidden="true"
/>
</button>
</div>
<div ref={messagesRef} className="chat-message-div">
{chatHistory.length > 0 ? (
chatHistory.map((chat, index) => (
<ChatMessage
lockChat={lockChat}
chat={chat}
lastMessage={
chatHistory.length - 1 === index ? true : false
}
key={index}
updateChat={() => {}}
/>
))
) : (
<div className="chat-alert-box">
<span>
👋{" "}
<span className="langflow-chat-span">
{LANGFLOW_CHAT_TITLE}
</span>
</span>
<br />
<div className="langflow-chat-desc">
<span className="langflow-chat-desc-span">
{CHAT_FIRST_INITIAL_TEXT}{" "}
<span>
<IconComponent
name="MessageSquare"
className="mx-1 inline h-5 w-5 animate-bounce "
/>
</span>{" "}
{CHAT_SECOND_INITIAL_TEXT}
</span>
</div>
</div>
)}
<div ref={ref}></div>
</div>
<div className="langflow-chat-input-div">
<div className="langflow-chat-input">
<ChatInput
chatValue={chatValue}
noInput={!chatKey}
lockChat={lockChat}
sendMessage={sendMessage}
setChatValue={(value) => {
setChatValue(value);
if (flowState && chatKey) {
setFlowState((old: FlowState | undefined) => {
let newFlowState = cloneDeep(old!);
newFlowState.input_keys![chatKey] = value;
return newFlowState;
});
}
}}
inputRef={ref}
/>
</div>
</div>
</div>
</div>
</div>
</DialogContent>
)}
</Dialog>
);
}

View file

@ -141,6 +141,7 @@ export default function ShareModal({
});
});
};
console.log("ShareModal");
const handleUpdateComponent = () => {
handleShareComponent(true);

View file

@ -12,7 +12,6 @@ import ReactFlow, {
updateEdge,
} from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import FlowToolbar from "../../../../components/chatComponent";
import {
INVALID_SELECTION_ERROR_ALERT,
UPLOAD_ALERT_LIST,
@ -38,7 +37,6 @@ import {
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
const nodeTypes = {
genericNode: GenericNode,
@ -59,6 +57,9 @@ export default function Page({
const templates = useTypesStore((state) => state.templates);
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const [showCanvas, setSHowCanvas] = useState(
Object.keys(templates).length > 0 && Object.keys(types).length > 0
);
const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance);
const setReactFlowInstance = useFlowStore(
@ -271,6 +272,12 @@ export default function Page({
};
}, []);
useEffect(() => {
setSHowCanvas(
Object.keys(templates).length > 0 && Object.keys(types).length > 0
);
}, [templates, types]);
const onConnectMod = useCallback(
(params: Connection) => {
takeSnapshot();
@ -432,71 +439,60 @@ export default function Page({
}
return (
<div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />}
{/* Main area */}
<main className="flex flex-1">
{/* Primary column */}
<div className="h-full w-full">
<div className="h-full w-full" ref={reactFlowWrapper}>
{Object.keys(templates).length > 0 &&
Object.keys(types).length > 0 ? (
<div id="react-flow-id" className="h-full w-full">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnectMod}
disableKeyboardA11y={true}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onMoveEnd={onMoveEnd}
onDrop={onDrop}
onSelectionChange={onSelectionChange}
deleteKeyCode={[]}
className="theme-attribution"
minZoom={0.01}
maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
proOptions={{ hideAttribution: true }}
onPaneClick={onPaneClick}
>
<Background className="" />
{!view && (
<Controls
className="bg-muted fill-foreground stroke-foreground text-primary
<div className="h-full w-full" ref={reactFlowWrapper}>
{showCanvas ? (
<div id="react-flow-id" className="h-full w-full">
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnectMod}
disableKeyboardA11y={true}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onNodeDragStart={onNodeDragStart}
onNodeDragStop={onNodeDragStop}
onSelectionDragStart={onSelectionDragStart}
onSelectionEnd={onSelectionEnd}
onSelectionStart={onSelectionStart}
connectionLineComponent={ConnectionLineComponent}
onDragOver={onDragOver}
onMoveEnd={onMoveEnd}
onDrop={onDrop}
onSelectionChange={onSelectionChange}
deleteKeyCode={[]}
className="theme-attribution"
minZoom={0.01}
maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
proOptions={{ hideAttribution: true }}
onPaneClick={onPaneClick}
>
<Background className="" />
{!view && (
<Controls
className="[&>button]:bg-muted fill-foreground stroke-foreground text-primary
[&>button]:border-b-border hover:[&>button]:bg-border"
></Controls>
)}
<SelectionMenu
isVisible={selectionMenuVisible}
nodes={lastSelection?.nodes}
onClick={() => {
handleGroupNode();
}}
/>
</ReactFlow>
{!view && <FlowToolbar flow={flow} />}
</div>
) : (
<></>
></Controls>
)}
</div>
<SelectionMenu
isVisible={selectionMenuVisible}
nodes={lastSelection?.nodes}
onClick={() => {
handleGroupNode();
}}
/>
</ReactFlow>
</div>
</main>
) : (
<></>
)}
</div>
);
}

View file

@ -18,7 +18,7 @@ export default function ParentDisclosureComponent({
data-testid={testId}
>
<div className="flex gap-4">
<span className="parent-disclosure-title">{title}</span>
<span className="parent-disclosure-title ">{title}</span>
</div>
<div className="components-disclosure-div">
{buttons.map((btn, index) => (
@ -28,9 +28,9 @@ export default function ParentDisclosureComponent({
))}
<div>
<IconComponent
name="ChevronRight"
name="ChevronsUpDownIcon"
className={`${
open || openDisc ? "rotate-90 transform" : ""
open || openDisc ? "" : ""
} h-4 w-4 text-foreground`}
/>
</div>

View file

@ -234,6 +234,20 @@ export default function ExtraSidebar(): JSX.Element {
[]
);
const getIcon = useMemo(() => {
return (SBSectionName: string) => {
if (nodeIconsLucide[SBSectionName]) {
return (
<IconComponent
name={SBSectionName}
strokeWidth={1.5}
className="w-[22px] text-primary"
/>
);
}
};
}, []);
return (
<div className="side-bar-arrangement">
<div className="side-bar-search-div-placement">
@ -271,6 +285,11 @@ export default function ExtraSidebar(): JSX.Element {
</div>
<Separator />
<div className="side-bar-components-div-arrangement">
<div className="parent-disclosure-arrangement">
<div className="flex items-center gap-4 align-middle">
<span className="parent-disclosure-title">Core Components</span>
</div>
</div>
{Object.keys(dataFilter)
.sort(sortKeys)
.filter((x) => PRIORITY_SIDEBAR_ORDER.includes(x))
@ -310,7 +329,7 @@ export default function ExtraSidebar(): JSX.Element {
<SidebarDraggableComponent
sectionName={SBSectionName as string}
apiClass={dataFilter[SBSectionName][SBItemName]}
key={index}
key={index + SBItemName}
onDragStart={(event) =>
onDragStart(event, {
//split type to remove type in nodes saved with same name removing it's
@ -344,9 +363,7 @@ export default function ExtraSidebar(): JSX.Element {
)
)}{" "}
<ParentDisclosureComponent
openDisc={
getFilterEdge.length !== 0 || search.length !== 0 ? true : false
}
openDisc={false}
key={"Extended"}
button={{
title: "Extended",
@ -361,6 +378,7 @@ export default function ExtraSidebar(): JSX.Element {
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
<>
<DisclosureComponent
isChild={false}
openDisc={
getFilterEdge.length !== 0 || search.length !== 0
? true

View file

@ -35,7 +35,6 @@ import ToolbarSelectItem from "./toolbarSelectItem";
export default function NodeToolbarComponent({
data,
deleteNode,
position,
setShowNode,
numberOfHandles,
showNode,
@ -65,12 +64,11 @@ export default function NodeToolbarComponent({
const hasStore = useStoreStore((state) => state.hasStore);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const validApiKey = useStoreStore((state) => state.validApiKey);
const [updateMinimize, setUpdateMinimize] = useState(showNode);
const isMinimal = numberOfHandles <= 1;
const isGroup = data.node?.flow ? true : false;
const frozen = data.node?.frozen ?? false;
// const frozen = data.node?.frozen ?? false;
const paste = useFlowStore((state) => state.paste);
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
@ -79,6 +77,7 @@ export default function NodeToolbarComponent({
const setEdges = useFlowStore((state) => state.setEdges);
const unselectAll = useFlowStore((state) => state.unselectAll);
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
const getNodePosition = useFlowStore((state) => state.getNodePosition);
const flows = useFlowsManagerStore((state) => state.flows);
const version = useDarkStore((state) => state.version);
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
@ -146,7 +145,7 @@ export default function NodeToolbarComponent({
takeSnapshot();
expandGroupNode(
data.id,
updateFlowPosition(position, data.node?.flow!),
updateFlowPosition(getNodePosition(data.id), data.node?.flow!),
data.node!.template,
nodes,
edges,
@ -268,6 +267,15 @@ export default function NodeToolbarComponent({
useEffect(() => {
function onKeyDown(event: KeyboardEvent) {
if (
selected &&
(hasApiKey || hasStore) &&
(event.ctrlKey || event.metaKey) &&
event.key === "u"
) {
event.preventDefault();
handleSelectChange("update");
}
if (
selected &&
isGroup &&
@ -309,7 +317,7 @@ export default function NodeToolbarComponent({
selected &&
(event.ctrlKey || event.metaKey) &&
event.shiftKey &&
event.key === "C"
event.key === "U"
) {
event.preventDefault();
if (hasCode) return setOpenModal((state) => !state);
@ -318,8 +326,8 @@ export default function NodeToolbarComponent({
if (
selected &&
!isGroup &&
(event.ctrlKey || event.metaKey) &&
event.key === "e"
(event.ctrlKey || event.metaKey) && event.shiftKey&&
event.key === "A"
) {
event.preventDefault();
setShowModalAdvanced((state) => !state);
@ -399,7 +407,23 @@ export default function NodeToolbarComponent({
</button>
</ShadTooltip>
<ShadTooltip content="Freeze" side="top">
<ShadTooltip content={"Duplicate"} side="top">
<button
data-testid="save-button-modal"
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
)}
onClick={(event) => {
event.preventDefault();
handleSelectChange("duplicate");
}
}
>
<IconComponent name="Copy" className="h-4 w-4" />
</button>
</ShadTooltip>
{/* <ShadTooltip content="Freeze" side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
@ -412,7 +436,7 @@ export default function NodeToolbarComponent({
...old.data,
node: {
...old.data.node,
frozen: old.data?.node?.frozen ? false : true,
// frozen: old.data?.node?.frozen ? false : true,
},
},
}));
@ -427,7 +451,7 @@ export default function NodeToolbarComponent({
)}
/>
</button>
</ShadTooltip>
</ShadTooltip> */}
<Select onValueChange={handleSelectChange} value="">
<ShadTooltip content="More" side="top">
@ -451,16 +475,16 @@ export default function NodeToolbarComponent({
{nodeLength > 0 && (
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
<ToolbarSelectItem
keyboardKey="E"
keyboardKey="A"
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
shift={false}
value={"Edit"}
shift={true}
value={"Advanced"}
icon={"Settings2"}
dataTestId="edit-button-modal"
/>
</SelectItem>
)}
<SelectItem value={"duplicate"}>
{/* <SelectItem value={"duplicate"}>
<ToolbarSelectItem
keyboardKey="D"
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
@ -469,7 +493,7 @@ export default function NodeToolbarComponent({
icon={"Copy"}
dataTestId="duplicate-button-modal"
/>
</SelectItem>
</SelectItem> */}
<SelectItem value={"copy"}>
<ToolbarSelectItem
keyboardKey="C"
@ -505,7 +529,7 @@ export default function NodeToolbarComponent({
value={"Share"}
icon={"Share3"}
styleObj={{
iconClasses: "relative top-0.5 -m-1 mr-1 h-6 w-6",
iconClasses: "relative top-0.5 -m-1 mr-[0.25rem] h-6 w-6",
}}
dataTestId="share-button-modal"
/>
@ -631,22 +655,24 @@ export default function NodeToolbarComponent({
)}
{hasCode && (
<div className="hidden">
<CodeAreaComponent
open={openModal}
setOpen={setOpenModal}
readonly={
data.node?.flow && data.node.template[name].dynamic
? true
: false
}
dynamic={data.node?.template[name].dynamic ?? false}
setNodeClass={handleNodeClass}
nodeClass={data.node}
disabled={false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-node-toolbar-" + name}
/>
{openModal && (
<CodeAreaComponent
open={openModal}
setOpen={setOpenModal}
readonly={
data.node?.flow && data.node.template[name].dynamic
? true
: false
}
dynamic={data.node?.template[name].dynamic ?? false}
setNodeClass={handleNodeClass}
nodeClass={data.node}
disabled={false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"code-input-node-toolbar-" + name}
/>
)}
</div>
)}
</span>

View file

@ -1,11 +1,13 @@
import { useEffect } from "react";
import { useParams } from "react-router-dom";
import FlowToolbar from "../../components/chatComponent";
import Header from "../../components/headerComponent";
import { useDarkStore } from "../../stores/darkStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import Page from "./components/PageComponent";
import ExtraSidebar from "./components/extraSidebarComponent";
export default function FlowPage(): JSX.Element {
export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
);
@ -17,12 +19,22 @@ export default function FlowPage(): JSX.Element {
useEffect(() => {
setCurrentFlowId(id!);
}, [id]);
return (
<>
<Header />
<div className="flow-page-positioning">
{currentFlow && <Page flow={currentFlow} />}
{currentFlow && (
<div className="flex h-full overflow-hidden">
{!view && <ExtraSidebar />}
<main className="flex flex-1">
{/* Primary column */}
<div className="h-full w-full">
<Page flow={currentFlow} />
</div>
{!view && <FlowToolbar />}
</main>
</div>
)}
<a
target={"_blank"}
href="https://logspace.ai/"

View file

@ -2,23 +2,20 @@ import { Group, ToyBrick } from "lucide-react";
import { useEffect, useState } from "react";
import { Outlet, useLocation, useNavigate } from "react-router-dom";
import DropdownButton from "../../components/DropdownButtonComponent";
import NewFlowCardComponent from "../../components/NewFLowCard2";
import IconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
import { Button } from "../../components/ui/button";
import UndrawCardComponent from "../../components/undrawCards";
import { CONSOLE_ERROR_MSG } from "../../constants/alerts_constants";
import {
MY_COLLECTION_DESC,
USER_PROJECTS_HEADER,
} from "../../constants/constants";
import BaseModal from "../../modals/baseModal";
import NewFlowModal from "../../modals/NewFlowModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlows } from "../../utils/reactflowUtils";
export default function HomePage(): JSX.Element {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const setCurrentFlowId = useFlowsManagerStore(
(state) => state.setCurrentFlowId
@ -29,7 +26,6 @@ export default function HomePage(): JSX.Element {
const location = useLocation();
const pathname = location.pathname;
const [openModal, setOpenModal] = useState(false);
const examples = useFlowsManagerStore((state) => state.examples);
const is_component = pathname === "/components";
const dropdownOptions = [
{
@ -119,26 +115,7 @@ export default function HomePage(): JSX.Element {
<Outlet />
</div>
</div>
<BaseModal size="three-cards" open={openModal} setOpen={setOpenModal}>
<BaseModal.Header description={"Select a template below"}>
<span className="pr-2" data-testid="modal-title">
Get Started
</span>
{/* <IconComponent
name="Group"
className="h-6 w-6 stroke-2 text-primary "
aria-hidden="true"
/> */}
</BaseModal.Header>
<BaseModal.Content>
<div className=" grid h-full w-full grid-cols-3 gap-3 overflow-auto p-4 custom-scroll">
<NewFlowCardComponent />
{examples.map((example, idx) => {
return <UndrawCardComponent key={idx} flow={example} />;
})}
</div>
</BaseModal.Content>
</BaseModal>
<NewFlowModal open={openModal} setOpen={setOpenModal} />
</PageLayout>
);
}

View file

@ -10,7 +10,6 @@ export default function DeleteAccountPage() {
// Implement your account deletion logic here
// For example, make an API call to delete the account
// Upon successful deletion, you can redirect the user to another page
console.log("Account deleted!");
// Implement the logic to redirect the user after account deletion.
// For example, use react-router-dom's useHistory hook.
};

View file

@ -70,6 +70,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
}
get().setFlowPool(newFlowPool);
},
getNodePosition: (nodeId: string) => {
const node = get().nodes.find((node) => node.id === nodeId);
return node?.position || { x: 0, y: 0 };
},
updateFlowPool: (
nodeId: string,
data: FlowPoolObjectType | ChatOutputType | chatInputType,
@ -525,9 +529,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
onGetOrderSuccess: () => {
setNoticeData({ title: "Running components" });
},
onBuildComplete: () => {
onBuildComplete: (allNodesValid) => {
const nodeId = startNodeId || stopNodeId;
if (nodeId) {
if (nodeId && allNodesValid) {
setSuccessData({
title: `${
get().nodes.find((node) => node.id === nodeId)?.data.node

View file

@ -96,19 +96,19 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
});
});
},
autoSaveCurrentFlow: debounce(
(nodes: Node[], edges: Edge[], viewport: Viewport) => {
set({ saveLoading: true });
if (get().currentFlow) {
get().saveFlow(
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
true
);
}
},
SAVE_DEBOUNCE_TIME
),
autoSaveCurrentFlow: (nodes: Node[], edges: Edge[], viewport: Viewport) => {
if (get().currentFlow) {
get().saveFlow(
{ ...get().currentFlow!, data: { nodes, edges, viewport } },
true
);
}
},
saveFlow: (flow: FlowType, silent?: boolean) => {
set({ saveLoading: true }); // set saveLoading true immediately
return get().saveFlowDebounce(flow, silent); // call the debounced function directly
},
saveFlowDebounce: debounce((flow: FlowType, silent?: boolean) => {
set({ saveLoading: true });
return new Promise<void>((resolve, reject) => {
updateFlowInDatabase(flow)
@ -142,7 +142,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
reject(err);
});
});
},
}, SAVE_DEBOUNCE_TIME),
uploadFlows: () => {
return new Promise<void>((resolve) => {
const input = document.createElement("input");

View file

@ -184,7 +184,7 @@
@apply fill-chat-trigger-disabled stroke-chat-trigger-disabled stroke-1;
}
.parent-disclosure-arrangement {
@apply flex w-full select-none items-center justify-between border-y border-y-input bg-background px-3 py-2;
@apply flex w-full select-none items-center justify-between bg-background px-3 py-1;
}
.components-disclosure-arrangement {
@apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2;
@ -194,7 +194,7 @@
@apply -mt-px flex w-full select-none items-center justify-between border-y border-y-input bg-muted px-3 py-2;
}
.parent-disclosure-title {
@apply p-2 px-2 text-sm font-semibold;
@apply p-2 px-2 text-sm font-medium;
}
.components-disclosure-title {
@apply flex items-center text-sm text-primary;
@ -917,7 +917,7 @@
@apply mr-2 h-5 w-5 rotate-[44deg];
}
.form-modal-play-icon {
@apply mx-1 h-5 w-5;
@apply mx-1 h-5 w-5 fill-current;
}
.form-modal-chat-position {
@apply flex-max-width px-2 py-6 pl-4 pr-9;

View file

@ -1,5 +1,6 @@
import { ReactElement, ReactNode, SetStateAction } from "react";
import { ReactFlowJsonObject, XYPosition } from "reactflow";
import { ReactFlowJsonObject } from "reactflow";
import { InputOutput } from "../../constants/enums";
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
import { ChatMessageType } from "../chat";
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
@ -171,6 +172,7 @@ export type RangeSpecType = {
export type IntComponentType = {
value: string;
disabled?: boolean;
rangeSpec: RangeSpecType;
onChange: (value: string) => void;
editNode?: boolean;
id?: string;
@ -219,6 +221,8 @@ export type AccordionComponentType = {
open?: string[];
trigger?: string | ReactElement;
keyValue?: string;
openDisc?: boolean;
sideBar?: boolean;
};
export type Side = "top" | "right" | "bottom" | "left";
@ -500,7 +504,6 @@ export type fileCardPropsType = {
export type nodeToolbarPropsType = {
data: NodeDataType;
deleteNode: (idx: string) => void;
position: XYPosition;
setShowNode: (boolean: any) => void;
numberOfHandles: number;
showNode: boolean;
@ -565,12 +568,6 @@ export type chatMessagePropsType = {
) => void;
};
export type formModalPropsType = {
open: boolean;
setOpen: Function;
flow: FlowType;
};
export type genericModalPropsType = {
field_name?: string;
setValue: (value: string) => void;
@ -585,6 +582,18 @@ export type genericModalPropsType = {
readonly?: boolean;
};
export type newFlowModalPropsType = {
open: boolean;
setOpen: (open: boolean) => void;
};
export type IOModalPropsType = {
children: JSX.Element;
open: boolean;
setOpen: (open: boolean) => void;
disable?: boolean;
};
export type buttonBoxPropsType = {
onClick: () => void;
title: string;
@ -695,15 +704,21 @@ export type dropdownButtonPropsType = {
dropdownOptions?: boolean;
};
export type IOInputProps = {
inputType: string;
inputId: string;
export type IOFieldViewProps = {
type: InputOutput;
fieldType: string;
fieldId: string;
left?: boolean;
};
export type IOOutputProps = {
outputType: string;
outputId: string;
left?: boolean;
export type UndrawCardComponentProps = { flow: FlowType };
export type chatViewProps = {
sendMessage: (count?: number) => void;
chatValue: string;
setChatValue: (value: string) => void;
lockChat: boolean;
setLockChat: (lock: boolean) => void;
};
export type IOFileInputProps = {

View file

@ -131,4 +131,5 @@ export type FlowStoreType = {
data: FlowPoolObjectType | ChatOutputType | chatInputType,
buildId?: string
) => void;
getNodePosition: (nodeId: string) => { x: number; y: number };
};

View file

@ -11,7 +11,11 @@ export type FlowsManagerStoreType = {
isLoading: boolean;
setIsLoading: (isLoading: boolean) => void;
refreshFlows: () => Promise<void>;
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void>;
saveFlow: (flow: FlowType, silent?: boolean) => Promise<void> | undefined;
saveFlowDebounce: (
flow: FlowType,
silent?: boolean
) => Promise<void> | undefined;
autoSaveCurrentFlow: (
nodes: Node[],
edges: Edge[],

View file

@ -204,12 +204,11 @@ export async function buildVertices({
if (stop) {
break;
}
if (onBuildComplete) {
const allNodesValid = buildResults.every((result) => result);
onBuildComplete(allNodesValid);
useFlowStore.getState().setIsBuilding(false);
}
}
if (onBuildComplete) {
const allNodesValid = buildResults.every((result) => result);
onBuildComplete(allNodesValid);
useFlowStore.getState().setIsBuilding(false);
}
}
async function buildVertex({

View file

@ -1,4 +1,4 @@
import { throttle } from "lodash";
import { debounce } from "lodash";
import { postCustomComponentUpdate } from "../controllers/API";
import { ResponseErrorTypeAPI } from "../types/api";
import { NodeDataType } from "../types/flow";
@ -38,4 +38,4 @@ export const handleUpdateValues = async (name: string, data: NodeDataType) => {
}
};
export const throttledHandleUpdateValues = throttle(handleUpdateValues, 10);
export const debouncedHandleUpdateValues = debounce(handleUpdateValues, 200);

View file

@ -1170,7 +1170,7 @@ export function downloadNode(NodeFLow: FlowType) {
type: "application/json",
});
element.href = URL.createObjectURL(file);
element.download = `${NodeFLow?.name??"node"}.json`;
element.download = `${NodeFLow?.name ?? "node"}.json`;
element.click();
}

View file

@ -22,6 +22,7 @@ import {
ChevronsRight,
ChevronsUpDown,
Circle,
ChevronsUpDownIcon,
CircleDot,
Clipboard,
Code,
@ -116,6 +117,7 @@ import {
TerminalIcon,
TerminalSquare,
TextCursorInput,
TextSearch,
ToyBrick,
Trash2,
Type,
@ -150,6 +152,7 @@ import { EvernoteIcon } from "../icons/Evernote";
import { FBIcon } from "../icons/FacebookMessenger";
import { GitBookIcon } from "../icons/GitBook";
import { GoogleIcon } from "../icons/Google";
import { GoogleGenerativeAIIcon } from "../icons/GoogleGenerativeAI";
import {
GradientInfinity,
GradientSave,
@ -217,11 +220,11 @@ export const gradients = [
];
export const nodeColors: { [char: string]: string } = {
inputs: "#9AAE42",
inputs: "#10B981",
outputs: "#AA2411",
data: "#6344BE",
data: "#4367BF",
prompts: "#4367BF",
models: "#AA2411",
models: "#6344BE",
model_specs: "#6344BE",
chains: "#FE7500",
Document: "#7AAE42",
@ -249,6 +252,9 @@ export const nodeColors: { [char: string]: string } = {
retrievers: "#e6b25a",
unknown: "#9CA3AF",
custom_components: "#ab11ab",
Records:"#31a3cc",
Record:"#31a3cc",
};
export const nodeNames: { [char: string]: string } = {
@ -319,6 +325,7 @@ export const nodeIconsLucide: iconsType = {
BingSearchAPIWrapper: BingIcon,
BingSearchRun: BingIcon,
Cohere: CohereIcon,
ChevronsUpDownIcon,
CohereEmbeddings: CohereIcon,
EverNoteLoader: EvernoteIcon,
FacebookChatLoader: FBIcon,
@ -327,6 +334,7 @@ export const nodeIconsLucide: iconsType = {
GoogleSearchResults: GoogleIcon,
GoogleSearchRun: GoogleIcon,
Google: GoogleIcon,
GoogleGenerativeAI: GoogleGenerativeAIIcon,
HNLoader: HackerNewsIcon,
HuggingFaceHub: HuggingFaceIcon,
HuggingFace: HuggingFaceIcon,
@ -371,7 +379,7 @@ export const nodeIconsLucide: iconsType = {
saved_components: GradientSave,
documentloaders: Paperclip,
vectorstores: Layers,
vectorsearch: Search,
vectorsearch: TextSearch,
toolkits: Package2,
textsplitters: Scissors,
wrappers: Gift,

View file

@ -289,11 +289,10 @@ export function buildTweakObject(tweak: tweakType) {
/**
* Function to get Chat Input Field
* @param {FlowType} flow - The current flow.
* @param {FlowsState} tabsState - The current tabs state.
* @returns {string} - The chat input field
*/
export function getChatInputField(flow: FlowType, flowState?: FlowState) {
export function getChatInputField(flowState?: FlowState) {
let chat_input_field = "text";
if (flowState && flowState.input_keys) {
@ -305,13 +304,14 @@ export function getChatInputField(flow: FlowType, flowState?: FlowState) {
/**
* Function to get the python code for the API
* @param {string} flowId - The id of the flow
* @param {boolean} isAuth - If the API is authenticated
* @param {any[]} tweak - The tweaks
* @returns {string} - The python code
*/
export function getPythonApiCode(
flow: FlowType,
isAuth: boolean,
tweak?: any[],
flowState?: FlowState
tweak?: any[]
): string {
const flowId = flow.id;
@ -320,13 +320,10 @@ export function getPythonApiCode(
// node.data.id
// }
const tweaks = buildTweaks(flow);
const inputs = buildInputs();
return `import requests
from typing import Optional
BASE_API_URL = "${window.location.protocol}//${
window.location.host
}/api/v1/process"
BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run"
FLOW_ID = "${flowId}"
# You can tweak the flow by adding a tweaks dictionary
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
@ -336,9 +333,12 @@ TWEAKS = ${
: JSON.stringify(tweaks, null, 2)
}
def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None${
!isAuth ? `, api_key: Optional[str] = None` : ""
}) -> dict:
def run_flow(message: str,
flow_id: str,
output_type: str = "chat",
input_type: str = "chat",
tweaks: Optional[dict] = None,
api_key: Optional[str] = None) -> dict:
"""
Run a flow with a given message and optional tweaks.
@ -349,7 +349,11 @@ def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None${
"""
api_url = f"{BASE_API_URL}/{flow_id}"
payload = {"inputs": inputs}
payload = {
"input_value": message,
"output_type": output_type,
"input_type": input_type,
}
headers = None
if tweaks:
payload["tweaks"] = tweaks
@ -359,9 +363,9 @@ def run_flow(inputs: dict, flow_id: str, tweaks: Optional[dict] = None${
return response.json()
# Setup any tweaks you want to apply to the flow
inputs = ${inputs}
message = "message"
${!isAuth ? `api_key = "<your api key>"` : ""}
print(run_flow(inputs, flow_id=FLOW_ID, tweaks=TWEAKS${
print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${
!isAuth ? `, api_key=api_key` : ""
}))`;
}
@ -369,28 +373,27 @@ print(run_flow(inputs, flow_id=FLOW_ID, tweaks=TWEAKS${
/**
* Function to get the curl code for the API
* @param {string} flowId - The id of the flow
* @param {boolean} isAuth - If the API is authenticated
* @returns {string} - The curl code
*/
export function getCurlCode(
flow: FlowType,
isAuth: boolean,
tweak?: any[],
flowState?: FlowState
tweak?: any[]
): string {
const flowId = flow.id;
const tweaks = buildTweaks(flow);
const inputs = buildInputs();
const arrayOfOutputs = getOutputIds(flow);
return `curl -X POST \\
${window.location.protocol}//${window.location.host}/api/v1/run/${flowId} \\
${window.location.protocol}//${
window.location.host
}/api/v1/run/${flowId}?stream=false \\
-H 'Content-Type: application/json'\\${
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
}
-d '{"inputs": [${inputs}],
"outputs": [${arrayOfOutputs}],
"stream": false,
-d '{"input_value": "message",
"output_type": "chat",
"input_type": "chat",
"tweaks": ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
@ -419,26 +422,23 @@ export function getOutputIds(flow) {
/**
* Function to get the python code for the API
* @param {string} flow - The current flow
* @param {any[]} tweak - The tweaks
* @returns {string} - The python code
*/
export function getPythonCode(
flow: FlowType,
tweak?: any[],
flowState?: FlowState
): string {
export function getPythonCode(flow: FlowType, tweak?: any[]): string {
const flowName = flow.name;
const tweaks = buildTweaks(flow);
const inputs = buildInputs();
return `from langflow.load import load_flow_from_json
return `from langflow.load import run_flow_from_json
TWEAKS = ${
tweak && tweak.length > 0
? buildTweakObject(tweak)
: JSON.stringify(tweaks, null, 2)
}
flow = load_flow_from_json("${flowName}.json", tweaks=TWEAKS)
# Now you can use it like any chain
inputs = ${inputs}
flow(inputs)`;
result = run_flow_from_json(flow="${flowName}.json",
input_value="message",
tweaks=TWEAKS)`;
}
/**
@ -454,7 +454,7 @@ export function getWidgetCode(
const flowId = flow.id;
const flowName = flow.name;
const inputs = buildInputs();
let chat_input_field = getChatInputField(flow, flowState);
let chat_input_field = getChatInputField(flowState);
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>