Merge branch 'zustand/io/migration' into cz/fixTestsIo
This commit is contained in:
commit
b6f27173e0
142 changed files with 2701 additions and 1587 deletions
39
src/frontend/package-lock.json
generated
39
src/frontend/package-lock.json
generated
|
|
@ -44,6 +44,7 @@
|
|||
"clsx": "^1.2.1",
|
||||
"dompurify": "^3.0.5",
|
||||
"esbuild": "^0.17.19",
|
||||
"framer-motion": "^11.0.6",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.331.0",
|
||||
"moment": "^2.29.4",
|
||||
|
|
@ -5918,6 +5919,44 @@
|
|||
"url": "https://github.com/sponsors/rawify"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion": {
|
||||
"version": "11.0.6",
|
||||
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.0.6.tgz",
|
||||
"integrity": "sha512-BpO3mWF8UwxzO3Ca5AmSkrg14QYTeJa9vKgoLOoBdBdTPj0e81i1dMwnX6EQJXRieUx20uiDBXq8bA6y7N6b8Q==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@emotion/is-prop-valid": "^0.8.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react": {
|
||||
"optional": true
|
||||
},
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion/node_modules/@emotion/is-prop-valid": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz",
|
||||
"integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emotion/memoize": "0.7.4"
|
||||
}
|
||||
},
|
||||
"node_modules/framer-motion/node_modules/@emotion/memoize": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz",
|
||||
"integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@
|
|||
"clsx": "^1.2.1",
|
||||
"dompurify": "^3.0.5",
|
||||
"esbuild": "^0.17.19",
|
||||
"framer-motion": "^11.0.6",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.331.0",
|
||||
"moment": "^2.29.4",
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import Router from "./routes";
|
|||
import useAlertStore from "./stores/alertStore";
|
||||
import { useDarkStore } from "./stores/darkStore";
|
||||
import useFlowsManagerStore from "./stores/flowsManagerStore";
|
||||
import { useStoreStore } from "./stores/storeStore";
|
||||
import { useTypesStore } from "./stores/typesStore";
|
||||
|
||||
export default function App() {
|
||||
|
|
@ -28,7 +29,6 @@ export default function App() {
|
|||
const tempNotificationList = useAlertStore(
|
||||
(state) => state.tempNotificationList
|
||||
);
|
||||
const loading = useAlertStore((state) => state.loading);
|
||||
const [fetchError, setFetchError] = useState(false);
|
||||
const isLoading = useFlowsManagerStore((state) => state.isLoading);
|
||||
|
||||
|
|
@ -38,9 +38,11 @@ export default function App() {
|
|||
|
||||
const { isAuthenticated } = useContext(AuthContext);
|
||||
const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows);
|
||||
const fetchApiData = useStoreStore((state) => state.fetchApiData);
|
||||
const getTypes = useTypesStore((state) => state.getTypes);
|
||||
const refreshVersion = useDarkStore((state) => state.refreshVersion);
|
||||
const refreshStars = useDarkStore((state) => state.refreshStars);
|
||||
const checkHasStore = useStoreStore((state) => state.checkHasStore);
|
||||
|
||||
useEffect(() => {
|
||||
refreshStars();
|
||||
|
|
@ -52,6 +54,8 @@ export default function App() {
|
|||
getTypes().then(() => {
|
||||
refreshFlows();
|
||||
});
|
||||
checkHasStore();
|
||||
fetchApiData();
|
||||
}
|
||||
}, [isAuthenticated]);
|
||||
|
||||
|
|
|
|||
|
|
@ -324,7 +324,9 @@ export default function ParameterComponent({
|
|||
) : (
|
||||
title
|
||||
)}
|
||||
<span className="text-status-red">{required ? " *" : ""}</span>
|
||||
<span className={(info === "" ? "" : "ml-1 ") + " text-status-red"}>
|
||||
{required ? " *" : ""}
|
||||
</span>
|
||||
<div className="">
|
||||
{info !== "" && (
|
||||
<ShadTooltip content={infoHtml.current}>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
import { useCallback, useEffect, useState } from "react";
|
||||
import { NodeToolbar } from "reactflow";
|
||||
import ShadTooltip from "../../components/ShadTooltipComponent";
|
||||
import Tooltip from "../../components/TooltipComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import InputComponent from "../../components/inputComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import Checkmark from "../../components/ui/checkmark";
|
||||
import Loading from "../../components/ui/loading";
|
||||
import { Textarea } from "../../components/ui/textarea";
|
||||
import Xmark from "../../components/ui/xmark";
|
||||
import { priorityFields } from "../../constants/constants";
|
||||
import { BuildStatus } from "../../constants/enums";
|
||||
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useTypesStore } from "../../stores/typesStore";
|
||||
|
|
@ -43,6 +45,7 @@ export default function GenericNode({
|
|||
const [nodeDescription, setNodeDescription] = useState(
|
||||
data.node?.description!
|
||||
);
|
||||
const buildStatus = useFlowStore((state) => state.flowBuildStatus[data.id]);
|
||||
const [validationStatus, setValidationStatus] =
|
||||
useState<validationStatusType | null>(null);
|
||||
const [handles, setHandles] = useState<number>(0);
|
||||
|
|
@ -89,14 +92,13 @@ export default function GenericNode({
|
|||
|
||||
// should be empty string if no duration
|
||||
// else should be `Duration: ${duration}`
|
||||
const getDurationString = (duration: number | null): string => {
|
||||
if (duration === null) {
|
||||
const getDurationString = (duration: number | undefined): string => {
|
||||
if (duration === undefined) {
|
||||
return "";
|
||||
} else {
|
||||
return `Duration: ${duration}`;
|
||||
}
|
||||
};
|
||||
|
||||
const durationString = getDurationString(validationStatus?.data.duration);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -118,10 +120,9 @@ export default function GenericNode({
|
|||
} else {
|
||||
setValidationStatus(null);
|
||||
}
|
||||
}, [flowPool, data.id]);
|
||||
}, [flowPool[data.id], data.id]);
|
||||
|
||||
const showNode = data.showNode ?? true;
|
||||
const pinned = data.node?.pinned ?? false;
|
||||
|
||||
const nameEditable = data.node?.flow || data.type === "CustomComponent";
|
||||
|
||||
|
|
@ -136,7 +137,6 @@ export default function GenericNode({
|
|||
const iconClassName = `generic-node-icon ${
|
||||
!showNode ? "absolute inset-x-6 h-12 w-12" : ""
|
||||
}`;
|
||||
|
||||
if (iconElement && isEmoji) {
|
||||
return nodeIconFragment(iconElement);
|
||||
} else {
|
||||
|
|
@ -158,36 +158,45 @@ export default function GenericNode({
|
|||
);
|
||||
};
|
||||
|
||||
const getIconPlayOrPauseComponent = (name, className) => (
|
||||
<IconComponent
|
||||
name={name}
|
||||
className={`absolute h-5 stroke-2 ${className} ml-0.5`}
|
||||
/>
|
||||
);
|
||||
|
||||
const getStatusClassName = (
|
||||
validationStatus: validationStatusType | null
|
||||
) => {
|
||||
if (validationStatus && validationStatus.valid) {
|
||||
return "green-status";
|
||||
} else if (validationStatus && !validationStatus.valid) {
|
||||
return "red-status";
|
||||
} else if (!validationStatus) {
|
||||
return "yellow-status";
|
||||
} else {
|
||||
return "status-build-animation";
|
||||
}
|
||||
};
|
||||
|
||||
const renderIconPlayOrPauseComponents = (
|
||||
const isDark = useDarkStore((state) => state.dark);
|
||||
const renderIconStatus = (
|
||||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
) => {
|
||||
if (buildStatus === BuildStatus.BUILDING) {
|
||||
return <Loading />;
|
||||
return <Loading className="text-medium-indigo" />;
|
||||
} else {
|
||||
const className = getStatusClassName(validationStatus);
|
||||
return <>{getIconPlayOrPauseComponent("Play", className)}</>;
|
||||
return (
|
||||
<>
|
||||
<IconComponent
|
||||
name="Play"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-medium-indigo opacity-0 transition-all group-hover:opacity-100"
|
||||
/>
|
||||
{validationStatus && validationStatus.valid ? (
|
||||
<Checkmark
|
||||
className="absolute ml-0.5 h-5 stroke-2 text-status-green opacity-100 transition-all group-hover:opacity-0"
|
||||
isVisible={true}
|
||||
/>
|
||||
) : validationStatus &&
|
||||
!validationStatus.valid &&
|
||||
buildStatus === BuildStatus.INACTIVE ? (
|
||||
<IconComponent
|
||||
name="Play"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-green opacity-30 transition-all group-hover:opacity-0"
|
||||
/>
|
||||
) : validationStatus && !validationStatus.valid ? (
|
||||
<Xmark
|
||||
isVisible={true}
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-status-red opacity-100 transition-all group-hover:opacity-0"
|
||||
/>
|
||||
) : (
|
||||
<IconComponent
|
||||
name="Play"
|
||||
className="absolute ml-0.5 h-5 fill-current stroke-2 text-muted-foreground opacity-100 transition-all group-hover:opacity-0"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -195,14 +204,18 @@ export default function GenericNode({
|
|||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
) => {
|
||||
if (
|
||||
buildStatus === BuildStatus.BUILT &&
|
||||
validationStatus &&
|
||||
!validationStatus.valid
|
||||
) {
|
||||
return "border-none ring ring-red-300";
|
||||
let isInvalid = validationStatus && !validationStatus.valid;
|
||||
|
||||
if (buildStatus === BuildStatus.INACTIVE && isInvalid) {
|
||||
// INACTIVE should have its own class
|
||||
return "inactive-status";
|
||||
}
|
||||
if (buildStatus === BuildStatus.BUILT && isInvalid) {
|
||||
return isDark
|
||||
? "border-none ring ring-[#751C1C]"
|
||||
: "built-invalid-status";
|
||||
} else if (buildStatus === BuildStatus.BUILDING) {
|
||||
return "border-none ring";
|
||||
return "building-status";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
|
|
@ -214,11 +227,17 @@ export default function GenericNode({
|
|||
buildStatus: BuildStatus | undefined,
|
||||
validationStatus: validationStatusType | null
|
||||
) => {
|
||||
const specificClassFromBuildStatus = getSpecificClassFromBuildStatus(
|
||||
buildStatus,
|
||||
validationStatus
|
||||
);
|
||||
const baseBorderClass = getBaseBorderClass(selected);
|
||||
const nodeSizeClass = getNodeSizeClass(showNode);
|
||||
return classNames(
|
||||
getBaseBorderClass(selected),
|
||||
getNodeSizeClass(showNode),
|
||||
baseBorderClass,
|
||||
nodeSizeClass,
|
||||
"generic-node-div",
|
||||
getSpecificClassFromBuildStatus(buildStatus, validationStatus)
|
||||
specificClassFromBuildStatus
|
||||
);
|
||||
};
|
||||
|
||||
|
|
@ -250,12 +269,11 @@ export default function GenericNode({
|
|||
onCloseAdvancedModal={() => {}}
|
||||
></NodeToolbarComponent>
|
||||
</NodeToolbar>
|
||||
|
||||
<div
|
||||
className={getNodeBorderClassName(
|
||||
selected,
|
||||
showNode,
|
||||
data?.build_status,
|
||||
buildStatus,
|
||||
validationStatus
|
||||
)}
|
||||
>
|
||||
|
|
@ -312,28 +330,37 @@ export default function GenericNode({
|
|||
</div>
|
||||
) : (
|
||||
<ShadTooltip content={data.node?.display_name}>
|
||||
<div
|
||||
className="flex items-center gap-2"
|
||||
onDoubleClick={(event) => {
|
||||
if (nameEditable) {
|
||||
setInputName(true);
|
||||
}
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div
|
||||
data-testid={"title-" + data.node?.display_name}
|
||||
className="generic-node-tooltip-div text-primary"
|
||||
>
|
||||
{data.node?.display_name}
|
||||
</div>
|
||||
<div className="group flex items-center gap-2.5">
|
||||
<ShadTooltip content={data.node?.display_name}>
|
||||
<div
|
||||
onDoubleClick={(event) => {
|
||||
if (nameEditable) {
|
||||
setInputName(true);
|
||||
}
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
data-testid={"title-" + data.node?.display_name}
|
||||
className="generic-node-tooltip-div text-primary"
|
||||
>
|
||||
{data.node?.display_name}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
{nameEditable && (
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="h-4 w-4 text-ring"
|
||||
/>
|
||||
<div
|
||||
onClick={(event) => {
|
||||
setInputName(true);
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="Pencil"
|
||||
className="hidden h-4 w-4 animate-pulse text-status-blue group-hover:block"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
|
|
@ -431,80 +458,40 @@ export default function GenericNode({
|
|||
</div>
|
||||
{showNode && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="h-9 px-1.5"
|
||||
variant="secondary"
|
||||
className={"group h-9 px-1.5"}
|
||||
onClick={() => {
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: {
|
||||
...old.data.node,
|
||||
pinned: old.data?.node?.pinned ? false : true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={<span>{pinned ? "Pin Output" : "Unpin Output"}</span>}
|
||||
>
|
||||
<div className="generic-node-status-position flex items-center">
|
||||
<IconComponent
|
||||
name={"Pin"}
|
||||
className={cn(
|
||||
"h-5 fill-transparent stroke-chat-trigger stroke-2 transition-all",
|
||||
pinned ? "animate-wiggle fill-chat-trigger" : ""
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</Button>
|
||||
)}
|
||||
{showNode && (
|
||||
<Button
|
||||
variant="outline"
|
||||
className={"h-9 px-1.5"}
|
||||
onClick={() => {
|
||||
if (data?.build_status === BuildStatus.BUILDING || isBuilding)
|
||||
if (buildStatus === BuildStatus.BUILDING || isBuilding)
|
||||
return;
|
||||
setValidationStatus(null);
|
||||
buildFlow(data.id);
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Tooltip
|
||||
title={
|
||||
data?.build_status === BuildStatus.BUILDING ? (
|
||||
<ShadTooltip
|
||||
content={
|
||||
buildStatus === BuildStatus.BUILDING ? (
|
||||
<span>Building...</span>
|
||||
) : !validationStatus ? (
|
||||
<span className="flex">
|
||||
Build{" "}
|
||||
<IconComponent
|
||||
name="Play"
|
||||
className=" h-5 stroke-build-trigger stroke-2"
|
||||
/>{" "}
|
||||
flow to validate status.
|
||||
</span>
|
||||
<span className="flex">Build to validate status.</span>
|
||||
) : (
|
||||
<div className="max-h-96 overflow-auto">
|
||||
{typeof validationStatus.params === "string"
|
||||
? `${durationString}\n${validationStatus.params}`
|
||||
? (`${durationString}\n${validationStatus.params}`
|
||||
.split("\n")
|
||||
.map((line, index) => (
|
||||
<div key={index}>{line}</div>
|
||||
))
|
||||
)))
|
||||
: durationString}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
side="bottom"
|
||||
>
|
||||
<div className="generic-node-status-position flex items-center justify-center">
|
||||
{renderIconPlayOrPauseComponents(
|
||||
data?.build_status,
|
||||
validationStatus
|
||||
)}
|
||||
{renderIconStatus(buildStatus, validationStatus)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
49
src/frontend/src/alerts_constants.tsx
Normal file
49
src/frontend/src/alerts_constants.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// ERROR
|
||||
export const MISSED_ERROR_ALERT = "Oops! Looks like you missed something";
|
||||
export const INVALID_FILE_ALERT = "Please select a valid file. Only these file types are allowed:";
|
||||
export const CONSOLE_ERROR_MSG = "Error occurred while uploading file";
|
||||
export const CONSOLE_SUCCESS_MSG = "File uploaded successfully";
|
||||
export const INFO_MISSING_ALERT = "Oops! Looks like you missed some required information:";
|
||||
export const FUNC_ERROR_ALERT = "There is an error in your function"
|
||||
export const IMPORT_ERROR_ALERT = "There is an error in your imports"
|
||||
export const BUG_ALERT = "Something went wrong, please try again"
|
||||
export const CODE_ERROR_ALERT = "There is something wrong with this code, please review it"
|
||||
export const CHAT_ERROR_ALERT = "Please build the flow again before using the chat."
|
||||
export const MSG_ERROR_ALERT = "There was an error sending the message"
|
||||
export const PROMPT_ERROR_ALERT = "There is something wrong with this prompt, please review it"
|
||||
export const API_ERROR_ALERT = "There was an error saving the API Key, please try again."
|
||||
export const USER_DEL_ERROR_ALERT = "Error on delete user"
|
||||
export const USER_EDIT_ERROR_ALERT = "Error on edit user"
|
||||
export const USER_ADD_ERROR_ALERT = "Error when adding new user"
|
||||
export const SIGNIN_ERROR_ALERT = "Error signing in"
|
||||
export const DEL_KEY_ERROR_ALERT = "Error on delete key"
|
||||
export const UPLOAD_ERROR_ALERT = "Error uploading file"
|
||||
export const WRONG_FILE_ERROR_ALERT = "Invalid file type"
|
||||
export const UPLOAD_ALERT_LIST = "Please upload a JSON file"
|
||||
export const INVALID_SELECTION_ERROR_ALERT = "Invalid selection"
|
||||
export const EDIT_PASSWORD_ERROR_ALERT = "Error changing password"
|
||||
export const EDIT_PASSWORD_ALERT_LIST = "Passwords do not match"
|
||||
export const SAVE_ERROR_ALERT = "Error saving changes"
|
||||
export const SIGNUP_ERROR_ALERT = "Error signing up"
|
||||
export const APIKEY_ERROR_ALERT = "API Key Error"
|
||||
export const NOAPI_ERROR_ALERT = "You don't have an API Key. Please add one to use the Langflow Store."
|
||||
export const INVALID_API_ERROR_ALERT = "Your API Key is not valid. Please add a valid API Key to use the Langflow Store."
|
||||
export const COMPONENTS_ERROR_ALERT = "Error getting components."
|
||||
|
||||
// NOTICE
|
||||
export const NOCHATOUTPUT_NOTICE_ALERT = "There is no ChatOutput node in the flow."
|
||||
export const API_WARNING_NOTICE_ALERT = "Warning: Critical data, JSON file may include API keys."
|
||||
export const COPIED_NOTICE_ALERT = "API Key copied!"
|
||||
export const TEMP_NOTICE_ALERT = "Your template does not have any variables."
|
||||
|
||||
|
||||
// SUCCESS
|
||||
export const CODE_SUCCESS_ALERT = "Code is ready to run"
|
||||
export const PROMPT_SUCCESS_ALERT = "Prompt is ready"
|
||||
export const API_SUCCESS_ALERT = "Success! Your API Key has been saved."
|
||||
export const USER_DEL_SUCCESS_ALERT = "Success! User deleted!"
|
||||
export const USER_EDIT_SUCCESS_ALERT = "Success! User edited!"
|
||||
export const USER_ADD_SUCCESS_ALERT = "Success! New user added!"
|
||||
export const DEL_KEY_SUCCESS_ALERT = "Success! Key deleted!"
|
||||
export const FLOW_BUILD_SUCCESS_ALERT = `Flow built successfully`
|
||||
export const SAVE_SUCCESS_ALERT = "Changes saved successfully!"
|
||||
|
|
@ -49,7 +49,7 @@ export default function AccordionComponent({
|
|||
>
|
||||
{trigger}
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="AccordionContent">
|
||||
<AccordionContent className="AccordionContent flex flex-col">
|
||||
{children}
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
|
|
|
|||
|
|
@ -12,12 +12,12 @@ export default function IOInputField({
|
|||
const setNode = useFlowStore((state) => state.setNode);
|
||||
const node = nodes.find((node) => node.id === inputId);
|
||||
function handleInputType() {
|
||||
if (!node) return "no node found";
|
||||
if (!node) return <>"No node found!"</>;
|
||||
switch (inputType) {
|
||||
case "TextInput":
|
||||
return (
|
||||
<Textarea
|
||||
className="h-full w-full custom-scroll"
|
||||
className="w-full"
|
||||
placeholder={"Enter text..."}
|
||||
value={node.data.node!.template["value"].value}
|
||||
onChange={(e) => {
|
||||
|
|
@ -47,7 +47,7 @@ export default function IOInputField({
|
|||
default:
|
||||
return (
|
||||
<Textarea
|
||||
className="h-full w-full custom-scroll"
|
||||
className="w-full custom-scroll"
|
||||
placeholder={"Enter text..."}
|
||||
value={node.data.node!.template["value"]}
|
||||
onChange={(e) => {
|
||||
|
|
@ -62,10 +62,5 @@ export default function IOInputField({
|
|||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="font-xl flex h-full w-full flex-col gap-4 p-4 font-semibold">
|
||||
{inputType}
|
||||
{handleInputType()}
|
||||
</div>
|
||||
);
|
||||
return handleInputType();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,15 +12,15 @@ export default function IOOutputView({
|
|||
const flowPool = useFlowStore((state) => state.flowPool);
|
||||
const node = nodes.find((node) => node.id === outputId);
|
||||
function handleOutputType() {
|
||||
if (!node) return "no node found";
|
||||
if (!node) return <>"No node found!"</>;
|
||||
switch (outputType) {
|
||||
case "TextOutput":
|
||||
return (
|
||||
<Textarea
|
||||
className="h-full w-full custom-scroll"
|
||||
placeholder={"Enter text..."}
|
||||
className="w-full custom-scroll"
|
||||
placeholder={"Empty"}
|
||||
// update to real value on flowPool
|
||||
value={flowPool[node.id][flowPool[node.id].length - 1].data.results}
|
||||
value={((flowPool[node.id] ?? [])[(flowPool[node.id]?.length ?? 1) - 1])?.params ?? ""}
|
||||
readOnly
|
||||
/>
|
||||
);
|
||||
|
|
@ -28,7 +28,7 @@ export default function IOOutputView({
|
|||
default:
|
||||
return (
|
||||
<Textarea
|
||||
className="h-full w-full custom-scroll"
|
||||
className="w-full custom-scroll"
|
||||
placeholder={"Enter text..."}
|
||||
value={node.data.node!.template["value"]}
|
||||
onChange={(e) => {
|
||||
|
|
@ -43,10 +43,5 @@ export default function IOOutputView({
|
|||
);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className="font-xl flex h-full w-full flex-col gap-4 p-4 font-semibold">
|
||||
{outputType}
|
||||
{handleOutputType()}
|
||||
</div>
|
||||
);
|
||||
return handleOutputType();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { ReactNode, useState } from "react";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import BaseModal from "../../modals/baseModal";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import { NodeType } from "../../types/flow";
|
||||
import { isInputType, isOutputType } from "../../utils/reactflowUtils";
|
||||
import { cn } from "../../utils/utils";
|
||||
import AccordionComponent from "../AccordionComponent";
|
||||
import IOInputField from "../IOInputField";
|
||||
|
|
@ -12,62 +12,70 @@ import IconComponent from "../genericIconComponent";
|
|||
import NewChatView from "../newChatView";
|
||||
import { Badge } from "../ui/badge";
|
||||
import { Button } from "../ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
|
||||
|
||||
export default function IOView({ children, open, setOpen }): JSX.Element {
|
||||
const inputs = useFlowStore((state) => state.inputs);
|
||||
const outputs = useFlowStore((state) => state.outputs);
|
||||
const inputIds = inputs.map((obj) => obj.id);
|
||||
const outputIds = outputs.map((obj) => obj.id);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
const categories = getCategories();
|
||||
const [selectedCategory, setSelectedCategory] = useState<number>(0);
|
||||
const [showChat, setShowChat] = useState<boolean>(false);
|
||||
const [selectedView, setSelectedView] = useState<{
|
||||
type: string;
|
||||
id?: string;
|
||||
}>(handleInitialView());
|
||||
const inputs = useFlowStore((state) => state.inputs).filter(
|
||||
(input) => input.type !== "ChatInput"
|
||||
);
|
||||
const chatInput = useFlowStore((state) => state.inputs).find(
|
||||
(input) => input.type === "ChatInput"
|
||||
);
|
||||
const outputs = useFlowStore((state) => state.outputs).filter(
|
||||
(output) => output.type !== "ChatOutput"
|
||||
);
|
||||
const chatOutput = useFlowStore((state) => state.outputs).find(
|
||||
(output) => output.type === "ChatOutput"
|
||||
);
|
||||
const nodes = useFlowStore((state) => state.nodes).filter(
|
||||
(node) =>
|
||||
inputs.some((input) => input.id === node.id) ||
|
||||
outputs.some((output) => output.id === node.id)
|
||||
);
|
||||
const haveChat = chatInput || chatOutput;
|
||||
const [selectedTab, setSelectedTab] = useState(
|
||||
inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0
|
||||
);
|
||||
const [selectedViewField, setSelectedViewField] = useState<
|
||||
{ type: string; id: string } | undefined
|
||||
>(undefined);
|
||||
|
||||
type CategoriesType = { name: string; icon: string };
|
||||
const { getNode, setNode, buildFlow, getFlow } = useFlowStore();
|
||||
const { setErrorData } = useAlertStore();
|
||||
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const [chatValue, setChatValue] = useState("");
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
|
||||
function handleInitialView() {
|
||||
if (
|
||||
outputs.map((output) => output.type).includes("ChatOutput") ||
|
||||
inputs.map((input) => input.type).includes("ChatInput")
|
||||
) {
|
||||
return { type: "ChatOutput" };
|
||||
async function sendMessage(count = 1): Promise<void> {
|
||||
if (isBuilding) return;
|
||||
const { nodes, edges } = getFlow();
|
||||
setIsBuilding(true);
|
||||
setLockChat(true);
|
||||
setChatValue("");
|
||||
const chatInputNode = nodes.find((node) => node.id === chatInput?.id);
|
||||
if (chatInputNode) {
|
||||
let newNode = cloneDeep(chatInputNode);
|
||||
newNode.data.node!.template["message"].value = chatValue;
|
||||
setNode(chatInput!.id, newNode);
|
||||
}
|
||||
return { type: "" };
|
||||
for (let i = 0; i < count; i++) {
|
||||
await buildFlow().catch((err) => {
|
||||
console.error(err);
|
||||
setLockChat(false);
|
||||
});
|
||||
}
|
||||
setLockChat(false);
|
||||
}
|
||||
|
||||
function getCategories() {
|
||||
const categories: CategoriesType[] = [];
|
||||
if (inputs.filter((input) => input.type !== "ChatInput").length > 0)
|
||||
categories.push({ name: "Inputs", icon: "TextCursorInput" });
|
||||
if (outputs.filter((output) => output.type !== "ChatOutput").length > 0)
|
||||
categories.push({ name: "Outputs", icon: "TerminalSquare" });
|
||||
return categories;
|
||||
}
|
||||
|
||||
function handleSelectChange(): ReactNode {
|
||||
const { type, id } = selectedView;
|
||||
if (type === "ChatOutput") return <NewChatView />;
|
||||
if (isInputType(type))
|
||||
return <IOInputField inputId={id!} inputType={type} />;
|
||||
if (isOutputType(type))
|
||||
return <IOOutputView outputId={id!} outputType={type} />;
|
||||
else return undefined;
|
||||
}
|
||||
|
||||
function UpdateAccordion() {
|
||||
return (categories[selectedCategory]?.name ?? "Inputs") === "Inputs"
|
||||
? inputs
|
||||
: outputs;
|
||||
}
|
||||
useEffect(() => {
|
||||
setSelectedViewField(undefined);
|
||||
setSelectedTab(inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0);
|
||||
}, [inputs.length, outputs.length]);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
size={handleSelectChange() ? "large" : "small"}
|
||||
size={haveChat ? (selectedTab === 0 ? "large-thin" : "large") : "small"}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
>
|
||||
|
|
@ -84,130 +92,229 @@ export default function IOView({ children, open, setOpen }): JSX.Element {
|
|||
</div>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<div className="flex-max-width mt-2 h-[80vh]">
|
||||
<div
|
||||
className={cn(
|
||||
"mr-6 flex h-full w-2/6 flex-col justify-start overflow-auto scrollbar-hide",
|
||||
handleSelectChange() ? "w-2/6" : "w-full"
|
||||
)}
|
||||
>
|
||||
<div className="flex w-full items-center justify-between py-2">
|
||||
<div className="flex items-start gap-4">
|
||||
{categories.map((category, index) => {
|
||||
return (
|
||||
//hide chat button if chat is alredy on the view
|
||||
<Button
|
||||
onClick={() => setSelectedCategory(index)}
|
||||
variant={
|
||||
index === selectedCategory ? "primary" : "secondary"
|
||||
}
|
||||
key={index}
|
||||
>
|
||||
<IconComponent
|
||||
name={category.icon}
|
||||
className=" file-component-variable"
|
||||
/>
|
||||
<span className="file-component-variables-span text-md">
|
||||
{category.name}
|
||||
</span>
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{(outputs.map((output) => output.type).includes("ChatOutput") ||
|
||||
inputs.map((output) => output.type).includes("chatInput")) &&
|
||||
selectedView.type !== "ChatOutput" && (
|
||||
<Button
|
||||
onClick={() => setSelectedView({ type: "ChatOutput" })}
|
||||
variant="outline"
|
||||
key={"chat"}
|
||||
className="self-end px-2.5"
|
||||
>
|
||||
<IconComponent
|
||||
name="MessageSquareMore"
|
||||
className="h-5 w-5"
|
||||
/>
|
||||
</Button>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="flex-max-width mt-2 h-full">
|
||||
{selectedTab !== 0 && (
|
||||
<div
|
||||
className={cn(
|
||||
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start overflow-auto scrollbar-hide",
|
||||
haveChat ? "w-2/6" : "w-full"
|
||||
)}
|
||||
</div>
|
||||
<div className="mx-2 mb-2 mt-4 flex items-center gap-2 font-semibold">
|
||||
{categories[selectedCategory]?.name === "Inputs" && (
|
||||
<>
|
||||
<IconComponent name={"FormInput"} />
|
||||
Text Inputs
|
||||
</>
|
||||
)}
|
||||
{categories[selectedCategory]?.name === "Outputs" && (
|
||||
<>
|
||||
<IconComponent name={"ChevronRightSquare"} />
|
||||
Prompt Outputs
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{UpdateAccordion()
|
||||
.filter(
|
||||
(input) =>
|
||||
input.type !== "ChatInput" && input.type !== "ChatOutput"
|
||||
)
|
||||
.map((input, index) => {
|
||||
const node: NodeType = nodes.find(
|
||||
(node) => node.id === input.id
|
||||
)!;
|
||||
return (
|
||||
<div className="file-component-accordion-div" key={index}>
|
||||
<AccordionComponent
|
||||
trigger={
|
||||
<div className="file-component-badge-div">
|
||||
<Badge variant="gray" size="md">
|
||||
{input.id}
|
||||
</Badge>
|
||||
<div
|
||||
className="-mb-1 pr-4"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setSelectedView({
|
||||
type: input.type,
|
||||
id: input.id,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
className="h-4 w-4"
|
||||
name="ExternalLink"
|
||||
></IconComponent>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
key={index}
|
||||
keyValue={input.id}
|
||||
>
|
||||
<div className="file-component-tab-column">
|
||||
<div className="">
|
||||
{node &&
|
||||
(categories[selectedCategory]?.name === "Inputs" ? (
|
||||
<IOInputField
|
||||
inputType={input.type}
|
||||
inputId={input.id}
|
||||
/>
|
||||
) : (
|
||||
<IOOutputView
|
||||
outputType={input.type}
|
||||
outputId={input.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</AccordionComponent>
|
||||
>
|
||||
<Tabs
|
||||
value={selectedTab.toString()}
|
||||
className={
|
||||
"flex h-full flex-col overflow-hidden rounded-md border bg-muted text-center"
|
||||
}
|
||||
onValueChange={(value) => {
|
||||
setSelectedTab(Number(value));
|
||||
}}
|
||||
>
|
||||
<div className="api-modal-tablist-div">
|
||||
<TabsList>
|
||||
{inputs.length > 0 && (
|
||||
<TabsTrigger value={"1"}>Inputs</TabsTrigger>
|
||||
)}
|
||||
{outputs.length > 0 && (
|
||||
<TabsTrigger value={"2"}>Outputs</TabsTrigger>
|
||||
)}
|
||||
</TabsList>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
||||
<TabsContent
|
||||
value={"1"}
|
||||
className="api-modal-tabs-content mt-4"
|
||||
>
|
||||
<div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold">
|
||||
<IconComponent className="h-4 w-4" name={"Type"} />
|
||||
Text Inputs
|
||||
</div>
|
||||
{nodes
|
||||
.filter((node) =>
|
||||
inputs.some((input) => input.id === node.id)
|
||||
)
|
||||
.map((node, index) => {
|
||||
const input = inputs.find(
|
||||
(input) => input.id === node.id
|
||||
)!;
|
||||
return (
|
||||
<div
|
||||
className="file-component-accordion-div"
|
||||
key={index}
|
||||
>
|
||||
<AccordionComponent
|
||||
trigger={
|
||||
<div className="file-component-badge-div">
|
||||
<Badge variant="gray" size="md">
|
||||
{input.id}
|
||||
</Badge>
|
||||
{haveChat && (
|
||||
<div
|
||||
className="-mb-1 pr-4"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setSelectedViewField(input);
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
className="h-4 w-4"
|
||||
name="ExternalLink"
|
||||
></IconComponent>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
key={index}
|
||||
keyValue={input.id}
|
||||
>
|
||||
<div className="file-component-tab-column">
|
||||
<div className="">
|
||||
{input && (
|
||||
<IOInputField
|
||||
inputType={input.type}
|
||||
inputId={input.id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AccordionComponent>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</TabsContent>
|
||||
<TabsContent
|
||||
value={"2"}
|
||||
className="api-modal-tabs-content mt-4"
|
||||
>
|
||||
<div className="mx-2 mb-2 flex items-center gap-2 text-sm font-bold">
|
||||
<IconComponent className="h-4 w-4" name={"Braces"} />
|
||||
Prompt Outputs
|
||||
</div>
|
||||
{nodes
|
||||
.filter((node) =>
|
||||
outputs.some((output) => output.id === node.id)
|
||||
)
|
||||
.map((node, index) => {
|
||||
const output = outputs.find(
|
||||
(output) => output.id === node.id
|
||||
)!;
|
||||
return (
|
||||
<div
|
||||
className="file-component-accordion-div"
|
||||
key={index}
|
||||
>
|
||||
<AccordionComponent
|
||||
trigger={
|
||||
<div className="file-component-badge-div">
|
||||
<Badge variant="gray" size="md">
|
||||
{output.id}
|
||||
</Badge>
|
||||
{haveChat && (
|
||||
<div
|
||||
className="-mb-1 pr-4"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
setSelectedViewField(output);
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
className="h-4 w-4"
|
||||
name="ExternalLink"
|
||||
></IconComponent>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
key={index}
|
||||
keyValue={output.id}
|
||||
>
|
||||
<div className="file-component-tab-column">
|
||||
<div className="">
|
||||
{output && (
|
||||
<IOOutputView
|
||||
outputType={output.type}
|
||||
outputId={output.id}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</AccordionComponent>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{haveChat ? (
|
||||
<div className="flex h-full w-full">
|
||||
{selectedViewField && (
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-full w-full flex-col items-start gap-4 p-4",
|
||||
!selectedViewField ? "hidden" : ""
|
||||
)}
|
||||
>
|
||||
<div className="font-xl flex items-center justify-center gap-3 font-semibold">
|
||||
<button onClick={() => setSelectedViewField(undefined)}>
|
||||
<IconComponent
|
||||
name={"ArrowLeft"}
|
||||
className="h-6 w-6"
|
||||
></IconComponent>
|
||||
</button>
|
||||
{selectedViewField.type}
|
||||
</div>
|
||||
<div className="h-full">
|
||||
{inputs.some(
|
||||
(input) => input.id === selectedViewField.id
|
||||
) ? (
|
||||
<IOInputField
|
||||
inputType={selectedViewField.type!}
|
||||
inputId={selectedViewField.id!}
|
||||
/>
|
||||
) : (
|
||||
<IOOutputView
|
||||
outputType={selectedViewField.type!}
|
||||
outputId={selectedViewField.id!}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={cn("flex w-full h-full",selectedViewField ? "hidden" : "")}
|
||||
>
|
||||
<NewChatView
|
||||
sendMessage={sendMessage}
|
||||
chatValue={chatValue}
|
||||
setChatValue={setChatValue}
|
||||
lockChat={lockChat}
|
||||
setLockChat={setLockChat}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="absolute bottom-8 right-8"></div>
|
||||
)}
|
||||
</div>
|
||||
{handleSelectChange() ? (
|
||||
handleSelectChange()
|
||||
) : (
|
||||
<div className="absolute bottom-8 right-8">
|
||||
<Button className="px-3">
|
||||
<IconComponent name="Play" className="h-6 w-6" />
|
||||
{!haveChat && (
|
||||
<div className="flex w-full justify-end pt-6">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className="flex gap-2 px-3"
|
||||
onClick={() => sendMessage(1)}
|
||||
>
|
||||
<IconComponent
|
||||
name={isBuilding ? "Loader2" : "Play"}
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
isBuilding
|
||||
? "animate-spin"
|
||||
: "fill-current text-medium-indigo"
|
||||
)}
|
||||
/>
|
||||
Run Flow
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ShadToolTipType } from "../../types/components";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||
|
||||
export default function ShadTooltip({
|
||||
|
|
@ -14,7 +15,7 @@ export default function ShadTooltip({
|
|||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||
|
||||
<TooltipContent
|
||||
className={styleClasses}
|
||||
className={cn(styleClasses, "max-w-96") }
|
||||
side={side}
|
||||
avoidCollisions={false}
|
||||
sticky="always"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default function ChatTrigger({}): JSX.Element {
|
|||
>
|
||||
<div className="flex gap-3">
|
||||
<IconComponent
|
||||
name="Sliders"
|
||||
name="Zap"
|
||||
className={"message-button-icon h-6 w-6 transition-all"}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import useFlowStore from "../../../stores/flowStore";
|
|||
import { validateNodes } from "../../../utils/reactflowUtils";
|
||||
import RadialProgressComponent from "../../RadialProgress";
|
||||
import IconComponent from "../../genericIconComponent";
|
||||
import { MISSED_ERROR_ALERT } from "../../../alerts_constants";
|
||||
|
||||
export default function BuildTrigger({
|
||||
open,
|
||||
|
|
@ -36,7 +37,7 @@ export default function BuildTrigger({
|
|||
const errors = validateNodes(nodes, edges);
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed something",
|
||||
title: MISSED_ERROR_ALERT,
|
||||
list: errors,
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import useAlertStore from "../../../../stores/alertStore";
|
|||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import Tooltip from "../../../TooltipComponent";
|
||||
import ShadTooltip from "../../../ShadTooltipComponent";
|
||||
import IconComponent from "../../../genericIconComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
|
||||
|
|
@ -125,8 +125,8 @@ export const MenuBar = ({
|
|||
setOpen={setOpenSettings}
|
||||
></FlowSettingsModal>
|
||||
</div>
|
||||
<Tooltip
|
||||
title={
|
||||
<ShadTooltip
|
||||
content={
|
||||
"Last saved at " +
|
||||
new Date(currentFlow.updated_at ?? "").toLocaleString("en-US", {
|
||||
hour: "numeric",
|
||||
|
|
@ -134,8 +134,10 @@ export const MenuBar = ({
|
|||
second: "numeric",
|
||||
})
|
||||
}
|
||||
side="bottom"
|
||||
styleClasses="cursor-default"
|
||||
>
|
||||
<div className="flex items-center gap-1.5 text-sm text-muted-foreground">
|
||||
<div className="flex cursor-default items-center gap-1.5 text-sm text-muted-foreground">
|
||||
<IconComponent
|
||||
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
||||
className={cn(
|
||||
|
|
@ -145,7 +147,7 @@ export const MenuBar = ({
|
|||
/>
|
||||
{printByBuildStatus()}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import useAlertStore from "../../stores/alertStore";
|
|||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { FileComponentType } from "../../types/components";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { CONSOLE_ERROR_MSG, CONSOLE_SUCCESS_MSG, INVALID_FILE_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function InputFileComponent({
|
||||
value,
|
||||
|
|
@ -61,7 +62,7 @@ export default function InputFileComponent({
|
|||
uploadFile(file, currentFlowId)
|
||||
.then((res) => res.data)
|
||||
.then((data) => {
|
||||
console.log("File uploaded successfully");
|
||||
console.log(CONSOLE_SUCCESS_MSG);
|
||||
// Get the file name from the response
|
||||
const { file_path } = data;
|
||||
console.log("File name:", file_path);
|
||||
|
|
@ -75,14 +76,13 @@ export default function InputFileComponent({
|
|||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("Error occurred while uploading file");
|
||||
console.error(CONSOLE_ERROR_MSG);
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
// Show an error if the file type is not allowed
|
||||
setErrorData({
|
||||
title:
|
||||
"Please select a valid file. Only these file types are allowed:",
|
||||
title: INVALID_FILE_ALERT,
|
||||
list: fileTypes,
|
||||
});
|
||||
setLoading(false);
|
||||
|
|
|
|||
|
|
@ -17,12 +17,65 @@ export default function ChatMessage({
|
|||
chat,
|
||||
lockChat,
|
||||
lastMessage,
|
||||
updateChat,
|
||||
}: chatMessagePropsType): JSX.Element {
|
||||
const convert = new Convert({ newline: true });
|
||||
const [hidden, setHidden] = useState(true);
|
||||
const template = chat.template;
|
||||
const [promptOpen, setPromptOpen] = useState(false);
|
||||
const chat_message = chat.message.toString();
|
||||
const [streamUrl, setStreamUrl] = useState(chat.stream_url);
|
||||
const [chatMessage, setChatMessage] = useState(chat.message.toString());
|
||||
const [isStreaming, setIsStreaming] = useState(false);
|
||||
|
||||
// The idea now is that chat.stream_url MAY be a URL if we should stream the output of the chat
|
||||
// probably the message is empty when we have a stream_url
|
||||
// what we need is to update the chat_message with the SSE data
|
||||
const streamChunks = (url: string) => {
|
||||
setIsStreaming(true); // Streaming starts
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
const eventSource = new EventSource(url);
|
||||
eventSource.onmessage = (event) => {
|
||||
let parsedData = JSON.parse(event.data);
|
||||
if (parsedData.chunk) {
|
||||
setChatMessage((prev) => prev + parsedData.chunk);
|
||||
}
|
||||
};
|
||||
eventSource.onerror = (event) => {
|
||||
reject(new Error("Streaming failed"));
|
||||
setIsStreaming(false);
|
||||
eventSource.close();
|
||||
};
|
||||
eventSource.addEventListener("close", (event) => {
|
||||
setStreamUrl(null); // Update state to reflect the stream is closed
|
||||
resolve(true);
|
||||
setIsStreaming(false);
|
||||
eventSource.close();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (streamUrl && chat.message === "") {
|
||||
streamChunks(streamUrl)
|
||||
.then(() => {
|
||||
if (updateChat) {
|
||||
updateChat(chat, chatMessage, streamUrl);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}, [streamUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
// This effect is specifically for calling updateChat after streaming ends
|
||||
if (!isStreaming && streamUrl) {
|
||||
if (updateChat) {
|
||||
updateChat(chat, chatMessage, streamUrl);
|
||||
}
|
||||
}
|
||||
}, [isStreaming]);
|
||||
|
||||
useEffect(() => {
|
||||
const element = document.getElementById("last-chat-message");
|
||||
|
|
@ -103,7 +156,7 @@ export default function ChatMessage({
|
|||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeMathjax]}
|
||||
className="markdown prose min-w-full text-primary word-break-break-word
|
||||
dark:prose-invert"
|
||||
dark:prose-invert"
|
||||
components={{
|
||||
pre({ node, ...props }) {
|
||||
return <>{props.children}</>;
|
||||
|
|
@ -161,10 +214,10 @@ export default function ChatMessage({
|
|||
},
|
||||
}}
|
||||
>
|
||||
{chat_message}
|
||||
{chatMessage}
|
||||
</Markdown>
|
||||
),
|
||||
[chat.message, chat_message]
|
||||
[chat.message, chatMessage]
|
||||
)}
|
||||
</div>
|
||||
{chat.files && (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { cloneDeep } from "lodash";
|
||||
import _ from "lodash";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { deleteFlowPool } from "../../controllers/API";
|
||||
|
|
@ -11,31 +11,23 @@ import {
|
|||
ChatOutputType,
|
||||
FlowPoolObjectType,
|
||||
} from "../../types/chat";
|
||||
import { NodeType } from "../../types/flow";
|
||||
import { validateNodes } from "../../utils/reactflowUtils";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import ChatInput from "./chatInput";
|
||||
import ChatMessage from "./chatMessage";
|
||||
import { INFO_MISSING_ALERT, NOCHATOUTPUT_NOTICE_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function newChatView(): JSX.Element {
|
||||
const [chatValue, setChatValue] = useState("");
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
const {
|
||||
flowPool,
|
||||
outputs,
|
||||
inputs,
|
||||
getNode,
|
||||
setNode,
|
||||
buildFlow,
|
||||
getFlow,
|
||||
CleanFlowPool,
|
||||
} = useFlowStore();
|
||||
const { setErrorData, setNoticeData } = useAlertStore();
|
||||
export default function NewChatView({
|
||||
sendMessage,
|
||||
chatValue,
|
||||
setChatValue,
|
||||
lockChat,
|
||||
setLockChat,
|
||||
}): JSX.Element {
|
||||
const { flowPool, outputs, inputs, CleanFlowPool } = useFlowStore();
|
||||
const { setNoticeData } = useAlertStore();
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
const setIsBuilding = useFlowStore((state) => state.setIsBuilding);
|
||||
const [lockChat, setLockChat] = useState(false);
|
||||
const messagesRef = useRef<HTMLDivElement | null>(null);
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
const [chatHistory, setChatHistory] = useState<ChatMessageType[]>([]);
|
||||
|
||||
const inputTypes = inputs.map((obj) => obj.type);
|
||||
const inputIds = inputs.map((obj) => obj.id);
|
||||
|
|
@ -44,7 +36,7 @@ export default function newChatView(): JSX.Element {
|
|||
|
||||
useEffect(() => {
|
||||
if (!outputTypes.includes("ChatOutput")) {
|
||||
setNoticeData({ title: "There is no ChatOutput node in the flow." });
|
||||
setNoticeData({ title: NOCHATOUTPUT_NOTICE_ALERT });
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
@ -67,20 +59,30 @@ export default function newChatView(): JSX.Element {
|
|||
});
|
||||
const chatMessages: ChatMessageType[] = chatOutputResponses
|
||||
.sort((a, b) => Date.parse(a.timestamp) - Date.parse(b.timestamp))
|
||||
.filter((output) => !!output.data.artifacts?.message)
|
||||
.map((output) => {
|
||||
//
|
||||
.filter((output) => output.data.artifacts?.message !== null)
|
||||
.map((output, index) => {
|
||||
try {
|
||||
const { sender, message, sender_name } = output.data
|
||||
const { sender, message, sender_name, stream_url } = output.data
|
||||
.artifacts as ChatOutputType;
|
||||
|
||||
const componentId = output.id + index;
|
||||
|
||||
const is_ai = sender === "Machine" || sender === null;
|
||||
return { isSend: !is_ai, message: message, sender_name };
|
||||
return {
|
||||
isSend: !is_ai,
|
||||
message: message,
|
||||
sender_name,
|
||||
id: componentId,
|
||||
stream_url: stream_url,
|
||||
};
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return {
|
||||
isSend: false,
|
||||
message: "Error parsing message",
|
||||
sender_name: "Error",
|
||||
id: output.id + index,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
|
@ -105,40 +107,6 @@ export default function newChatView(): JSX.Element {
|
|||
}
|
||||
}, []);
|
||||
|
||||
async function sendMessage(count = 1): Promise<void> {
|
||||
if (isBuilding) return;
|
||||
const { nodes, edges } = getFlow();
|
||||
let nodeValidationErrors = validateNodes(nodes, edges);
|
||||
if (nodeValidationErrors.length === 0) {
|
||||
setIsBuilding(true);
|
||||
setLockChat(true);
|
||||
setChatValue("");
|
||||
const chatInputId = inputIds.find((inputId) =>
|
||||
inputId.includes("ChatInput")
|
||||
);
|
||||
const chatInput: NodeType = getNode(chatInputId!) as NodeType;
|
||||
if (chatInput) {
|
||||
let newNode = cloneDeep(chatInput);
|
||||
newNode.data.node!.template["message"].value = chatValue;
|
||||
setNode(chatInputId!, newNode);
|
||||
}
|
||||
for (let i = 0; i < count; i++) {
|
||||
await buildFlow().catch((err) => {
|
||||
console.error(err);
|
||||
setLockChat(false);
|
||||
});
|
||||
}
|
||||
setLockChat(false);
|
||||
|
||||
//set chat message in the flow and run build
|
||||
//@ts-ignore
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed some required information:",
|
||||
list: nodeValidationErrors,
|
||||
});
|
||||
}
|
||||
}
|
||||
function clearChat(): void {
|
||||
setChatHistory([]);
|
||||
deleteFlowPool(currentFlowId).then((_) => {
|
||||
|
|
@ -148,6 +116,32 @@ export default function newChatView(): JSX.Element {
|
|||
if (lockChat) setLockChat(false);
|
||||
}
|
||||
|
||||
function updateChat(
|
||||
chat: ChatMessageType,
|
||||
message: string,
|
||||
stream_url: string | null
|
||||
) {
|
||||
if (message === "") return;
|
||||
console.log(`updateChat: ${message}`);
|
||||
console.log("chatHistory:", chatHistory);
|
||||
chat.message = message;
|
||||
chat.stream_url = stream_url;
|
||||
// chat is one of the chatHistory
|
||||
setChatHistory((oldChatHistory) => {
|
||||
const index = oldChatHistory.findIndex((ch) => ch.id === chat.id);
|
||||
|
||||
if (index === -1) return oldChatHistory;
|
||||
let newChatHistory = _.cloneDeep(oldChatHistory);
|
||||
newChatHistory = [
|
||||
...newChatHistory.slice(0, index),
|
||||
chat,
|
||||
...newChatHistory.slice(index + 1),
|
||||
];
|
||||
console.log("newChatHistory:", newChatHistory);
|
||||
return newChatHistory;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="eraser-column-arrangement">
|
||||
<div className="eraser-size">
|
||||
|
|
@ -172,7 +166,8 @@ export default function newChatView(): JSX.Element {
|
|||
lockChat={lockChat}
|
||||
chat={chat}
|
||||
lastMessage={chatHistory.length - 1 === index ? true : false}
|
||||
key={index}
|
||||
key={`${chat.id}-${index}`}
|
||||
updateChat={updateChat}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -20,48 +20,42 @@ export default function TextAreaComponent({
|
|||
}, [disabled]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"flex w-full items-center " + (disabled ? "pointer-events-none" : "")
|
||||
}
|
||||
>
|
||||
<GenericModal
|
||||
type={TypeModal.TEXT}
|
||||
buttonText="Finishing Editing"
|
||||
modalTitle="Edit Text"
|
||||
value={value}
|
||||
setValue={(value: string) => {
|
||||
onChange(value);
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full items-center" data-testid={"div-" + id}>
|
||||
<Input
|
||||
id={id}
|
||||
data-testid={id}
|
||||
<div className={"flex w-full items-center " + (disabled ? "" : "")}>
|
||||
<div className="flex w-full items-center" data-testid={"div-" + id}>
|
||||
<Input
|
||||
id={id}
|
||||
data-testid={id}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
className={editNode ? "input-edit-node w-full" : " w-full"}
|
||||
placeholder={"Type something..."}
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value);
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<GenericModal
|
||||
type={TypeModal.TEXT}
|
||||
buttonText="Finish Editing"
|
||||
modalTitle="Edit Text"
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
className={
|
||||
editNode
|
||||
? "input-edit-node pointer-events-none "
|
||||
: " pointer-events-none"
|
||||
}
|
||||
placeholder={"Type something..."}
|
||||
onChange={(event) => {
|
||||
onChange(event.target.value);
|
||||
setValue={(value: string) => {
|
||||
onChange(value);
|
||||
}}
|
||||
/>
|
||||
{!editNode && (
|
||||
<IconComponent
|
||||
id={id}
|
||||
name="ExternalLink"
|
||||
className={
|
||||
"icons-parameters-comp" +
|
||||
(disabled ? " text-ring" : " hover:text-accent-foreground")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{!editNode && (
|
||||
<IconComponent
|
||||
id={id}
|
||||
name="ExternalLink"
|
||||
className={
|
||||
"icons-parameters-comp" +
|
||||
(disabled ? " text-ring" : " hover:text-accent-foreground")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</GenericModal>
|
||||
</div>
|
||||
</GenericModal>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
31
src/frontend/src/components/ui/checkmark.tsx
Normal file
31
src/frontend/src/components/ui/checkmark.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { AnimatePresence, motion } from "framer-motion";
|
||||
export default function Checkmark({ initial = true, isVisible, className }) {
|
||||
return (
|
||||
<AnimatePresence initial={initial}>
|
||||
{isVisible && (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className={"CheckIcon " + className}
|
||||
>
|
||||
<motion.path
|
||||
initial={{ pathLength: 0, pathOffset: 1 }}
|
||||
animate={{ pathLength: 1, pathOffset: 0 }}
|
||||
exit={{ pathLength: 0, pathOffset: 1 }}
|
||||
transition={{
|
||||
type: "tween",
|
||||
duration: 0.3,
|
||||
ease: isVisible ? "easeOut" : "easeIn",
|
||||
}}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M20 6 9 17l-5-5"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
46
src/frontend/src/components/ui/xmark.tsx
Normal file
46
src/frontend/src/components/ui/xmark.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import { AnimatePresence, motion } from "framer-motion";
|
||||
export default function Xmark({ initial = true, isVisible, className }) {
|
||||
return (
|
||||
<AnimatePresence initial={initial}>
|
||||
{isVisible && (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
className={"CheckIcon " + className}
|
||||
>
|
||||
<motion.path
|
||||
|
||||
initial={{ pathLength: 0, pathOffset: 1, strokeLinecap: "butt" }}
|
||||
animate={{ pathLength: 1, pathOffset: 0, strokeLinecap: "round" }}
|
||||
exit={{ pathLength: 0, pathOffset: 1, strokeLinecap: "butt" }}
|
||||
transition={{
|
||||
type: "tween",
|
||||
duration: 0.3,
|
||||
ease: isVisible ? "easeOut" : "easeIn",
|
||||
delay: 0.2,
|
||||
}}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M18 6 6 18"
|
||||
/>
|
||||
<motion.path
|
||||
initial={{ pathLength: 0 }}
|
||||
animate={{ pathLength: 1 }}
|
||||
exit={{ pathLength: 0 }}
|
||||
transition={{
|
||||
type: "tween",
|
||||
duration: 0.3,
|
||||
ease: isVisible ? "easeOut" : "easeIn",
|
||||
}}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="m6 6 12 12"
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
|
@ -681,4 +681,4 @@ export const LANGFLOW_SUPPORTED_TYPES = new Set([
|
|||
export const priorityFields = new Set(["code", "template"]);
|
||||
|
||||
export const INPUT_TYPES = new Set(["ChatInput", "TextInput"]);
|
||||
export const OUTPUT_TYPES = new Set(["ChatOutput"]);
|
||||
export const OUTPUT_TYPES = new Set(["ChatOutput", "TextOutput"]);
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ export enum BuildStatus {
|
|||
BUILDING = "BUILDING",
|
||||
TO_BUILD = "TO_BUILD",
|
||||
BUILT = "BUILT",
|
||||
INACTIVE = "INACTIVE",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import useAlertStore from "../../stores/alertStore";
|
|||
import { ApiKeyType } from "../../types/components";
|
||||
import { nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
import { COPIED_NOTICE_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function SecretKeyModal({
|
||||
title,
|
||||
|
|
@ -47,7 +48,7 @@ export default function SecretKeyModal({
|
|||
inputRef?.current?.focus();
|
||||
inputRef?.current?.select();
|
||||
setSuccessData({
|
||||
title: "API Key copied!",
|
||||
title: COPIED_NOTICE_ALERT,
|
||||
});
|
||||
setTextCopied(false);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import useAlertStore from "../../stores/alertStore";
|
|||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import { StoreApiKeyType } from "../../types/components";
|
||||
import BaseModal from "../baseModal";
|
||||
import { API_ERROR_ALERT, API_SUCCESS_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function StoreApiKeyModal({
|
||||
children,
|
||||
|
|
@ -32,7 +33,7 @@ export default function StoreApiKeyModal({
|
|||
addApiKeyStore(apiKeyValue).then(
|
||||
() => {
|
||||
setSuccessData({
|
||||
title: "Success! Your API Key has been saved.",
|
||||
title: API_SUCCESS_ALERT,
|
||||
});
|
||||
storeApiKey(apiKeyValue);
|
||||
setOpen(false);
|
||||
|
|
@ -42,7 +43,7 @@ export default function StoreApiKeyModal({
|
|||
},
|
||||
(error) => {
|
||||
setErrorData({
|
||||
title: "There was an error saving the API Key, please try again.",
|
||||
title: API_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
setHasApiKey(false);
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ type TriggerProps = {
|
|||
};
|
||||
|
||||
const Content: React.FC<ContentProps> = ({ children }) => {
|
||||
return <div className="h-full w-full">{children}</div>;
|
||||
return <div className="h-full w-full flex flex-col">{children}</div>;
|
||||
};
|
||||
const Trigger: React.FC<TriggerProps> = ({ children, asChild, disable }) => {
|
||||
return (
|
||||
|
|
@ -66,6 +66,7 @@ interface BaseModalProps {
|
|||
| "small"
|
||||
| "medium"
|
||||
| "large"
|
||||
| "large-thin"
|
||||
| "large-h-full"
|
||||
| "small-h-full"
|
||||
| "medium-h-full"
|
||||
|
|
@ -128,6 +129,10 @@ function BaseModal({
|
|||
minWidth = "min-w-[80vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
case "large-thin":
|
||||
minWidth = "min-w-[65vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
case "large-h-full":
|
||||
minWidth = "min-w-[80vw]";
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useDarkStore } from "../../stores/darkStore";
|
|||
import useFlowStore from "../../stores/flowStore";
|
||||
import { codeAreaModalPropsType } from "../../types/components";
|
||||
import BaseModal from "../baseModal";
|
||||
import { BUG_ALERT, CODE_ERROR_ALERT, CODE_SUCCESS_ALERT, FUNC_ERROR_ALERT, IMPORT_ERROR_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function CodeAreaModal({
|
||||
value,
|
||||
|
|
@ -58,7 +59,7 @@ export default function CodeAreaModal({
|
|||
let funcErrors = apiReturn.data.function.errors;
|
||||
if (funcErrors.length === 0 && importsErrors.length === 0) {
|
||||
setSuccessData({
|
||||
title: "Code is ready to run",
|
||||
title: CODE_SUCCESS_ALERT,
|
||||
});
|
||||
setOpen(false);
|
||||
setValue(code);
|
||||
|
|
@ -66,26 +67,26 @@ export default function CodeAreaModal({
|
|||
} else {
|
||||
if (funcErrors.length !== 0) {
|
||||
setErrorData({
|
||||
title: "There is an error in your function",
|
||||
title: FUNC_ERROR_ALERT,
|
||||
list: funcErrors,
|
||||
});
|
||||
}
|
||||
if (importsErrors.length !== 0) {
|
||||
setErrorData({
|
||||
title: "There is an error in your imports",
|
||||
title: IMPORT_ERROR_ALERT,
|
||||
list: importsErrors,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Something went wrong, please try again",
|
||||
title: BUG_ALERT,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((_) => {
|
||||
setErrorData({
|
||||
title: "There is something wrong with this code, please review it",
|
||||
title: CODE_ERROR_ALERT,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { useDarkStore } from "../../stores/darkStore";
|
|||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { downloadFlow, removeApiKeys } from "../../utils/reactflowUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
import { API_WARNING_NOTICE_ALERT } from "../../alerts_constants";
|
||||
|
||||
const ExportModal = forwardRef(
|
||||
(props: { children: ReactNode }, ref): JSX.Element => {
|
||||
|
|
@ -78,7 +79,7 @@ const ExportModal = forwardRef(
|
|||
);
|
||||
setNoticeData({
|
||||
title:
|
||||
"Warning: Critical data, JSON file may include API keys.",
|
||||
API_WARNING_NOTICE_ALERT,
|
||||
});
|
||||
} else
|
||||
downloadFlow(
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import useAlertStore from "../../stores/alertStore";
|
|||
import useFlowStore from "../../stores/flowStore";
|
||||
import { FlowState } from "../../types/tabs";
|
||||
import { validateNodes } from "../../utils/reactflowUtils";
|
||||
import { CHAT_ERROR_ALERT, INFO_MISSING_ALERT, MSG_ERROR_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function FormModal({
|
||||
flow,
|
||||
|
|
@ -169,7 +170,7 @@ export default function FormModal({
|
|||
connectWS();
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Please build the flow again before using the chat.",
|
||||
title: CHAT_ERROR_ALERT,
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
@ -356,7 +357,7 @@ export default function FormModal({
|
|||
}
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "There was an error sending the message",
|
||||
title: MSG_ERROR_ALERT,
|
||||
list: [(error as { message: string }).message],
|
||||
});
|
||||
setChatValue(data.inputs);
|
||||
|
|
@ -401,7 +402,7 @@ export default function FormModal({
|
|||
}
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed some required information:",
|
||||
title: INFO_MISSING_ALERT,
|
||||
list: nodeValidationErrors,
|
||||
});
|
||||
}
|
||||
|
|
@ -576,6 +577,7 @@ export default function FormModal({
|
|||
chatHistory.length - 1 === index ? true : false
|
||||
}
|
||||
key={index}
|
||||
updateChat={() => {}}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { genericModalPropsType } from "../../types/components";
|
|||
import { handleKeyDown } from "../../utils/reactflowUtils";
|
||||
import { classNames, varHighlightHTML } from "../../utils/utils";
|
||||
import BaseModal from "../baseModal";
|
||||
import { BUG_ALERT, PROMPT_ERROR_ALERT, PROMPT_SUCCESS_ALERT, TEMP_NOTICE_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function GenericModal({
|
||||
field_name = "",
|
||||
|
|
@ -133,17 +134,17 @@ export default function GenericModal({
|
|||
}
|
||||
if (!inputVariables || inputVariables.length === 0) {
|
||||
setNoticeData({
|
||||
title: "Your template does not have any variables.",
|
||||
title: TEMP_NOTICE_ALERT,
|
||||
});
|
||||
} else {
|
||||
setSuccessData({
|
||||
title: "Prompt is ready",
|
||||
title: PROMPT_SUCCESS_ALERT,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setIsEdit(true);
|
||||
setErrorData({
|
||||
title: "Something went wrong, please try again",
|
||||
title: BUG_ALERT,
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
@ -151,7 +152,7 @@ export default function GenericModal({
|
|||
console.log(error);
|
||||
setIsEdit(true);
|
||||
return setErrorData({
|
||||
title: "There is something wrong with this prompt, please review it",
|
||||
title: PROMPT_ERROR_ALERT,
|
||||
list: [error.toString()],
|
||||
});
|
||||
});
|
||||
|
|
@ -182,7 +183,7 @@ export default function GenericModal({
|
|||
{myModalTitle}
|
||||
</span>
|
||||
<IconComponent
|
||||
name="FileText"
|
||||
name={myModalTitle === "Edit Prompt" ? "TerminalSquare" : "FileText"}
|
||||
className="h-6 w-6 pl-1 text-primary "
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
|
@ -255,7 +256,7 @@ export default function GenericModal({
|
|||
>
|
||||
<div className="flex flex-wrap items-center">
|
||||
<IconComponent
|
||||
name="Variable"
|
||||
name="Braces"
|
||||
className=" -ml-px mr-1 flex h-4 w-4 text-primary"
|
||||
/>
|
||||
<span className="text-md font-semibold text-primary">
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
inputHandlerEventType,
|
||||
loginInputStateType,
|
||||
} from "../../../types/components";
|
||||
import { SIGNIN_ERROR_ALERT } from "../../../alerts_constants";
|
||||
|
||||
export default function LoginAdminPage() {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -39,7 +40,7 @@ export default function LoginAdminPage() {
|
|||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error signing in",
|
||||
title: SIGNIN_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import useAlertStore from "../../stores/alertStore";
|
|||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { Users } from "../../types/api";
|
||||
import { UserInputType } from "../../types/components";
|
||||
import { USER_ADD_ERROR_ALERT, USER_ADD_SUCCESS_ALERT, USER_DEL_ERROR_ALERT, USER_DEL_SUCCESS_ALERT, USER_EDIT_ERROR_ALERT, USER_EDIT_SUCCESS_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function AdminPage() {
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
|
|
@ -117,12 +118,12 @@ export default function AdminPage() {
|
|||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User deleted!",
|
||||
title: USER_DEL_SUCCESS_ALERT,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on delete user",
|
||||
title: USER_DEL_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
|
|
@ -133,12 +134,12 @@ export default function AdminPage() {
|
|||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User edited!",
|
||||
title: USER_EDIT_SUCCESS_ALERT,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on edit user",
|
||||
title: USER_EDIT_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
|
|
@ -152,12 +153,12 @@ export default function AdminPage() {
|
|||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User edited!",
|
||||
title: USER_EDIT_SUCCESS_ALERT,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on edit user",
|
||||
title: USER_EDIT_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
|
|
@ -170,12 +171,12 @@ export default function AdminPage() {
|
|||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! User edited!",
|
||||
title: USER_EDIT_SUCCESS_ALERT,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on edit user",
|
||||
title: USER_EDIT_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
|
|
@ -190,13 +191,13 @@ export default function AdminPage() {
|
|||
}).then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! New user added!",
|
||||
title: USER_ADD_SUCCESS_ALERT,
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error when adding new user",
|
||||
title: USER_ADD_ERROR_ALERT,
|
||||
list: [error.response.data.detail],
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "../../constants/constants";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { ApiKey } from "../../types/components";
|
||||
import { DEL_KEY_ERROR_ALERT, DEL_KEY_SUCCESS_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function ApiKeysPage() {
|
||||
const [loadingKeys, setLoadingKeys] = useState(true);
|
||||
|
|
@ -63,12 +64,12 @@ export default function ApiKeysPage() {
|
|||
.then((res) => {
|
||||
resetFilter();
|
||||
setSuccessData({
|
||||
title: "Success! Key deleted!",
|
||||
title: DEL_KEY_SUCCESS_ALERT,
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error on delete key",
|
||||
title: DEL_KEY_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@ import {
|
|||
generateNodeFromFlow,
|
||||
getNodeId,
|
||||
isValidConnection,
|
||||
reconnectEdges,
|
||||
validateSelection,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { getRandomName, isWrappedWithClass } from "../../../../utils/utils";
|
||||
import ConnectionLineComponent from "../ConnectionLineComponent";
|
||||
import SelectionMenu from "../SelectionMenuComponent";
|
||||
import ExtraSidebar from "../extraSidebarComponent";
|
||||
import { INVALID_SELECTION_ERROR_ALERT, UPLOAD_ALERT_LIST, UPLOAD_ERROR_ALERT, WRONG_FILE_ERROR_ALERT } from "../../../../alerts_constants";
|
||||
|
||||
const nodeTypes = {
|
||||
genericNode: GenericNode,
|
||||
|
|
@ -256,14 +258,14 @@ export default function Page({
|
|||
position: position,
|
||||
}).catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error uploading file",
|
||||
title: UPLOAD_ERROR_ALERT,
|
||||
list: [error],
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Invalid file type",
|
||||
list: ["Please upload a JSON file"],
|
||||
title: WRONG_FILE_ERROR_ALERT,
|
||||
list: [UPLOAD_ALERT_LIST],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -394,7 +396,7 @@ export default function Page({
|
|||
if (
|
||||
validateSelection(lastSelection!, edges).length === 0
|
||||
) {
|
||||
const { newFlow } = generateFlow(
|
||||
const { newFlow, removedEdges } = generateFlow(
|
||||
lastSelection!,
|
||||
nodes,
|
||||
edges,
|
||||
|
|
@ -404,6 +406,10 @@ export default function Page({
|
|||
newFlow,
|
||||
getNodeId
|
||||
);
|
||||
const newEdges = reconnectEdges(
|
||||
newGroupNode,
|
||||
removedEdges
|
||||
);
|
||||
setNodes((oldNodes) => [
|
||||
...oldNodes.filter(
|
||||
(oldNodes) =>
|
||||
|
|
@ -414,19 +420,20 @@ export default function Page({
|
|||
),
|
||||
newGroupNode,
|
||||
]);
|
||||
setEdges((oldEdges) =>
|
||||
oldEdges.filter(
|
||||
setEdges((oldEdges) => [
|
||||
...oldEdges.filter(
|
||||
(oldEdge) =>
|
||||
!lastSelection!.nodes.some(
|
||||
(selectionNode) =>
|
||||
selectionNode.id === oldEdge.target ||
|
||||
selectionNode.id === oldEdge.source
|
||||
)
|
||||
)
|
||||
);
|
||||
),
|
||||
...newEdges,
|
||||
]);
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Invalid selection",
|
||||
title: INVALID_SELECTION_ERROR_ALERT,
|
||||
list: validateSelection(lastSelection!, edges),
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from "../../../../utils/utils";
|
||||
import DisclosureComponent from "../DisclosureComponent";
|
||||
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../alerts_constants";
|
||||
|
||||
export default function ExtraSidebar(): JSX.Element {
|
||||
const data = useTypesStore((state) => state.data);
|
||||
|
|
@ -255,7 +256,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
uploadFlow({ newProject: false, isComponent: false }).catch(
|
||||
(error) => {
|
||||
setErrorData({
|
||||
title: "Error uploading file",
|
||||
title: UPLOAD_ERROR_ALERT,
|
||||
list: [error],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ import {
|
|||
expandGroupNode,
|
||||
updateFlowPosition,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
import { classNames, cn } from "../../../../utils/utils";
|
||||
import { useUpdateNodeInternals } from "reactflow";
|
||||
|
||||
export default function NodeToolbarComponent({
|
||||
data,
|
||||
|
|
@ -60,6 +61,7 @@ export default function NodeToolbarComponent({
|
|||
const isMinimal = numberOfHandles <= 1;
|
||||
const isGroup = data.node?.flow ? true : false;
|
||||
|
||||
const pinned = data.node?.pinned ?? false;
|
||||
const paste = useFlowStore((state) => state.paste);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
|
|
@ -85,6 +87,8 @@ export default function NodeToolbarComponent({
|
|||
onCloseAdvancedModal!(false);
|
||||
}
|
||||
}, [showModalAdvanced]);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
||||
|
|
@ -107,6 +111,9 @@ export default function NodeToolbarComponent({
|
|||
takeSnapshot();
|
||||
setShowNode(data.showNode ?? true ? false : true);
|
||||
break;
|
||||
case "Share":
|
||||
if (hasApiKey || hasStore) setShowconfirmShare(true);
|
||||
break;
|
||||
case "Download":
|
||||
downloadNode(flowComponent!);
|
||||
break;
|
||||
|
|
@ -187,6 +194,7 @@ export default function NodeToolbarComponent({
|
|||
|
||||
return newNode;
|
||||
});
|
||||
updateNodeInternals(data.id);
|
||||
};
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
|
@ -222,7 +230,7 @@ export default function NodeToolbarComponent({
|
|||
id={"code-input-node-toolbar-" + name}
|
||||
/>
|
||||
</div>
|
||||
<IconComponent name="Code2" className="h-4 w-4" />
|
||||
<IconComponent name="Code" className="h-4 w-4" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
) : (
|
||||
|
|
@ -269,22 +277,35 @@ export default function NodeToolbarComponent({
|
|||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
{hasStore && (
|
||||
<ShadTooltip content="Share" 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",
|
||||
!hasApiKey || !validApiKey ? " text-muted-foreground" : ""
|
||||
|
||||
<ShadTooltip content="Pin" 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();
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: {
|
||||
...old.data.node,
|
||||
pinned: old.data?.node?.pinned ? false : true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="Pin"
|
||||
className={cn(
|
||||
"h-4 w-4 transition-all",
|
||||
pinned ? "animate-wiggle fill-current" : ""
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
if (hasApiKey || hasStore) setShowconfirmShare(true);
|
||||
}}
|
||||
>
|
||||
<IconComponent name="Share3" className="-m-1 h-6 w-6" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
|
||||
<Select onValueChange={handleSelectChange} value="">
|
||||
<ShadTooltip content="More" side="top">
|
||||
|
|
@ -340,6 +361,20 @@ export default function NodeToolbarComponent({
|
|||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
{hasStore && (
|
||||
<SelectItem
|
||||
value={"Share"}
|
||||
disabled={!hasApiKey || !validApiKey}
|
||||
>
|
||||
<div className="flex" data-testid="save-button-modal">
|
||||
<IconComponent
|
||||
name="Share3"
|
||||
className="relative top-0.5 -m-1 mr-1 h-6 w-6"
|
||||
/>{" "}
|
||||
Share{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
)}
|
||||
{!hasStore && (
|
||||
<SelectItem value={"Download"}>
|
||||
<div className="flex">
|
||||
|
|
@ -360,9 +395,7 @@ export default function NodeToolbarComponent({
|
|||
name="FileText"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
{data.node?.documentation === ""
|
||||
? "Coming Soon"
|
||||
: "Documentation"}
|
||||
Docs
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
{isMinimal && (
|
||||
|
|
@ -388,13 +421,19 @@ export default function NodeToolbarComponent({
|
|||
</SelectItem>
|
||||
)}
|
||||
|
||||
<SelectItem value={"delete"}>
|
||||
<div className="font-red flex text-red-500 hover:text-red-500">
|
||||
<SelectItem value={"delete"} className="focus:bg-red-400/[.20]">
|
||||
<div className="font-red flex text-status-red">
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||
/>{" "}
|
||||
Delete{" "}
|
||||
<span className="">Delete</span>{" "}
|
||||
<span>
|
||||
<IconComponent
|
||||
name="Delete"
|
||||
className="absolute right-2 top-2 h-4 w-4 text-red-300"
|
||||
></IconComponent>
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import { Button } from "../../../../components/ui/button";
|
|||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { FlowType } from "../../../../types/flow";
|
||||
import { CONSOLE_ERROR_MSG, UPLOAD_ALERT_LIST, WRONG_FILE_ERROR_ALERT } from "../../../../alerts_constants";
|
||||
|
||||
export default function ComponentsComponent({
|
||||
is_component = true,
|
||||
|
|
@ -76,14 +77,14 @@ export default function ComponentsComponent({
|
|||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error uploading file",
|
||||
title: CONSOLE_ERROR_MSG,
|
||||
list: [error],
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setErrorData({
|
||||
title: "Invalid file type",
|
||||
list: ["Please upload a JSON file"],
|
||||
title: WRONG_FILE_ERROR_ALERT,
|
||||
list: [UPLOAD_ALERT_LIST],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
|||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { downloadFlows } from "../../utils/reactflowUtils";
|
||||
import { CONSOLE_ERROR_MSG } from "../../alerts_constants";
|
||||
export default function HomePage(): JSX.Element {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
|
|
@ -40,7 +41,7 @@ export default function HomePage(): JSX.Element {
|
|||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error uploading file",
|
||||
title: CONSOLE_ERROR_MSG,
|
||||
list: [error],
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
patchUserInputStateType,
|
||||
} from "../../types/components";
|
||||
import { gradients } from "../../utils/styleUtils";
|
||||
import { EDIT_PASSWORD_ALERT_LIST, EDIT_PASSWORD_ERROR_ALERT, SAVE_ERROR_ALERT, SAVE_SUCCESS_ALERT } from "../../alerts_constants";
|
||||
export default function ProfileSettingsPage(): JSX.Element {
|
||||
const setCurrentFlowId = useFlowsManagerStore(
|
||||
(state) => state.setCurrentFlowId
|
||||
|
|
@ -37,8 +38,8 @@ export default function ProfileSettingsPage(): JSX.Element {
|
|||
async function handlePatchUser() {
|
||||
if (password !== cnfPassword) {
|
||||
setErrorData({
|
||||
title: "Error changing password",
|
||||
list: ["Passwords do not match"],
|
||||
title: EDIT_PASSWORD_ERROR_ALERT,
|
||||
list: [EDIT_PASSWORD_ALERT_LIST],
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -54,10 +55,10 @@ export default function ProfileSettingsPage(): JSX.Element {
|
|||
}
|
||||
handleInput({ target: { name: "password", value: "" } });
|
||||
handleInput({ target: { name: "cnfPassword", value: "" } });
|
||||
setSuccessData({ title: "Changes saved successfully!" });
|
||||
setSuccessData({ title: SAVE_SUCCESS_ALERT });
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: "Error saving changes",
|
||||
title: SAVE_ERROR_ALERT,
|
||||
list: [(error as any).response.data.detail],
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,17 +21,14 @@ import {
|
|||
SelectValue,
|
||||
} from "../../components/ui/select";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import {
|
||||
checkHasApiKey,
|
||||
getStoreComponents,
|
||||
getStoreTags,
|
||||
} from "../../controllers/API";
|
||||
import { getStoreComponents, getStoreTags } from "../../controllers/API";
|
||||
import StoreApiKeyModal from "../../modals/StoreApiKeyModal";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import { storeComponent } from "../../types/store";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { APIKEY_ERROR_ALERT, COMPONENTS_ERROR_ALERT, INVALID_API_ERROR_ALERT, NOAPI_ERROR_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function StorePage(): JSX.Element {
|
||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||
|
|
@ -39,8 +36,6 @@ export default function StorePage(): JSX.Element {
|
|||
const loadingApiKey = useStoreStore((state) => state.loadingApiKey);
|
||||
|
||||
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
|
||||
const setLoadingApiKey = useStoreStore((state) => state.updateLoadingApiKey);
|
||||
const setHasApiKey = useStoreStore((state) => state.updateHasApiKey);
|
||||
|
||||
const { apiKey } = useContext(AuthContext);
|
||||
|
||||
|
|
@ -48,6 +43,7 @@ export default function StorePage(): JSX.Element {
|
|||
const setCurrentFlowId = useFlowsManagerStore(
|
||||
(state) => state.setCurrentFlowId
|
||||
);
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [loadingTags, setLoadingTags] = useState(true);
|
||||
const { id } = useParams();
|
||||
|
|
@ -63,32 +59,29 @@ export default function StorePage(): JSX.Element {
|
|||
const [searchNow, setSearchNow] = useState("");
|
||||
const [selectFilter, setSelectFilter] = useState("all");
|
||||
|
||||
useEffect(() => {
|
||||
handleGetTags();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingApiKey) {
|
||||
if (!hasApiKey) {
|
||||
setErrorData({
|
||||
title: "API Key Error",
|
||||
title: APIKEY_ERROR_ALERT,
|
||||
list: [
|
||||
"You don't have an API Key. Please add one to use the Langflow Store.",
|
||||
NOAPI_ERROR_ALERT,
|
||||
],
|
||||
});
|
||||
setLoading(false);
|
||||
} else if (!validApiKey) {
|
||||
setErrorData({
|
||||
title: "API Key Error",
|
||||
title: APIKEY_ERROR_ALERT,
|
||||
list: [
|
||||
"Your API Key is not valid. Please add a valid API Key to use the Langflow Store.",
|
||||
INVALID_API_ERROR_ALERT,
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [loadingApiKey, validApiKey, hasApiKey]);
|
||||
}, [loadingApiKey, validApiKey, hasApiKey, currentFlowId]);
|
||||
|
||||
useEffect(() => {
|
||||
handleGetTags();
|
||||
handleGetComponents();
|
||||
}, [
|
||||
tabActive,
|
||||
|
|
@ -119,7 +112,7 @@ export default function StorePage(): JSX.Element {
|
|||
}
|
||||
|
||||
function handleGetComponents() {
|
||||
if (!hasApiKey || loadingApiKey) return;
|
||||
if (loadingApiKey) return;
|
||||
setLoading(true);
|
||||
getStoreComponents({
|
||||
component_id: id,
|
||||
|
|
@ -159,7 +152,7 @@ export default function StorePage(): JSX.Element {
|
|||
setTotalRowsCount(0);
|
||||
setLoading(false);
|
||||
setErrorData({
|
||||
title: "Error getting components.",
|
||||
title: COMPONENTS_ERROR_ALERT,
|
||||
list: [err["response"]["data"]["detail"]],
|
||||
});
|
||||
}
|
||||
|
|
@ -176,23 +169,6 @@ export default function StorePage(): JSX.Element {
|
|||
setPageSize(12);
|
||||
}
|
||||
|
||||
const fetchApiData = async () => {
|
||||
setLoadingApiKey(true);
|
||||
try {
|
||||
const res = await checkHasApiKey();
|
||||
setHasApiKey(res?.has_api_key ?? false);
|
||||
setValidApiKey(res?.is_valid ?? false);
|
||||
setLoadingApiKey(false);
|
||||
} catch (e) {
|
||||
setLoadingApiKey(false);
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchApiData();
|
||||
}, [apiKey]);
|
||||
|
||||
return (
|
||||
<PageLayout
|
||||
betaIcon
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
inputHandlerEventType,
|
||||
loginInputStateType,
|
||||
} from "../../types/components";
|
||||
import { SIGNIN_ERROR_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function LoginPage(): JSX.Element {
|
||||
const [inputState, setInputState] =
|
||||
|
|
@ -42,7 +43,7 @@ export default function LoginPage(): JSX.Element {
|
|||
})
|
||||
.catch((error) => {
|
||||
setErrorData({
|
||||
title: "Error signing in",
|
||||
title: SIGNIN_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
inputHandlerEventType,
|
||||
signUpInputStateType,
|
||||
} from "../../types/components";
|
||||
import { SIGNUP_ERROR_ALERT } from "../../alerts_constants";
|
||||
|
||||
export default function SignUp(): JSX.Element {
|
||||
const [inputState, setInputState] =
|
||||
|
|
@ -60,7 +61,7 @@ export default function SignUp(): JSX.Element {
|
|||
},
|
||||
} = error;
|
||||
setErrorData({
|
||||
title: "Error signing up",
|
||||
title: SIGNUP_ERROR_ALERT,
|
||||
list: [detail],
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import { create } from "zustand";
|
|||
import { getRepoStars, getVersion } from "../controllers/API";
|
||||
import { DarkStoreType } from "../types/zustand/dark";
|
||||
|
||||
const startedStars = Number(window.localStorage.getItem("githubStars")) ?? 0;
|
||||
|
||||
export const useDarkStore = create<DarkStoreType>((set) => ({
|
||||
dark: JSON.parse(window.localStorage.getItem("isDark")!) ?? false,
|
||||
stars: 0,
|
||||
stars: startedStars,
|
||||
version: "",
|
||||
setDark: (dark) => set(() => ({ dark: dark })),
|
||||
refreshVersion: () => {
|
||||
|
|
@ -13,7 +15,13 @@ export const useDarkStore = create<DarkStoreType>((set) => ({
|
|||
});
|
||||
},
|
||||
refreshStars: () => {
|
||||
if (window.localStorage.getItem("githubStars") !== null) {
|
||||
set(() => ({ stars: startedStars }));
|
||||
return;
|
||||
}
|
||||
|
||||
getRepoStars("logspace-ai", "langflow").then((res) => {
|
||||
window.localStorage.setItem("githubStars", res.toString());
|
||||
set(() => ({ stars: res }));
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,8 +9,10 @@ import {
|
|||
applyNodeChanges,
|
||||
} from "reactflow";
|
||||
import { create } from "zustand";
|
||||
import { FLOW_BUILD_SUCCESS_ALERT } from "../alerts_constants";
|
||||
import { BuildStatus } from "../constants/enums";
|
||||
import { getFlowPool, updateFlowInDatabase } from "../controllers/API";
|
||||
import { VertexBuildTypeAPI } from "../types/api";
|
||||
import {
|
||||
NodeDataType,
|
||||
NodeType,
|
||||
|
|
@ -25,6 +27,7 @@ import {
|
|||
getNodeId,
|
||||
scapeJSONParse,
|
||||
scapedJSONStringfy,
|
||||
validateNodes,
|
||||
} from "../utils/reactflowUtils";
|
||||
import { getInputsAndOutputs } from "../utils/storeUtils";
|
||||
import useAlertStore from "./alertStore";
|
||||
|
|
@ -34,6 +37,7 @@ import useFlowsManagerStore from "./flowsManagerStore";
|
|||
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
||||
const useFlowStore = create<FlowStoreType>((set, get) => ({
|
||||
flowState: undefined,
|
||||
flowBuildStatus: {},
|
||||
nodes: [],
|
||||
edges: [],
|
||||
isBuilding: false,
|
||||
|
|
@ -151,7 +155,6 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
? change(get().nodes.find((node) => node.id === id)!)
|
||||
: change;
|
||||
|
||||
console.log(newChange);
|
||||
get().setNodes((oldNodes) =>
|
||||
oldNodes.map((node) => {
|
||||
if (node.id === id) {
|
||||
|
|
@ -375,17 +378,29 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
const setSuccessData = useAlertStore.getState().setSuccessData;
|
||||
const setErrorData = useAlertStore.getState().setErrorData;
|
||||
const setNoticeData = useAlertStore.getState().setNoticeData;
|
||||
function handleBuildUpdate(data: any) {
|
||||
const responseData = data.data[data.id];
|
||||
// responseData MAY contain inactive_vertices or it
|
||||
// may be null, so we need to check for it
|
||||
if (responseData && responseData.inactive_vertices) {
|
||||
useFlowStore
|
||||
.getState()
|
||||
.setInactiveNodes(responseData.inactive_vertices);
|
||||
function validateSubgraph(nodes: string[]) {
|
||||
const errors = validateNodes(
|
||||
get().nodes.filter((node) => nodes.includes(node.id)),
|
||||
get().edges
|
||||
);
|
||||
if (errors.length > 0) {
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed something",
|
||||
list: errors,
|
||||
});
|
||||
get().setIsBuilding(false);
|
||||
throw new Error("Invalid nodes");
|
||||
}
|
||||
get().addDataToFlowPool(data.data[data.id], data.id);
|
||||
useFlowStore.getState().updateBuildStatus([data.id], BuildStatus.BUILT);
|
||||
}
|
||||
function handleBuildUpdate(
|
||||
vertexBuildData: VertexBuildTypeAPI,
|
||||
status: BuildStatus
|
||||
) {
|
||||
if (vertexBuildData && vertexBuildData.inactive_vertices) {
|
||||
get().removeFromVerticesBuild(vertexBuildData.inactive_vertices);
|
||||
}
|
||||
get().addDataToFlowPool(vertexBuildData, vertexBuildData.id);
|
||||
useFlowStore.getState().updateBuildStatus([vertexBuildData.id], status);
|
||||
}
|
||||
await updateFlowInDatabase({
|
||||
data: {
|
||||
|
|
@ -397,15 +412,22 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
name: currentFlow!.name,
|
||||
description: currentFlow!.description,
|
||||
});
|
||||
setNoticeData({ title: "Running components" });
|
||||
await buildVertices({
|
||||
flowId: currentFlow!.id,
|
||||
nodeId,
|
||||
onGetOrderSuccess: () => {
|
||||
setNoticeData({ title: "Running components" });
|
||||
},
|
||||
onBuildComplete: () => {
|
||||
if (nodeId) {
|
||||
setSuccessData({ title: `${nodeId} built successfully` });
|
||||
setSuccessData({
|
||||
title: `${
|
||||
get().nodes.find((node) => node.id === nodeId)?.data.node
|
||||
?.display_name
|
||||
} built successfully`,
|
||||
});
|
||||
} else {
|
||||
setSuccessData({ title: `Flow built successfully` });
|
||||
setSuccessData({ title: FLOW_BUILD_SUCCESS_ALERT });
|
||||
}
|
||||
get().setIsBuilding(false);
|
||||
},
|
||||
|
|
@ -417,7 +439,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
onBuildStart: (idList) => {
|
||||
useFlowStore.getState().updateBuildStatus(idList, BuildStatus.BUILDING);
|
||||
},
|
||||
validateNodes: validateSubgraph,
|
||||
});
|
||||
get().revertBuiltStatusFromBuilding();
|
||||
},
|
||||
getFlow: () => {
|
||||
return {
|
||||
|
|
@ -426,23 +450,33 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
viewport: get().reactFlowInstance?.getViewport()!,
|
||||
};
|
||||
},
|
||||
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
|
||||
nodeIdList.forEach((id) => {
|
||||
const nodeToUpdate = get().nodes.find((node) => node.id === id);
|
||||
if (nodeToUpdate) {
|
||||
nodeToUpdate.data.build_status = status;
|
||||
get().setNodes(get().nodes);
|
||||
}
|
||||
});
|
||||
},
|
||||
updateVerticesBuild: (vertices: string[]) => {
|
||||
set({ verticesBuild: vertices });
|
||||
},
|
||||
verticesBuild: [],
|
||||
setInactiveNodes(newState) {
|
||||
set({ inactiveNodes: newState });
|
||||
|
||||
removeFromVerticesBuild: (vertices: string[]) => {
|
||||
set({
|
||||
verticesBuild: get().verticesBuild.filter(
|
||||
(vertex) => !vertices.includes(vertex)
|
||||
),
|
||||
});
|
||||
},
|
||||
updateBuildStatus: (nodeIdList: string[], status: BuildStatus) => {
|
||||
const newFlowBuildStatus = { ...get().flowBuildStatus };
|
||||
nodeIdList.forEach((id) => {
|
||||
newFlowBuildStatus[id] = status;
|
||||
});
|
||||
set({ flowBuildStatus: newFlowBuildStatus });
|
||||
},
|
||||
revertBuiltStatusFromBuilding: () => {
|
||||
const newFlowBuildStatus = { ...get().flowBuildStatus };
|
||||
Object.keys(newFlowBuildStatus).forEach((id) => {
|
||||
if (newFlowBuildStatus[id] === BuildStatus.BUILDING) {
|
||||
newFlowBuildStatus[id] = BuildStatus.BUILT;
|
||||
}
|
||||
});
|
||||
},
|
||||
inactiveNodes: [],
|
||||
}));
|
||||
|
||||
export default useFlowStore;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { create } from "zustand";
|
||||
import { checkHasStore } from "../controllers/API";
|
||||
import { checkHasApiKey, checkHasStore } from "../controllers/API";
|
||||
import { StoreStoreType } from "../types/zustand/store";
|
||||
|
||||
export const useStoreStore = create<StoreStoreType>((set) => ({
|
||||
|
|
@ -7,13 +7,27 @@ export const useStoreStore = create<StoreStoreType>((set) => ({
|
|||
validApiKey: false,
|
||||
hasApiKey: false,
|
||||
loadingApiKey: true,
|
||||
updateHasStore: (hasStore) => set(() => ({ hasStore: hasStore })),
|
||||
checkHasStore: () => {
|
||||
checkHasStore().then((res) => {
|
||||
set({ hasStore: res?.enabled ?? false });
|
||||
});
|
||||
},
|
||||
updateValidApiKey: (validApiKey) => set(() => ({ validApiKey: validApiKey })),
|
||||
updateLoadingApiKey: (loadingApiKey) =>
|
||||
set(() => ({ loadingApiKey: loadingApiKey })),
|
||||
updateHasApiKey: (hasApiKey) => set(() => ({ hasApiKey: hasApiKey })),
|
||||
fetchApiData: async () => {
|
||||
set({ loadingApiKey: true });
|
||||
try {
|
||||
const res = await checkHasApiKey();
|
||||
set({
|
||||
validApiKey: res?.is_valid ?? false,
|
||||
hasApiKey: res?.has_api_key ?? false,
|
||||
loadingApiKey: false,
|
||||
});
|
||||
} catch (e) {
|
||||
set({ loadingApiKey: false });
|
||||
console.log(e);
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
checkHasStore().then((res) => {
|
||||
useStoreStore.setState({ hasStore: res?.enabled ?? false });
|
||||
});
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@
|
|||
@apply fixed bottom-4 right-4;
|
||||
}
|
||||
.message-button-icon {
|
||||
@apply fill-chat-trigger stroke-chat-trigger stroke-1;
|
||||
@apply fill-medium-indigo stroke-medium-indigo stroke-1;
|
||||
}
|
||||
.disabled-message-button-icon {
|
||||
@apply fill-chat-trigger-disabled stroke-chat-trigger-disabled stroke-1;
|
||||
|
|
@ -276,7 +276,7 @@
|
|||
@apply h-10 w-10 rounded p-1;
|
||||
}
|
||||
.generic-node-tooltip-div {
|
||||
@apply ml-2 truncate;
|
||||
@apply ml-2 max-w-[220px] truncate;
|
||||
}
|
||||
.generic-node-validation-div {
|
||||
@apply max-h-96 overflow-auto;
|
||||
|
|
@ -290,17 +290,34 @@
|
|||
@apply hidden h-4 w-4 animate-spin rounded-full bg-ring opacity-0;
|
||||
}
|
||||
.generic-node-status {
|
||||
@apply opacity-100 animate-wiggle;
|
||||
@apply animate-wiggle opacity-100;
|
||||
}
|
||||
.green-status {
|
||||
@apply generic-node-status text-status-green;
|
||||
}
|
||||
.gray-status {
|
||||
@apply generic-node-status text-status-gray;
|
||||
}
|
||||
|
||||
.red-status {
|
||||
@apply generic-node-status text-status-red;
|
||||
}
|
||||
.yellow-status {
|
||||
@apply generic-node-status text-status-yellow;
|
||||
}
|
||||
.inactive-status {
|
||||
/* what colour for inactive status?
|
||||
muted-foreground is too strong, maybe use a lighter shade of it?
|
||||
|
||||
*/
|
||||
@apply border-none ring ring-muted-foreground;
|
||||
}
|
||||
.built-invalid-status {
|
||||
@apply border-none ring ring-[#FF9090];
|
||||
}
|
||||
.building-status {
|
||||
@apply border-none ring;
|
||||
}
|
||||
.status-build-animation {
|
||||
@apply opacity-0;
|
||||
}
|
||||
|
|
@ -828,7 +845,7 @@
|
|||
@apply flex h-full max-w-full flex-col overflow-hidden rounded-md border bg-muted text-center sm:w-[75vw] md:w-[75vw] lg:w-[75vw] xl:w-[76vw] 2xl:w-full;
|
||||
}
|
||||
.api-modal-tablist-div {
|
||||
@apply flex items-center justify-between px-2;
|
||||
@apply flex items-center justify-between px-2 py-2;
|
||||
}
|
||||
.api-modal-tabs-content {
|
||||
@apply -mt-1 h-full w-full overflow-hidden px-4 pb-4;
|
||||
|
|
@ -1009,7 +1026,7 @@
|
|||
}
|
||||
|
||||
.beta-badge-wrapper {
|
||||
@apply absolute right-0 top-0 h-16 w-16 overflow-hidden rounded-tr-lg pointer-events-none;
|
||||
@apply pointer-events-none absolute right-0 top-0 h-16 w-16 overflow-hidden rounded-tr-lg;
|
||||
}
|
||||
.beta-badge-content {
|
||||
@apply mt-2 w-24 rotate-45 bg-beta-background text-center text-xs font-semibold text-beta-foreground;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@
|
|||
--chat-send: #059669;
|
||||
--status-green: #4ade80;
|
||||
--status-blue: #2563eb;
|
||||
--status-gray: #6b7280;
|
||||
--connection: #555;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { Edge, Node, Viewport } from "reactflow";
|
||||
import { BuildStatus } from "../../constants/enums";
|
||||
import { FlowType } from "../flow";
|
||||
//kind and class are just representative names to represent the actual structure of the object received by the API
|
||||
export type APIDataType = { [key: string]: APIKindType };
|
||||
|
|
@ -37,7 +36,6 @@ export type APIClassType = {
|
|||
| CustomFieldsType
|
||||
| boolean
|
||||
| undefined;
|
||||
build_status?: BuildStatus;
|
||||
};
|
||||
|
||||
export type TemplateVariableType = {
|
||||
|
|
@ -139,8 +137,18 @@ export type VerticesOrderTypeAPI = {
|
|||
|
||||
export type VertexBuildTypeAPI = {
|
||||
id: string;
|
||||
inactive_vertices: Array<string> | null;
|
||||
valid: boolean;
|
||||
params: string;
|
||||
data: VertexDataTypeAPI;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
// data is the object received by the API
|
||||
// it has results, artifacts, timedelta, duration
|
||||
export type VertexDataTypeAPI = {
|
||||
results: { [key: string]: { [key: string]: string } };
|
||||
artifacts: { [key: string]: string };
|
||||
timedelta?: number;
|
||||
duration?: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ export type ChatMessageType = {
|
|||
files?: Array<{ data: string; type: string; data_type: string }>;
|
||||
prompt?: string;
|
||||
chatKey?: string;
|
||||
id?: string;
|
||||
stream_url?: string | null;
|
||||
sender_name?: string;
|
||||
};
|
||||
|
||||
|
|
@ -16,6 +18,7 @@ export type ChatOutputType = {
|
|||
message: string;
|
||||
sender: string;
|
||||
sender_name: string;
|
||||
stream_url?: string;
|
||||
};
|
||||
|
||||
export type chatInputType = {
|
||||
|
|
|
|||
|
|
@ -524,6 +524,11 @@ export type chatMessagePropsType = {
|
|||
chat: ChatMessageType;
|
||||
lockChat: boolean;
|
||||
lastMessage: boolean;
|
||||
updateChat: (
|
||||
chat: ChatMessageType,
|
||||
message: string,
|
||||
stream_url: string
|
||||
) => void;
|
||||
};
|
||||
|
||||
export type formModalPropsType = {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export type NodeDataType = {
|
|||
node?: APIClassType;
|
||||
id: string;
|
||||
output_types?: string[];
|
||||
build_status?: BuildStatus;
|
||||
buildStatus?: BuildStatus;
|
||||
};
|
||||
// FlowStyleType is the type of the style object that is used to style the
|
||||
// Flow card with an emoji and a color.
|
||||
|
|
|
|||
|
|
@ -86,9 +86,10 @@ export type FlowStoreType = {
|
|||
unselectAll: () => void;
|
||||
buildFlow: (nodeId?: string) => Promise<void>;
|
||||
getFlow: () => { nodes: Node[]; edges: Edge[]; viewport: Viewport };
|
||||
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
|
||||
updateVerticesBuild: (vertices: string[]) => void;
|
||||
removeFromVerticesBuild: (vertices: string[]) => void;
|
||||
verticesBuild: string[];
|
||||
inactiveNodes: string[];
|
||||
setInactiveNodes: (newState: string[]) => void;
|
||||
updateBuildStatus: (nodeId: string[], status: BuildStatus) => void;
|
||||
revertBuiltStatusFromBuilding: () => void;
|
||||
flowBuildStatus: { [key: string]: BuildStatus };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ export type StoreStoreType = {
|
|||
validApiKey: boolean;
|
||||
hasApiKey: boolean;
|
||||
loadingApiKey: boolean;
|
||||
updateHasStore: (hasStore: boolean) => void;
|
||||
checkHasStore: () => void;
|
||||
updateValidApiKey: (validApiKey: boolean) => void;
|
||||
updateHasApiKey: (hasApiKey: boolean) => void;
|
||||
updateLoadingApiKey: (loadingApiKey: boolean) => void;
|
||||
fetchApiData: () => Promise<void>;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,33 +1,73 @@
|
|||
import { AxiosError } from "axios";
|
||||
import { BuildStatus } from "../constants/enums";
|
||||
import { getVerticesOrder, postBuildVertex } from "../controllers/API";
|
||||
import useAlertStore from "../stores/alertStore";
|
||||
import useFlowStore from "../stores/flowStore";
|
||||
import { VertexBuildTypeAPI } from "../types/api";
|
||||
|
||||
type BuildVerticesParams = {
|
||||
flowId: string; // Assuming FlowType is the type for your flow
|
||||
nodeId?: string | null; // Assuming nodeId is of type string, and it's optional
|
||||
onProgressUpdate?: (progress: number) => void; // Replace number with the actual type if it's not a number
|
||||
onBuildUpdate?: (data: any) => void; // Replace any with the actual type of data
|
||||
onGetOrderSuccess?: () => void;
|
||||
onBuildUpdate?: (data: VertexBuildTypeAPI, status: BuildStatus) => void; // Replace any with the actual type if it's not any
|
||||
onBuildComplete?: (allNodesValid: boolean) => void;
|
||||
onBuildError?: (title, list, idList: string[]) => void;
|
||||
onBuildStart?: (idList: string[]) => void;
|
||||
validateNodes?: (nodes: string[]) => void;
|
||||
};
|
||||
|
||||
function getInactiveVertexData(vertexId: string): VertexBuildTypeAPI {
|
||||
// Build VertexBuildTypeAPI
|
||||
let inactiveData = {
|
||||
results: {},
|
||||
artifacts: { repr: "Inactive" },
|
||||
};
|
||||
let inactiveVertexData = {
|
||||
id: vertexId,
|
||||
data: inactiveData,
|
||||
params: "Inactive",
|
||||
inactive_vertices: null,
|
||||
valid: false,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return inactiveVertexData;
|
||||
}
|
||||
|
||||
export async function buildVertices({
|
||||
flowId,
|
||||
nodeId = null,
|
||||
onProgressUpdate,
|
||||
onGetOrderSuccess,
|
||||
onBuildUpdate,
|
||||
onBuildComplete,
|
||||
onBuildError,
|
||||
onBuildStart,
|
||||
validateNodes,
|
||||
}: BuildVerticesParams) {
|
||||
let orderResponse = await getVerticesOrder(flowId, nodeId);
|
||||
const setErrorData = useAlertStore.getState().setErrorData;
|
||||
let orderResponse;
|
||||
try {
|
||||
orderResponse = await getVerticesOrder(flowId, nodeId);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
setErrorData({
|
||||
title: "Oops! Looks like you missed something",
|
||||
list: [error.response?.data?.detail ?? "Unknown Error"],
|
||||
});
|
||||
useFlowStore.getState().setIsBuilding(false);
|
||||
throw new Error("Invalid nodes");
|
||||
}
|
||||
if (onGetOrderSuccess) onGetOrderSuccess();
|
||||
let verticesOrder: Array<Array<string>> = orderResponse.data.ids;
|
||||
let vertices_layers: Array<Array<string>> = [];
|
||||
let stop = false;
|
||||
|
||||
if (validateNodes) {
|
||||
try {
|
||||
validateNodes(verticesOrder.flatMap((id) => id));
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (nodeId) {
|
||||
for (let i = 0; i < verticesOrder.length; i += 1) {
|
||||
const innerArray = verticesOrder[i];
|
||||
|
|
@ -57,11 +97,13 @@ export async function buildVertices({
|
|||
if (onBuildStart) onBuildStart(layer);
|
||||
for (const id of layer) {
|
||||
// Check if id is in the list of inactive nodes
|
||||
if (useFlowStore.getState().inactiveNodes.includes(id)) {
|
||||
// If it is, skip building
|
||||
// it should be true because it did
|
||||
// what it was supposed to do
|
||||
buildResults.push(true);
|
||||
if (
|
||||
!useFlowStore.getState().verticesBuild.includes(id) &&
|
||||
onBuildUpdate
|
||||
) {
|
||||
// If it is, skip building and set the state to inactive
|
||||
onBuildUpdate(getInactiveVertexData(id), BuildStatus.INACTIVE);
|
||||
buildResults.push(false);
|
||||
continue;
|
||||
}
|
||||
await buildVertex({
|
||||
|
|
@ -88,7 +130,6 @@ export async function buildVertices({
|
|||
const allNodesValid = buildResults.every((result) => result);
|
||||
onBuildComplete(allNodesValid);
|
||||
useFlowStore.getState().setIsBuilding(false);
|
||||
useFlowStore.getState().setInactiveNodes([]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +144,7 @@ async function buildVertex({
|
|||
}: {
|
||||
flowId: string;
|
||||
id: string;
|
||||
onBuildUpdate?: (data: any) => void;
|
||||
onBuildUpdate?: (data: any, status: BuildStatus) => void;
|
||||
onBuildError?: (title, list, idList: string[]) => void;
|
||||
verticesIds: string[];
|
||||
buildResults: boolean[];
|
||||
|
|
@ -113,7 +154,6 @@ async function buildVertex({
|
|||
const buildRes = await postBuildVertex(flowId, id);
|
||||
const buildData: VertexBuildTypeAPI = buildRes.data;
|
||||
if (onBuildUpdate) {
|
||||
let data = {};
|
||||
if (!buildData.valid) {
|
||||
onBuildError!(
|
||||
"Error Building Component",
|
||||
|
|
@ -122,8 +162,7 @@ async function buildVertex({
|
|||
);
|
||||
stopBuild();
|
||||
}
|
||||
data[buildData.id] = buildData;
|
||||
onBuildUpdate({ data, id: buildData.id });
|
||||
onBuildUpdate(buildData, BuildStatus.BUILT);
|
||||
}
|
||||
buildResults.push(buildData.valid);
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -597,8 +597,7 @@ export function generateFlow(
|
|||
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
/* remove edges that are not connected to selected nodes on both ends
|
||||
in future we can save this edges to when ungrouping reconect to the old nodes
|
||||
*/
|
||||
*/
|
||||
newFlowData.edges = selection.edges.filter(
|
||||
(edge) =>
|
||||
selection.nodes.some((node) => node.id === edge.target) &&
|
||||
|
|
@ -619,12 +618,48 @@ export function generateFlow(
|
|||
// in the future we can use a better aproach using a set
|
||||
return {
|
||||
newFlow,
|
||||
removedEdges: selection.edges.filter(
|
||||
(edge) => !newFlowData.edges.includes(edge)
|
||||
removedEdges: edges.filter(
|
||||
(edge) =>
|
||||
(selection.nodes.some((node) => node.id === edge.target) ||
|
||||
selection.nodes.some((node) => node.id === edge.source)) &&
|
||||
newFlowData.edges.every((e) => e.id !== edge.id)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
|
||||
let newEdges = cloneDeep(excludedEdges);
|
||||
if (!groupNode.data.node!.flow) return [];
|
||||
const { nodes, edges } = groupNode.data.node!.flow!.data!;
|
||||
const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
|
||||
newEdges.forEach((edge) => {
|
||||
if (lastNode && edge.source === lastNode.id) {
|
||||
edge.source = groupNode.id;
|
||||
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!
|
||||
);
|
||||
newSourceHandle.id = groupNode.id;
|
||||
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
||||
edge.data.sourceHandle = newSourceHandle;
|
||||
}
|
||||
if (nodes.some((node) => node.id === edge.target)) {
|
||||
const targetNode = nodes.find((node) => node.id === edge.target)!;
|
||||
console.log("targetNode", targetNode);
|
||||
const targetHandle: targetHandleType = scapeJSONParse(edge.targetHandle!);
|
||||
console.log("targetHandle", targetHandle);
|
||||
const proxy = { id: targetNode.id, field: targetHandle.fieldName };
|
||||
let newTargetHandle: targetHandleType = cloneDeep(targetHandle);
|
||||
newTargetHandle.id = groupNode.id;
|
||||
newTargetHandle.proxy = proxy;
|
||||
edge.target = groupNode.id;
|
||||
newTargetHandle.fieldName = targetHandle.fieldName + "_" + targetNode.id;
|
||||
edge.targetHandle = scapedJSONStringfy(newTargetHandle);
|
||||
edge.data.targetHandle = newTargetHandle;
|
||||
}
|
||||
});
|
||||
return newEdges;
|
||||
}
|
||||
|
||||
export function filterFlow(
|
||||
selection: OnSelectionChangeParams,
|
||||
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import {
|
||||
AlertCircle,
|
||||
ArrowLeft,
|
||||
ArrowUpToLine,
|
||||
Bell,
|
||||
BookMarked,
|
||||
BookmarkPlus,
|
||||
Bot,
|
||||
Boxes,
|
||||
Braces,
|
||||
Cable,
|
||||
Check,
|
||||
CheckCircle2,
|
||||
|
|
@ -18,6 +20,7 @@ import {
|
|||
ChevronsRight,
|
||||
ChevronsUpDown,
|
||||
Circle,
|
||||
CircleDot,
|
||||
Clipboard,
|
||||
Code,
|
||||
Code2,
|
||||
|
|
@ -25,6 +28,7 @@ import {
|
|||
Compass,
|
||||
Copy,
|
||||
Cpu,
|
||||
Delete,
|
||||
Download,
|
||||
DownloadCloud,
|
||||
Edit,
|
||||
|
|
@ -101,6 +105,7 @@ import {
|
|||
TextCursorInput,
|
||||
ToyBrick,
|
||||
Trash2,
|
||||
Type,
|
||||
Undo,
|
||||
Ungroup,
|
||||
Unplug,
|
||||
|
|
@ -194,7 +199,7 @@ export const gradients = [
|
|||
export const nodeColors: { [char: string]: string } = {
|
||||
prompts: "#4367BF",
|
||||
models: "#AA2411",
|
||||
llms: "#6344BE",
|
||||
model_specs: "#6344BE",
|
||||
chains: "#FE7500",
|
||||
Document: "#7AAE42",
|
||||
list: "#9AAE42",
|
||||
|
|
@ -223,7 +228,7 @@ export const nodeColors: { [char: string]: string } = {
|
|||
export const nodeNames: { [char: string]: string } = {
|
||||
prompts: "Prompts",
|
||||
models: "Language Models",
|
||||
llms: "Model Settings",
|
||||
model_specs: "Model Specs",
|
||||
chains: "Chains",
|
||||
agents: "Agents",
|
||||
tools: "Tools",
|
||||
|
|
@ -295,7 +300,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
chains: Link,
|
||||
memories: Cpu,
|
||||
models: Bot,
|
||||
llms: Lightbulb,
|
||||
model_specs: Lightbulb,
|
||||
prompts: TerminalSquare,
|
||||
tools: Wrench,
|
||||
advanced: Laptop2,
|
||||
|
|
@ -336,6 +341,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
Bell,
|
||||
ChevronLeft,
|
||||
ChevronDown,
|
||||
ArrowLeft,
|
||||
Shield,
|
||||
Plus,
|
||||
Redo,
|
||||
|
|
@ -344,6 +350,7 @@ export const nodeIconsLucide: iconsType = {
|
|||
FileSearch2,
|
||||
ChevronRight,
|
||||
Circle,
|
||||
CircleDot,
|
||||
Clipboard,
|
||||
Code2,
|
||||
Variable,
|
||||
|
|
@ -413,7 +420,10 @@ export const nodeIconsLucide: iconsType = {
|
|||
Sliders,
|
||||
ScreenShare,
|
||||
Code,
|
||||
Type,
|
||||
Braces,
|
||||
FlaskConical,
|
||||
AlertCircle,
|
||||
Bot,
|
||||
Delete,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -58,11 +58,15 @@ export function normalCaseToSnakeCase(str: string): string {
|
|||
.join("_");
|
||||
}
|
||||
|
||||
export function toTitleCase(str: string | undefined): string {
|
||||
export function toTitleCase(
|
||||
str: string | undefined,
|
||||
isNodeField?: boolean
|
||||
): string {
|
||||
if (!str) return "";
|
||||
let result = str
|
||||
.split("_")
|
||||
.map((word, index) => {
|
||||
if (isNodeField) return word;
|
||||
if (index === 0) {
|
||||
return checkUpperWords(
|
||||
word[0].toUpperCase() + word.slice(1).toLowerCase()
|
||||
|
|
@ -75,6 +79,7 @@ export function toTitleCase(str: string | undefined): string {
|
|||
return result
|
||||
.split("-")
|
||||
.map((word, index) => {
|
||||
if (isNodeField) return word;
|
||||
if (index === 0) {
|
||||
return checkUpperWords(
|
||||
word[0].toUpperCase() + word.slice(1).toLowerCase()
|
||||
|
|
@ -642,6 +647,6 @@ export function getFieldTitle(
|
|||
return template[templateField].display_name
|
||||
? template[templateField].display_name!
|
||||
: template[templateField].name
|
||||
? toTitleCase(template[templateField].name!)
|
||||
: toTitleCase(templateField);
|
||||
? toTitleCase(template[templateField].name!, true)
|
||||
: toTitleCase(templateField, true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ module.exports = {
|
|||
"status-green": "var(--status-green)",
|
||||
"status-red": "var(--status-red)",
|
||||
"status-yellow": "var(--status-yellow)",
|
||||
"status-gray": "var(--status-gray)",
|
||||
"success-background": "var(--success-background)",
|
||||
"success-foreground": "var(--success-foreground)",
|
||||
"beta-background": "var(--beta-background)",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue