Merge branch 'feature/output_dropdown' into two_edges
This commit is contained in:
commit
c6b315bf60
21 changed files with 844 additions and 87 deletions
|
|
@ -201,7 +201,9 @@ class Vertex:
|
|||
|
||||
self.description = self.data["node"].get("description", "")
|
||||
self.frozen = self.data["node"].get("frozen", False)
|
||||
self.selected_output_type = self.data["node"].get("selected_output_type")
|
||||
self.selected_output_type = (
|
||||
str(self.data.get("selected_output_type")).strip() if self.data.get("selected_output_type") else None
|
||||
)
|
||||
self.is_input = self.data["node"].get("is_input") or self.is_input
|
||||
self.is_output = self.data["node"].get("is_output") or self.is_output
|
||||
template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -253,6 +253,9 @@ def build_class_constructor(compiled_class, exec_globals, class_name):
|
|||
globals()[module_name] = module
|
||||
|
||||
instance = exec_globals[class_name](*args, **kwargs)
|
||||
# Get selected type from global scope
|
||||
if instance.selected_output_type in exec_globals:
|
||||
instance.selected_output_type = exec_globals[instance.selected_output_type]
|
||||
return instance
|
||||
|
||||
build_custom_class.__globals__.update(exec_globals)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import { useRef } from "react";
|
||||
import { TOOLTIP_EMPTY } from "../../../../constants/constants";
|
||||
import { groupByFamily } from "../../../../utils/utils";
|
||||
import TooltipRenderComponent from "../tooltipRenderComponent";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
import { NodeType } from "../../../../types/flow";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
|
||||
export default function HandleTooltips({
|
||||
left,
|
||||
tooltipTitle,
|
||||
}: {
|
||||
left: boolean;
|
||||
nodes: NodeType[];
|
||||
tooltipTitle: string;
|
||||
}) {
|
||||
const myData = useTypesStore((state) => state.data);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
|
||||
let groupedObj: any = groupByFamily(myData, tooltipTitle!, left, nodes!);
|
||||
|
||||
if (groupedObj && groupedObj.length > 0) {
|
||||
//@ts-ignore
|
||||
return groupedObj.map((item, index) => {
|
||||
return <TooltipRenderComponent index={index} item={item} left={left} />;
|
||||
});
|
||||
} else {
|
||||
//@ts-ignore
|
||||
return <span data-testid={`empty-tooltip-filter`}>{TOOLTIP_EMPTY}</span>;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import ForwardedIconComponent from "../../../../components/genericIconComponent";
|
||||
import { outputComponentType } from "../../../../types/components";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import { NodeDataType } from "../../../../types/flow";
|
||||
import { cloneDeep } from "lodash";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../../../../components/ui/dropdown-menu";
|
||||
import { useUpdateNodeInternals } from "reactflow";
|
||||
|
||||
export default function OutputComponent({
|
||||
selected,
|
||||
types,
|
||||
frozen = false,
|
||||
nodeId,
|
||||
idx,
|
||||
}: outputComponentType) {
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
if (types.length < 2) {
|
||||
return <span className={cn(frozen ? " text-ice" : "")}>{selected}</span>;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<span
|
||||
className={cn(frozen ? " text-ice" : "", "flex items-center gap-1")}
|
||||
>
|
||||
{selected}
|
||||
<ForwardedIconComponent name="ChevronDown" className="h-4 w-4" />
|
||||
</span>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{types.map((type) => (
|
||||
<DropdownMenuItem
|
||||
onSelect={() => {
|
||||
// TODO: UDPDATE SET NODE TO NEW NODE FORM
|
||||
setNode(nodeId, (node) => {
|
||||
const newNode = cloneDeep(node);
|
||||
(newNode.data as NodeDataType).node!.outputs![idx].selected =
|
||||
type;
|
||||
return newNode;
|
||||
});
|
||||
updateNodeInternals(nodeId);
|
||||
}}
|
||||
>
|
||||
{type}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,591 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import { ReactNode, useEffect, useRef, useState } from "react";
|
||||
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
|
||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||
import DictComponent from "../../../../components/dictComponent";
|
||||
import Dropdown from "../../../../components/dropdownComponent";
|
||||
import FloatComponent from "../../../../components/floatComponent";
|
||||
import ForwardedIconComponent, {
|
||||
default as IconComponent,
|
||||
} from "../../../../components/genericIconComponent";
|
||||
import InputFileComponent from "../../../../components/inputFileComponent";
|
||||
import InputGlobalComponent from "../../../../components/inputGlobalComponent";
|
||||
import InputListComponent from "../../../../components/inputListComponent";
|
||||
import IntComponent from "../../../../components/intComponent";
|
||||
import KeypairListComponent from "../../../../components/keypairListComponent";
|
||||
import PromptAreaComponent from "../../../../components/promptComponent";
|
||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||
import TextAreaComponent from "../../../../components/textAreaComponent";
|
||||
import ToggleShadComponent from "../../../../components/toggleShadComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../../../../components/ui/dropdown-menu";
|
||||
import { RefreshButton } from "../../../../components/ui/refreshButton";
|
||||
import {
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
TOOLTIP_EMPTY,
|
||||
} from "../../../../constants/constants";
|
||||
import { Case } from "../../../../shared/components/caseComponent";
|
||||
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 { ParameterComponentType } from "../../../../types/components";
|
||||
import {
|
||||
debouncedHandleUpdateValues,
|
||||
handleUpdateValues,
|
||||
} from "../../../../utils/parameterUtils";
|
||||
import {
|
||||
convertObjToArray,
|
||||
convertValuesToNumbers,
|
||||
hasDuplicateKeys,
|
||||
isValidConnection,
|
||||
scapedJSONStringfy,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { nodeColors } from "../../../../utils/styleUtils";
|
||||
import { classNames, cn, groupByFamily } from "../../../../utils/utils";
|
||||
import useFetchDataOnMount from "../../../hooks/use-fetch-data-on-mount";
|
||||
import useHandleOnNewValue from "../../../hooks/use-handle-new-value";
|
||||
import useHandleNodeClass from "../../../hooks/use-handle-node-class";
|
||||
import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons";
|
||||
import TooltipRenderComponent from "../tooltipRenderComponent";
|
||||
import HandleTooltips from "../HandleTooltipComponent";
|
||||
import OutputComponent from "../OutputComponent";
|
||||
|
||||
export default function ParameterComponent({
|
||||
left,
|
||||
id,
|
||||
data,
|
||||
tooltipTitle,
|
||||
title,
|
||||
color,
|
||||
type,
|
||||
name = "",
|
||||
required = false,
|
||||
optionalHandle = null,
|
||||
info = "",
|
||||
proxy,
|
||||
showNode,
|
||||
index,
|
||||
}: ParameterComponentType): JSX.Element {
|
||||
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
const myData = useTypesStore((state) => state.data);
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const [errorDuplicateKey, setErrorDuplicateKey] = useState(false);
|
||||
const setFilterEdge = useFlowStore((state) => state.setFilterEdge);
|
||||
|
||||
const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue(
|
||||
data,
|
||||
name,
|
||||
takeSnapshot,
|
||||
handleUpdateValues,
|
||||
debouncedHandleUpdateValues,
|
||||
setNode,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
);
|
||||
|
||||
const { handleNodeClass: handleNodeClassHook } = useHandleNodeClass(
|
||||
data,
|
||||
name,
|
||||
takeSnapshot,
|
||||
setNode,
|
||||
updateNodeInternals,
|
||||
);
|
||||
|
||||
const { handleRefreshButtonPress: handleRefreshButtonPressHook } =
|
||||
useHandleRefreshButtonPress(setIsLoading, setNode);
|
||||
|
||||
let disabled =
|
||||
edges.some(
|
||||
(edge) =>
|
||||
edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id),
|
||||
) ?? false;
|
||||
|
||||
const handleRefreshButtonPress = async (name, data) => {
|
||||
handleRefreshButtonPressHook(name, data);
|
||||
};
|
||||
|
||||
useFetchDataOnMount(data, name, handleUpdateValues, setNode, setIsLoading);
|
||||
|
||||
const handleOnNewValue = async (
|
||||
newValue: string | string[] | boolean | Object[],
|
||||
skipSnapshot: boolean | undefined = false,
|
||||
): Promise<void> => {
|
||||
handleOnNewValueHook(newValue, skipSnapshot);
|
||||
};
|
||||
|
||||
const handleNodeClass = (newNodeClass: APIClassType, code?: string): void => {
|
||||
handleNodeClassHook(newNodeClass, code);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// @ts-ignore
|
||||
infoHtml.current = (
|
||||
<div className="h-full w-full break-words">
|
||||
{info.split("\n").map((line, index) => (
|
||||
<p key={index} className="block">
|
||||
{line}
|
||||
</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}, [info]);
|
||||
|
||||
function renderTitle() {
|
||||
return !left ? (
|
||||
<OutputComponent
|
||||
idx={index}
|
||||
types={type?.split("|") ?? []}
|
||||
selected={title}
|
||||
nodeId={data.id}
|
||||
frozen={data.node?.frozen}
|
||||
/>
|
||||
) : (
|
||||
<span>{title}</span>
|
||||
);
|
||||
}
|
||||
|
||||
// If optionalHandle is an empty list, then it is not an optional handle
|
||||
if (optionalHandle && optionalHandle.length === 0) {
|
||||
optionalHandle = null;
|
||||
}
|
||||
|
||||
return !showNode ? (
|
||||
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
|
||||
<div className="flex">
|
||||
<ShadTooltip
|
||||
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
|
||||
delayDuration={1000}
|
||||
content={
|
||||
<HandleTooltips
|
||||
left={left}
|
||||
nodes={nodes}
|
||||
tooltipTitle={tooltipTitle!}
|
||||
/>
|
||||
}
|
||||
side={left ? "left" : "right"}
|
||||
>
|
||||
<Handle
|
||||
data-test-id={`handle-${title.toLowerCase()}-${
|
||||
left ? "target" : "source"
|
||||
}`}
|
||||
type={left ? "target" : "source"}
|
||||
position={left ? Position.Left : Position.Right}
|
||||
key={
|
||||
proxy
|
||||
? scapedJSONStringfy({ ...id, proxy })
|
||||
: scapedJSONStringfy(id)
|
||||
}
|
||||
id={
|
||||
proxy
|
||||
? scapedJSONStringfy({ ...id, proxy })
|
||||
: scapedJSONStringfy(id)
|
||||
}
|
||||
isValidConnection={(connection) =>
|
||||
isValidConnection(connection, nodes, edges)
|
||||
}
|
||||
className={classNames(
|
||||
left ? "my-12 -ml-0.5 " : " my-12 -mr-0.5 ",
|
||||
"h-3 w-3 rounded-full border-2 bg-background",
|
||||
!showNode ? "mt-0" : "",
|
||||
)}
|
||||
style={{
|
||||
borderColor: color ?? nodeColors.unknown,
|
||||
}}
|
||||
onClick={() => {
|
||||
setFilterEdge(
|
||||
groupByFamily(myData, tooltipTitle!, left, nodes!),
|
||||
);
|
||||
}}
|
||||
></Handle>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</Button>
|
||||
)
|
||||
) : (
|
||||
<div
|
||||
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)
|
||||
? " hidden "
|
||||
: "")
|
||||
}
|
||||
>
|
||||
<>
|
||||
<div
|
||||
className={
|
||||
"flex w-full items-center truncate text-sm" +
|
||||
(left ? "" : " justify-end")
|
||||
}
|
||||
>
|
||||
<Case condition={left && data.node?.frozen}>
|
||||
<div className="pr-1">
|
||||
<IconComponent className="h-5 w-5 text-ice" name={"Snowflake"} />
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
{proxy ? (
|
||||
<ShadTooltip content={<span>{proxy.id}</span>}>
|
||||
{renderTitle()}
|
||||
</ShadTooltip>
|
||||
) : (
|
||||
renderTitle()
|
||||
)}
|
||||
<span className={(required ? "ml-2 " : "") + "text-status-red"}>
|
||||
{required ? "*" : ""}
|
||||
</span>
|
||||
<div className="">
|
||||
{info !== "" && (
|
||||
<ShadTooltip content={infoHtml.current}>
|
||||
{/* put div to avoid bug that does not display tooltip */}
|
||||
<div>
|
||||
<IconComponent
|
||||
name="Info"
|
||||
className="relative bottom-px ml-1.5 h-3 w-4"
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
|
||||
<div className="flex">
|
||||
<ShadTooltip
|
||||
styleClasses={"tooltip-fixed-width custom-scroll nowheel"}
|
||||
delayDuration={1000}
|
||||
content={
|
||||
<HandleTooltips
|
||||
left={left}
|
||||
nodes={nodes}
|
||||
tooltipTitle={tooltipTitle!}
|
||||
/>
|
||||
}
|
||||
side={left ? "left" : "right"}
|
||||
>
|
||||
<Handle
|
||||
data-test-id={`handle-${title.toLowerCase()}-${
|
||||
left ? "left" : "right"
|
||||
}`}
|
||||
type={left ? "target" : "source"}
|
||||
position={left ? Position.Left : Position.Right}
|
||||
key={scapedJSONStringfy(proxy ? { ...id, proxy } : id)}
|
||||
id={scapedJSONStringfy(proxy ? { ...id, proxy } : id)}
|
||||
isValidConnection={(connection) =>
|
||||
isValidConnection(connection, nodes, edges)
|
||||
}
|
||||
className={classNames(
|
||||
left ? "-ml-0.5" : "-mr-0.5",
|
||||
"h-3 w-3 rounded-full border-2 bg-background",
|
||||
)}
|
||||
style={{ borderColor: color ?? nodeColors.unknown }}
|
||||
onClick={() => {
|
||||
setFilterEdge(
|
||||
groupByFamily(myData, tooltipTitle!, left, nodes!),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Case
|
||||
condition={
|
||||
left === true &&
|
||||
type === "str" &&
|
||||
!data.node?.template[name]?.options
|
||||
}
|
||||
>
|
||||
<div className="w-full">
|
||||
<Case condition={data.node?.template[name]?.list}>
|
||||
<div
|
||||
className={
|
||||
// Commenting this out until we have a better
|
||||
// way to display
|
||||
// (data.node?.template[name]?.refresh ? "w-5/6 " : "") +
|
||||
"flex-grow"
|
||||
}
|
||||
>
|
||||
<InputListComponent
|
||||
componentName={name}
|
||||
disabled={disabled}
|
||||
value={
|
||||
!data.node!.template[name]?.value ||
|
||||
data.node!.template[name]?.value === ""
|
||||
? [""]
|
||||
: data.node!.template[name]?.value
|
||||
}
|
||||
onChange={handleOnNewValue}
|
||||
/>
|
||||
</div>
|
||||
</Case>
|
||||
<Case condition={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>
|
||||
</Case>
|
||||
<Case
|
||||
condition={
|
||||
!data.node?.template[name]?.multiline &&
|
||||
!data.node?.template[name]?.list
|
||||
}
|
||||
>
|
||||
<div className="mt-2 flex w-full items-center">
|
||||
<div
|
||||
className={
|
||||
"flex-grow " +
|
||||
(data.node?.template[name]?.refresh_button ? "w-5/6" : "")
|
||||
}
|
||||
>
|
||||
<InputGlobalComponent
|
||||
disabled={disabled}
|
||||
onChange={handleOnNewValue}
|
||||
setDb={(value) => {
|
||||
setNode(data.id, (oldNode) => {
|
||||
let newNode = cloneDeep(oldNode);
|
||||
newNode.data = {
|
||||
...newNode.data,
|
||||
};
|
||||
newNode.data.node.template[name].load_from_db = value;
|
||||
return newNode;
|
||||
});
|
||||
}}
|
||||
name={name}
|
||||
data={data}
|
||||
/>
|
||||
</div>
|
||||
{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={handleRefreshButtonPress}
|
||||
id={"refresh-button-" + name}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Case>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "bool"}>
|
||||
<div className="mt-2 w-full">
|
||||
<ToggleShadComponent
|
||||
id={"toggle-" + name}
|
||||
disabled={disabled}
|
||||
enabled={data.node?.template[name]?.value ?? false}
|
||||
setEnabled={handleOnNewValue}
|
||||
size="large"
|
||||
editNode={false}
|
||||
/>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "float"}>
|
||||
<div className="mt-2 w-full">
|
||||
<FloatComponent
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
rangeSpec={data.node?.template[name]?.rangeSpec}
|
||||
onChange={handleOnNewValue}
|
||||
/>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case
|
||||
condition={
|
||||
left === true &&
|
||||
type === "str" &&
|
||||
(data.node?.template[name]?.options ||
|
||||
data.node?.template[name]?.real_time_refresh)
|
||||
}
|
||||
>
|
||||
<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}
|
||||
value={data.node!.template[name]?.value}
|
||||
id={"dropdown-" + name}
|
||||
/>
|
||||
</div>
|
||||
{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}
|
||||
className="extra-side-bar-buttons ml-2 mt-1"
|
||||
handleUpdateValues={handleRefreshButtonPress}
|
||||
id={"refresh-button-" + name}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "code"}>
|
||||
<div className="mt-2 w-full">
|
||||
<CodeAreaComponent
|
||||
readonly={
|
||||
data.node?.flow && data.node.template[name]?.dynamic
|
||||
? true
|
||||
: false
|
||||
}
|
||||
dynamic={data.node?.template[name]?.dynamic ?? false}
|
||||
setNodeClass={handleNodeClass}
|
||||
nodeClass={data.node}
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
id={"code-input-" + name}
|
||||
/>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "file"}>
|
||||
<div className="mt-2 w-full">
|
||||
<InputFileComponent
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
fileTypes={data.node?.template[name]?.fileTypes}
|
||||
onFileChange={(filePath: string) => {
|
||||
data.node!.template[name].file_path = filePath;
|
||||
}}
|
||||
></InputFileComponent>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "int"}>
|
||||
<div className="mt-2 w-full">
|
||||
<IntComponent
|
||||
rangeSpec={data.node?.template[name]?.rangeSpec}
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
id={"int-input-" + name}
|
||||
/>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "prompt"}>
|
||||
<div className="mt-2 w-full">
|
||||
<PromptAreaComponent
|
||||
readonly={data.node?.flow ? true : false}
|
||||
field_name={name}
|
||||
setNodeClass={handleNodeClass}
|
||||
nodeClass={data.node}
|
||||
disabled={disabled}
|
||||
value={data.node?.template[name]?.value ?? ""}
|
||||
onChange={handleOnNewValue}
|
||||
id={"prompt-input-" + name}
|
||||
data-testid={"prompt-input-" + name}
|
||||
/>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "NestedDict"}>
|
||||
<div className="mt-2 w-full">
|
||||
<DictComponent
|
||||
disabled={disabled}
|
||||
editNode={false}
|
||||
value={
|
||||
!data.node!.template[name]?.value ||
|
||||
data.node!.template[name]?.value?.toString() === "{}"
|
||||
? {
|
||||
// yourkey: "value",
|
||||
}
|
||||
: data.node!.template[name]?.value
|
||||
}
|
||||
onChange={handleOnNewValue}
|
||||
id="div-dict-input"
|
||||
/>
|
||||
</div>
|
||||
</Case>
|
||||
|
||||
<Case condition={left === true && type === "dict"}>
|
||||
<div className="mt-2 w-full">
|
||||
<KeypairListComponent
|
||||
disabled={disabled}
|
||||
editNode={false}
|
||||
value={
|
||||
data.node!.template[name]?.value?.length === 0 ||
|
||||
!data.node!.template[name]?.value
|
||||
? [{ "": "" }]
|
||||
: convertObjToArray(data.node!.template[name]?.value, type!)
|
||||
}
|
||||
duplicateKey={errorDuplicateKey}
|
||||
onChange={(newValue) => {
|
||||
const valueToNumbers = convertValuesToNumbers(newValue);
|
||||
setErrorDuplicateKey(hasDuplicateKeys(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>
|
||||
</Case>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -553,7 +553,7 @@ export default function GenericNode({
|
|||
data.node!.template[templateField].show &&
|
||||
!data.node!.template[templateField].advanced && (
|
||||
<ParameterComponent
|
||||
index={idx.toString()}
|
||||
index={idx}
|
||||
key={scapedJSONStringfy({
|
||||
inputTypes:
|
||||
data.node!.template[templateField].input_types,
|
||||
|
|
@ -626,6 +626,7 @@ export default function GenericNode({
|
|||
)
|
||||
)}
|
||||
<ParameterComponent
|
||||
index={0}
|
||||
key={scapedJSONStringfy({
|
||||
baseClasses: data.node!.base_classes,
|
||||
id: data.id,
|
||||
|
|
@ -636,7 +637,7 @@ export default function GenericNode({
|
|||
title={
|
||||
data.node?.output_types &&
|
||||
data.node.output_types.length > 0
|
||||
? data.node.output_types.join(" | ")
|
||||
? data.node.output_types.join("|")
|
||||
: data.type
|
||||
}
|
||||
tooltipTitle={data.node?.base_classes.join("\n")}
|
||||
|
|
@ -644,6 +645,7 @@ export default function GenericNode({
|
|||
baseClasses: data.node!.base_classes,
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
idx: 0,
|
||||
}}
|
||||
type={data.node?.base_classes.join("|")}
|
||||
left={false}
|
||||
|
|
@ -803,7 +805,7 @@ export default function GenericNode({
|
|||
{data.node!.template[templateField].show &&
|
||||
!data.node!.template[templateField].advanced ? (
|
||||
<ParameterComponent
|
||||
index={idx.toString()}
|
||||
index={idx}
|
||||
key={scapedJSONStringfy({
|
||||
inputTypes:
|
||||
data.node!.template[templateField].input_types,
|
||||
|
|
@ -880,29 +882,36 @@ export default function GenericNode({
|
|||
>
|
||||
{" "}
|
||||
</div>
|
||||
<div>
|
||||
{data.node!.base_classes.length > 0 &&
|
||||
// if conditional_paths in data.node.conditional_paths
|
||||
// then buildParameterComponent for each conditionalPath
|
||||
// else buildParameterComponent for each data.node.base_classes
|
||||
// first we check if there are any conditional paths
|
||||
data.node!.conditional_paths &&
|
||||
data.node!.conditional_paths.length > 0
|
||||
? data.node!.conditional_paths.map((conditionalPath) =>
|
||||
buildParameterComponent({
|
||||
data,
|
||||
conditionalPath,
|
||||
showNode,
|
||||
left: false,
|
||||
})
|
||||
)
|
||||
: buildParameterComponent({
|
||||
data,
|
||||
conditionalPath: null,
|
||||
showNode,
|
||||
left: false,
|
||||
{data.node!.outputs &&
|
||||
data.node!.outputs.length > 0 &&
|
||||
data.node!.outputs.map((output, idx) => (
|
||||
<ParameterComponent
|
||||
index={idx}
|
||||
key={scapedJSONStringfy({
|
||||
baseClasses: [output.selected ?? output.types[0]],
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
})}
|
||||
</div>
|
||||
data={data}
|
||||
color={
|
||||
nodeColors[output.selected ?? output.types[0]] ??
|
||||
nodeColors[types[output.selected ?? output.types[0]]] ??
|
||||
nodeColors[types[data.type]] ??
|
||||
nodeColors.unknown
|
||||
}
|
||||
title={output.selected ?? output.types[0]}
|
||||
tooltipTitle={output.selected ?? output.types[0]}
|
||||
id={{
|
||||
baseClasses: [output.selected ?? output.types[0]],
|
||||
id: data.id,
|
||||
dataType: data.type,
|
||||
idx: idx,
|
||||
}}
|
||||
type={output.types.join("|")}
|
||||
left={false}
|
||||
showNode={showNode}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ const useFetchDataOnMount = (
|
|||
name,
|
||||
handleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
setIsLoading,
|
||||
) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
|
@ -44,7 +43,6 @@ const useFetchDataOnMount = (
|
|||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
renderTooltips();
|
||||
}
|
||||
}
|
||||
fetchData();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const useHandleOnNewValue = (
|
|||
handleUpdateValues,
|
||||
debouncedHandleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
) => {
|
||||
|
|
@ -65,8 +64,6 @@ const useHandleOnNewValue = (
|
|||
|
||||
return newNode;
|
||||
});
|
||||
|
||||
renderTooltips();
|
||||
};
|
||||
|
||||
return { handleOnNewValue };
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ const useHandleNodeClass = (
|
|||
takeSnapshot,
|
||||
setNode,
|
||||
updateNodeInternals,
|
||||
renderTooltips,
|
||||
) => {
|
||||
const handleNodeClass = (newNodeClass, code) => {
|
||||
if (!data.node) return;
|
||||
|
|
@ -30,8 +29,6 @@ const useHandleNodeClass = (
|
|||
});
|
||||
|
||||
updateNodeInternals(data.id);
|
||||
|
||||
renderTooltips();
|
||||
};
|
||||
|
||||
return { handleNodeClass };
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import useAlertStore from "../../stores/alertStore";
|
|||
import { ResponseErrorDetailAPI } from "../../types/api";
|
||||
import { handleUpdateValues } from "../../utils/parameterUtils";
|
||||
|
||||
const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => {
|
||||
const useHandleRefreshButtonPress = (setIsLoading, setNode) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
const handleRefreshButtonPress = async (name, data) => {
|
||||
|
|
@ -30,7 +30,6 @@ const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => {
|
|||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
renderTooltips();
|
||||
};
|
||||
|
||||
return { handleRefreshButtonPress };
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ export async function addFolder(data: AddFolderType): Promise<FolderType> {
|
|||
|
||||
export async function updateFolder(
|
||||
body: FolderType,
|
||||
folderId: string
|
||||
folderId: string,
|
||||
): Promise<FolderType> {
|
||||
try {
|
||||
const response = await api.patch(
|
||||
`${BASE_URL_API}folders/${folderId}`,
|
||||
body
|
||||
body,
|
||||
);
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
|
|
@ -68,7 +68,7 @@ export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
|||
}> {
|
||||
try {
|
||||
const response = await api.get(
|
||||
`${BASE_URL_API}folders/download/${folderId}`
|
||||
`${BASE_URL_API}folders/download/${folderId}`,
|
||||
);
|
||||
if (response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response?.status}`);
|
||||
|
|
@ -82,7 +82,7 @@ export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
|||
}
|
||||
|
||||
export async function uploadFlowsFromFolders(
|
||||
flows: FormData
|
||||
flows: FormData,
|
||||
): Promise<FlowType[]> {
|
||||
try {
|
||||
const response = await api.post(`${BASE_URL_API}folders/upload/`, flows);
|
||||
|
|
@ -99,11 +99,11 @@ export async function uploadFlowsFromFolders(
|
|||
|
||||
export async function moveFlowToFolder(
|
||||
flowId: string,
|
||||
folderId: string
|
||||
folderId: string,
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(
|
||||
`${BASE_URL_API}folders/move_to_folder/${flowId}/${folderId}`
|
||||
`${BASE_URL_API}folders/move_to_folder/${flowId}/${folderId}`,
|
||||
);
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -199,11 +199,10 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
position?: XYPosition,
|
||||
fromDragAndDrop?: boolean,
|
||||
): Promise<string | undefined> => {
|
||||
let flowData = flow
|
||||
? processDataFromFlow(flow)
|
||||
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
if (newProject) {
|
||||
let flowData = flow
|
||||
? processDataFromFlow(flow)
|
||||
: { nodes: [], edges: [], viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
const folder_id = useFolderStore.getState().folderUrl;
|
||||
const my_collection_id = useFolderStore.getState().myCollectionId;
|
||||
|
|
@ -272,7 +271,7 @@ const useFlowsManagerStore = create<FlowsManagerStoreType>((set, get) => ({
|
|||
useFlowStore
|
||||
.getState()
|
||||
.paste(
|
||||
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
|
||||
{ nodes: flowData?.nodes, edges: flowData?.edges },
|
||||
position ?? { x: 10, y: 10 },
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export type APIClassType = {
|
|||
documentation: string;
|
||||
error?: string;
|
||||
official?: boolean;
|
||||
outputs?: Array<{ types: Array<string>; selected?: string }>;
|
||||
frozen?: boolean;
|
||||
flow?: FlowType;
|
||||
field_order?: string[];
|
||||
|
|
@ -39,7 +40,8 @@ export type APIClassType = {
|
|||
| FlowType
|
||||
| CustomFieldsType
|
||||
| boolean
|
||||
| undefined;
|
||||
| undefined
|
||||
| Array<{ types: Array<string>; selected?: string }>;
|
||||
};
|
||||
|
||||
export type TemplateVariableType = {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ export type ParameterComponentType = {
|
|||
info?: string;
|
||||
proxy?: { field: string; id: string };
|
||||
showNode?: boolean;
|
||||
index?: string;
|
||||
index: number;
|
||||
onCloseModal?: (close: boolean) => void;
|
||||
};
|
||||
export type InputListComponentType = {
|
||||
|
|
@ -112,6 +112,14 @@ export type TextAreaComponentType = {
|
|||
readonly?: boolean;
|
||||
};
|
||||
|
||||
export type outputComponentType = {
|
||||
types: string[];
|
||||
selected: string;
|
||||
nodeId: string;
|
||||
frozen?: boolean;
|
||||
idx: number;
|
||||
};
|
||||
|
||||
export type PromptAreaComponentType = {
|
||||
field_name?: string;
|
||||
nodeClass?: APIClassType;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export type NodeDataType = {
|
|||
node?: APIClassType;
|
||||
id: string;
|
||||
output_types?: string[];
|
||||
selected_output_type?: string;
|
||||
buildStatus?: BuildStatus;
|
||||
};
|
||||
// FlowStyleType is the type of the style object that is used to style the
|
||||
|
|
@ -58,6 +59,7 @@ export type sourceHandleType = {
|
|||
id: string;
|
||||
baseClasses: string[];
|
||||
conditionalPath?: string | null;
|
||||
idx: number;
|
||||
};
|
||||
//left side
|
||||
export type targetHandleType = {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ type BuildVerticesParams = {
|
|||
onBuildUpdate?: (
|
||||
data: VertexBuildTypeAPI,
|
||||
status: BuildStatus,
|
||||
buildId: string
|
||||
buildId: string,
|
||||
) => void; // Replace any with the actual type if it's not any
|
||||
onBuildComplete?: (allNodesValid: boolean) => void;
|
||||
onBuildError?: (title, list, idList: VertexLayerElementType[]) => void;
|
||||
|
|
@ -53,7 +53,7 @@ export async function updateVerticesOrder(
|
|||
startNodeId?: string | null,
|
||||
stopNodeId?: string | null,
|
||||
nodes?: Node[],
|
||||
edges?: Edge[]
|
||||
edges?: Edge[],
|
||||
): Promise<{
|
||||
verticesLayers: VertexLayerElementType[][];
|
||||
verticesIds: string[];
|
||||
|
|
@ -69,7 +69,7 @@ export async function updateVerticesOrder(
|
|||
startNodeId,
|
||||
stopNodeId,
|
||||
nodes,
|
||||
edges
|
||||
edges,
|
||||
);
|
||||
} catch (error: any) {
|
||||
setErrorData({
|
||||
|
|
@ -125,7 +125,7 @@ export async function buildVertices({
|
|||
startNodeId,
|
||||
stopNodeId,
|
||||
nodes,
|
||||
edges
|
||||
edges,
|
||||
);
|
||||
if (onValidateNodes) {
|
||||
try {
|
||||
|
|
@ -187,14 +187,14 @@ export async function buildVertices({
|
|||
onBuildUpdate(
|
||||
getInactiveVertexData(element.id),
|
||||
BuildStatus.INACTIVE,
|
||||
runId
|
||||
runId,
|
||||
);
|
||||
}
|
||||
if (element.reference) {
|
||||
onBuildUpdate(
|
||||
getInactiveVertexData(element.reference),
|
||||
BuildStatus.INACTIVE,
|
||||
runId
|
||||
runId,
|
||||
);
|
||||
}
|
||||
buildResults.push(false);
|
||||
|
|
@ -219,7 +219,7 @@ export async function buildVertices({
|
|||
if (stop) {
|
||||
return;
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
// Once the current layer is built, move to the next layer
|
||||
currentLayerIndex += 1;
|
||||
|
|
@ -262,7 +262,7 @@ async function buildVertex({
|
|||
onBuildError!(
|
||||
"Error Building Component",
|
||||
[buildData.params],
|
||||
verticesIds.map((id) => ({ id }))
|
||||
verticesIds.map((id) => ({ id })),
|
||||
);
|
||||
stopBuild();
|
||||
}
|
||||
|
|
@ -273,7 +273,7 @@ async function buildVertex({
|
|||
onBuildError!(
|
||||
"Error Building Component",
|
||||
[(error as AxiosError<any>).response?.data?.detail ?? "Unknown Error"],
|
||||
verticesIds.map((id) => ({ id }))
|
||||
verticesIds.map((id) => ({ id })),
|
||||
);
|
||||
stopBuild();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ import {
|
|||
specialCharsRegex,
|
||||
} from "../constants/constants";
|
||||
import { downloadFlowsFromDatabase } from "../controllers/API";
|
||||
import getFieldTitle from "../customNodes/utils/get-field-title";
|
||||
import { DESCRIPTIONS } from "../flow_constants";
|
||||
import {
|
||||
APIClassType,
|
||||
APIKindType,
|
||||
|
|
@ -37,8 +39,6 @@ import {
|
|||
updateEdgesHandleIdsType,
|
||||
} from "../types/utils/reactflowUtils";
|
||||
import { createRandomKey, toTitleCase } from "./utils";
|
||||
import { DESCRIPTIONS } from "../flow_constants";
|
||||
import getFieldTitle from "../customNodes/utils/get-field-title";
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
||||
export function checkChatInput(nodes: Node[]) {
|
||||
|
|
@ -46,6 +46,7 @@ export function checkChatInput(nodes: Node[]) {
|
|||
}
|
||||
|
||||
export function cleanEdges(nodes: Node[], edges: Edge[]) {
|
||||
console.log("cleanEdges");
|
||||
let newEdges = cloneDeep(edges);
|
||||
edges.forEach((edge) => {
|
||||
// check if the source and target node still exists
|
||||
|
|
@ -75,10 +76,12 @@ export function cleanEdges(nodes: Node[], edges: Edge[]) {
|
|||
}
|
||||
}
|
||||
if (sourceHandle) {
|
||||
const index = scapeJSONParse(sourceHandle).idx ?? 0;
|
||||
const id: sourceHandleType = {
|
||||
id: sourceNode.data.id,
|
||||
baseClasses: sourceNode.data.node!.base_classes,
|
||||
baseClasses: [sourceNode.data.node.outputs[index].selected],
|
||||
dataType: sourceNode.data.type,
|
||||
idx: index,
|
||||
};
|
||||
if (scapedJSONStringfy(id) !== sourceHandle) {
|
||||
newEdges = newEdges.filter((e) => e.id !== edge.id);
|
||||
|
|
@ -191,8 +194,8 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
|
|||
let data = flow?.data ? flow.data : null;
|
||||
if (data) {
|
||||
processFlowEdges(flow);
|
||||
//prevent node update for now
|
||||
// processFlowNodes(flow);
|
||||
//add dropdown option to nodeOutputs
|
||||
processFlowNodes(flow);
|
||||
//add animation to text type edges
|
||||
updateEdges(data.edges);
|
||||
// updateNodes(data.nodes, data.edges);
|
||||
|
|
@ -397,6 +400,7 @@ export function updateEdgesHandleIds({
|
|||
id: sourceNode.data.id,
|
||||
baseClasses: sourceNode.data.node!.base_classes,
|
||||
dataType: sourceNode.data.type,
|
||||
idx: 0,
|
||||
};
|
||||
}
|
||||
edge.sourceHandle = scapedJSONStringfy(newSource!);
|
||||
|
|
@ -410,6 +414,49 @@ export function updateEdgesHandleIds({
|
|||
return newEdges;
|
||||
}
|
||||
|
||||
export function updateNewOutput({ nodes, edges }: updateEdgesHandleIdsType) {
|
||||
console.log("updateNewOutput");
|
||||
let newEdges = cloneDeep(edges);
|
||||
let newNodes = cloneDeep(nodes);
|
||||
newEdges.forEach((edge) => {
|
||||
if (edge.sourceHandle && edge.targetHandle) {
|
||||
let newSourceHandle: sourceHandleType = scapeJSONParse(edge.sourceHandle);
|
||||
let newTargetHandle: targetHandleType = scapeJSONParse(edge.targetHandle);
|
||||
let intersection;
|
||||
if (newTargetHandle.inputTypes && newTargetHandle.inputTypes.length > 0) {
|
||||
//conjuction subtraction
|
||||
intersection = newSourceHandle.baseClasses.filter((type) =>
|
||||
newTargetHandle.inputTypes!.includes(type),
|
||||
);
|
||||
} else {
|
||||
intersection = newSourceHandle.baseClasses.filter(
|
||||
(type) => type === newTargetHandle.type,
|
||||
);
|
||||
}
|
||||
const selected = intersection[0];
|
||||
newSourceHandle.baseClasses = [selected];
|
||||
const id = newSourceHandle.id;
|
||||
newSourceHandle.idx = 0;
|
||||
const sourceNodeIndex = newNodes.findIndex((node) => node.id === id);
|
||||
if (sourceNodeIndex > -1) {
|
||||
const sourceNode = newNodes[sourceNodeIndex];
|
||||
if (
|
||||
!sourceNode.data.node?.outputs ||
|
||||
sourceNode.data.node!.outputs!.length === 0
|
||||
) {
|
||||
sourceNode.data.node!.outputs = [
|
||||
{ types: sourceNode.data.node!.base_classes, selected: selected },
|
||||
];
|
||||
}
|
||||
}
|
||||
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
||||
edge.data.sourceHandle = newSourceHandle;
|
||||
}
|
||||
});
|
||||
|
||||
return { nodes: newNodes, edges: newEdges };
|
||||
}
|
||||
|
||||
export function handleKeyDown(
|
||||
e:
|
||||
| React.KeyboardEvent<HTMLInputElement>
|
||||
|
|
@ -561,6 +608,10 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean {
|
|||
);
|
||||
}
|
||||
|
||||
export function checkOldNodesOutput(nodes: NodeType[]): boolean {
|
||||
return nodes.some((node) => !node.data.node?.outputs);
|
||||
}
|
||||
|
||||
export function customStringify(obj: any): string {
|
||||
if (typeof obj === "undefined") {
|
||||
return "null";
|
||||
|
|
@ -1017,6 +1068,15 @@ export function processFlowEdges(flow: FlowType) {
|
|||
});
|
||||
}
|
||||
|
||||
export function processFlowNodes(flow: FlowType) {
|
||||
if (!flow.data || !flow.data.nodes) return;
|
||||
if (checkOldNodesOutput(flow.data.nodes)) {
|
||||
const { nodes, edges } = updateNewOutput(flow.data);
|
||||
flow.data.nodes = nodes;
|
||||
flow.data.edges = edges;
|
||||
}
|
||||
}
|
||||
|
||||
export function expandGroupNode(
|
||||
id: string,
|
||||
flow: FlowType,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ test("chat_io_teste", async ({ page }) => {
|
|||
|
||||
const jsonContent = readFileSync(
|
||||
"tests/end-to-end/assets/ChatTest.json",
|
||||
"utf-8"
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
await page.getByTestId("blank-flow").click();
|
||||
|
|
@ -47,7 +47,7 @@ test("chat_io_teste", async ({ page }) => {
|
|||
"drop",
|
||||
{
|
||||
dataTransfer,
|
||||
}
|
||||
},
|
||||
);
|
||||
await page.getByLabel("fit view").click();
|
||||
await page.getByText("Playground", { exact: true }).click();
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
|
|||
.getByTestId("textarea-input_value")
|
||||
.nth(1)
|
||||
.fill(
|
||||
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!"
|
||||
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!",
|
||||
);
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-sender_name")
|
||||
|
|
@ -85,8 +85,8 @@ test("user must interact with chat with Input/Output", async ({ page }) => {
|
|||
await page
|
||||
.getByText(
|
||||
"testtesttesttesttesttestte;.;.,;,.;,.;.,;,..,;;;;;;;;;;;;;;;;;;;;;,;.;,.;,.,;.,;.;.,~~çççççççççççççççççççççççççççççççççççççççisdajfdasiopjfaodisjhvoicxjiovjcxizopjviopasjioasfhjaiohf23432432432423423sttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttestççççççççççççççççççççççççççççççççç,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,.,!",
|
||||
{ exact: true }
|
||||
{ exact: true },
|
||||
)
|
||||
.isVisible()
|
||||
.isVisible(),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,19 +41,19 @@ test("InputListComponent", async ({ page }) => {
|
|||
await page.getByTestId("edit-button-modal").click();
|
||||
|
||||
expect(
|
||||
await page.getByTestId("showmetadata_indexing_exclude").isChecked()
|
||||
await page.getByTestId("showmetadata_indexing_exclude").isChecked(),
|
||||
).toBeFalsy();
|
||||
await page.getByTestId("showmetadata_indexing_exclude").click();
|
||||
expect(
|
||||
await page.getByTestId("showmetadata_indexing_exclude").isChecked()
|
||||
await page.getByTestId("showmetadata_indexing_exclude").isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
await page.getByTestId("showmetadata_indexing_include").isChecked()
|
||||
await page.getByTestId("showmetadata_indexing_include").isChecked(),
|
||||
).toBeFalsy();
|
||||
await page.getByTestId("showmetadata_indexing_include").click();
|
||||
expect(
|
||||
await page.getByTestId("showmetadata_indexing_include").isChecked()
|
||||
await page.getByTestId("showmetadata_indexing_include").isChecked(),
|
||||
).toBeTruthy();
|
||||
|
||||
await page
|
||||
|
|
@ -93,7 +93,7 @@ test("InputListComponent", async ({ page }) => {
|
|||
.click();
|
||||
|
||||
const plusButtonLocator = page.getByTestId(
|
||||
"input-list-plus-btn_metadata_indexing_include-1"
|
||||
"input-list-plus-btn_metadata_indexing_include-1",
|
||||
);
|
||||
const elementCount = await plusButtonLocator?.count();
|
||||
|
||||
|
|
@ -164,12 +164,12 @@ test("InputListComponent", async ({ page }) => {
|
|||
.click();
|
||||
|
||||
const plusButtonLocatorEdit0 = await page.getByTestId(
|
||||
"input-list-plus-btn-edit_metadata_indexing_include-0"
|
||||
"input-list-plus-btn-edit_metadata_indexing_include-0",
|
||||
);
|
||||
const elementCountEdit0 = await plusButtonLocatorEdit0?.count();
|
||||
|
||||
const plusButtonLocatorEdit2 = await page.getByTestId(
|
||||
"input-list-plus-btn-edit_metadata_indexing_include-2"
|
||||
"input-list-plus-btn-edit_metadata_indexing_include-2",
|
||||
);
|
||||
const elementCountEdit2 = await plusButtonLocatorEdit2?.count();
|
||||
|
||||
|
|
@ -178,13 +178,13 @@ test("InputListComponent", async ({ page }) => {
|
|||
}
|
||||
|
||||
const minusButtonLocatorEdit1 = await page.getByTestId(
|
||||
"input-list-minus-btn-edit_metadata_indexing_include-1"
|
||||
"input-list-minus-btn-edit_metadata_indexing_include-1",
|
||||
);
|
||||
|
||||
const elementCountMinusEdit1 = await minusButtonLocatorEdit1?.count();
|
||||
|
||||
const minusButtonLocatorEdit2 = await page.getByTestId(
|
||||
"input-list-minus-btn-edit_metadata_indexing_include-2"
|
||||
"input-list-minus-btn-edit_metadata_indexing_include-2",
|
||||
);
|
||||
|
||||
const elementCountMinusEdit2 = await minusButtonLocatorEdit2?.count();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue