Merge zustand/io/migration into codeShortcut

This commit is contained in:
igorrCarvalho 2024-03-07 18:21:47 -03:00
commit b16848c0b4
211 changed files with 6695 additions and 1763 deletions

View file

@ -100,6 +100,7 @@
"prettier-plugin-tailwindcss": "^0.3.0",
"pretty-quick": "^3.1.3",
"tailwindcss": "^3.3.3",
"tailwindcss-dotted-background": "^1.1.0",
"typescript": "^5.2.2",
"vite": "^4.5.2"
}
@ -10854,6 +10855,15 @@
"tailwindcss": ">=3.0.0 || insiders"
}
},
"node_modules/tailwindcss-dotted-background": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/tailwindcss-dotted-background/-/tailwindcss-dotted-background-1.1.0.tgz",
"integrity": "sha512-uFzCW5Bpyn8XgppTkyzqdHecH7XCDaS/eXvegDrOCYE6PxTm7dRrD9cuUcZe6mxQFVfkLu9rDmHJdqbjz9FdLA==",
"dev": true,
"peerDependencies": {
"tailwindcss": "^3.0.0"
}
},
"node_modules/tailwindcss/node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",

View file

@ -122,6 +122,7 @@
"prettier-plugin-tailwindcss": "^0.3.0",
"pretty-quick": "^3.1.3",
"tailwindcss": "^3.3.3",
"tailwindcss-dotted-background": "^1.1.0",
"typescript": "^5.2.2",
"vite": "^4.5.2"
}

View file

@ -90,3 +90,12 @@ body {
.jv-indent::-webkit-scrollbar-thumb:hover {
background-color: #bbb !important;
}
.custom-hover {
transition: background-color 0.5s ease;
}
.custom-hover:hover {
background-color: rgba(99, 102, 241, 0.1); /* Medium indigo color with 20% opacity */
}

View file

@ -69,7 +69,7 @@ export default function App() {
.catch(() => {
setFetchError(true);
});
}, 20000);
}, 20000); // 20 seconds
// Clean up the timer on component unmount
return () => {

View file

@ -23,14 +23,16 @@ import {
OUTPUT_HANDLER_HOVER,
TOOLTIP_EMPTY,
} from "../../../../constants/constants";
import { postCustomComponentUpdate } from "../../../../controllers/API";
import useAlertStore from "../../../../stores/alertStore";
import useFlowStore from "../../../../stores/flowStore";
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
import { useTypesStore } from "../../../../stores/typesStore";
import { APIClassType } from "../../../../types/api";
import { APIClassType, ResponseErrorTypeAPI } from "../../../../types/api";
import { ParameterComponentType } from "../../../../types/components";
import { NodeDataType } from "../../../../types/flow";
import {
handleUpdateValues,
throttledHandleUpdateValues,
} from "../../../../utils/parameterUtils";
import {
convertObjToArray,
convertValuesToNumbers,
@ -86,72 +88,92 @@ export default function ParameterComponent({
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const handleUpdateValues = async (
name: string,
data: NodeDataType,
delayAnimation: boolean = true
) => {
const handleRefreshButtonPress = async (name, data) => {
setIsLoading(true);
const code = data.node?.template["code"]?.value;
if (!code) {
console.error("Code not found in the template");
return;
}
try {
const res = await postCustomComponentUpdate(code, name);
if (res.status === 200 && data.node?.template) {
let newTemplate = await handleUpdateValues(name, data);
if (newTemplate) {
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[name] = res.data.template[name];
newNode.data.node.template = newTemplate;
return newNode;
});
}
} catch (err) {
setErrorData(err as { title: string; list?: Array<string> });
} catch (error) {
let responseError = error as ResponseErrorTypeAPI;
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail.error ?? "Unknown error"],
});
}
setIsLoading(false);
renderTooltips();
if (delayAnimation) {
try {
// Wait for at least 500 milliseconds
await new Promise((resolve) => setTimeout(resolve, 500));
// Continue with the request
// If the request takes longer than 500 milliseconds, it will not wait an additional 500 milliseconds
} catch (error) {
console.error("Error occurred while waiting for refresh:", error);
} finally {
setIsLoading(false);
}
} else setIsLoading(false);
};
useEffect(() => {
function fetchData() {
async function fetchData() {
if (
data.node?.template[name]?.refresh &&
Object.keys(data.node?.template[name]?.options ?? {}).length === 0
(data.node?.template[name]?.real_time_refresh ||
data.node?.template[name]?.refresh_button) &&
// options can be undefined but not an empty array
(data.node?.template[name]?.options?.length ?? 0) === 0
) {
handleUpdateValues(name, data, false);
setIsLoading(true);
try {
let newTemplate = await handleUpdateValues(name, data);
if (newTemplate) {
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template = newTemplate;
return newNode;
});
}
} catch (error) {
let responseError = error as ResponseErrorTypeAPI;
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail.error ?? "Unknown error"],
});
}
setIsLoading(false);
renderTooltips();
}
}
fetchData();
}, []);
const handleOnNewValue = (
const handleOnNewValue = async (
newValue: string | string[] | boolean | Object[]
): void => {
): Promise<void> => {
if (data.node!.template[name].value !== newValue) {
takeSnapshot();
}
const shouldUpdate =
data.node?.template[name].real_time_refresh &&
!data.node?.template[name].refresh_button &&
data.node!.template[name].value !== newValue;
data.node!.template[name].value = newValue; // necessary to enable ctrl+z inside the input
let newTemplate;
if (shouldUpdate) {
setIsLoading(true);
try {
newTemplate = await throttledHandleUpdateValues(name, data);
} catch (error) {
let responseError = error as ResponseErrorTypeAPI;
setErrorData({
title: "Error while updating the Component",
list: [responseError.response.data.detail.error ?? "Unknown error"],
});
}
setIsLoading(false);
// this de
}
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
@ -159,7 +181,9 @@ export default function ParameterComponent({
...newNode.data,
};
newNode.data.node.template[name].value = newValue;
if (data.node?.template[name].real_time_refresh && newTemplate) {
newNode.data.node.template = newTemplate;
} else newNode.data.node.template[name].value = newValue;
return newNode;
});
@ -253,15 +277,15 @@ export default function ParameterComponent({
{item.display_name === "" ? "" : " - "}
{item.display_name.split(", ").length > 2
? item.display_name.split(", ").map((el, index) => (
<React.Fragment key={el + name}>
<span>
{index ===
<React.Fragment key={el + name}>
<span>
{index ===
item.display_name.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.display_name}
</span>
) : (
@ -270,14 +294,14 @@ export default function ParameterComponent({
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, index) => (
<React.Fragment key={el + name}>
<span>
{index === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
<React.Fragment key={el + name}>
<span>
{index === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
</React.Fragment>
))
: item.type}
</span>
)}
@ -291,11 +315,14 @@ export default function ParameterComponent({
refHtml.current = <span>{TOOLTIP_EMPTY}</span>;
}
}
// If optionalHandle is an empty list, then it is not an optional handle
if (optionalHandle && optionalHandle.length === 0) {
optionalHandle = null;
}
useEffect(() => {
renderTooltips();
}, [tooltipTitle, flow]);
return !showNode ? (
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
<></>
@ -346,7 +373,7 @@ export default function ParameterComponent({
className={
"relative mt-1 flex w-full flex-wrap items-center justify-between bg-muted px-5 py-2" +
((name === "code" && type === "code") ||
(name.includes("code") && proxy)
(name.includes("code") && proxy)
? " hidden "
: "")
}
@ -355,21 +382,26 @@ export default function ParameterComponent({
<div
className={
"w-full truncate text-sm" +
(left ? "" : " gap-2 flex justify-end items-center") +
(left ? "" : " flex items-center justify-end gap-2") +
(info !== "" ? " flex items-center" : "")
}
>
{!left && data.node?.pinned &&
{!left && data.node?.frozen && (
<div>
<IconComponent className="w-5 h-5 text-ice" name={"Snowflake"} />
</div>}
<IconComponent className="h-5 w-5 text-ice" name={"Snowflake"} />
</div>
)}
{proxy ? (
<ShadTooltip content={<span>{proxy.id}</span>}>
<span className={!left && data.node?.pinned?" text-ice":""}>{title}</span>
<span className={!left && data.node?.frozen ? " text-ice" : ""}>
{title}
</span>
</ShadTooltip>
) : (
<span className={!left && data.node?.pinned?" text-ice":""}>{title}</span>
)}
<span className={!left && data.node?.frozen ? " text-ice" : ""}>
{title}
</span>
)}
<span className={(info === "" ? "" : "ml-1 ") + " text-status-red"}>
{required ? " *" : ""}
</span>
@ -431,22 +463,29 @@ export default function ParameterComponent({
)}
{left === true &&
type === "str" &&
!data.node?.template[name].options ? (
type === "str" &&
!data.node?.template[name].options ? (
<div className="mt-2 w-full">
{data.node?.template[name].list ? (
<div className="w-5/6 flex-grow">
<div
className={
// Commenting this out until we have a better
// way to display
// (data.node?.template[name].refresh ? "w-5/6 " : "") +
"flex-grow"
}
>
<InputListComponent
disabled={disabled}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={handleOnNewValue}
/>
{data.node?.template[name].refresh && (
{/* {data.node?.template[name].refresh_button && (
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
@ -454,23 +493,49 @@ export default function ParameterComponent({
name={name}
data={data}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>
)} */}
</div>
) : data.node?.template[name].multiline ? (
<div className="mt-2 flex w-full flex-col ">
<div className="flex-grow">
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"textarea-" + data.node.template[name].name}
data-testid={"textarea-" + data.node.template[name].name}
/>
</div>
{data.node?.template[name].refresh_button && (
<div className="flex-grow">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
button_text={
data.node?.template[name].refresh_button_text ??
"Refresh"
}
className="extra-side-bar-buttons mt-1"
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>
)}
</div>
) : data.node?.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
onChange={handleOnNewValue}
id={"textarea-" + data.node.template[name].name}
data-testid={"textarea-" + data.node.template[name].name}
/>
) : (
<div className="mt-2 flex w-full items-center">
<div className="w-5/6 flex-grow">
<div
className={
"flex-grow " +
(data.node?.template[name].refresh_button ? "w-5/6" : "")
}
>
<InputComponent
id={"input-" + name}
disabled={disabled}
@ -479,15 +544,19 @@ export default function ParameterComponent({
onChange={handleOnNewValue}
/>
</div>
{data.node?.template[name].refresh && (
{data.node?.template[name].refresh_button && (
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
button_text={
data.node?.template[name].refresh_button_text ??
"Refresh"
}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>
@ -518,11 +587,12 @@ export default function ParameterComponent({
) : left === true &&
type === "str" &&
(data.node?.template[name].options ||
data.node?.template[name]?.refresh) ? (
data.node?.template[name]?.real_time_refresh) ? (
// TODO: Improve CSS
<div className="mt-2 flex w-full items-center">
<div className="w-5/6 flex-grow">
<Dropdown
disabled={disabled}
isLoading={isLoading}
options={data.node.template[name].options}
onSelect={handleOnNewValue}
@ -530,15 +600,18 @@ export default function ParameterComponent({
id={"dropdown-" + name}
/>
</div>
{data.node?.template[name].refresh && (
{data.node?.template[name].refresh_button && (
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
button_text={
data.node?.template[name].refresh_button_text ?? "Refresh"
}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
handleUpdateValues={handleRefreshButtonPress}
id={"refresh-button-" + name}
/>
</div>
@ -603,10 +676,10 @@ export default function ParameterComponent({
editNode={false}
value={
!data.node!.template[name].value ||
data.node!.template[name].value?.toString() === "{}"
data.node!.template[name].value?.toString() === "{}"
? {
yourkey: "value",
}
yourkey: "value",
}
: data.node!.template[name].value
}
onChange={handleOnNewValue}
@ -620,7 +693,7 @@ export default function ParameterComponent({
editNode={false}
value={
data.node!.template[name].value?.length === 0 ||
!data.node!.template[name].value
!data.node!.template[name].value
? [{ "": "" }]
: convertObjToArray(data.node!.template[name].value)
}
@ -628,8 +701,14 @@ export default function ParameterComponent({
onChange={(newValue) => {
const valueToNumbers = convertValuesToNumbers(newValue);
setErrorDuplicateKey(hasDuplicateKeys(valueToNumbers));
handleOnNewValue(valueToNumbers);
// if data.node?.template[name].list is true, then the value is an array of objects
// else we need to get the first object of the array
if (data.node?.template[name].list) {
handleOnNewValue(valueToNumbers);
} else handleOnNewValue(valueToNumbers[0]);
}}
isList={data.node?.template[name].list ?? false}
/>
</div>
) : (

View file

@ -12,10 +12,10 @@ import {
RUN_TIMESTAMP_PREFIX,
STATUS_BUILD,
STATUS_BUILDING,
priorityFields,
} from "../../constants/constants";
import { BuildStatus } from "../../constants/enums";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
@ -24,7 +24,7 @@ import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, cn, getFieldTitle } from "../../utils/utils";
import { classNames, cn, getFieldTitle, sortFields } from "../../utils/utils";
import ParameterComponent from "./components/parameterComponent";
export default function GenericNode({
@ -43,6 +43,7 @@ export default function GenericNode({
const flowPool = useFlowStore((state) => state.flowPool);
const buildFlow = useFlowStore((state) => state.buildFlow);
const setNode = useFlowStore((state) => state.setNode);
const setErrorData = useAlertStore((state) => state.setErrorData);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const [inputName, setInputName] = useState(false);
const [nodeName, setNodeName] = useState(data.node!.display_name);
@ -64,6 +65,18 @@ export default function GenericNode({
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
if (!data.node!.template) {
setErrorData({
title: `Error in component ${data.node!.display_name}`,
list: [
`The component ${data.node!.display_name} has no template.`,
`Please contact the developer of the component to fix this issue.`,
],
});
takeSnapshot();
deleteNode(data.id);
}
function countHandles(): void {
let count = Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
@ -511,10 +524,10 @@ export default function GenericNode({
) : !validationStatus ? (
<span className="flex">{STATUS_BUILD}</span>
) : (
<div className="max-h-100">
<div className="max-h-100 p-2">
<div>
{lastRunTime && (
<div className="justify-left flex text-muted-foreground">
<div className="justify-left flex text-muted-foreground font-normal">
<div>{RUN_TIMESTAMP_PREFIX}</div>
<div className="ml-1 text-status-blue">
{lastRunTime}
@ -522,19 +535,19 @@ export default function GenericNode({
</div>
)}
</div>
<div className="justify-left flex text-muted-foreground">
<div className="justify-left flex text-muted-foreground font-normal">
<div>Duration:</div>
<div className="ml-1 text-status-blue">
<div className="ml-1 text-status-blue mb-3">
{validationStatus?.data.duration}
</div>
</div>
<hr />
<span className="flex justify-center text-muted-foreground ">
<span className="flex justify-center text-muted-foreground mt-2 mb-2 font-semibold">
Output
</span>
<div className="max-h-96 overflow-auto custom-scroll">
<div className="max-h-96 overflow-auto custom-scroll font-normal">
{validationString.split("\n").map((line, index) => (
<div key={index}>{line}</div>
<div className="font-normal" key={index}>{line}</div>
))}
</div>
</div>
@ -639,15 +652,7 @@ export default function GenericNode({
<>
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.sort((a, b) => {
if (priorityFields.has(a.toLowerCase())) {
return -1;
} else if (priorityFields.has(b.toLowerCase())) {
return 1;
} else {
return a.localeCompare(b);
}
})
.sort((a, b) => sortFields(a, b, data.node?.field_order ?? []))
.map((templateField: string, idx) => (
<div key={idx}>
{data.node!.template[templateField].show &&

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -50,9 +50,7 @@ export default function AccordionComponent({
{trigger}
</AccordionTrigger>
<AccordionContent>
<div className="AccordionContent flex flex-col">
{children}
</div>
<div className="AccordionContent flex flex-col">{children}</div>
</AccordionContent>
</AccordionItem>
</Accordion>

View file

@ -20,7 +20,12 @@ import { Badge } from "../ui/badge";
import { Button } from "../ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
export default function IOView({ children, open, setOpen }): JSX.Element {
export default function IOView({ children, open, setOpen, disable }: {
children: JSX.Element;
open: boolean;
setOpen: (open: boolean) => void;
disable?: boolean;
}): JSX.Element {
const inputs = useFlowStore((state) => state.inputs).filter(
(input) => input.type !== "ChatInput"
);
@ -98,6 +103,7 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
size={haveChat ? (selectedTab === 0 ? "large-thin" : "large") : "small"}
open={open}
setOpen={setOpen}
disable={disable}
>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
{/* TODO ADAPT TO ALL TYPES OF INPUTS AND OUTPUTS */}

View file

@ -0,0 +1,26 @@
import { Card, CardContent, CardDescription, CardTitle } from "../ui/card";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { useNavigate } from "react-router-dom";
import IconComponent from "../genericIconComponent";
import { cn } from "../../utils/utils";
export default function NewFlowCardComponent() {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
return (
<Card onClick={() => {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}} className="pt-4 w-80 h-64 cursor-pointer bg-background">
<CardContent className="w-full h-full">
<div className="bg-dotted-spacing-6 bg-dotted-muted-foreground bg-dotted-radius-px rounded-md bg-muted w-full h-full flex flex-col items-center align-middle justify-center">
</div>
</CardContent>
<CardDescription className="px-6 pb-4">
<CardTitle className="text-primary text-lg">Blank Flow</CardTitle>
</CardDescription>
</Card>
)
}

View file

@ -0,0 +1,33 @@
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,14 +1,26 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import useFlowStore from "../../stores/flowStore";
import { ChatType } from "../../types/chat";
import IOView from "../IOview";
import ChatTrigger from "../ViewTriggers/chat";
import { Transition } from "@headlessui/react";
import ForwardedIconComponent from "../genericIconComponent";
import { Separator } from "../ui/separator";
import ShareModal from "../../modals/shareModal";
import { useStoreStore } from "../../stores/storeStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { classNames } from "../../utils/utils";
import ApiModal from "../../modals/ApiModal";
export default function Chat({ flow }: ChatType): JSX.Element {
export default function FlowToolbar({ flow }: ChatType): 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);
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
@ -29,16 +41,102 @@ export default function Chat({ flow }: ChatType): JSX.Element {
const prevNodesRef = useRef<any[] | undefined>();
const ModalMemo = useMemo(
() => (
<ShareModal
is_component={false}
component={currentFlow!}
disabled={!hasApiKey || !validApiKey || !hasStore}
>
<button
disabled={!hasApiKey || !validApiKey || !hasStore}
className={classNames(
"relative inline-flex w-full h-full items-center justify-center hover:bg-hover bg-muted hover:bg-background px-5 py-3 text-foreground transition-all duration-500 ease-in-out gap-[4px] text-sm font-semibold ",
!hasApiKey || !validApiKey || !hasStore
? " button-disable text-muted-foreground "
: ""
)}
>
<ForwardedIconComponent
name="Share3"
className={classNames(
"-m-0.5 -ml-1 h-6 w-6",
!hasApiKey || !validApiKey || !hasStore
? "extra-side-bar-save-disable"
: ""
)}
/>
Share
</button>
</ShareModal>
),
[hasApiKey, validApiKey, currentFlow, hasStore]
);
return (
<>
<div className="flex flex-col">
{/* <BuildTrigger open={open} flow={flow} /> */}
{hasIO && (
<IOView open={open} setOpen={setOpen}>
<ChatTrigger />
<Transition
show={true}
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={
"shadow-round-btn-shadow hover:shadow-round-btn-shadow message-button-position flex items-center justify-center rounded-sm bg-muted shadow-md transition-all gap-7 border"
}
>
<div className="flex">
<div className="flex gap-1 text-medium-indigo rounded-sm transition-all w-full h-full">
{hasIO ? (
<IOView open={open} setOpen={setOpen} disable={!hasIO}>
<div className="relative inline-flex w-full items-center justify-center hover:bg-hover transition-all duration-500 ease-in-out px-5 py-3 text-medium-indigo ease-in-out gap-1 text-sm font-semibold transition-all">
<ForwardedIconComponent
name="Zap"
className={"message-button-icon h-5 w-5 transition-all"}
/>
Run
</div>
</IOView>
) : (
<div className={`relative inline-flex w-full items-center justify-center transition-all duration-500 ease-in-out px-5 py-3 text-muted-foreground ease-in-out gap-1 text-sm font-semibold cursor-not-allowed`}>
<ForwardedIconComponent
name="Zap"
className={"message-button-icon h-5 w-5 transition-all fill-muted-foreground stroke-muted-foreground"}
/>
Run
</div>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
<div className="flex items-center gap-2 cursor-pointer">
{currentFlow && currentFlow.data && (
<ApiModal flow={currentFlow}>
<div className={classNames("relative inline-flex w-full items-center justify-center hover:bg-hover px-5 py-3 text-foreground transition-all duration-500 ease-in-out gap-1 text-sm font-semibold")}>
<ForwardedIconComponent
name="Code2"
className={" h-5 w-5"}
/>
API
</div>
</ApiModal>
)}
</div>
<div>
<Separator orientation="vertical" />
</div>
<div className="flex items-center gap-2">
<div className={`side-bar-button ${!hasApiKey || !validApiKey || !hasStore ? " cursor-not-allowed" : " cursor-pointer"}`}>{ModalMemo}</div>
</div>
</div>
</div>
</Transition>
</>
);
}

View file

@ -126,7 +126,7 @@ export default function CodeTabsComponent({
<Tabs
value={activeTab}
className={
"api-modal-tabs m-0 inset-0 " +
"api-modal-tabs inset-0 m-0 " +
(isMessage ? "dark " : "") +
(dark && isMessage ? "bg-background" : "")
}
@ -195,7 +195,7 @@ export default function CodeTabsComponent({
<SyntaxHighlighter
language={tab.language}
style={oneDark}
className="mt-0 h-full overflow-auto custom-scroll rounded-sm text-left"
className="mt-0 h-full overflow-auto rounded-sm text-left custom-scroll"
>
{tab.code}
</SyntaxHighlighter>

View file

@ -5,6 +5,7 @@ import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
export default function Dropdown({
disabled,
isLoading,
value,
options,
@ -28,6 +29,7 @@ export default function Dropdown({
<>
<Listbox
value={internalValue}
disabled={disabled}
onChange={(value) => {
setInternalValue(value);
onSelect(value);

View file

@ -0,0 +1,100 @@
import { useNavigate } from "react-router-dom";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowType } from "../../types/flow";
import { updateIds } from "../../utils/reactflowUtils";
import { cn } from "../../utils/utils";
import ShadTooltip from "../ShadTooltipComponent";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "../ui/card";
export default function CollectionCardComponent({
flow,
}: {
flow: FlowType;
authorized?: boolean;
}) {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
const emojiRegex = /\p{Emoji}/u;
const isEmoji = (str: string) => emojiRegex.test(str);
return (
<Card
className={cn(
"group relative flex h-48 w-2/6 flex-col justify-between overflow-hidden transition-all hover:shadow-md"
)}
>
<div>
<CardHeader>
<div>
<CardTitle className="flex w-full items-center justify-between gap-3 text-xl">
{flow.icon && isEmoji(flow.icon) && (
<div
className="flex items-center justify-center rounded-md p-2 align-middle"
style={{ backgroundColor: flow.icon_bg_color }}
>
<div className="h-7 w-7 pl-0.5">{flow.icon}</div>
</div>
)}
{(!flow.icon || !isEmoji(flow.icon)) && (
<div
className="flex items-center justify-center rounded-md p-2 align-middle"
style={{ backgroundColor: flow.icon_bg_color }}
>
<IconComponent
className={cn("h-7 w-7 flex-shrink-0 text-flow-icon")}
name={flow.icon || "Group"}
/>
</div>
)}
<ShadTooltip content={flow.name}>
<div className="w-full truncate">{flow.name}</div>
</ShadTooltip>
</CardTitle>
</div>
<CardDescription className="pb-2 pt-2">
<ShadTooltip
side="bottom"
styleClasses="z-50"
content={flow.description}
>
<div className="truncate-doubleline">{flow.description}</div>
</ShadTooltip>
</CardDescription>
</CardHeader>
</div>
<CardFooter>
<div className="flex w-full items-center justify-between gap-2">
<div className="flex w-full flex-wrap justify-end gap-2">
<Button
onClick={() => {
updateIds(flow.data!);
addFlow(true, flow).then((id) => {
navigate("/flow/" + id);
});
}}
tabIndex={-1}
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button select-none"
/>
Select Flow
</Button>
</div>
</div>
</CardFooter>
</Card>
);
}

View file

@ -1,36 +1,53 @@
import { forwardRef } from "react";
import dynamicIconImports from "lucide-react/dynamicIconImports";
import { Suspense, forwardRef, lazy, memo } from "react";
import { IconComponentProps } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
const ForwardedIconComponent = forwardRef(
(
{
name,
className,
iconColor,
stroke,
strokeWidth,
id = "",
}: IconComponentProps,
ref
) => {
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
const ForwardedIconComponent = memo(
forwardRef(
(
{
name,
className,
iconColor,
stroke,
strokeWidth,
id = "",
}: IconComponentProps,
ref
) => {
let TargetIcon = nodeIconsLucide[name];
if (!TargetIcon) {
// check if name exists in dynamicIconImports
if (!dynamicIconImports[name]) {
TargetIcon = nodeIconsLucide["unknown"];
} else TargetIcon = lazy(dynamicIconImports[name]);
}
const style = {
strokeWidth: strokeWidth ?? 1.5,
...(stroke && { stroke: stroke }),
...(iconColor && { color: iconColor, stroke: stroke }),
};
const style = {
strokeWidth: strokeWidth ?? 1.5,
...(stroke && { stroke: stroke }),
...(iconColor && { color: iconColor, stroke: stroke }),
};
return (
<TargetIcon
className={className}
style={style}
ref={ref}
data-testid={id ? `${id}-${name}` : `icon-${name}`}
/>
);
}
if (!TargetIcon) {
return null; // Render nothing until the icon is loaded
}
const fallback = (
<div style={{ background: "#ddd", width: 24, height: 24 }} />
);
return (
<Suspense fallback={fallback}>
<TargetIcon
className={className}
style={style}
ref={ref}
data-testid={id ? `${id}-${name}` : `icon-${name}`}
/>
</Suspense>
);
}
)
);
export default ForwardedIconComponent;

View file

@ -18,6 +18,9 @@ import { cn } from "../../../../utils/utils";
import ShadTooltip from "../../../ShadTooltipComponent";
import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import ExportModal from "../../../../modals/exportModal";
import { useStoreStore } from "../../../../stores/storeStore";
export const MenuBar = ({
removeFunction,
@ -32,7 +35,9 @@ export const MenuBar = ({
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
const [openSettings, setOpenSettings] = useState(false);
const n = useFlowStore((state) => state.nodes);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const hasApiKey = useStoreStore((state) => state.hasApiKey);
const validApiKey = useStoreStore((state) => state.validApiKey);
const navigate = useNavigate();
const isBuilding = useFlowStore((state) => state.isBuilding);
@ -100,7 +105,28 @@ export const MenuBar = ({
/>
Settings
</DropdownMenuItem>
<DropdownMenuItem
className="cursor-pointer"
onClick={() => {
uploadFlow({ newProject: false, isComponent: false }).catch(
(error) => {
setErrorData({
title: UPLOAD_ERROR_ALERT,
list: [error],
});
}
);
}}
>
<IconComponent name="FileUp" className="header-menu-options " />
Import
</DropdownMenuItem>
<ExportModal>
<div className="header-menubar-item">
<IconComponent name="FileDown" className="header-menu-options" />
Export
</div>
</ExportModal>
<DropdownMenuItem
onClick={() => {
undo();

View file

@ -12,10 +12,11 @@ export default function KeypairListComponent({
disabled,
editNode = false,
duplicateKey,
isList = true,
}: KeyPairListComponentType): JSX.Element {
useEffect(() => {
if (disabled && value.length > 0 && value[0] !== "") {
onChange([""]);
onChange([{ "": "" }]);
}
}, [disabled]);
@ -79,6 +80,7 @@ export default function KeypairListComponent({
: "keypair" + (index + 100).toString()
}
type="text"
disabled={disabled}
value={obj[key]}
className={editNode ? "input-edit-node" : ""}
placeholder="Type a value..."
@ -87,7 +89,7 @@ export default function KeypairListComponent({
}
/>
{index === ref.current.length - 1 ? (
{isList && index === ref.current.length - 1 ? (
<button
onClick={() => {
let newInputList = _.cloneDeep(ref.current);
@ -110,7 +112,7 @@ export default function KeypairListComponent({
className={"h-4 w-4 hover:text-accent-foreground"}
/>
</button>
) : (
) : isList ? (
<button
onClick={() => {
let newInputList = _.cloneDeep(ref.current);
@ -133,6 +135,8 @@ export default function KeypairListComponent({
className="h-4 w-4 hover:text-status-red"
/>
</button>
) : (
""
)}
</div>
);

View file

@ -134,7 +134,7 @@ export default function ChatMessage({
)}
</div>
{!chat.isSend ? (
<div className="form-modal-chat-text-position flex-grow min-w-96">
<div className="form-modal-chat-text-position min-w-96 flex-grow">
<div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
@ -155,9 +155,9 @@ export default function ChatMessage({
/>
)}
{chat.thought && chat.thought !== "" && !hidden && <br></br>}
<div className="w-full flex flex-col">
<div className="w-full flex flex-col dark:text-white">
<div className="w-full flex flex-col">
<div className="flex w-full flex-col">
<div className="flex w-full flex-col dark:text-white">
<div className="flex w-full flex-col">
{useMemo(
() =>
chatMessage === "" && lockChat ? (
@ -169,7 +169,7 @@ export default function ChatMessage({
<Markdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown flex flex-col prose text-primary word-break-break-word
className="markdown prose flex flex-col text-primary word-break-break-word
dark:prose-invert"
components={{
pre({ node, ...props }) {

View file

@ -4,7 +4,7 @@ import * as React from "react";
import { cn } from "../../utils/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
{
variants: {
variant: {

View file

@ -43,7 +43,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
"flex flex-col fixed left-[50%] top-[50%] z-50 w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
"fixed left-[50%] top-[50%] z-50 flex w-full max-w-lg translate-x-[-50%] translate-y-[-50%] flex-col gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full",
className
)}
{...props}

View file

@ -1,11 +1,13 @@
import IconComponent from "../../components/genericIconComponent";
import { NodeDataType } from "../../types/flow";
import { cn } from "../../utils/utils";
import { Button } from "./button";
function RefreshButton({
isLoading,
disabled,
name,
button_text,
data,
handleUpdateValues,
className,
@ -14,6 +16,7 @@ function RefreshButton({
isLoading: boolean;
disabled: boolean;
name: string;
button_text: string;
data: NodeDataType;
className?: string;
handleUpdateValues: (name: string, data: NodeDataType) => void;
@ -24,10 +27,7 @@ function RefreshButton({
handleUpdateValues(name, data);
};
const classNames = cn(
className,
disabled ? "cursor-not-allowed" : "cursor-pointer"
);
const classNames = cn(className, disabled ? "cursor-not-allowed" : "");
// icon class name should take into account the disabled state and the loading state
const disabledIconTextClass = disabled ? "text-muted-foreground" : "";
@ -38,13 +38,20 @@ function RefreshButton({
);
return (
<button className={classNames} onClick={handleClick} id={id}>
<Button
variant="primary"
disabled={disabled}
className={classNames}
onClick={handleClick}
id={id}
>
<span className="mr-1">{button_text}</span>
<IconComponent
name={isLoading ? "Loader2" : "RefreshCcw"}
className={iconClassName}
id={id + "-icon"}
/>
</button>
</Button>
);
}

View file

@ -0,0 +1,49 @@
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"
//@ts-ignore
import { ReactComponent as BasicPrompt } from "../../assets/undraw_design_components_9vy6.svg"
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { FlowType } from "../../types/flow"
import { updateIds } from "../../utils/reactflowUtils";
import ShadTooltip from "../ShadTooltipComponent"
import { Card, CardContent, CardDescription, CardFooter, CardTitle } from "../ui/card"
export default function UndrawCardComponent({
flow
}: { flow: FlowType }) {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const navigate = useNavigate();
function selectImage() {
switch (flow.name) {
case "Data Ingestion":
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
case "Basic Prompting":
return <BasicPrompt style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
default:
return <TransferFiles style={{ width: '80%', height: '80%', preserveAspectRatio: 'xMidYMid meet' }} />
}
}
return (
<Card onClick={() => {
updateIds(flow.data!);
addFlow(true, flow).then((id) => {
navigate("/flow/" + id);
});
}} className="pt-4 w-80 h-64 cursor-pointer bg-background">
<CardContent className="w-full h-full">
<div className="rounded-md p-1 bg-muted w-full h-full flex flex-col items-center align-middle justify-center">
{selectImage()}
</div>
</CardContent>
<CardDescription className="px-6 pb-4">
<CardTitle className="text-primary text-lg">{flow.name}</CardTitle>
</CardDescription>
</Card>
)
}

View file

@ -58,4 +58,3 @@ export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`;
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!";
// Generic Node

View file

@ -1,6 +1,7 @@
// src/constants/constants.ts
import { languageMap } from "../types/components";
import { FlowType } from "../types/flow";
/**
* invalid characters for flow name
@ -727,3 +728,87 @@ export const STATUS_BUILD = "Build to validate status.";
export const STATUS_BUILDING = "Building...";
export const SAVED_HOVER = "Last saved at ";
export const RUN_TIMESTAMP_PREFIX = "Last Run: ";
export const STARTER_FOLDER_NAME = "Starter Projects";
export const PRIORITY_SIDEBAR_ORDER = [
"saved_components",
"inputs",
"outputs",
"prompts",
"data",
"prompt",
"models",
"helpers",
"experimental",
];
/*
Data ingestion
Basic Prompting
Chat com memória
Working with data (file/website)
API requests
Vector Store
Assistant
*/
export const EXAMPLES_MOCK:FlowType[] = [
{
name: "Working with data",
id: "Working with data Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "Basic Prompting",
id: "Basic Prompting Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "Chat with memory",
id: "Chat with memory Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "API requests",
id: "API requests Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
{
name: "Assistant",
id: "Assistant Description",
data: {
nodes: [],
edges: [],
viewport: { zoom: 1, x: 1, y: 1 }
},
description: "This flow represents the first process in our application.",
folder: STARTER_FOLDER_NAME,
user_id: undefined,
},
];

View file

@ -369,11 +369,13 @@ export async function postCustomComponent(
export async function postCustomComponentUpdate(
code: string,
field: string
field: string,
field_value: any
): Promise<AxiosResponse<APIClassType>> {
return await api.post(`${BASE_URL_API}custom_component/update`, {
code,
field,
field_value,
});
}

View file

@ -98,6 +98,9 @@ const ApiModal = forwardRef(
let arrNodesWithValues: string[] = [];
flow["data"]!["nodes"].forEach((node) => {
if (!node["data"]["node"]["template"]) {
return;
}
Object.keys(node["data"]["node"]["template"])
.filter(
(templateField) =>

View file

@ -35,8 +35,7 @@ export default function DeleteConfirmationModal({
</DialogTitle>
</DialogHeader>
<span>
Confirm deletion of {description ?? "component"}?
<br></br>
Confirm deletion of {description ?? "component"}?<br></br>
Note: This action is irreversible.
</span>
<DialogFooter>

View file

@ -165,8 +165,19 @@ const EditNodeModal = forwardRef(
)
) ?? false;
return (
<TableRow key={index} className={"h-10 " + ((templateParam==="code" && myData.node?.template[templateParam].type==="code") || (templateParam.includes("code") && myData.node?.template[templateParam].proxy) ? " hidden " : "")
}>
<TableRow
key={index}
className={
"h-10 " +
((templateParam === "code" &&
myData.node?.template[templateParam].type ===
"code") ||
(templateParam.includes("code") &&
myData.node?.template[templateParam].proxy)
? " hidden "
: "")
}
>
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
<ShadTooltip
content={

View file

@ -66,6 +66,7 @@ interface BaseModalProps {
| "small"
| "medium"
| "large"
| "three-cards"
| "large-thin"
| "large-h-full"
| "small-h-full"
@ -129,6 +130,10 @@ function BaseModal({
minWidth = "min-w-[85vw]";
height = "h-[80vh]";
break;
case "three-cards":
minWidth = "min-w-[1066px]";
height = "h-[80vh]";
break;
case "large-thin":
minWidth = "min-w-[65vw]";
height = "h-[80vh]";

View file

@ -24,6 +24,7 @@ import { postCustomComponent, postValidateCode } from "../../controllers/API";
import useAlertStore from "../../stores/alertStore";
import { useDarkStore } from "../../stores/darkStore";
import useFlowStore from "../../stores/flowStore";
import { CodeErrorDataTypeAPI } from "../../types/api";
import { codeAreaModalPropsType } from "../../types/components";
import BaseModal from "../baseModal";
@ -49,7 +50,7 @@ export default function CodeAreaModal({
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
const [error, setError] = useState<{
detail: { error: string | undefined; traceback: string | undefined };
detail: CodeErrorDataTypeAPI;
} | null>(null);
const nodes = useFlowStore((state) => state.nodes);

View file

@ -179,7 +179,7 @@ export default function ShareModal({
</span>
<br></br>
<span className=" text-xs text-destructive ">
Note: This action is irreversible.
Note: This action is irreversible.
</span>
</ConfirmationModal.Content>
</ConfirmationModal>
@ -204,7 +204,9 @@ export default function ShareModal({
{children ? children : <></>}
</BaseModal.Trigger>
<BaseModal.Header
description={`Publish ${is_component ? "your component" : "workflow"} to the Langflow Store.`}
description={`Publish ${
is_component ? "your component" : "workflow"
} to the Langflow Store.`}
>
<span className="pr-2">Share</span>
<IconComponent
@ -239,7 +241,8 @@ export default function ShareModal({
</label>
</div>
<span className=" text-xs text-destructive ">
<b>Attention:</b> API keys in specified fields are automatically removed upon sharing.
<b>Attention:</b> API keys in specified fields are automatically
removed upon sharing.
</span>
</BaseModal.Content>

View file

@ -12,7 +12,6 @@ import ReactFlow, {
updateEdge,
} from "reactflow";
import GenericNode from "../../../../CustomNodes/GenericNode";
import Chat from "../../../../components/chatComponent";
import {
INVALID_SELECTION_ERROR_ALERT,
UPLOAD_ALERT_LIST,
@ -38,6 +37,7 @@ import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
import SelectionMenu from "../SelectionMenuComponent";
import ExtraSidebar from "../extraSidebarComponent";
import FlowToolbar from "../../../../components/chatComponent";
const nodeTypes = {
genericNode: GenericNode,
@ -109,7 +109,7 @@ export default function Page({
...old.data,
node: {
...old.data.node,
pinned: old.data?.node?.pinned ? false : true,
frozen: old.data?.node?.frozen ? false : true,
},
},
}));
@ -481,7 +481,7 @@ export default function Page({
}}
/>
</ReactFlow>
{!view && <Chat flow={flow} />}
{!view && <FlowToolbar flow={flow} />}
</div>
) : (
<></>

View file

@ -1,11 +1,11 @@
import { cloneDeep } from "lodash";
import { LinkIcon, SparklesIcon } from "lucide-react";
import { useEffect, useMemo, useState } from "react";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { Input } from "../../../../components/ui/input";
import { Separator } from "../../../../components/ui/separator";
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
import ApiModal from "../../../../modals/ApiModal";
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
import ExportModal from "../../../../modals/exportModal";
import ShareModal from "../../../../modals/shareModal";
import useAlertStore from "../../../../stores/alertStore";
@ -26,6 +26,7 @@ import {
} from "../../../../utils/utils";
import DisclosureComponent from "../DisclosureComponent";
import SidebarDraggableComponent from "./sideBarDraggableComponent";
import { sortKeys } from "./utils";
export default function ExtraSidebar(): JSX.Element {
const data = useTypesStore((state) => state.data);
@ -234,66 +235,6 @@ export default function ExtraSidebar(): JSX.Element {
return (
<div className="side-bar-arrangement">
<div className="side-bar-buttons-arrangement">
{hasStore && validApiKey && (
<ShadTooltip
content={
!hasApiKey || !validApiKey
? "Please review your API key."
: "Share"
}
side="top"
styleClasses="cursor-default"
>
<div className="side-bar-button">{ModalMemo}</div>
</ShadTooltip>
)}
<div className="side-bar-button">
<ShadTooltip content="Import" side="top">
<button
className="extra-side-bar-buttons"
onClick={() => {
uploadFlow({ newProject: false, isComponent: false }).catch(
(error) => {
setErrorData({
title: UPLOAD_ERROR_ALERT,
list: [error],
});
}
);
}}
>
<IconComponent name="FileUp" className="side-bar-button-size " />
</button>
</ShadTooltip>
</div>
{(!hasApiKey || !validApiKey) && (
<ShadTooltip
content="Export"
side="top"
styleClasses="cursor-default"
>
<div className="side-bar-button">{ExportMemo}</div>
</ShadTooltip>
)}
<ShadTooltip content={"Code"} side="top">
<div className="side-bar-button">
{currentFlow && currentFlow.data && (
<ApiModal flow={currentFlow}>
<button className={"w-full "}>
<div className={classNames("extra-side-bar-buttons")}>
<IconComponent
name="Code2"
className={"side-bar-button-size"}
/>
</div>
</button>
</ApiModal>
)}
</div>
</ShadTooltip>
</div>
<Separator />
<div className="side-bar-search-div-placement">
<Input
onFocusCapture={() => handleBlur()}
@ -317,82 +258,110 @@ export default function ExtraSidebar(): JSX.Element {
/>
</div>
</div>
<Separator />
<div className="side-bar-components-div-arrangement">
{Object.keys(dataFilter)
.sort((a, b) => {
if (a.toLowerCase() === "saved_components") {
return -1;
} else if (b.toLowerCase() === "saved_components") {
return 1;
} else if (a.toLowerCase() === "custom_components") {
return -2;
} else if (b.toLowerCase() === "custom_components") {
return 2;
} else {
return a.localeCompare(b);
}
})
.sort(sortKeys)
.map((SBSectionName: keyof APIObjectType, index) =>
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
<DisclosureComponent
openDisc={
getFilterEdge.length !== 0 || search.length !== 0
? true
: false
}
key={index + search + JSON.stringify(getFilterEdge)}
button={{
title: nodeNames[SBSectionName] ?? nodeNames.unknown,
Icon:
nodeIconsLucide[SBSectionName] ?? nodeIconsLucide.unknown,
}}
>
<div className="side-bar-components-gap">
{Object.keys(dataFilter[SBSectionName])
.sort((a, b) =>
sensitiveSort(
dataFilter[SBSectionName][a].display_name,
dataFilter[SBSectionName][b].display_name
<>
{index === 0 && (
<div className="pt-0.5">
<div className="p-2 px-4 font-semibold text-sm" key={index}>
Native Components
</div>
</div>
)}
{index === PRIORITY_SIDEBAR_ORDER.length - 1 && (
<>
<a
target={"_blank"}
href="https://langflow.store"
className="components-disclosure-arrangement"
>
<div className="flex gap-4">
{/* BUG ON THIS ICON */}
<SparklesIcon
strokeWidth={1.5}
className="text-primary w-[22px]"
/>
<span className="components-disclosure-title">
Discover More
</span>
</div>
<div className="components-disclosure-div">
<div>
<LinkIcon className="h-4 w-4 text-foreground" />
</div>
</div>
</a>
<div className="p-2 px-4 font-semibold text-sm" key={index}>
Legacy Components
</div>
</>
)}
<DisclosureComponent
openDisc={
getFilterEdge.length !== 0 || search.length !== 0
? true
: false
}
key={index + search + JSON.stringify(getFilterEdge)}
button={{
title: nodeNames[SBSectionName] ?? nodeNames.unknown,
Icon:
nodeIconsLucide[SBSectionName] ?? nodeIconsLucide.unknown,
}}
>
<div className="side-bar-components-gap">
{Object.keys(dataFilter[SBSectionName])
.sort((a, b) =>
sensitiveSort(
dataFilter[SBSectionName][a].display_name,
dataFilter[SBSectionName][b].display_name
)
)
)
.map((SBItemName: string, index) => (
<ShadTooltip
content={
dataFilter[SBSectionName][SBItemName].display_name
}
side="right"
key={index}
>
<SidebarDraggableComponent
sectionName={SBSectionName as string}
apiClass={dataFilter[SBSectionName][SBItemName]}
key={index}
onDragStart={(event) =>
onDragStart(event, {
//split type to remove type in nodes saved with same name removing it's
type: removeCountFromString(SBItemName),
node: dataFilter[SBSectionName][SBItemName],
})
}
color={nodeColors[SBSectionName]}
itemName={SBItemName}
//convert error to boolean
error={!!dataFilter[SBSectionName][SBItemName].error}
display_name={
.map((SBItemName: string, index) => (
<ShadTooltip
content={
dataFilter[SBSectionName][SBItemName].display_name
}
official={
dataFilter[SBSectionName][SBItemName].official ===
false
? false
: true
}
/>
</ShadTooltip>
))}
</div>
</DisclosureComponent>
side="right"
key={index}
>
<SidebarDraggableComponent
sectionName={SBSectionName as string}
apiClass={dataFilter[SBSectionName][SBItemName]}
key={index}
onDragStart={(event) =>
onDragStart(event, {
//split type to remove type in nodes saved with same name removing it's
type: removeCountFromString(SBItemName),
node: dataFilter[SBSectionName][SBItemName],
})
}
color={nodeColors[SBSectionName]}
itemName={SBItemName}
//convert error to boolean
error={
!!dataFilter[SBSectionName][SBItemName].error
}
display_name={
dataFilter[SBSectionName][SBItemName].display_name
}
official={
dataFilter[SBSectionName][SBItemName].official ===
false
? false
: true
}
/>
</ShadTooltip>
))}
</div>
</DisclosureComponent>
</>
) : (
<div key={index}></div>
)

View file

@ -43,6 +43,7 @@ export const SidebarDraggableComponent = forwardRef(
const deleteComponent = useFlowsManagerStore(
(state) => state.deleteComponent
);
const version = useDarkStore((state) => state.version);
const [cursorPos, setCursorPos] = useState({ x: 0, y: 0 });
const popoverRef = useRef<HTMLDivElement>(null);

View file

@ -0,0 +1,26 @@
import { PRIORITY_SIDEBAR_ORDER } from "../../../../constants/constants";
export function sortKeys(a: string, b: string) {
// Define the order of specific keys
const indexA = PRIORITY_SIDEBAR_ORDER.indexOf(a.toLowerCase());
const indexB = PRIORITY_SIDEBAR_ORDER.indexOf(b.toLowerCase());
// Check if both keys are in the predefined order
if (indexA !== -1 && indexB !== -1) {
return indexA - indexB;
}
// If only 'a' is in the predefined order, it should come first
if (indexA !== -1) {
return -1;
}
// If only 'b' is in the predefined order, it should come first
if (indexB !== -1) {
return 1;
}
// If neither 'a' nor 'b' are in the predefined order, sort them alphabetically
return a.localeCompare(b);
}

View file

@ -65,7 +65,7 @@ export default function NodeToolbarComponent({
const isMinimal = numberOfHandles <= 1;
const isGroup = data.node?.flow ? true : false;
const pinned = data.node?.pinned ?? 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);
@ -155,6 +155,23 @@ export default function NodeToolbarComponent({
case "copy":
const node = nodes.filter((node) => node.id === data.id);
setLastCopiedSelection({ nodes: _.cloneDeep(node), edges: [] });
break;
case "duplicate":
paste(
{
nodes: [nodes.find((node) => node.id === data.id)!],
edges: [],
},
{
x: 50,
y: 10,
paneX: nodes.find((node) => node.id === data.id)?.position
.x,
paneY: nodes.find((node) => node.id === data.id)?.position
.y,
}
);
break;
}
};
@ -308,7 +325,7 @@ export default function NodeToolbarComponent({
<>
<div className="w-26 h-10">
<span className="isolate inline-flex rounded-md shadow-sm">
{hasCode ? (
{hasCode && (
<ShadTooltip content="Code" side="top">
<button
className="relative inline-flex items-center rounded-l-md 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"
@ -317,51 +334,28 @@ export default function NodeToolbarComponent({
}}
data-testid="code-button-modal"
>
<IconComponent name="TerminalSquare" className="h-4 w-4" />
</button>
</ShadTooltip>
) : (
<ShadTooltip content="Save" 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",
hasCode ? "" : "rounded-l-md"
)}
onClick={() => {
isSaved
? setShowOverrideModal(true)
: saveComponent(cloneDeep(data), false);
}}
>
<IconComponent name="SaveAll" className=" h-4 w-4" />
<IconComponent name="Code" className="h-4 w-4" />
</button>
</ShadTooltip>
)}
<ShadTooltip content="Duplicate" side="top">
<ShadTooltip content={"Save"} 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"
)}
onClick={(event) => {
event.preventDefault();
paste(
{
nodes: [nodes.find((node) => node.id === data.id)!],
edges: [],
},
{
x: 50,
y: 10,
paneX: nodes.find((node) => node.id === data.id)?.position
.x,
paneY: nodes.find((node) => node.id === data.id)?.position
.y,
}
);
if (isSaved) {
return setShowOverrideModal(true);
}
saveComponent(cloneDeep(data), false);
}}
>
<IconComponent name="Copy" className="h-4 w-4" />
<IconComponent
name="SaveAll"
className="h-4 w-4"
/>
</button>
</ShadTooltip>
@ -378,7 +372,7 @@ export default function NodeToolbarComponent({
...old.data,
node: {
...old.data.node,
pinned: old.data?.node?.pinned ? false : true,
frozen: old.data?.node?.frozen ? false : true,
},
},
}));
@ -389,7 +383,7 @@ export default function NodeToolbarComponent({
className={cn(
"h-4 w-4 transition-all",
// TODO UPDATE THIS COLOR TO BE A VARIABLE
pinned ? "animate-wiggle text-ice" : ""
frozen ? "animate-wiggle text-ice" : ""
)}
/>
</button>
@ -433,53 +427,17 @@ export default function NodeToolbarComponent({
<span className="absolute right-2 top-[0.46em]">E</span>
</div>
</SelectItem>
)}
{isSaved ? (
<SelectItem value={"override"}>
<div className="flex">
<IconComponent
name="SaveAll"
className="relative top-0.5 mr-2 h-4 w-4 "
/>{" "}
<span className="">Save</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
)}
<span className="absolute right-2 top-[0.45em]">S</span>
</div>
</SelectItem>
) : (
hasCode && (
<SelectItem value={"SaveAll"}>
<div className="flex">
<IconComponent
name="SaveAll"
className="relative top-0.5 mr-2 h-4 w-4 "
/>{" "}
<span className="">Save</span>{" "}
{navigator.userAgent.toUpperCase().includes("MAC") ? (
<IconComponent
name="Command"
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
></IconComponent>
) : (
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">Ctrl + </span>
)}
<span className="absolute right-2 top-[0.46em]">S</span>
</div>
</SelectItem>
)
)}
)}
<SelectItem value={"duplicate"}>
<div className="flex" data-testid="save-button-modal">
<IconComponent name="Copy" className="relative top-0.5 mr-2 h-4 w-4" />
Duplicate
</div>{" "}
</SelectItem>
<SelectItem value={"copy"}>
<div className="flex">
<IconComponent
name="Copy"
name="Clipboard"
className="relative top-0.5 mr-2 h-4 w-4 "
/>{" "}
<span className="">Copy</span>{" "}

View file

@ -24,6 +24,7 @@ export default function ComponentsComponent({
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
const removeFlow = useFlowsManagerStore((state) => state.removeFlow);
const isLoading = useFlowsManagerStore((state) => state.isLoading);
const setExamples = useFlowsManagerStore((state) => state.setExamples);
const flows = useFlowsManagerStore((state) => state.flows);
const setSuccessData = useAlertStore((state) => state.setSuccessData);
const setErrorData = useAlertStore((state) => state.setErrorData);
@ -35,7 +36,7 @@ export default function ComponentsComponent({
useEffect(() => {
if (isLoading) return;
const all = flows
let all = flows
.filter((f) => (f.is_component ?? false) === is_component)
.sort((a, b) => {
if (a?.updated_at && b?.updated_at) {

View file

@ -1,19 +1,24 @@
import { Group, ToyBrick } from "lucide-react";
import { useEffect } from "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 ExampleCardComponent from "../../components/exampleComponent";
import IconComponent from "../../components/genericIconComponent";
import PageLayout from "../../components/pageLayout";
import SidebarNav from "../../components/sidebarComponent";
import { Button } from "../../components/ui/button";
import { CONSOLE_ERROR_MSG } from "../../constants/alerts_constants";
import {
EXAMPLES_MOCK,
MY_COLLECTION_DESC,
USER_PROJECTS_HEADER,
} from "../../constants/constants";
import BaseModal from "../../modals/baseModal";
import useAlertStore from "../../stores/alertStore";
import useFlowsManagerStore from "../../stores/flowsManagerStore";
import { downloadFlows } from "../../utils/reactflowUtils";
import UndrawCardComponent from "../../components/undrawCards";
export default function HomePage(): JSX.Element {
const addFlow = useFlowsManagerStore((state) => state.addFlow);
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
@ -25,6 +30,8 @@ export default function HomePage(): JSX.Element {
const setErrorData = useAlertStore((state) => state.setErrorData);
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 = [
{
@ -98,11 +105,7 @@ export default function HomePage(): JSX.Element {
</Button>
<DropdownButton
firstButtonName="New Project"
onFirstBtnClick={() => {
addFlow(true).then((id) => {
navigate("/flow/" + id);
});
}}
onFirstBtnClick={() => setOpenModal(true)}
options={dropdownOptions}
/>
</div>
@ -116,6 +119,28 @@ 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="flex h-full w-full flex-wrap gap-3 overflow-auto p-4 custom-scroll">
{EXAMPLES_MOCK.map((example, idx) => {
return <UndrawCardComponent key={idx} flow={example} />;
})}
<NewFlowCardComponent />
</div>
</BaseModal.Content>
</BaseModal>
</PageLayout>
);
}

View file

@ -190,8 +190,8 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
get().setNodes((oldNodes) =>
oldNodes.map((node) => {
if (node.id === id) {
if((node.data as NodeDataType).node?.pinned){
(newChange.data as NodeDataType).node!.pinned = false;
if ((node.data as NodeDataType).node?.frozen) {
(newChange.data as NodeDataType).node!.frozen = false;
}
return newChange;
}
@ -450,9 +450,10 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
status: BuildStatus,
runId: string
) {
if (vertexBuildData && vertexBuildData.inactive_vertices) {
get().removeFromVerticesBuild(vertexBuildData.inactive_vertices);
if (vertexBuildData && vertexBuildData.inactivated_vertices) {
get().removeFromVerticesBuild(vertexBuildData.inactivated_vertices);
}
if (vertexBuildData.next_vertices_ids) {
// next_vertices_ids is a list of vertices that are going to be built next
// verticesLayers is a list of list of vertices ids, where each list is a layer of vertices
@ -534,10 +535,19 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
runId: string;
} | null
) => {
console.log("updateVerticesBuild", vertices);
set({ verticesBuild: vertices });
},
verticesBuild: null,
addToVerticesBuild: (vertices: string[]) => {
const verticesBuild = get().verticesBuild;
if (!verticesBuild) return;
set({
verticesBuild: {
...verticesBuild,
verticesIds: [...verticesBuild.verticesIds, ...vertices],
},
});
},
removeFromVerticesBuild: (vertices: string[]) => {
const verticesBuild = get().verticesBuild;
if (!verticesBuild) return;
@ -551,7 +561,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
});
},
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
console.log("updateBuildStatus", nodeIdList, status);
const newFlowBuildStatus = { ...get().flowBuildStatus };
nodeIdList.forEach((id) => {
newFlowBuildStatus[id] = {
@ -561,7 +570,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
const timestamp_string = new Date(Date.now()).toLocaleString();
newFlowBuildStatus[id].timestamp = timestamp_string;
}
console.log("updateBuildStatus", newFlowBuildStatus);
});
set({ flowBuildStatus: newFlowBuildStatus });
},

View file

@ -2,6 +2,7 @@ import { AxiosError } from "axios";
import { cloneDeep } from "lodash";
import { Edge, Node, Viewport, XYPosition } from "reactflow";
import { create } from "zustand";
import { STARTER_FOLDER_NAME } from "../constants/constants";
import {
deleteFlowFromDatabase,
readFlowsFromDatabase,
@ -37,6 +38,10 @@ const past = {};
const future = {};
const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
examples: [],
setExamples: (examples: FlowType[]) => {
set({ examples });
},
currentFlowId: "",
setCurrentFlowId: (currentFlowId: string) => {
set((state) => ({
@ -62,7 +67,16 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
.then((dbData) => {
if (dbData) {
const { data, flows } = processFlows(dbData, false);
get().setFlows(flows);
get().setExamples(
flows.filter(
(f) => f.folder === STARTER_FOLDER_NAME && !f.user_id
)
);
get().setFlows(
flows.filter(
(f) => !(f.folder === STARTER_FOLDER_NAME && !f.user_id)
)
);
useTypesStore.setState((state) => ({
data: { ...state.data, ["saved_components"]: data },
}));
@ -92,7 +106,6 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
true
);
}
set({ saveLoading: true });
}, 500); // Delay of 500ms because chat message depends on it.
},
saveFlow: (flow: FlowType, silent?: boolean) => {

View file

@ -69,7 +69,7 @@
@apply flex h-full w-[14.5rem] flex-col overflow-hidden border-r scrollbar-hide;
}
.side-bar-search-div-placement {
@apply relative mx-auto mb-2 mt-2 flex items-center;
@apply relative mx-auto flex items-center py-3;
}
.side-bar-components-icon {
@apply h-6 w-4 text-ring;
@ -114,7 +114,10 @@
@apply pointer-events-none;
}
.extra-side-bar-buttons {
@apply relative inline-flex w-full items-center justify-center rounded-md bg-background px-2 py-2 text-foreground shadow-sm ring-1 ring-inset ring-input transition-all duration-500 ease-in-out;
@apply relative inline-flex w-full items-center justify-center rounded-md bg-background px-2 py-2 text-foreground transition-all duration-500 ease-in-out;
}
.header-menubar-item {
@apply relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-accent hover:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 cursor-pointer;
}
.extra-side-bar-buttons:hover {
@apply hover:bg-muted;
@ -316,7 +319,7 @@
@apply border-none ring ring-[#FF9090];
}
.built-invalid-status-dark {
@apply border-none ring ring-[#751C1C]
@apply border-none ring ring-[#751C1C];
}
.building-status {
@apply border-none ring;
@ -431,7 +434,9 @@
.code-area-external-link:hover {
@apply hover:text-accent-foreground;
}
.dropdown-component-disabled {
@apply pointer-events-none cursor-not-allowed;
}
.dropdown-component-outline {
@apply input-edit-node relative pr-8;
}
@ -441,11 +446,17 @@
.dropdown-component-display {
@apply block w-full truncate bg-background;
}
.dropdown-component-display-disabled {
@apply text-muted-foreground;
}
.dropdown-component-arrow {
@apply pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2;
}
.dropdown-component-arrow-color {
@apply extra-side-bar-save-disable h-5 w-5;
@apply h-5 w-5 text-accent-foreground;
}
.dropdown-component-arrow-color-disable {
@apply h-5 w-5 text-muted-foreground;
}
.dropdown-component-options {
@apply z-10 mt-1 max-h-60 overflow-auto rounded-md bg-background py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm;
@ -487,7 +498,7 @@
@apply flex items-center gap-0.5 rounded-md px-1.5 py-1 text-sm font-medium;
}
.header-menu-bar-display {
@apply flex max-w-[120px] cursor-pointer items-center gap-2 lg:max-w-[200px];
@apply flex max-w-[115px] cursor-pointer items-center gap-2 lg:max-w-[145px];
}
.header-menu-flow-name {
@apply flex-1 truncate;
@ -902,7 +913,7 @@
@apply flex-max-width px-2 py-6 pl-4 pr-9;
}
.form-modal-chatbot-icon {
@apply flex flex-col mb-3 ml-3 mr-6 mt-1;
@apply mb-3 ml-3 mr-6 mt-1 flex flex-col;
}
.form-modal-chat-image {
@apply flex flex-col items-center gap-1;

View file

@ -50,6 +50,9 @@
--component-icon: #d8598a;
--flow-icon: #2f67d0;
--hover: #F2F4F5;
--disabled-run: #6366f1;
/* Colors that are shared in dark and light mode */
--blur-shared: #151923de;
@ -69,6 +72,8 @@
--background: 224 35% 7.5%; /* hsl(224 40% 10%) */
--foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--ice: #60A5FA;
--hover: #1A202E;
--disabled-run: #6366f1;
--muted: 223 27% 11%; /* hsl(223 27% 11%) */
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */

View file

@ -27,8 +27,9 @@ export type APIClassType = {
documentation: string;
error?: string;
official?: boolean;
pinned?: boolean;
frozen?: boolean;
flow?: FlowType;
field_order?: string[];
[key: string]:
| Array<string>
| string
@ -54,7 +55,9 @@ export type TemplateVariableType = {
input_types?: Array<string>;
display_name?: string;
name?: string;
refresh?: boolean;
real_time_refresh?: boolean;
refresh_button?: boolean;
refresh_button_text?: string;
[key: string]: any;
};
export type sendAllProps = {
@ -141,8 +144,8 @@ export type VerticesOrderTypeAPI = {
export type VertexBuildTypeAPI = {
id: string;
inactivated_vertices: Array<string> | null;
next_vertices_ids: Array<string>;
inactive_vertices: Array<string> | null;
run_id: string;
valid: boolean;
params: string;
@ -158,3 +161,17 @@ export type VertexDataTypeAPI = {
timedelta?: number;
duration?: string;
};
export type CodeErrorDataTypeAPI = {
error: string | undefined;
traceback: string | undefined;
};
// the error above is inside this error.response.data.detail.error
// which comes from a request to the API
// to type the error we need to know the structure of the object
// error that has a response, that has a data, that has a detail, that has an error
export type ResponseErrorTypeAPI = {
response: { data: { detail: CodeErrorDataTypeAPI } };
};

View file

@ -30,6 +30,7 @@ export type ToggleComponentType = {
editNode?: boolean;
};
export type DropDownComponentType = {
disabled?: boolean;
isLoading?: boolean;
value: string;
options: string[];
@ -70,6 +71,7 @@ export type KeyPairListComponentType = {
editNode?: boolean;
duplicateKey?: boolean;
editNodeModal?: boolean;
isList?: boolean;
};
export type DictComponentType = {

View file

@ -13,6 +13,10 @@ export type FlowType = {
updated_at?: string;
date_created?: string;
parent?: string;
folder?: string;
user_id?: string;
icon?: string;
icon_bg_color?: string;
};
export type NodeType = {

View file

@ -107,6 +107,7 @@ export type FlowStoreType = {
runId: string;
} | null
) => void;
addToVerticesBuild: (vertices: string[]) => void;
removeFromVerticesBuild: (vertices: string[]) => void;
verticesBuild: {
verticesIds: string[];

View file

@ -44,6 +44,8 @@ export type FlowsManagerStoreType = {
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
examples: Array<FlowType>;
setExamples: (examples: FlowType[]) => void;
};
export type UseUndoRedoOptions = {

View file

@ -32,6 +32,7 @@ function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
id: vertexId,
data: inactiveData,
params: "Inactive",
inactivated_vertices: null,
run_id: "",
next_vertices_ids: [],
inactive_vertices: null,

View file

@ -0,0 +1,35 @@
import { throttle } from "lodash";
import { postCustomComponentUpdate } from "../controllers/API";
import { ResponseErrorTypeAPI } from "../types/api";
import { NodeDataType } from "../types/flow";
export const handleUpdateValues = async (name: string, data: NodeDataType) => {
const code = data.node?.template["code"]?.value;
if (!code) {
console.error("Code not found in the template");
return;
}
try {
let newTemplate = await postCustomComponentUpdate(
code,
name,
data.node?.template[name]?.value
)
.then((res) => {
console.log("res", res);
if (res.status === 200 && data.node?.template) {
return res.data.template;
}
})
.catch((error) => {
throw error;
});
return newTemplate;
} catch (error) {
console.error("Error occurred while updating the node:", error);
let errorType = error as ResponseErrorTypeAPI;
throw errorType;
}
};
export const throttledHandleUpdateValues = throttle(handleUpdateValues, 10);

View file

@ -4,13 +4,13 @@ import {
ArrowLeft,
ArrowUpToLine,
Bell,
Binary,
BookMarked,
BookmarkPlus,
Bot,
Snowflake,
Boxes,
Braces,
Cable,
BrainCircuit,
Check,
CheckCircle2,
ChevronDown,
@ -31,7 +31,9 @@ import {
Compass,
Copy,
Cpu,
Database,
Delete,
Dot,
Download,
DownloadCloud,
Edit,
@ -41,12 +43,13 @@ import {
EyeOff,
File,
FileDown,
SquarePen,
FileSearch,
FileSearch2,
FileSliders,
FileText,
FileType2,
FileUp,
Fingerprint,
FlaskConical,
FolderPlus,
FormInput,
@ -64,7 +67,6 @@ import {
Key,
Laptop2,
Layers,
Lightbulb,
Link,
Loader2,
Lock,
@ -81,16 +83,19 @@ import {
MoonIcon,
MoreHorizontal,
Network,
Package2,
Paperclip,
Pencil,
PencilLine,
Pin,
Play,
Plus,
PlusCircle,
PlusSquare,
PocketKnife,
Redo,
RefreshCcw,
Repeat,
Rocket,
Save,
SaveAll,
Scissors,
@ -101,6 +106,7 @@ import {
Share2,
Shield,
Sliders,
Snowflake,
Sparkles,
Square,
Store,
@ -123,7 +129,6 @@ import {
Variable,
Wand2,
Workflow,
Wrench,
X,
XCircle,
Zap,
@ -206,6 +211,9 @@ export const gradients = [
];
export const nodeColors: { [char: string]: string } = {
inputs: "#9AAE42",
outputs: "#AA2411",
data: "#6344BE",
prompts: "#4367BF",
models: "#AA2411",
model_specs: "#6344BE",
@ -225,19 +233,23 @@ export const nodeColors: { [char: string]: string } = {
textsplitters: "#B47CB5",
toolkits: "#DB2C2C",
wrappers: "#E6277A",
utilities: "#31A3CC",
helpers: "#31A3CC",
experimental: "#E6277A",
langchain_utilities: "#31A3CC",
output_parsers: "#E6A627",
str: "#31a3cc",
Text: "#31a3cc",
retrievers: "#e6b25a",
unknown: "#9CA3AF",
custom_components: "#ab11ab",
io: "#e6b25a",
};
export const nodeNames: { [char: string]: string } = {
inputs: "Inputs",
outputs: "Outputs",
data: "Data",
prompts: "Prompts",
models: "Language Models",
models: "Models",
model_specs: "Model Specs",
chains: "Chains",
agents: "Agents",
@ -253,14 +265,18 @@ export const nodeNames: { [char: string]: string } = {
wrappers: "Wrappers",
textsplitters: "Text Splitters",
retrievers: "Retrievers",
utilities: "Utilities",
helpers: "Helpers",
experimental: "Experimental",
langchain_utilities: "Utilities",
output_parsers: "Output Parsers",
custom_components: "Custom",
io: "I/O",
unknown: "Other",
};
export const nodeIconsLucide: iconsType = {
inputs: Download,
outputs: Upload,
data: Database,
AzureChatOpenAi: AzureIcon,
Ollama: OllamaIcon,
ChatOllama: OllamaIcon,
@ -323,26 +339,28 @@ export const nodeIconsLucide: iconsType = {
VertexAIEmbeddings: VertexAIIcon,
Share3: ShareIcon,
Share4: Share2Icon,
agents: Rocket,
agents: Bot,
Workflow,
User,
WikipediaAPIWrapper: SvgWikipedia,
chains: Link,
memories: Cpu,
models: Bot,
model_specs: Lightbulb,
models: BrainCircuit,
model_specs: FileSliders,
prompts: TerminalSquare,
tools: Wrench,
tools: Hammer,
advanced: Laptop2,
chat: MessageCircle,
embeddings: Fingerprint,
embeddings: Binary,
saved_components: GradientSave,
documentloaders: Paperclip,
vectorstores: Layers,
toolkits: Hammer,
toolkits: Package2,
textsplitters: Scissors,
wrappers: Gift,
utilities: Wand2,
helpers: Wand2,
experimental: FlaskConical,
langchain_utilities: PocketKnife,
WolframAlphaAPIWrapper: SvgWolfram,
output_parsers: Compass,
retrievers: FileSearch,
@ -359,6 +377,7 @@ export const nodeIconsLucide: iconsType = {
XCircle,
Info,
CheckCircle2,
SquarePen,
Zap,
MessagesSquare,
ExternalLink,
@ -383,6 +402,8 @@ export const nodeIconsLucide: iconsType = {
Circle,
CircleDot,
Clipboard,
PlusCircle,
PlusSquare,
Code2,
Variable,
Snowflake,
@ -449,7 +470,6 @@ export const nodeIconsLucide: iconsType = {
TerminalSquare,
TextCursorInput,
Repeat,
io: Cable,
Sliders,
ScreenShare,
Code,
@ -461,4 +481,5 @@ export const nodeIconsLucide: iconsType = {
Delete,
Command,
ArrowBigUp,
Dot,
};

View file

@ -1,5 +1,6 @@
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { priorityFields } from "../constants/constants";
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "../flow_constants";
import {
APIDataType,
@ -143,7 +144,7 @@ export function groupByFamily(
// se existir o flow
for (const node of flow) {
// para cada node do flow
if (node!.data!.node!.flow) break; // não faz nada se o node for um group
if (node!.data!.node!.flow || !node!.data!.node!.template) break; // não faz nada se o node for um group
const nodeData = node.data;
const foundNode = checkedNodes.get(nodeData.type); // verifica se o tipo do node já foi checado
@ -642,3 +643,42 @@ export function getFieldTitle(
? template[templateField].display_name!
: template[templateField].name ?? templateField;
}
export function sortFields(a, b, fieldOrder) {
// Early return for empty fields
if (!a && !b) return 0;
if (!a) return 1;
if (!b) return -1;
// Normalize the case to ensure case-insensitive comparison
const normalizedFieldA = a.toLowerCase();
const normalizedFieldB = b.toLowerCase();
const aIsPriority = priorityFields.has(normalizedFieldA);
const bIsPriority = priorityFields.has(normalizedFieldB);
// Sort by priority
if (aIsPriority && !bIsPriority) return -1;
if (!aIsPriority && bIsPriority) return 1;
// Check if either field is in the fieldOrder array
const indexOfA = fieldOrder.indexOf(normalizedFieldA);
const indexOfB = fieldOrder.indexOf(normalizedFieldB);
// If both fields are in fieldOrder, sort by their order in the array
if (indexOfA !== -1 && indexOfB !== -1) {
return indexOfA - indexOfB;
}
// If only one of the fields is in fieldOrder, that field comes first
if (indexOfA !== -1) {
return -1;
}
if (indexOfB !== -1) {
return 1;
}
// Default case for fields not in priorityFields and not found in fieldOrder
// You might want to sort them alphabetically or in another specific manner
return a.localeCompare(b);
}

View file

@ -88,7 +88,7 @@ module.exports = {
"chat-bot-icon": "var(--chat-bot-icon)",
"chat-user-icon": "var(--chat-user-icon)",
"ice": "var(--ice)",
hover: "var(--hover)",
white: "var(--white)",
border: "hsl(var(--border))",
input: "hsl(var(--input))",
@ -224,5 +224,6 @@ module.exports = {
}),
require("@tailwindcss/typography"),
require("daisyui"),
require('tailwindcss-dotted-background'),
],
};

File diff suppressed because one or more lines are too long

View file

@ -37,9 +37,6 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
@ -53,9 +50,6 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
@ -69,9 +63,6 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
@ -85,9 +76,6 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showdescription"]').click();
expect(
await page.locator('//*[@id="showdescription"]').isChecked()
@ -101,9 +89,6 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.locator('//*[@id="showreturn_direct"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.locator('//*[@id="code-input-0"]');
@ -114,22 +99,6 @@ test("CodeAreaModalComponent", async ({ page }) => {
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="code-area-editcode"]').click();
let value = await page.locator('//*[@id="codeValue"]').inputValue();
if (
value !=
'def python_function(text: str) -> str: """This is a default python function that returns the input text""" return text'
) {
expect(false).toBeTruthy();
}
await page.locator('//*[@id="checkAndSaveBtn"]').click();
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.getByTestId("div-generic-node").click();

View file

@ -43,36 +43,132 @@ test("dropDownComponent", async ({ page }) => {
expect(false).toBeTruthy();
}
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showcache"]').click();
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showcache"]').click();
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showcredentials_profile_name"]').click();
expect(
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcredentials_profile_name"]').click();
expect(
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showendpoint_url"]').click();
expect(
await page.locator('//*[@id="showendpoint_url"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showendpoint_url"]').click();
expect(
await page.locator('//*[@id="showendpoint_url"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showregion_name"]').click();
expect(
await page.locator('//*[@id="showregion_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showregion_name"]').click();
expect(
await page.locator('//*[@id="showregion_name"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showstreaming"]').click();
expect(
await page.locator('//*[@id="showstreaming"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showstreaming"]').click();
expect(
await page.locator('//*[@id="showstreaming"]').isChecked()
).toBeTruthy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
expect(await page.locator('//*[@id="showmodel_id"]').isChecked()).toBeFalsy();
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
expect(
await page.locator('//*[@id="showmodel_id"]').isChecked()
).toBeTruthy();
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showcache"]').click();
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showcache"]').click();
expect(await page.locator('//*[@id="showcache"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showcredentials_profile_name"]').click();
expect(
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showcredentials_profile_name"]').click();
expect(
await page.locator('//*[@id="showcredentials_profile_name"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showendpoint_url"]').click();
expect(
await page.locator('//*[@id="showendpoint_url"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showendpoint_url"]').click();
expect(
await page.locator('//*[@id="showendpoint_url"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showregion_name"]').click();
expect(
await page.locator('//*[@id="showregion_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showregion_name"]').click();
expect(
await page.locator('//*[@id="showregion_name"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showstreaming"]').click();
expect(
await page.locator('//*[@id="showstreaming"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showstreaming"]').click();
expect(
await page.locator('//*[@id="showstreaming"]').isChecked()
).toBeTruthy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
expect(await page.locator('//*[@id="showmodel_id"]').isChecked()).toBeFalsy();
// showcode
await page.locator('//*[@id="showcode"]').click();
expect(await page.locator('//*[@id="showcode"]').isChecked()).toBeFalsy();
// showmodel_id
await page.locator('//*[@id="showmodel_id"]').click();
expect(

View file

@ -19,7 +19,7 @@ test.describe("Flow Page tests", () => {
await page.waitForTimeout(2000);
await page
.locator('//*[@id="custom_componentsCustomComponent"]')
.locator('//*[@id="utilitiesCustomComponent"]')
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();

View file

@ -8,32 +8,32 @@ test("IntComponent", async ({ page }) => {
await page.waitForTimeout(2000);
await page.getByPlaceholder("Search").click();
await page.getByPlaceholder("Search").fill("getrequest");
await page.getByPlaceholder("Search").fill("openai");
await page.waitForTimeout(2000);
await page
.getByTestId("utilitiesGET Request")
.getByTestId("modelsOpenAI Model")
.first()
.dragTo(page.locator('//*[@id="react-flow-id"]'));
await page.mouse.up();
await page.mouse.down();
await page.getByTestId("int-input-timeout").click();
await page.getByTestId("int-input-max_tokens").click();
await page
.getByTestId("int-input-timeout")
.getByTestId("int-input-max_tokens")
.fill("123456789123456789123456789");
let value = await page.getByTestId("int-input-timeout").inputValue();
let value = await page.getByTestId("int-input-max_tokens").inputValue();
if (value != "123456789123456789123456789") {
expect(false).toBeTruthy();
}
await page.getByTestId("int-input-timeout").click();
await page.getByTestId("int-input-timeout").fill("0");
await page.getByTestId("int-input-max_tokens").click();
await page.getByTestId("int-input-max_tokens").fill("0");
value = await page.getByTestId("int-input-timeout").inputValue();
value = await page.getByTestId("int-input-max_tokens").inputValue();
if (value != "0") {
expect(false).toBeTruthy();
@ -42,35 +42,119 @@ test("IntComponent", async ({ page }) => {
await page.getByTestId("more-options-modal").click();
await page.getByTestId("edit-button-modal").click();
value = await page.getByTestId("edit-int-input-timeout").inputValue();
value = await page.getByTestId("edit-int-input-max_tokens").inputValue();
if (value != "0") {
expect(false).toBeTruthy();
}
await page.getByTestId("edit-int-input-timeout").click();
await page.getByTestId("edit-int-input-max_tokens").click();
await page
.getByTestId("edit-int-input-timeout")
.getByTestId("edit-int-input-max_tokens")
.fill("123456789123456789123456789");
await page.locator('//*[@id="showheaders"]').click();
expect(await page.locator('//*[@id="showheaders"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showinput_value"]').click();
expect(
await page.locator('//*[@id="showinput_value"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showtimeout"]').click();
expect(await page.locator('//*[@id="showtimeout"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showurl"]').click();
expect(await page.locator('//*[@id="showurl"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showmodel_name"]').click();
expect(
await page.locator('//*[@id="showmodel_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showheaders"]').click();
expect(await page.locator('//*[@id="showheaders"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showopenai_api_base"]').click();
expect(
await page.locator('//*[@id="showopenai_api_base"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showurl"]').click();
expect(await page.locator('//*[@id="showurl"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showopenai_api_key"]').click();
expect(
await page.locator('//*[@id="showopenai_api_key"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showstream"]').click();
expect(await page.locator('//*[@id="showstream"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showtemperature"]').click();
expect(
await page.locator('//*[@id="showtemperature"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showinput_value"]').click();
expect(
await page.locator('//*[@id="showinput_value"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmodel_name"]').click();
expect(
await page.locator('//*[@id="showmodel_name"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showopenai_api_base"]').click();
expect(
await page.locator('//*[@id="showopenai_api_base"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showopenai_api_key"]').click();
expect(
await page.locator('//*[@id="showopenai_api_key"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showstream"]').click();
expect(await page.locator('//*[@id="showstream"]').isChecked()).toBeTruthy();
await page.locator('//*[@id="showtemperature"]').click();
expect(
await page.locator('//*[@id="showtemperature"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showinput_value"]').click();
expect(
await page.locator('//*[@id="showinput_value"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showmodel_kwargs"]').click();
expect(
await page.locator('//*[@id="showmodel_kwargs"]').isChecked()
).toBeTruthy();
await page.locator('//*[@id="showmodel_name"]').click();
expect(
await page.locator('//*[@id="showmodel_name"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showopenai_api_base"]').click();
expect(
await page.locator('//*[@id="showopenai_api_base"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showopenai_api_key"]').click();
expect(
await page.locator('//*[@id="showopenai_api_key"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="showstream"]').click();
expect(await page.locator('//*[@id="showstream"]').isChecked()).toBeFalsy();
await page.locator('//*[@id="showtemperature"]').click();
expect(
await page.locator('//*[@id="showtemperature"]').isChecked()
).toBeFalsy();
await page.locator('//*[@id="saveChangesBtn"]').click();
const plusButtonLocator = page.getByTestId("int-input-timeout");
const plusButtonLocator = page.getByTestId("int-input-max_tokens");
const elementCount = await plusButtonLocator.count();
if (elementCount === 0) {
expect(true).toBeTruthy();
@ -84,7 +168,7 @@ test("IntComponent", async ({ page }) => {
).toBeTruthy();
const valueEditNode = await page
.getByTestId("edit-int-input-timeout")
.getByTestId("edit-int-input-max_tokens")
.inputValue();
if (valueEditNode != "123456789123456789123456789") {
@ -92,19 +176,19 @@ test("IntComponent", async ({ page }) => {
}
await page.locator('//*[@id="saveChangesBtn"]').click();
await page.getByTestId("int-input-timeout").click();
await page.getByTestId("int-input-timeout").fill("3");
await page.getByTestId("int-input-max_tokens").click();
await page.getByTestId("int-input-max_tokens").fill("3");
let value = await page.getByTestId("int-input-timeout").inputValue();
let value = await page.getByTestId("int-input-max_tokens").inputValue();
if (value != "3") {
expect(false).toBeTruthy();
}
await page.getByTestId("int-input-timeout").click();
await page.getByTestId("int-input-timeout").fill("-3");
await page.getByTestId("int-input-max_tokens").click();
await page.getByTestId("int-input-max_tokens").fill("-3");
value = await page.getByTestId("int-input-timeout").inputValue();
value = await page.getByTestId("int-input-max_tokens").inputValue();
if (value != "0") {
expect(false).toBeTruthy();