Merge branch 'zustand/io/migration' into state_theories

This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-03-04 18:53:12 -03:00
commit 481df3de8f
67 changed files with 798 additions and 599 deletions

View file

@ -16,6 +16,7 @@ import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleShadComponent from "../../../../components/toggleShadComponent";
import { Button } from "../../../../components/ui/button";
import { RefreshButton } from "../../../../components/ui/refreshButton";
import {
INPUT_HANDLER_HOVER,
LANGFLOW_SUPPORTED_TYPES,
@ -68,7 +69,7 @@ export default function ParameterComponent({
const nodes = useFlowStore((state) => state.nodes);
const edges = useFlowStore((state) => state.edges);
const setNode = useFlowStore((state) => state.setNode);
const [isLoading, setIsLoading] = useState(false);
const flow = currentFlow?.data?.nodes ?? null;
const groupedEdge = useRef(null);
@ -85,7 +86,12 @@ export default function ParameterComponent({
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
const handleUpdateValues = async (name: string, data: NodeDataType) => {
const handleUpdateValues = async (
name: string,
data: NodeDataType,
delayAnimation: boolean = true
) => {
setIsLoading(true);
const code = data.node?.template["code"]?.value;
if (!code) {
console.error("Code not found in the template");
@ -95,13 +101,48 @@ export default function ParameterComponent({
try {
const res = await postCustomComponentUpdate(code, name);
if (res.status === 200 && data.node?.template) {
data.node!.template[name] = res.data.template[name];
setNode(data.id, (oldNode) => {
let newNode = cloneDeep(oldNode);
newNode.data = {
...newNode.data,
};
newNode.data.node.template[name] = res.data.template[name];
return newNode;
});
}
} catch (err) {
setErrorData(err as { title: string; list?: Array<string> });
}
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() {
if (
data.node?.template[name]?.refresh &&
Object.keys(data.node?.template[name]?.options ?? {}).length === 0
) {
handleUpdateValues(name, data, false);
}
}
fetchData();
}, []);
const handleOnNewValue = (
newValue: string | string[] | boolean | Object[]
): void => {
@ -314,16 +355,25 @@ export default function ParameterComponent({
<div
className={
"w-full truncate text-sm" +
(left ? "" : " text-end") +
(left ? "" : " flex items-center justify-end gap-2") +
(info !== "" ? " flex items-center" : "")
}
>
{!left && data.node?.frozen && (
<div>
<IconComponent className="h-5 w-5 text-ice" name={"Snowflake"} />
</div>
)}
{proxy ? (
<ShadTooltip content={<span>{proxy.id}</span>}>
<span>{title}</span>
<span className={!left && data.node?.frozen ? " text-ice" : ""}>
{title}
</span>
</ShadTooltip>
) : (
title
<span className={!left && data.node?.frozen ? " text-ice" : ""}>
{title}
</span>
)}
<span className={(info === "" ? "" : "ml-1 ") + " text-status-red"}>
{required ? " *" : ""}
@ -390,16 +440,31 @@ export default function ParameterComponent({
!data.node?.template[name].options ? (
<div className="mt-2 w-full">
{data.node?.template[name].list ? (
<InputListComponent
disabled={disabled}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={handleOnNewValue}
/>
<div className="w-5/6 flex-grow">
<InputListComponent
disabled={disabled}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={handleOnNewValue}
/>
{data.node?.template[name].refresh && (
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
id={"refresh-button-" + name}
/>
</div>
)}
</div>
) : data.node?.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
@ -420,14 +485,17 @@ export default function ParameterComponent({
/>
</div>
{data.node?.template[name].refresh && (
<button
className="extra-side-bar-buttons ml-2 mt-1 w-1/6"
onClick={() => {
handleUpdateValues(name, data);
}}
>
<IconComponent name="RefreshCcw" />
</button>
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
id={"refresh-button-" + name}
/>
</div>
)}
</div>
)}
@ -454,11 +522,14 @@ export default function ParameterComponent({
</div>
) : left === true &&
type === "str" &&
data.node?.template[name].options ? (
(data.node?.template[name].options ||
data.node?.template[name]?.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}
value={data.node.template[name].value ?? "Choose an option"}
@ -466,14 +537,17 @@ export default function ParameterComponent({
/>
</div>
{data.node?.template[name].refresh && (
<button
className="extra-side-bar-buttons ml-2 mt-1 w-1/6"
onClick={() => {
handleUpdateValues(name, data);
}}
>
<IconComponent name="RefreshCcw" />
</button>
<div className="w-1/6">
<RefreshButton
isLoading={isLoading}
disabled={disabled}
name={name}
data={data}
className="extra-side-bar-buttons ml-2 mt-1"
handleUpdateValues={handleUpdateValues}
id={"refresh-button-" + name}
/>
</div>
)}
</div>
) : left === true && type === "code" ? (

View file

@ -108,7 +108,7 @@ export default function GenericNode({
if (duration === undefined) {
return "";
} else {
return `Duration: ${duration}`;
return `${duration}`;
}
};
const durationString = getDurationString(validationStatus?.data.duration);
@ -758,15 +758,6 @@ export default function GenericNode({
showNode={showNode}
/>
)}
<div>
{lastRunTime && (
<div className="flex justify-center text-muted-foreground">
{lastRunTime.split("\n").map((line, index) => (
<div key={index}>{line}</div>
))}
</div>
)}
</div>
</>
</div>
)}

View file

@ -5,6 +5,8 @@ import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
export default function Dropdown({
disabled,
isLoading,
value,
options,
onSelect,
@ -27,6 +29,7 @@ export default function Dropdown({
<>
<Listbox
value={internalValue}
disabled={disabled}
onChange={(value) => {
setInternalValue(value);
onSelect(value);
@ -129,11 +132,17 @@ export default function Dropdown({
</>
) : (
<>
<div>
<span className="text-sm italic">
No parameters are available for display.
</span>
</div>
{(!isLoading && (
<div>
<span className="text-sm italic">
No parameters are available for display.
</span>
</div>
)) || (
<div>
<span className="text-sm italic">Loading...</span>
</div>
)}
</>
)}
</>

View file

@ -1,4 +1,5 @@
import { forwardRef } from "react";
import dynamicIconImports from "lucide-react/dynamicIconImports";
import { Suspense, forwardRef, lazy } from "react";
import { IconComponentProps } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
@ -14,7 +15,13 @@ const ForwardedIconComponent = forwardRef(
}: IconComponentProps,
ref
) => {
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
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,
@ -22,13 +29,21 @@ const ForwardedIconComponent = forwardRef(
...(iconColor && { color: iconColor, stroke: stroke }),
};
if (!TargetIcon) {
return null; // Render nothing until the icon is loaded
}
const fallback = (
<div style={{ background: "#ddd", width: 24, height: 24 }} />
);
return (
<TargetIcon
className={className}
style={style}
ref={ref}
data-testid={id ? `${id}-${name}` : `icon-${name}`}
/>
<Suspense fallback={fallback}>
<TargetIcon
className={className}
style={style}
ref={ref}
data-testid={id ? `${id}-${name}` : `icon-${name}`}
/>
</Suspense>
);
}
);

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

@ -0,0 +1,55 @@
import IconComponent from "../../components/genericIconComponent";
import { NodeDataType } from "../../types/flow";
import { cn } from "../../utils/utils";
import { Button } from "./button";
function RefreshButton({
isLoading,
disabled,
name,
data,
handleUpdateValues,
className,
id,
}: {
isLoading: boolean;
disabled: boolean;
name: string;
data: NodeDataType;
className?: string;
handleUpdateValues: (name: string, data: NodeDataType) => void;
id: string;
}) {
const handleClick = async () => {
if (disabled) return;
handleUpdateValues(name, data);
};
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" : "";
const iconClassName = cn(
"h-4 w-4",
isLoading ? "animate-spin" : "animate-wiggle",
disabledIconTextClass
);
return (
<Button
variant="primary"
disabled={disabled}
className={classNames}
onClick={handleClick}
id={id}
>
<IconComponent
name={isLoading ? "Loader2" : "RefreshCcw"}
className={iconClassName}
id={id + "-icon"}
/>
</Button>
);
}
export { RefreshButton };

View file

@ -31,6 +31,7 @@ import {
getNodeId,
isValidConnection,
reconnectEdges,
scapeJSONParse,
validateSelection,
} from "../../../../utils/reactflowUtils";
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
@ -108,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,
},
},
}));
@ -320,6 +321,8 @@ export default function Page({
(oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, nodes, edges)) {
edgeUpdateSuccessful.current = true;
oldEdge.data.targetHandle = scapeJSONParse(newConnection.targetHandle!);
oldEdge.data.sourceHandle = scapeJSONParse(newConnection.sourceHandle!);
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
},
@ -488,4 +491,4 @@ export default function Page({
</main>
</div>
);
}
}

View file

@ -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);
@ -320,19 +321,7 @@ export default function ExtraSidebar(): JSX.Element {
<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

View file

@ -0,0 +1,31 @@
export function sortKeys(a: string, b: string) {
// Define the order of specific keys
const order = [
"saved_components",
"inputs",
"outputs",
"data",
"utilities",
"models",
];
const indexA = order.indexOf(a.toLowerCase());
const indexB = 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

@ -62,7 +62,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);
@ -267,7 +267,7 @@ export default function NodeToolbarComponent({
</button>
</ShadTooltip>
<ShadTooltip content="Pin" side="top">
<ShadTooltip content="Freeze" side="top">
<button
className={classNames(
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
@ -280,17 +280,18 @@ export default function NodeToolbarComponent({
...old.data,
node: {
...old.data.node,
pinned: old.data?.node?.pinned ? false : true,
frozen: old.data?.node?.frozen ? false : true,
},
},
}));
}}
>
<IconComponent
name="Pin"
name="Snowflake"
className={cn(
"h-4 w-4 transition-all",
pinned ? "animate-wiggle fill-current" : ""
// TODO UPDATE THIS COLOR TO BE A VARIABLE
frozen ? "animate-wiggle text-ice" : ""
)}
/>
</button>

View file

@ -13,7 +13,6 @@ import {
FLOW_BUILD_SUCCESS_ALERT,
MISSED_ERROR_ALERT,
} from "../constants/alerts_constants";
import { RUN_TIMESTAMP_PREFIX } from "../constants/constants";
import { BuildStatus } from "../constants/enums";
import { getFlowPool } from "../controllers/API";
import { VertexBuildTypeAPI } from "../types/api";
@ -188,10 +187,12 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
typeof change === "function"
? change(get().nodes.find((node) => node.id === id)!)
: change;
get().setNodes((oldNodes) =>
oldNodes.map((node) => {
if (node.id === id) {
if ((node.data as NodeDataType).node?.frozen) {
(newChange.data as NodeDataType).node!.frozen = false;
}
return newChange;
}
return node;
@ -572,9 +573,7 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
};
if (status == BuildStatus.BUILT) {
const timestamp_string = new Date(Date.now()).toLocaleString();
newFlowBuildStatus[
id
].timestamp = `${RUN_TIMESTAMP_PREFIX} ${timestamp_string}`;
newFlowBuildStatus[id].timestamp = timestamp_string;
}
console.log("updateBuildStatus", newFlowBuildStatus);
});

View file

@ -316,7 +316,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 +431,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 +443,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;
@ -902,7 +910,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

@ -27,6 +27,7 @@
--radius: 0.5rem;
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
--round-btn-shadow: #00000063;
--ice: #31a3cc;
--error-background: #fef2f2;
--error-foreground: #991b1b;
@ -67,6 +68,7 @@
.dark {
--background: 224 35% 7.5%; /* hsl(224 40% 10%) */
--foreground: 213 31% 80%; /* hsl(213 31% 91%) */
--ice: #60A5FA;
--muted: 223 27% 11%; /* hsl(223 27% 11%) */
--muted-foreground: 215.4 16.3% 56.9%; /* hsl(215 16% 56%) */

View file

@ -27,7 +27,7 @@ export type APIClassType = {
documentation: string;
error?: string;
official?: boolean;
pinned?: boolean;
frozen?: boolean;
flow?: FlowType;
[key: string]:
| Array<string>
@ -54,6 +54,7 @@ export type TemplateVariableType = {
input_types?: Array<string>;
display_name?: string;
name?: string;
refresh?: boolean;
[key: string]: any;
};
export type sendAllProps = {

View file

@ -30,6 +30,8 @@ export type ToggleComponentType = {
editNode?: boolean;
};
export type DropDownComponentType = {
disabled?: boolean;
isLoading?: boolean;
value: string;
options: string[];
onSelect: (value: string) => void;

View file

@ -126,7 +126,8 @@ export async function buildVertices({
if (validateNodes) {
try {
validateNodes(verticesIds);
const nodes = useFlowStore.getState().nodes;
validateNodes(nodes.map((node) => node.id));
} catch (e) {
return;
}

View file

@ -8,7 +8,6 @@ import {
Bot,
Boxes,
Braces,
Cable,
Check,
CheckCircle2,
ChevronDown,
@ -46,6 +45,7 @@ import {
FileUp,
Fingerprint,
FlaskConical,
FolderOpen,
FolderPlus,
FormInput,
Forward,
@ -99,6 +99,7 @@ import {
Share2,
Shield,
Sliders,
Snowflake,
Sparkles,
Square,
Store,
@ -204,6 +205,9 @@ export const gradients = [
];
export const nodeColors: { [char: string]: string } = {
inputs: "#9AAE42",
outputs: "#AA2411",
data: "#6344BE",
prompts: "#4367BF",
models: "#AA2411",
model_specs: "#6344BE",
@ -224,16 +228,19 @@ export const nodeColors: { [char: string]: string } = {
toolkits: "#DB2C2C",
wrappers: "#E6277A",
utilities: "#31A3CC",
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",
model_specs: "Model Specs",
@ -252,13 +259,16 @@ export const nodeNames: { [char: string]: string } = {
textsplitters: "Text Splitters",
retrievers: "Retrievers",
utilities: "Utilities",
langchain_utilities: "Langchain Utilities",
output_parsers: "Output Parsers",
custom_components: "Custom",
io: "I/O",
unknown: "Other",
};
export const nodeIconsLucide: iconsType = {
inputs: Download,
outputs: Upload,
data: FolderOpen,
AzureChatOpenAi: AzureIcon,
Ollama: OllamaIcon,
ChatOllama: OllamaIcon,
@ -341,6 +351,7 @@ export const nodeIconsLucide: iconsType = {
textsplitters: Scissors,
wrappers: Gift,
utilities: Wand2,
langchain_utilities: Wand2,
WolframAlphaAPIWrapper: SvgWolfram,
output_parsers: Compass,
retrievers: FileSearch,
@ -383,6 +394,7 @@ export const nodeIconsLucide: iconsType = {
Clipboard,
Code2,
Variable,
Snowflake,
Store,
Download,
Eraser,
@ -446,7 +458,6 @@ export const nodeIconsLucide: iconsType = {
TerminalSquare,
TextCursorInput,
Repeat,
io: Cable,
Sliders,
ScreenShare,
Code,