Merge remote-tracking branch 'origin/dev' into newHandleId

This commit is contained in:
anovazzi1 2023-08-17 16:17:14 -03:00
commit 11a2d12c6b
245 changed files with 18156 additions and 6877 deletions

View file

@ -0,0 +1,2 @@
**/node_modules
**/build

File diff suppressed because it is too large Load diff

View file

@ -12,11 +12,13 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-form": "^0.0.3",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-menubar": "^1.0.3",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-progress": "^1.0.3",
"@radix-ui/react-select": "^1.2.2",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
@ -69,7 +71,8 @@
"start": "vite",
"build": "vite build",
"serve": "vite preview",
"format": "npx prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\""
"format": "npx prettier --write \"**/*.{js,jsx,ts,tsx,json,md}\"",
"type-check": "tsc --noEmit --pretty --project tsconfig.json && vite"
},
"eslintConfig": {
"extends": [

View file

@ -9,7 +9,7 @@ import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success";
import CrashErrorComponent from "./components/CrashErrorComponent";
import Header from "./components/headerComponent";
import LoadingComponent from "./components/loadingComponent";
import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext";
import { TabsContext } from "./contexts/tabsContext";
@ -25,6 +25,7 @@ export default function App() {
setIsStackedOpen(true);
}, [location.pathname, setCurrent, setIsStackedOpen, setShowSideBar]);
const { hardReset } = useContext(TabsContext);
const {
errorData,
errorOpen,
@ -35,6 +36,7 @@ export default function App() {
successData,
successOpen,
setSuccessOpen,
loading,
} = useContext(alertContext);
// Initialize state variable for the list of alerts
@ -133,8 +135,15 @@ export default function App() {
}}
FallbackComponent={CrashErrorComponent}
>
<Header />
<Router />
{loading ? (
<div className="loading-page-panel">
<LoadingComponent remSize={50} />
</div>
) : (
<>
<Router />
</>
)}
</ErrorBoundary>
<div></div>
<div className="app-div" style={{ zIndex: 999 }}>

View file

@ -1,5 +1,11 @@
import { cloneDeep } from "lodash";
import React, { useContext, useEffect, useRef, useState } from "react";
import React, {
ReactNode,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { Handle, Position, useUpdateNodeInternals } from "reactflow";
import ShadTooltip from "../../../../components/ShadTooltipComponent";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
@ -17,6 +23,7 @@ import { TOOLTIP_EMPTY } from "../../../../constants/constants";
import { TabsContext } from "../../../../contexts/tabsContext";
import { typesContext } from "../../../../contexts/typesContext";
import { ParameterComponentType } from "../../../../types/components";
import { TabsState } from "../../../../types/tabs";
import {
isValidConnection,
scapedJSONStringfy,
@ -41,15 +48,15 @@ export default function ParameterComponent({
required = false,
optionalHandle = null,
info = "",
}: ParameterComponentType) {
const ref = useRef(null);
const refHtml = useRef(null);
const infoHtml = useRef(null);
}: ParameterComponentType): JSX.Element {
const ref = useRef<HTMLDivElement>(null);
const refHtml = useRef<HTMLDivElement & ReactNode>(null);
const infoHtml = useRef<HTMLDivElement & ReactNode>(null);
const updateNodeInternals = useUpdateNodeInternals();
const [position, setPosition] = useState(0);
const { setTabsState, tabId, save, flows } = useContext(TabsContext);
const flow = flows.find((f) => f.id === tabId).data?.nodes ?? null;
const flow = flows.find((flow) => flow.id === tabId)?.data?.nodes ?? null;
// Update component position
useEffect(() => {
@ -67,16 +74,17 @@ export default function ParameterComponent({
let disabled =
reactFlowInstance
?.getEdges()
.some((e) => e.targetHandle === scapedJSONStringfy(id)) ?? false;
.some((edge) => edge.targetHandle === scapedJSONStringfy(id)) ?? false;
const { data: myData } = useContext(typesContext);
const handleOnNewValue = (newValue: any) => {
const handleOnNewValue = (newValue: string | string[] | boolean): void => {
let newData = cloneDeep(data);
newData.node.template[name].value = newValue;
newData.node!.template[name].value = newValue;
setData(newData);
// Set state to pending
setTabsState((prev) => {
//@ts-ignore
setTabsState((prev: TabsState) => {
return {
...prev,
[tabId]: {
@ -91,10 +99,11 @@ export default function ParameterComponent({
useEffect(() => {
if (name === "openai_api_base") console.log(info);
// @ts-ignore
infoHtml.current = (
<div className="h-full w-full break-words">
{info.split("\n").map((line, i) => (
<p key={i} className="block">
{info.split("\n").map((line, index) => (
<p key={index} className="block">
{line}
</p>
))}
@ -103,18 +112,20 @@ export default function ParameterComponent({
}, [info]);
function renderTooltips() {
let groupedObj = groupByFamily(myData, tooltipTitle, left, flow);
let groupedObj = groupByFamily(myData, tooltipTitle!, left, flow!);
if (groupedObj && groupedObj.length > 0) {
refHtml.current = groupedObj.map((item, i) => {
//@ts-ignore
//@ts-ignore
refHtml.current = groupedObj.map((item, index) => {
const Icon: any =
nodeIconsLucide[item.family] ?? nodeIconsLucide["unknown"];
return (
<span
key={i}
key={index}
className={classNames(
i > 0 ? "mt-2 flex items-center" : "flex items-center"
index > 0 ? "mt-2 flex items-center" : "flex items-center"
)}
>
<div
@ -137,10 +148,10 @@ export default function ParameterComponent({
{" "}
{item.type === "" ? "" : " - "}
{item.type.split(", ").length > 2
? item.type.split(", ").map((el, i) => (
<React.Fragment key={el + i}>
? item.type.split(", ").map((el, index) => (
<React.Fragment key={el + index}>
<span>
{i === item.type.split(", ").length - 1
{index === item.type.split(", ").length - 1
? el
: (el += `, `)}
</span>
@ -153,6 +164,7 @@ export default function ParameterComponent({
);
});
} else {
//@ts-ignore
refHtml.current = <span>{TOOLTIP_EMPTY}</span>;
}
}
@ -212,7 +224,7 @@ export default function ParameterComponent({
position={left ? Position.Left : Position.Right}
id={scapedJSONStringfy(id)}
isValidConnection={(connection) =>
isValidConnection(connection, reactFlowInstance)
isValidConnection(connection, reactFlowInstance!)
}
className={classNames(
left ? "-ml-0.5 " : "-mr-0.5 ",
@ -228,9 +240,9 @@ export default function ParameterComponent({
{left === true &&
type === "str" &&
!data.node.template[name].options ? (
!data.node?.template[name].options ? (
<div className="mt-2 w-full">
{data.node.template[name].list ? (
{data.node?.template[name].list ? (
<InputListComponent
disabled={disabled}
value={
@ -241,7 +253,7 @@ export default function ParameterComponent({
}
onChange={handleOnNewValue}
/>
) : data.node.template[name].multiline ? (
) : data.node?.template[name].multiline ? (
<TextAreaComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
@ -250,8 +262,8 @@ export default function ParameterComponent({
) : (
<InputComponent
disabled={disabled}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
password={data.node?.template[name].password ?? false}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
)}
@ -260,9 +272,9 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<ToggleShadComponent
disabled={disabled}
enabled={data.node.template[name].value ?? false}
setEnabled={(t) => {
handleOnNewValue(t);
enabled={data.node?.template[name].value ?? false}
setEnabled={(isEnabled) => {
handleOnNewValue(isEnabled);
}}
size="large"
/>
@ -271,13 +283,13 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<FloatComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
</div>
) : left === true &&
type === "str" &&
data.node.template[name].options ? (
data.node?.template[name].options ? (
<div className="mt-2 w-full">
<Dropdown
options={data.node.template[name].options}
@ -288,13 +300,13 @@ export default function ParameterComponent({
) : left === true && type === "code" ? (
<div className="mt-2 w-full">
<CodeAreaComponent
dynamic={data.node.template[name].dynamic ?? false}
dynamic={data.node?.template[name].dynamic ?? false}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
nodeClass={data.node}
disabled={disabled}
value={data.node.template[name].value ?? ""}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
</div>
@ -302,12 +314,12 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<InputFileComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].file_path = t;
fileTypes={data.node?.template[name].fileTypes}
suffixes={data.node?.template[name].suffixes}
onFileChange={(filePath: string) => {
data.node!.template[name].file_path = filePath;
save();
}}
></InputFileComponent>
@ -316,7 +328,7 @@ export default function ParameterComponent({
<div className="mt-2 w-full">
<IntComponent
disabled={disabled}
value={data.node.template[name].value ?? ""}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
</div>
@ -329,7 +341,7 @@ export default function ParameterComponent({
}}
nodeClass={data.node}
disabled={disabled}
value={data.node.template[name].value ?? ""}
value={data.node?.template[name].value ?? ""}
onChange={handleOnNewValue}
/>
</div>

View file

@ -8,6 +8,7 @@ import { useSSE } from "../../contexts/SSEContext";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent";
import { validationStatusType } from "../../types/components";
import { NodeDataType } from "../../types/flow";
import { cleanEdges, scapedJSONStringfy } from "../../utils/reactflowUtils";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
@ -20,13 +21,14 @@ export default function GenericNode({
}: {
data: NodeDataType;
selected: boolean;
}) {
}): JSX.Element {
const [data, setData] = useState(olddata);
const { updateFlow, flows, tabId } = useContext(TabsContext);
const updateNodeInternals = useUpdateNodeInternals();
const { types, deleteNode, reactFlowInstance } = useContext(typesContext);
const name = nodeIconsLucide[data.type] ? data.type : types[data.type];
const [validationStatus, setValidationStatus] = useState(null);
const [validationStatus, setValidationStatus] =
useState<validationStatusType | null>(null);
// State for outline color
const { sseData, isBuilding } = useSSE();
useEffect(() => {
@ -41,7 +43,7 @@ export default function GenericNode({
nodes: flow.data.nodes,
},
updateEdge: (edge) => {
flow.data.edges = edge;
flow.data!.edges = edge;
reactFlowInstance.setEdges(edge);
updateNodeInternals(data.id);
},
@ -77,7 +79,7 @@ export default function GenericNode({
"generic-node-div"
)}
>
{data.node.beta && (
{data.node?.beta && (
<div className="beta-badge-wrapper">
<div className="beta-badge-content">BETA</div>
</div>
@ -90,9 +92,9 @@ export default function GenericNode({
iconColor={`${nodeColors[types[data.type]]}`}
/>
<div className="generic-node-tooltip-div">
<ShadTooltip content={data.node.display_name}>
<ShadTooltip content={data.node?.display_name}>
<div className="generic-node-tooltip-div text-primary">
{data.node.display_name}
{data.node?.display_name}
</div>
</ShadTooltip>
</div>
@ -117,7 +119,9 @@ export default function GenericNode({
{typeof validationStatus.params === "string"
? validationStatus.params
.split("\n")
.map((line, index) => <div key={index}>{line}</div>)
.map((line: string, index: number) => (
<div key={index}>{line}</div>
))
: ""}
</div>
)
@ -155,52 +159,59 @@ export default function GenericNode({
</div>
<div className="generic-node-desc">
<div className="generic-node-desc-text">{data.node.description}</div>
<div className="generic-node-desc-text">{data.node?.description}</div>
<>
{Object.keys(data.node.template)
.filter((t) => t.charAt(0) !== "_")
.map((t: string, idx) => (
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.map((templateField: string, idx) => (
<div key={idx}>
{data.node.template[t].show &&
!data.node.template[t].advanced ? (
{data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? (
<ParameterComponent
key={scapedJSONStringfy({
inputTypes: data.node.template[t].input_types,
type: data.node.template[t].type,
inputTypes:
data.node!.template[templateField].input_types,
type: data.node!.template[templateField].type,
id: data.id,
fieldName: t,
fieldName: templateField,
})}
data={data}
setData={setData}
color={
nodeColors[types[data.node.template[t].type]] ??
nodeColors[data.node.template[t].type] ??
nodeColors[
types[data.node?.template[templateField].type!]
] ??
nodeColors[data.node?.template[templateField].type!] ??
nodeColors.unknown
}
title={
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? toTitleCase(data.node.template[t].name)
: toTitleCase(t)
data.node?.template[templateField].display_name
? data.node.template[templateField].display_name
: data.node?.template[templateField].name
? toTitleCase(data.node.template[templateField].name)
: toTitleCase(templateField)
}
info={data.node.template[t].info}
name={t}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node.template[t].input_types?.join("\n") ??
data.node.template[t].type
data.node?.template[templateField].input_types?.join(
"\n"
) ?? data.node?.template[templateField].type
}
required={data.node.template[t].required}
required={data.node!.template[templateField].required}
id={{
inputTypes: data.node.template[t].input_types,
type: data.node.template[t].type,
inputTypes:
data.node!.template[templateField].input_types,
type: data.node!.template[templateField].type,
id: data.id,
fieldName: t,
fieldName: templateField,
}}
left={true}
type={data.node.template[t].type}
optionalHandle={data.node.template[t].input_types}
type={data.node?.template[templateField].type}
optionalHandle={
data.node?.template[templateField].input_types
}
/>
) : (
<></>
@ -209,7 +220,7 @@ export default function GenericNode({
))}
<div
className={classNames(
Object.keys(data.node.template).length < 1 ? "hidden" : "",
Object.keys(data.node!.template).length < 1 ? "hidden" : "",
"flex-max-width justify-center"
)}
>
@ -217,7 +228,7 @@ export default function GenericNode({
</div>
<ParameterComponent
key={scapedJSONStringfy({
baseClasses: data.node.base_classes,
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
})}
@ -225,17 +236,17 @@ export default function GenericNode({
setData={setData}
color={nodeColors[types[data.type]] ?? nodeColors.unknown}
title={
data.node.output_types && data.node.output_types.length > 0
data.node?.output_types && data.node.output_types.length > 0
? data.node.output_types.join("|")
: data.type
}
tooltipTitle={data.node.base_classes.join("\n")}
tooltipTitle={data.node?.base_classes.join("\n")}
id={{
baseClasses: data.node.base_classes,
baseClasses: data.node!.base_classes,
id: data.id,
dataType: data.type,
}}
type={data.node.base_classes.join("|")}
type={data.node?.base_classes.join("|")}
left={false}
/>
</>

View file

@ -7,7 +7,7 @@ import { SingleAlertComponentType } from "../../../../types/alerts";
export default function SingleAlert({
dropItem,
removeAlert,
}: SingleAlertComponentType) {
}: SingleAlertComponentType): JSX.Element {
const [show, setShow] = useState(true);
const type = dropItem.type;
@ -36,14 +36,14 @@ export default function SingleAlert({
/>
</div>
<div className="ml-3">
<h3 className="break-words text-sm font-medium text-error-foreground">
<h3 className="text-sm font-medium text-error-foreground word-break-break-word">
{dropItem.title}
</h3>
{dropItem.list ? (
<div className="mt-2 text-sm text-error-foreground">
<ul className="list-disc space-y-1 pl-5">
{dropItem.list.map((item, idx) => (
<li className="break-words" key={idx}>
<li className="word-break-break-word" key={idx}>
{item}
</li>
))}

View file

@ -9,7 +9,9 @@ import { alertContext } from "../../contexts/alertContext";
import { AlertDropdownType } from "../../types/alerts";
import SingleAlert from "./components/singleAlertComponent";
export default function AlertDropdown({ children }: AlertDropdownType) {
export default function AlertDropdown({
children,
}: AlertDropdownType): JSX.Element {
const {
notificationList,
clearNotificationList,
@ -22,9 +24,9 @@ export default function AlertDropdown({ children }: AlertDropdownType) {
return (
<Popover
open={open}
onOpenChange={(k) => {
setOpen(k);
if (k) setNotificationCenter(false);
onOpenChange={(target) => {
setOpen(target);
if (target) setNotificationCenter(false);
}}
>
<PopoverTrigger>{children}</PopoverTrigger>

View file

@ -8,7 +8,7 @@ export default function ErrorAlert({
list = [],
id,
removeAlert,
}: ErrorAlertType) {
}: ErrorAlertType): JSX.Element {
const [show, setShow] = useState(true);
useEffect(() => {
if (show) {

View file

@ -2,7 +2,7 @@ import { useEffect } from "react";
export function useOnClickOutside(ref, handler) {
useEffect(() => {
const listener = (event) => {
const listener = (event: Event) => {
// Do nothing if clicking ref's element or its children
if (!ref.current || ref.current.contains(event.target)) {
return;

View file

@ -9,7 +9,7 @@ export default function NoticeAlert({
link = "",
id,
removeAlert,
}: NoticeAlertType) {
}: NoticeAlertType): JSX.Element {
const [show, setShow] = useState(true);
useEffect(() => {
if (show) {
@ -47,7 +47,9 @@ export default function NoticeAlert({
/>
</div>
<div className="ml-3 flex-1 md:flex md:justify-between">
<p className="text-sm text-info-foreground">{title}</p>
<p className="text-sm text-info-foreground word-break-break-word">
{title}
</p>
<p className="mt-3 text-sm md:ml-6 md:mt-0">
{link !== "" ? (
<Link

View file

@ -7,7 +7,7 @@ export default function SuccessAlert({
title,
id,
removeAlert,
}: SuccessAlertType) {
}: SuccessAlertType): JSX.Element {
const [show, setShow] = useState(true);
useEffect(() => {
if (show) {

View file

@ -12,12 +12,12 @@ export default function AccordionComponent({
children,
open = [],
keyValue,
}: AccordionComponentType) {
}: AccordionComponentType): JSX.Element {
const [value, setValue] = useState(
open.length === 0 ? "" : getOpenAccordion()
);
function getOpenAccordion() {
function getOpenAccordion(): string {
let value = "";
open.forEach((el) => {
if (el == trigger) {
@ -28,10 +28,17 @@ export default function AccordionComponent({
return value;
}
function handleClick() {
value === "" ? setValue(keyValue) : setValue("");
function handleClick(): void {
value === "" ? setValue(keyValue!) : setValue("");
}
const handleKeyDown = (event) => {
if (event.key === "Backspace") {
event.preventDefault();
event.stopPropagation();
}
};
return (
<>
<Accordion
@ -39,8 +46,9 @@ export default function AccordionComponent({
className="w-full"
value={value}
onValueChange={setValue}
onKeyDown={handleKeyDown}
>
<AccordionItem value={keyValue} className="border-b">
<AccordionItem value={keyValue!} className="border-b">
<AccordionTrigger
onClick={() => {
handleClick();

View file

@ -1,4 +1,9 @@
export default function CrashErrorComponent({ error, resetErrorBoundary }) {
import { crashComponentPropsType } from "../../types/components";
export default function CrashErrorComponent({
error,
resetErrorBoundary,
}: crashComponentPropsType): JSX.Element {
return (
<div className="fixed left-0 top-0 z-50 flex h-full w-full items-center justify-center bg-foreground bg-opacity-50">
<div className="flex h-1/3 min-h-fit max-w-4xl flex-col justify-evenly rounded-lg bg-background p-8 text-start shadow-lg">

View file

@ -3,19 +3,8 @@ import { Input } from "../../components/ui/input";
import { Label } from "../../components/ui/label";
import { Textarea } from "../../components/ui/textarea";
import { readFlowsFromDatabase } from "../../controllers/API";
type InputProps = {
name: string | null;
description: string | null;
maxLength?: number;
flows: Array<{ id: string; name: string; description: string }>;
tabId: string;
invalidName: boolean;
setInvalidName: (invalidName: boolean) => void;
setName: (name: string) => void;
setDescription: (description: string) => void;
updateFlow: (flow: { id: string; name: string }) => void;
};
import { InputProps } from "../../types/components";
import { FlowType } from "../../types/flow";
export const EditFlowSettings: React.FC<InputProps> = ({
name,
@ -27,13 +16,12 @@ export const EditFlowSettings: React.FC<InputProps> = ({
tabId,
setName,
setDescription,
updateFlow,
}) => {
}: InputProps): JSX.Element => {
const [isMaxLength, setIsMaxLength] = useState(false);
const nameLists = useRef([]);
const nameLists = useRef<string[]>([]);
useEffect(() => {
readFlowsFromDatabase().then((flows) => {
flows.forEach((flow) => {
flows.forEach((flow: FlowType) => {
nameLists.current.push(flow.name);
});
});
@ -46,21 +34,34 @@ export const EditFlowSettings: React.FC<InputProps> = ({
} else {
setIsMaxLength(false);
}
if (invalidName !== undefined) {
if (!nameLists.current.includes(value)) {
setInvalidName(false);
} else {
setInvalidName(true);
}
}
if (!nameLists.current.includes(value)) {
setInvalidName(false);
setInvalidName!(false);
} else {
setInvalidName(true);
setInvalidName!(true);
}
setName(value);
setCurrentName(value);
};
const [desc, setDesc] = useState(
flows.find((f) => f.id === tabId).description
);
const [currentName, setCurrentName] = useState(name);
const [currentDescription, setCurrentDescription] = useState(description);
useEffect(() => {
setCurrentName(name);
setCurrentDescription(description);
}, [name, description]);
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
flows.find((f) => f.id === tabId).description = event.target.value;
setDesc(flows.find((f) => f.id === tabId).description);
setCurrentDescription(flows.find((f) => f.id === tabId).description);
setDescription(event.target.value);
};
@ -81,7 +82,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
onChange={handleNameChange}
type="text"
name="name"
value={name ?? ""}
value={currentName ?? ""}
placeholder="File name"
id="name"
maxLength={maxLength}
@ -96,7 +97,7 @@ export const EditFlowSettings: React.FC<InputProps> = ({
name="description"
id="description"
onChange={handleDescriptionChange}
value={desc}
value={currentDescription}
placeholder="Flow description"
className="mt-2 max-h-[100px] font-normal"
rows={3}

View file

@ -0,0 +1,118 @@
import { useState } from "react";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../../components/ui/select";
import { PaginatorComponentType } from "../../types/components";
import IconComponent from "../genericIconComponent";
import { Button } from "../ui/button";
export default function PaginatorComponent({
pageSize = 10,
pageIndex = 1,
rowsCount = [10, 20, 30],
totalRowsCount = 0,
paginate,
}: PaginatorComponentType) {
const [size, setPageSize] = useState(pageSize);
const [index, setPageIndex] = useState(pageIndex);
const [maxIndex, setMaxPageIndex] = useState(
Math.ceil(totalRowsCount / pageSize)
);
return (
<>
<div className="flex items-center justify-between px-2">
<div className="flex-1 text-sm text-muted-foreground"></div>
<div className="flex items-center space-x-6 lg:space-x-8">
<div className="flex items-center space-x-2">
<p className="text-sm font-medium">Rows per page</p>
<Select
onValueChange={(pageSize: string) => {
setPageSize(Number(pageSize));
setMaxPageIndex(Math.ceil(totalRowsCount / Number(pageSize)));
paginate(Number(pageSize), index);
}}
>
<SelectTrigger className="w-[100px]">
<SelectValue placeholder="10" />
</SelectTrigger>
<SelectContent>
{rowsCount.map((item, i) => (
<SelectItem key={i} value={item.toString()}>
{item}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
Page {index} of {maxIndex}
</div>
<div className="flex items-center space-x-2">
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => {
setPageIndex(1);
paginate(size, 1);
}}
>
<span className="sr-only">Go to first page</span>
<IconComponent name="ChevronsLeft" className="h-4 w-4" />
</Button>
<Button
onClick={() => {
if (index <= 1) {
setPageIndex(1);
paginate(size, 1);
} else {
{
setPageIndex(index - 1);
paginate(size, index - 1);
}
}
}}
variant="outline"
className="h-8 w-8 p-0"
>
<span className="sr-only">Go to previous page</span>
<IconComponent name="ChevronLeft" className="h-4 w-4" />
</Button>
<Button
onClick={() => {
if (index >= maxIndex) {
setPageIndex(maxIndex);
paginate(size, maxIndex);
} else {
setPageIndex(index + 1);
paginate(size, index + 1);
}
}}
variant="outline"
className="h-8 w-8 p-0"
>
<span className="sr-only">Go to next page</span>
<IconComponent name="ChevronRight" className="h-4 w-4" />
</Button>
<Button
variant="outline"
className="hidden h-8 w-8 p-0 lg:flex"
onClick={() => {
setPageIndex(maxIndex);
paginate(size, maxIndex);
}}
>
<span className="sr-only">Go to last page</span>
<IconComponent name="ChevronsRight" className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</>
);
}

View file

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

View file

@ -3,20 +3,9 @@ import type { FC } from "react";
import React from "react";
import { Tooltip as ReactTooltip } from "react-tooltip";
import "react-tooltip/dist/react-tooltip.css";
import { TooltipProps } from "../../types/components";
import { classNames } from "../../utils/utils";
type TooltipProps = {
selector: string;
content?: string;
disabled?: boolean;
htmlContent?: React.ReactNode;
className?: string; // This should use !impornant to override the default styles eg: '!bg-white'
position?: "top" | "right" | "bottom" | "left";
clickable?: boolean;
children: React.ReactNode;
delayShow?: number;
};
const TooltipReact: FC<TooltipProps> = ({
selector,
content,
@ -27,7 +16,7 @@ const TooltipReact: FC<TooltipProps> = ({
className,
clickable,
delayShow,
}) => {
}: TooltipProps): JSX.Element => {
return (
<div className="tooltip-container">
{React.cloneElement(children as React.ReactElement, {
@ -38,7 +27,7 @@ const TooltipReact: FC<TooltipProps> = ({
content={content}
className={classNames(
"z-[9999] !bg-white !text-xs !font-normal !text-foreground !opacity-100 !shadow-md",
className
className!
)}
place={position}
clickable={clickable}

View file

@ -1,11 +1,12 @@
import DOMPurify from "dompurify";
import { SanitizedHTMLWrapperType } from "../../types/components";
const SanitizedHTMLWrapper = ({
className,
content,
onClick,
suppressWarning = false,
}) => {
}: SanitizedHTMLWrapperType): JSX.Element => {
const sanitizedHTML = DOMPurify.sanitize(content);
return (

View file

@ -8,7 +8,7 @@ export default function ShadTooltip({
children,
styleClasses,
delayDuration = 500,
}: ShadToolTipType) {
}: ShadToolTipType): JSX.Element {
return (
<Tooltip delayDuration={delayDuration}>
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>

View file

@ -5,7 +5,7 @@ export default function Tooltip({
children,
title,
placement,
}: TooltipComponentType) {
}: TooltipComponentType): JSX.Element {
return (
<LightTooltip placement={placement} title={title} arrow>
{children}

View file

@ -1,6 +1,6 @@
import { useContext } from "react";
import { TabsContext } from "../../contexts/tabsContext";
import { FlowType } from "../../types/flow";
import { cardComponentPropsType } from "../../types/components";
import { gradients } from "../../utils/styleUtils";
import IconComponent from "../genericIconComponent";
import {
@ -16,12 +16,7 @@ export const CardComponent = ({
id,
onDelete,
button,
}: {
flow: FlowType;
id: string;
onDelete?: () => void;
button?: JSX.Element;
}) => {
}: cardComponentPropsType): JSX.Element => {
const { removeFlow } = useContext(TabsContext);
return (

View file

@ -8,6 +8,8 @@ import { postBuildInit } from "../../../controllers/API";
import { FlowType } from "../../../types/flow";
import { TabsContext } from "../../../contexts/tabsContext";
import { parsedDataType } from "../../../types/components";
import { TabsState } from "../../../types/tabs";
import { validateNodes } from "../../../utils/reactflowUtils";
import RadialProgressComponent from "../../RadialProgress";
import IconComponent from "../../genericIconComponent";
@ -21,7 +23,7 @@ export default function BuildTrigger({
flow: FlowType;
setIsBuilt: any;
isBuilt: boolean;
}) {
}): JSX.Element {
const { updateSSEData, isBuilding, setIsBuilding, sseData } = useSSE();
const { reactFlowInstance } = useContext(typesContext);
const { setTabsState } = useContext(TabsContext);
@ -30,12 +32,12 @@ export default function BuildTrigger({
const eventClick = isBuilding ? "pointer-events-none" : "";
const [progress, setProgress] = useState(0);
async function handleBuild(flow: FlowType) {
async function handleBuild(flow: FlowType): Promise<void> {
try {
if (isBuilding) {
return;
}
const errors = validateNodes(reactFlowInstance);
const errors = validateNodes(reactFlowInstance!);
if (errors.length > 0) {
setErrorData({
title: "Oops! Looks like you missed something",
@ -69,7 +71,7 @@ export default function BuildTrigger({
const response = await postBuildInit(flow);
const { flowId } = response.data;
// Step 2: Use the session ID to establish an SSE connection using EventSource
let validationResults = [];
let validationResults: boolean[] = [];
let finished = false;
const apiUrl = `/api/v1/build/stream/${flowId}`;
const eventSource = new EventSource(apiUrl);
@ -82,14 +84,17 @@ export default function BuildTrigger({
const parsedData = JSON.parse(event.data);
// if the event is the end of the stream, close the connection
if (parsedData.end_of_stream) {
// Close the connection and finish
finished = true;
eventSource.close();
return;
} else if (parsedData.log) {
// If the event is a log, log it
setSuccessData({ title: parsedData.log });
} else if (parsedData.input_keys) {
setTabsState((old) => {
} else if (parsedData.input_keys !== undefined) {
//@ts-ignore
setTabsState((old: TabsState) => {
return {
...old,
[flowId]: {
@ -118,13 +123,13 @@ export default function BuildTrigger({
// Step 3: Wait for the stream to finish
while (!finished) {
await new Promise((resolve) => setTimeout(resolve, 100));
finished = validationResults.length === flow.data.nodes.length;
finished = validationResults.length === flow.data!.nodes.length;
}
// Step 4: Return true if all nodes are valid, false otherwise
return validationResults.every((result) => result);
}
function processStreamResult(parsedData) {
function processStreamResult(parsedData: parsedDataType) {
// Process each chunk of data here
// Parse the chunk and update the context
try {

View file

@ -8,12 +8,18 @@ import {
FLOW_NOT_BUILT_TITLE,
} from "../../../constants/constants";
import { alertContext } from "../../../contexts/alertContext";
import { chatTriggerPropType } from "../../../types/components";
import IconComponent from "../../genericIconComponent";
export default function ChatTrigger({ open, setOpen, isBuilt, canOpen }) {
export default function ChatTrigger({
open,
setOpen,
isBuilt,
canOpen,
}: chatTriggerPropType): JSX.Element {
const { setErrorData } = useContext(alertContext);
function handleClick() {
function handleClick(): void {
if (isBuilt) {
if (canOpen) {
setOpen(true);

View file

@ -10,7 +10,7 @@ import { getBuildStatus } from "../../controllers/API";
import FormModal from "../../modals/formModal";
import { NodeType } from "../../types/flow";
export default function Chat({ flow }: ChatType) {
export default function Chat({ flow }: ChatType): JSX.Element {
const [open, setOpen] = useState(false);
const [canOpen, setCanOpen] = useState(false);
const { tabsState, isBuilt, setIsBuilt } = useContext(TabsContext);
@ -44,11 +44,11 @@ export default function Chat({ flow }: ChatType) {
}, [flow]);
const prevNodesRef = useRef<any[] | undefined>();
const nodes = useNodes();
const nodes: NodeType[] = useNodes();
useEffect(() => {
const prevNodes = prevNodesRef.current;
const currentNodes = nodes.map((node: NodeType) =>
_.cloneDeep(node.data.node.template)
_.cloneDeep(node.data.node?.template)
);
if (
tabsState &&
@ -62,8 +62,7 @@ export default function Chat({ flow }: ChatType) {
tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
tabsState[flow.id].formKeysData.input_keys &&
Object.keys(tabsState[flow.id].formKeysData.input_keys).length > 0
tabsState[flow.id].formKeysData.input_keys !== null
) {
setCanOpen(true);
} else {

View file

@ -33,10 +33,10 @@ export default function CodeAreaComponent({
dynamic={dynamic}
value={myValue}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
setValue={(t: string) => {
setMyValue(t);
onChange(t);
setNodeClass={setNodeClass!}
setValue={(value: string) => {
setMyValue(value);
onChange(value);
}}
>
<div className="flex w-full items-center">

View file

@ -29,7 +29,7 @@ import {
TabsTrigger,
} from "../../components/ui/tabs";
import { darkContext } from "../../contexts/darkContext";
import { FlowType } from "../../types/flow/index";
import { codeTabsPropsType } from "../../types/components";
import { classNames } from "../../utils/utils";
import IconComponent from "../genericIconComponent";
@ -40,28 +40,15 @@ export default function CodeTabsComponent({
setActiveTab,
isMessage,
tweaks,
}: {
flow?: FlowType;
tabs: any;
activeTab: string;
setActiveTab: any;
isMessage?: boolean;
tweaks?: {
tweak?: any;
tweaksList?: any;
buildContent?: any;
getValue?: any;
buildTweakObject?: any;
};
}) {
}: codeTabsPropsType) {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const [data, setData] = useState(flow ? flow["data"]["nodes"] : null);
const [openAccordion, setOpenAccordion] = useState([]);
const [data, setData] = useState(flow ? flow["data"]!["nodes"] : null);
const [openAccordion, setOpenAccordion] = useState<string[]>([]);
const { dark } = useContext(darkContext);
useEffect(() => {
if (flow && flow["data"]["nodes"]) {
setData(flow["data"]["nodes"]);
if (flow && flow["data"]!["nodes"]) {
setData(flow["data"]!["nodes"]);
}
}, [flow]);
@ -102,8 +89,8 @@ export default function CodeTabsComponent({
};
function openAccordions() {
let accordionsToOpen = [];
tweaks.tweak.current.forEach((el) => {
let accordionsToOpen: string[] = [];
tweaks?.tweak!.current.forEach((el) => {
Object.keys(el).forEach((key) => {
if (Object.keys(el[key]).length > 0) {
accordionsToOpen.push(key);
@ -132,21 +119,25 @@ export default function CodeTabsComponent({
}}
>
<div className="api-modal-tablist-div">
<TabsList>
{tabs.map((tab, index) => (
<TabsTrigger
className={
isMessage ? "data-[state=active]:bg-primary-foreground" : ""
}
key={index}
value={index.toString()}
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
{tabs.length > 0 && tabs[0].name !== "" ? (
<TabsList>
{tabs.map((tab, index) => (
<TabsTrigger
className={
isMessage ? "data-[state=active]:bg-primary-foreground" : ""
}
key={index}
value={index.toString()}
>
{tab.name}
</TabsTrigger>
))}
</TabsList>
) : (
<div></div>
)}
{Number(activeTab) < 4 && (
<div className="float-right mx-1 flex gap-2">
<div className="float-right mx-1 mb-1 mt-2 flex gap-2">
<button
className="flex items-center gap-1.5 rounded bg-none p-1 text-xs text-gray-500 dark:text-gray-300"
onClick={copyToClipboard}
@ -201,13 +192,15 @@ export default function CodeTabsComponent({
: "overflow-hidden"
)}
>
{data.map((t: any, index) => (
{data?.map((node: any, index) => (
<div className="px-3" key={index}>
{tweaks.tweaksList.current.includes(t["data"]["id"]) && (
{tweaks?.tweaksList!.current.includes(
node["data"]["id"]
) && (
<AccordionComponent
trigger={t["data"]["id"]}
trigger={node["data"]["id"]}
open={openAccordion}
keyValue={t["data"]["id"]}
keyValue={node["data"]["id"]}
>
<div className="api-modal-table-arrangement">
<Table className="table-fixed bg-muted outline-1">
@ -222,78 +215,92 @@ export default function CodeTabsComponent({
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(t["data"]["node"]["template"])
{Object.keys(node["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type === "str" ||
t.data.node.template[n].type ===
"bool" ||
t.data.node.template[n].type ===
"float" ||
t.data.node.template[n].type ===
"code" ||
t.data.node.template[n].type ===
"prompt" ||
t.data.node.template[n].type ===
"file" ||
t.data.node.template[n].type === "int")
(templateField) =>
templateField.charAt(0) !== "_" &&
node.data.node.template[templateField]
.show &&
(node.data.node.template[templateField]
.type === "str" ||
node.data.node.template[templateField]
.type === "bool" ||
node.data.node.template[templateField]
.type === "float" ||
node.data.node.template[templateField]
.type === "code" ||
node.data.node.template[templateField]
.type === "prompt" ||
node.data.node.template[templateField]
.type === "file" ||
node.data.node.template[templateField]
.type === "int")
)
.map((n, i) => {
.map((templateField, index) => {
return (
<TableRow
key={i}
key={index}
className="h-10 dark:border-b-muted"
>
<TableCell className="p-0 text-center text-sm text-foreground">
{n}
{templateField}
</TableCell>
<TableCell className="p-0 text-xs text-foreground">
<div className="m-auto w-[250px]">
{t.data.node.template[n].type ===
"str" &&
!t.data.node.template[n].options ? (
{node.data.node.template[
templateField
].type === "str" &&
!node.data.node.template[
templateField
].options ? (
<div className="mx-auto">
{t.data.node.template[n]
.list ? (
{node.data.node.template[
templateField
].list ? (
<InputListComponent
editNode={true}
disabled={false}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? [""]
: t.data.node.template[
n
: node.data.node
.template[
templateField
].value
}
onChange={(k) => {
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
templateField
]
);
}}
/>
) : t.data.node.template[n]
.multiline ? (
) : node.data.node.template[
templateField
].multiline ? (
<ShadTooltip
content={tweaks.buildContent(
t.data.node.template[n]
.value
content={tweaks.buildContent!(
node.data.node.template[
templateField
].value
)}
>
<div>
@ -301,33 +308,38 @@ export default function CodeTabsComponent({
disabled={false}
editNode={true}
value={
!t.data.node.template[
n
!node.data.node
.template[
templateField
].value ||
t.data.node.template[
n
node.data.node
.template[
templateField
].value === ""
? ""
: t.data.node
.template[n]
.value
: node.data.node
.template[
templateField
].value
}
onChange={(k) => {
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node
.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node
.template[
templateField
]
);
}}
/>
@ -338,80 +350,94 @@ export default function CodeTabsComponent({
editNode={true}
disabled={false}
password={
t.data.node.template[n]
.password ?? false
node.data.node.template[
templateField
].password ?? false
}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[
n
: node.data.node
.template[
templateField
].value
}
onChange={(k) => {
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
templateField
]
);
}}
/>
)}
</div>
) : t.data.node.template[n].type ===
"bool" ? (
) : node.data.node.template[
templateField
].type === "bool" ? (
<div className="ml-auto">
{" "}
<ToggleShadComponent
enabled={
t.data.node.template[n]
.value
node.data.node.template[
templateField
].value
}
setEnabled={(e) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
templateField
].value = e;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
tweaks.buildTweakObject!(
node["data"]["id"],
e,
t.data.node.template[n]
node.data.node.template[
templateField
]
);
}}
size="small"
disabled={false}
/>
</div>
) : t.data.node.template[n].type ===
"file" ? (
) : node.data.node.template[
templateField
].type === "file" ? (
<ShadTooltip
content={tweaks.buildContent(
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
content={tweaks.buildContent!(
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[n]
.value
: node.data.node.template[
templateField
].value
)}
>
<div className="mx-auto">
@ -419,147 +445,176 @@ export default function CodeTabsComponent({
editNode={true}
disabled={false}
value={
t.data.node.template[n]
.value ?? ""
node.data.node.template[
templateField
].value ?? ""
}
onChange={(k: any) => {}}
onChange={(
target: any
) => {}}
fileTypes={
t.data.node.template[n]
.fileTypes
node.data.node.template[
templateField
].fileTypes
}
suffixes={
t.data.node.template[n]
.suffixes
node.data.node.template[
templateField
].suffixes
}
onFileChange={(
value: any
) => {
t.data.node.template[
n
node.data.node.template[
templateField
].file_path = value;
}}
></InputFileComponent>
</div>
</ShadTooltip>
) : t.data.node.template[n].type ===
"float" ? (
) : node.data.node.template[
templateField
].type === "float" ? (
<div className="mx-auto">
<FloatComponent
disabled={false}
editNode={true}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[n]
.value
: node.data.node.template[
templateField
].value
}
onChange={(k) => {
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
templateField
]
);
}}
/>
</div>
) : t.data.node.template[n].type ===
"str" &&
t.data.node.template[n]
.options ? (
) : node.data.node.template[
templateField
].type === "str" &&
node.data.node.template[
templateField
].options ? (
<div className="mx-auto">
<Dropdown
editNode={true}
apiModal={true}
options={
t.data.node.template[n]
.options
node.data.node.template[
templateField
].options
}
onSelect={(k) => {
onSelect={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
templateField
]
);
}}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[n]
.value
: node.data.node.template[
templateField
].value
}
></Dropdown>
</div>
) : t.data.node.template[n].type ===
"int" ? (
) : node.data.node.template[
templateField
].type === "int" ? (
<div className="mx-auto">
<IntComponent
disabled={false}
editNode={true}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[n]
.value
: node.data.node.template[
templateField
].value
}
onChange={(k) => {
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
templateField
]
);
}}
/>
</div>
) : t.data.node.template[n].type ===
"prompt" ? (
) : node.data.node.template[
templateField
].type === "prompt" ? (
<ShadTooltip
content={tweaks.buildContent(
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
content={tweaks.buildContent!(
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[n]
.value
: node.data.node.template[
templateField
].value
)}
>
<div className="mx-auto">
@ -567,44 +622,53 @@ export default function CodeTabsComponent({
editNode={true}
disabled={false}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[
n
: node.data.node
.template[
templateField
].value
}
onChange={(k) => {
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
templateField
]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n].type ===
"code" ? (
) : node.data.node.template[
templateField
].type === "code" ? (
<ShadTooltip
content={tweaks.buildContent(
tweaks.getValue(
t.data.node.template[n]
.value,
t.data,
t.data.node.template[n]
content={tweaks.buildContent!(
tweaks.getValue!(
node.data.node.template[
templateField
].value,
node.data,
node.data.node.template[
templateField
]
)
)}
>
@ -613,37 +677,43 @@ export default function CodeTabsComponent({
disabled={false}
editNode={true}
value={
!t.data.node.template[n]
.value ||
t.data.node.template[n]
.value === ""
!node.data.node.template[
templateField
].value ||
node.data.node.template[
templateField
].value === ""
? ""
: t.data.node.template[
n
: node.data.node
.template[
templateField
].value
}
onChange={(k) => {
onChange={(target) => {
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
n
].value = k;
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
t["data"]["id"],
k,
t.data.node.template[n]
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
templateField
]
);
}}
/>
</div>
</ShadTooltip>
) : t.data.node.template[n].type ===
"Any" ? (
) : node.data.node.template[
templateField
].type === "Any" ? (
"-"
) : (
<div className="hidden"></div>
@ -659,7 +729,7 @@ export default function CodeTabsComponent({
</AccordionComponent>
)}
{tweaks.tweaksList.current.length === 0 && (
{tweaks?.tweaksList!.current.length === 0 && (
<>
<div className="pt-3">
No tweaks are available for this flow.

View file

@ -11,7 +11,7 @@ export default function Dropdown({
editNode = false,
numberOfOptions = 0,
apiModal = false,
}: DropDownComponentType) {
}: DropDownComponentType): JSX.Element {
let [internalValue, setInternalValue] = useState(
value === "" || !value ? "Choose an option" : value
);

View file

@ -1,5 +1,6 @@
import { useEffect } from "react";
import { FloatComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { Input } from "../ui/input";
export default function FloatComponent({
@ -7,7 +8,7 @@ export default function FloatComponent({
onChange,
disabled,
editNode = false,
}: FloatComponentType) {
}: FloatComponentType): JSX.Element {
const step = 0.1;
const min = 0;
const max = 1;
@ -25,12 +26,12 @@ export default function FloatComponent({
type="number"
step={step}
min={min}
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value < min.toString()) {
e.target.value = min.toString();
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value < min.toString()) {
event.target.value = min.toString();
}
if (e.target.value > max.toString()) {
e.target.value = max.toString();
if (event.target.value > max.toString()) {
event.target.value = max.toString();
}
}}
max={max}
@ -40,8 +41,11 @@ export default function FloatComponent({
placeholder={
editNode ? "Number 0 to 1" : "Type a number from zero to one"
}
onChange={(e) => {
onChange(e.target.value);
onChange={(event) => {
onChange(event.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "0");
}}
/>
</div>

View file

@ -7,5 +7,11 @@ export default function IconComponent({
iconColor,
}: IconComponentProps): JSX.Element {
const TargetIcon = nodeIconsLucide[name] ?? nodeIconsLucide["unknown"];
return <TargetIcon className={className} style={{ color: iconColor }} />;
return (
<TargetIcon
strokeWidth={1.5}
className={className}
style={{ color: iconColor }}
/>
);
}

View file

@ -12,10 +12,11 @@ import { Link, useNavigate } from "react-router-dom";
import { alertContext } from "../../../../contexts/alertContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
import { menuBarPropsType } from "../../../../types/components";
import IconComponent from "../../../genericIconComponent";
import { Button } from "../../../ui/button";
export const MenuBar = ({ flows, tabId }) => {
export const MenuBar = ({ flows, tabId }: menuBarPropsType): JSX.Element => {
const { addFlow } = useContext(TabsContext);
const { setErrorData } = useContext(alertContext);
const { undo, redo } = useContext(undoRedoContext);
@ -25,12 +26,12 @@ export const MenuBar = ({ flows, tabId }) => {
function handleAddFlow() {
try {
addFlow(null, true).then((id) => {
addFlow(undefined, true).then((id) => {
navigate("/flow/" + id);
});
// saveFlowStyleInDataBase();
} catch (err) {
setErrorData(err);
setErrorData(err as { title: string; list?: Array<string> });
}
}
let current_flow = flows.find((flow) => flow.id === tabId);
@ -45,7 +46,9 @@ export const MenuBar = ({ flows, tabId }) => {
<DropdownMenuTrigger asChild>
<Button asChild variant="primary" size="sm">
<div className="header-menu-bar-display">
<div className="header-menu-flow-name">{current_flow.name}</div>
<div className="header-menu-flow-name">
{current_flow!.name}
</div>
<IconComponent name="ChevronDown" className="h-4 w-4" />
</div>
</Button>

View file

@ -12,7 +12,7 @@ import { Button } from "../ui/button";
import { Separator } from "../ui/separator";
import MenuBar from "./components/menuBar";
export default function Header() {
export default function Header(): JSX.Element {
const { flows, tabId } = useContext(TabsContext);
const { dark, setDark } = useContext(darkContext);
const { notificationCenter } = useContext(alertContext);
@ -34,6 +34,9 @@ export default function Header() {
<Link to="/">
<span className="ml-4 text-2xl">⛓️</span>
</Link>
<Button variant="outline" className="">
Sign out
</Button>
{flows.findIndex((f) => tabId === f.id) !== -1 && tabId !== "" && (
<MenuBar flows={flows} tabId={tabId} />
)}

View file

@ -1,5 +1,7 @@
import * as Form from "@radix-ui/react-form";
import { useEffect, useState } from "react";
import { InputComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { classNames } from "../../utils/utils";
import { Input } from "../ui/input";
@ -7,9 +9,13 @@ export default function InputComponent({
value,
onChange,
disabled,
required = false,
isForm = false,
password,
editNode = false,
}: InputComponentType) {
placeholder = "Type something...",
className,
}: InputComponentType): JSX.Element {
const [pwdVisible, setPwdVisible] = useState(false);
// Clear component state
@ -21,20 +27,53 @@ export default function InputComponent({
return (
<div className="relative w-full">
<Input
value={value}
disabled={disabled}
className={classNames(
password && !pwdVisible && value !== "" ? " text-clip password " : "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : ""
)}
placeholder={password && editNode ? "Key" : "Type something..."}
onChange={(e) => {
onChange(e.target.value);
}}
/>
{isForm ? (
<Form.Control asChild>
<Input
value={value}
disabled={disabled}
required={required}
className={classNames(
password && !pwdVisible && value !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : "",
className!
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
onChange(e.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
}}
/>
</Form.Control>
) : (
<Input
value={value}
disabled={disabled}
required={required}
className={classNames(
password && !pwdVisible && value !== ""
? " text-clip password "
: "",
editNode ? " input-edit-node " : "",
password && editNode ? "pr-8" : "",
password && !editNode ? "pr-10" : "",
className!
)}
placeholder={password && editNode ? "Key" : placeholder}
onChange={(e) => {
onChange(e.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, value, "");
}}
/>
)}
{password && (
<button
className={classNames(

View file

@ -13,7 +13,7 @@ export default function InputFileComponent({
fileTypes,
onFileChange,
editNode = false,
}: FileComponentType) {
}: FileComponentType): JSX.Element {
const [myValue, setMyValue] = useState(value);
const [loading, setLoading] = useState(false);
const { setErrorData } = useContext(alertContext);
@ -41,7 +41,7 @@ export default function InputFileComponent({
setMyValue(value);
}, [value]);
const handleButtonClick = () => {
const handleButtonClick = (): void => {
// Create a file input element
const input = document.createElement("input");
input.type = "file";
@ -49,11 +49,11 @@ export default function InputFileComponent({
input.style.display = "none"; // Hidden from view
input.multiple = false; // Allow only one file selection
input.onchange = (e: Event) => {
input.onchange = (event: Event): void => {
setLoading(true);
// Get the selected file
const file = (e.target as HTMLInputElement).files?.[0];
const file = (event.target as HTMLInputElement).files?.[0];
// Check if the file type is correct
if (file && checkFileType(file.name)) {

View file

@ -11,7 +11,7 @@ export default function InputListComponent({
onChange,
disabled,
editNode = false,
}: InputListComponentType) {
}: InputListComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
onChange([""]);
@ -25,20 +25,26 @@ export default function InputListComponent({
"flex flex-col gap-3"
)}
>
{value.map((i, idx) => {
{value.map((singleValue, idx) => {
return (
<div key={idx} className="flex w-full gap-3">
<Input
disabled={disabled}
type="text"
value={i}
value={singleValue}
className={editNode ? "input-edit-node" : ""}
placeholder="Type something..."
onChange={(e) => {
onChange={(event) => {
let newInputList = _.cloneDeep(value);
newInputList[idx] = e.target.value;
newInputList[idx] = event.target.value;
onChange(newInputList);
}}
onKeyDown={(e) => {
if (e.ctrlKey && e.key === "Backspace") {
e.preventDefault();
e.stopPropagation();
}
}}
/>
{idx === value.length - 1 ? (
<button

View file

@ -1,5 +1,6 @@
import { useEffect } from "react";
import { FloatComponentType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { Input } from "../ui/input";
export default function IntComponent({
@ -7,7 +8,7 @@ export default function IntComponent({
onChange,
disabled,
editNode = false,
}: FloatComponentType) {
}: FloatComponentType): JSX.Element {
const min = 0;
// Clear component state
@ -37,21 +38,22 @@ export default function IntComponent({
) {
event.preventDefault();
}
handleKeyDown(event, value, "0");
}}
type="number"
step="1"
min={min}
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.value < min.toString()) {
e.target.value = min.toString();
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.value < min.toString()) {
event.target.value = min.toString();
}
}}
value={value ?? ""}
className={editNode ? "input-edit-node" : ""}
disabled={disabled}
placeholder={editNode ? "Integer number" : "Type an integer number"}
onChange={(e) => {
onChange(e.target.value);
onChange={(event) => {
onChange(event.target.value);
}}
/>
</div>

View file

@ -1,8 +1,8 @@
type LoadingComponentProps = {
remSize: number;
};
import { LoadingComponentProps } from "../../types/components";
export default function LoadingComponent({ remSize }: LoadingComponentProps) {
export default function LoadingComponent({
remSize,
}: LoadingComponentProps): JSX.Element {
return (
<div role="status" className="m-auto w-min">
<svg

View file

@ -14,7 +14,7 @@ export default function PromptAreaComponent({
onChange,
disabled,
editNode = false,
}: TextAreaComponentType) {
}: TextAreaComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
onChange("");
@ -23,9 +23,9 @@ export default function PromptAreaComponent({
useEffect(() => {
if (value !== "" && !editNode) {
postValidatePrompt(field_name, value, nodeClass).then((apiReturn) => {
postValidatePrompt(field_name!, value, nodeClass!).then((apiReturn) => {
if (apiReturn.data) {
setNodeClass(apiReturn.data.frontend_node);
setNodeClass!(apiReturn.data.frontend_node);
// need to update reactFlowInstance to re-render the nodes.
}
});
@ -39,8 +39,8 @@ export default function PromptAreaComponent({
value={value}
buttonText="Check & Save"
modalTitle="Edit Prompt"
setValue={(t: string) => {
onChange(t);
setValue={(value: string) => {
onChange(value);
}}
nodeClass={nodeClass}
setNodeClass={setNodeClass}

View file

@ -10,7 +10,7 @@ export default function TextAreaComponent({
onChange,
disabled,
editNode = false,
}: TextAreaComponentType) {
}: TextAreaComponentType): JSX.Element {
// Clear text area
useEffect(() => {
if (disabled) {
@ -25,8 +25,8 @@ export default function TextAreaComponent({
disabled={disabled}
className={editNode ? "input-edit-node" : ""}
placeholder={"Type something..."}
onChange={(e) => {
onChange(e.target.value);
onChange={(event) => {
onChange(event.target.value);
}}
/>
<div>
@ -35,8 +35,8 @@ export default function TextAreaComponent({
buttonText="Finishing Editing"
modalTitle="Edit Text"
value={value}
setValue={(t: string) => {
onChange(t);
setValue={(value: string) => {
onChange(value);
}}
>
{!editNode && (

View file

@ -7,7 +7,7 @@ export default function ToggleComponent({
enabled,
setEnabled,
disabled,
}: ToggleComponentType) {
}: ToggleComponentType): JSX.Element {
// set component state as disabled
useEffect(() => {
if (disabled) {
@ -18,8 +18,8 @@ export default function ToggleComponent({
<div className={disabled ? "pointer-events-none cursor-not-allowed" : ""}>
<Switch
checked={enabled}
onChange={(x: boolean) => {
setEnabled(x);
onChange={(isEnabled: boolean) => {
setEnabled(isEnabled);
}}
className={classNames(
enabled ? "bg-primary" : "bg-input",

View file

@ -6,7 +6,7 @@ export default function ToggleShadComponent({
setEnabled,
disabled,
size,
}: ToggleComponentType) {
}: ToggleComponentType): JSX.Element {
let scaleX, scaleY;
switch (size) {
case "small":
@ -35,8 +35,8 @@ export default function ToggleShadComponent({
disabled={disabled}
className=""
checked={enabled}
onCheckedChange={(x: boolean) => {
setEnabled(x);
onCheckedChange={(isEnabled: boolean) => {
setEnabled(isEnabled);
}}
></Switch>
</div>

View file

@ -27,4 +27,4 @@ const PopoverContent = React.forwardRef<
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };
export { Popover, PopoverContent, PopoverTrigger };

View file

@ -15,22 +15,22 @@ export default function RenameLabel(props) {
useEffect(() => {
if (isRename) {
setMyValue(props.value);
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
setIsRename(false);
props.setValue("");
}
});
if (inputRef.current) {
setTimeout(() => {
inputRef.current.focus();
inputRef.current?.focus();
}, 100);
}
}
resizeInput();
}, [isRename]);
const inputRef = useRef(null);
const inputRef = useRef<HTMLInputElement | null>(null);
const resizeInput = () => {
const input = inputRef.current;
@ -67,8 +67,8 @@ export default function RenameLabel(props) {
}
}}
value={myValue}
onChange={(e) => {
setMyValue(e.target.value);
onChange={(event) => {
setMyValue(event.target.value);
}}
/>
) : (

View file

@ -0,0 +1,120 @@
"use client";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown } from "lucide-react";
import * as React from "react";
import { cn } from "../../utils/utils";
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
)}
position={position}
{...props}
>
<SelectPrimitive.Viewport
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View file

@ -29,6 +29,7 @@ export const INVALID_CHARACTERS = [
*/
export const regexHighlight = /\{([^}]+)\}/g;
export const specialCharsRegex = /[!@#$%^&*()\-_=+[\]{}|;:'",.<>/?\\`´]/;
export const programmingLanguages: languageMap = {
javascript: ".js",
@ -508,3 +509,96 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
"/api/v1/custom_component",
"/api/v1/validate/prompt",
];
export const skipNodeUpdate = ["CustomComponent"];
export const CONTROL_INPUT_STATE = {
password: "",
cnfPassword: "",
username: "",
};
export const CONTROL_LOGIN_STATE = {
username: "",
password: "",
};
export const tabsCode = [];
export function tabsArray(codes: string[], method: number) {
if (!method) return;
if (method === 0) {
return [
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[0],
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: codes[1],
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[2],
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[3],
},
];
}
return [
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[0],
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: codes[1],
},
{
name: "Python Code",
mode: "python",
language: "py",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: codes[2],
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[3],
},
{
name: "Tweaks",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[4],
},
];
}

View file

@ -1,34 +1,15 @@
import { createContext, ReactNode, useState } from "react";
import { AlertItemType } from "../types/alerts";
import { alertContextType } from "../types/typesContext";
import _ from "lodash";
//types for alertContextType
type alertContextType = {
errorData: { title: string; list?: Array<string> };
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
errorOpen: boolean;
setErrorOpen: (newState: boolean) => void;
noticeData: { title: string; link?: string };
setNoticeData: (newState: { title: string; link?: string }) => void;
noticeOpen: boolean;
setNoticeOpen: (newState: boolean) => void;
successData: { title: string };
setSuccessData: (newState: { title: string }) => void;
successOpen: boolean;
setSuccessOpen: (newState: boolean) => void;
notificationCenter: boolean;
setNotificationCenter: (newState: boolean) => void;
notificationList: Array<AlertItemType>;
pushNotificationList: (Object: AlertItemType) => void;
clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void;
};
//initial values to alertContextType
const initialValue: alertContextType = {
errorData: { title: "", list: [] },
setErrorData: () => {},
loading: true,
setLoading: () => {},
errorOpen: false,
setErrorOpen: () => {},
noticeData: { title: "", link: "" },
@ -55,6 +36,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
list?: Array<string>;
}>({ title: "", list: [] });
const [errorOpen, setErrorOpen] = useState(false);
const [loading, setLoading] = useState(true);
const [noticeData, setNoticeDataState] = useState<{
title: string;
link?: string;
@ -65,7 +47,7 @@ export function AlertProvider({ children }: { children: ReactNode }) {
});
const [successOpen, setSuccessOpen] = useState(false);
const [notificationCenter, setNotificationCenter] = useState(false);
const [notificationList, setNotificationList] = useState([]);
const [notificationList, setNotificationList] = useState<AlertItemType[]>([]);
const pushNotificationList = (notification: AlertItemType) => {
setNotificationList((old) => {
let newNotificationList = _.cloneDeep(old);
@ -141,6 +123,8 @@ export function AlertProvider({ children }: { children: ReactNode }) {
removeFromNotificationList,
clearNotificationList,
notificationList,
loading,
setLoading,
pushNotificationList,
setNotificationCenter,
notificationCenter,

View file

@ -0,0 +1,77 @@
import { createContext, useEffect, useState } from "react";
import { AuthContextType, userData } from "../types/contexts/auth";
const initialValue: AuthContextType = {
isAuthenticated: false,
accessToken: null,
login: () => {},
logout: () => {},
refreshAccessToken: () => Promise.resolve(),
userData: null,
setUserData: () => {},
};
const AuthContext = createContext<AuthContextType>(initialValue);
export function AuthProvider({ children }): React.ReactElement {
const [accessToken, setAccessToken] = useState<string | null>(null);
const [userData, setUserData] = useState<userData | null>(null);
useEffect(() => {
const storedAccessToken = localStorage.getItem("access_token");
if (storedAccessToken) {
setAccessToken(storedAccessToken);
}
}, []);
function login(newAccessToken: string, refreshToken: string) {
localStorage.setItem("access_token", newAccessToken);
setAccessToken(newAccessToken);
// Store refreshToken if needed
}
function logout() {
localStorage.removeItem("access_token");
// Clear refreshToken if used
setAccessToken(null);
}
async function refreshAccessToken(refreshToken: string) {
try {
// Call your API to refresh the access token using the refresh token
const response = await fetch("/api/refresh-token", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ refreshToken }),
});
if (response.ok) {
const data = await response.json();
login(data.accessToken, refreshToken);
} else {
logout();
}
} catch (error) {
logout();
}
}
return (
// !! to convert string to boolean
<AuthContext.Provider
value={{
isAuthenticated: !!accessToken,
accessToken,
login,
logout,
refreshAccessToken,
setUserData,
userData,
}}
>
{children}
</AuthContext.Provider>
);
}

View file

@ -1,9 +1,5 @@
import { createContext, useEffect, useState } from "react";
type darkContextType = {
dark: {};
setDark: (newState: {}) => void;
};
import { darkContextType } from "../types/typesContext";
const initialValue = {
dark: {},
@ -14,13 +10,13 @@ export const darkContext = createContext<darkContextType>(initialValue);
export function DarkProvider({ children }) {
const [dark, setDark] = useState(
JSON.parse(window.localStorage.getItem("isDark")) ?? false
JSON.parse(window.localStorage.getItem("isDark")!) ?? false
);
useEffect(() => {
if (dark) {
document.getElementById("body").classList.add("dark");
document.getElementById("body")!.classList.add("dark");
} else {
document.getElementById("body").classList.remove("dark");
document.getElementById("body")!.classList.remove("dark");
}
window.localStorage.setItem("isDark", dark.toString());
}, [dark]);

View file

@ -16,17 +16,17 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
<TooltipProvider>
<ReactFlowProvider>
<DarkProvider>
<TypesProvider>
<LocationProvider>
<AlertProvider>
<AlertProvider>
<TypesProvider>
<LocationProvider>
<SSEProvider>
<TabsProvider>
<UndoRedoProvider>{children}</UndoRedoProvider>
</TabsProvider>
</SSEProvider>
</AlertProvider>
</LocationProvider>
</TypesProvider>
</LocationProvider>
</TypesProvider>
</AlertProvider>
</DarkProvider>
</ReactFlowProvider>
</TooltipProvider>

View file

@ -1,34 +1,5 @@
import { createContext, ReactNode, useState } from "react";
//types for location context
type locationContextType = {
current: Array<string>;
setCurrent: (newState: Array<string>) => void;
isStackedOpen: boolean;
setIsStackedOpen: (newState: boolean) => void;
showSideBar: boolean;
setShowSideBar: (newState: boolean) => void;
extraNavigation: {
title: string;
options?: Array<{
name: string;
href: string;
icon: any;
children?: Array<any>;
}>;
};
setExtraNavigation: (newState: {
title: string;
options?: Array<{
name: string;
href: string;
icon: any;
children?: Array<any>;
}>;
}) => void;
extraComponent: any;
setExtraComponent: (newState: any) => void;
};
import { locationContextType } from "../types/typesContext";
//initial value for location context
const initialValue = {

View file

@ -7,8 +7,9 @@ import {
useRef,
useState,
} from "react";
import { Edge, addEdge } from "reactflow";
import { Edge, Node, ReactFlowJsonObject, addEdge } from "reactflow";
import ShortUniqueId from "short-unique-id";
import { skipNodeUpdate } from "../constants/constants";
import {
deleteFlowFromDatabase,
downloadFlowsFromDatabase,
@ -18,13 +19,15 @@ import {
uploadFlowsToDatabase,
} from "../controllers/API";
import { APIClassType, APITemplateType } from "../types/api";
import { tweakType } from "../types/components";
import {
FlowType,
NodeDataType,
NodeType,
sourceHandleType,
targetHandleType,
} from "../types/flow";
import { TabsContextType, TabsState } from "../types/tabs";
import { TabsContextType, TabsState, errorsVarType } from "../types/tabs";
import {
addVersionToDuplicates,
checkOldEdgesHandles,
@ -82,9 +85,12 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const [flows, setFlows] = useState<Array<FlowType>>([]);
const [id, setId] = useState(uid());
const { templates, reactFlowInstance } = useContext(typesContext);
const [lastCopiedSelection, setLastCopiedSelection] = useState(null);
const [lastCopiedSelection, setLastCopiedSelection] = useState<{
nodes: any;
edges: any;
} | null>(null);
const [tabsState, setTabsState] = useState<TabsState>({});
const [getTweak, setTweak] = useState([]);
const [getTweak, setTweak] = useState<tweakType>([]);
const newNodeId = useRef(uid());
function incrementNodeId() {
@ -139,8 +145,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
//get tabs from db
return readFlowsFromDatabase();
}
function processDBData(DbData) {
DbData.forEach((flow) => {
function processDBData(DbData: FlowType[]) {
DbData.forEach((flow: FlowType) => {
try {
if (!flow.data) {
return;
@ -167,16 +173,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
function updateDisplay_name(node: NodeType, template: APIClassType) {
node.data.node.display_name = template["display_name"] || node.data.type;
node.data.node!.display_name = template["display_name"] || node.data.type;
}
function updateNodeDocumentation(node: NodeType, template: APIClassType) {
node.data.node.documentation = template["documentation"];
node.data.node!.documentation = template["documentation"];
}
function processFlowNodes(flow) {
function processFlowNodes(flow: FlowType) {
if (!flow.data || !flow.data.nodes) return;
flow.data.nodes.forEach((node: NodeType) => {
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` });
@ -194,7 +201,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
function updateNodeBaseClasses(node: NodeType, template: APIClassType) {
node.data.node.base_classes = template["base_classes"];
node.data.node!.base_classes = template["base_classes"];
}
function updateNodeEdges(
@ -202,10 +209,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
node: NodeType,
template: APIClassType
) {
flow.data.edges.forEach((edge) => {
flow.data!.edges.forEach((edge) => {
if (edge.source === node.id) {
let sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle
edge.sourceHandle!
);
sourceHandleObject.baseClasses = sourceHandleObject.baseClasses.concat(
template["base_classes"]
@ -216,17 +223,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
function updateNodeDescription(node: NodeType, template: APIClassType) {
node.data.node.description = template["description"];
node.data.node!.description = template["description"];
}
function updateNodeTemplate(node: NodeType, template: APIClassType) {
node.data.node.template = updateTemplate(
node.data.node!.template = updateTemplate(
template["template"] as unknown as APITemplateType,
node.data.node.template as APITemplateType
node.data.node!.template as APITemplateType
);
}
function updateStateWithDbData(tabsData) {
function updateStateWithDbData(tabsData: FlowType[]) {
setFlows(tabsData);
}
@ -257,7 +264,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
link.download = `${
flowName && flowName != ""
? flowName
: flows.find((f) => f.id === tabId).name
: flows.find((f) => f.id === tabId)!.name
}.json`;
// simulate a click on the link element to trigger the download
@ -309,10 +316,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
input.onchange = (e: Event) => {
// check if the file type is application/json
if (
(e.target as HTMLInputElement).files[0].type === "application/json"
(e.target as HTMLInputElement).files![0].type === "application/json"
) {
// get the file from the file input
const currentfile = (e.target as HTMLInputElement).files[0];
const currentfile = (e.target as HTMLInputElement).files![0];
// read the file as text
currentfile.text().then((text) => {
// parse the text into a JSON object
@ -332,11 +339,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const input = document.createElement("input");
input.type = "file";
// add a change event listener to the file input
input.onchange = (e: Event) => {
input.onchange = (event: Event) => {
// check if the file type is application/json
if ((e.target as HTMLInputElement).files[0].type === "application/json") {
if (
(event.target as HTMLInputElement).files![0].type === "application/json"
) {
// get the file from the file input
const file = (e.target as HTMLInputElement).files[0];
const file = (event.target as HTMLInputElement).files![0];
// read the file as text
const formData = new FormData();
formData.append("file", file);
@ -367,69 +376,69 @@ export function TabsProvider({ children }: { children: ReactNode }) {
*/
function paste(
selectionInstance,
selectionInstance: { nodes: Node[]; edges: Edge[] },
position: { x: number; y: number; paneX?: number; paneY?: number }
) {
let minimumX = Infinity;
let minimumY = Infinity;
let idsMap = {};
let nodes = reactFlowInstance.getNodes();
let edges = reactFlowInstance.getEdges();
selectionInstance.nodes.forEach((n) => {
if (n.position.y < minimumY) {
minimumY = n.position.y;
let nodes: Node<NodeDataType>[] = reactFlowInstance!.getNodes();
let edges = reactFlowInstance!.getEdges();
selectionInstance.nodes.forEach((node: Node) => {
if (node.position.y < minimumY) {
minimumY = node.position.y;
}
if (n.position.x < minimumX) {
minimumX = n.position.x;
if (node.position.x < minimumX) {
minimumX = node.position.x;
}
});
const insidePosition = position.paneX
? { x: position.paneX + position.x, y: position.paneY + position.y }
: reactFlowInstance.project({ x: position.x, y: position.y });
? { x: position.paneX + position.x, y: position.paneY! + position.y }
: reactFlowInstance!.project({ x: position.x, y: position.y });
selectionInstance.nodes.forEach((n: NodeType) => {
selectionInstance.nodes.forEach((node: NodeType) => {
// Generate a unique node ID
let newId = getNodeId(n.data.type);
idsMap[n.id] = newId;
let newId = getNodeId(node.data.type);
idsMap[node.id] = newId;
// Create a new node object
const newNode: NodeType = {
id: newId,
type: "genericNode",
position: {
x: insidePosition.x + n.position.x - minimumX,
y: insidePosition.y + n.position.y - minimumY,
x: insidePosition.x + node.position!.x - minimumX,
y: insidePosition.y + node.position!.y - minimumY,
},
data: {
..._.cloneDeep(n.data),
..._.cloneDeep(node.data),
id: newId,
},
};
// Add the new node to the list of nodes in state
nodes = nodes
.map((e) => ({ ...e, selected: false }))
.map((node) => ({ ...node, selected: false }))
.concat({ ...newNode, selected: false });
});
reactFlowInstance.setNodes(nodes);
reactFlowInstance!.setNodes(nodes);
selectionInstance.edges.forEach((e: Edge) => {
let source = idsMap[e.source];
let target = idsMap[e.target];
selectionInstance.edges.forEach((edge: Edge) => {
let source = idsMap[edge.source];
let target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
e.sourceHandle
edge.sourceHandle!
);
let sourceHandle = scapedJSONStringfy({
...sourceHandleObject,
id: e.source,
id: edge.source,
});
const targetHandleObject: targetHandleType = scapeJSONParse(
e.targetHandle
edge.targetHandle!
);
let targetHandle = scapedJSONStringfy({
...targetHandleObject,
id: e.target,
id: edge.target,
});
let id =
"reactflow__edge-" +
@ -453,24 +462,26 @@ export function TabsProvider({ children }: { children: ReactNode }) {
animated: targetHandleObject.type === "Text",
selected: false,
},
edges.map((e) => ({ ...e, selected: false }))
edges.map((edge) => ({ ...edge, selected: false }))
);
});
reactFlowInstance.setEdges(edges);
reactFlowInstance!.setEdges(edges);
}
const addFlow = async (
flow?: FlowType,
newProject?: Boolean
): Promise<String> => {
): Promise<String | undefined> => {
if (newProject) {
let flowData = extractDataFromFlow(flow);
let flowData = extractDataFromFlow(flow!);
if (flowData.description == "") {
flowData.description = getRandomDescription();
}
// Create a new flow with a default name if no flow is provided.
const newFlow = createNewFlow(flowData, flow);
const newFlow = createNewFlow(flowData, flow!);
processFlowEdges(newFlow);
processFlowNodes(newFlow);
const flowName = addVersionToDuplicates(newFlow, flows);
@ -493,13 +504,13 @@ export function TabsProvider({ children }: { children: ReactNode }) {
}
} else {
paste(
{ nodes: flow.data.nodes, edges: flow.data.edges },
{ nodes: flow!.data!.nodes, edges: flow!.data!.edges },
{ x: 10, y: 10 }
);
}
};
const extractDataFromFlow = (flow) => {
const extractDataFromFlow = (flow: FlowType) => {
let data = flow?.data ? flow.data : null;
const description = flow?.description ? flow.description : "";
@ -517,7 +528,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
const updateEdges = (edges: Edge[]) => {
edges.forEach((edge) => {
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle
edge.targetHandle!
);
edge.className =
(targetHandleObject.type === "Text"
@ -527,8 +538,9 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
};
const updateNodes = (nodes, edges) => {
const updateNodes = (nodes: Node[], edges: Edge[]) => {
nodes.forEach((node) => {
if (skipNodeUpdate.includes(node.data.type)) return;
const template = templates[node.data.type];
if (!template) {
setErrorData({ title: `Unknown node type: ${node.data.type}` });
@ -538,7 +550,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
node.data.node.base_classes = template["base_classes"];
edges.forEach((edge) => {
let sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle
edge.sourceHandle!
);
if (edge.source === node.id) {
let newSourceHandle = sourceHandleObject;
@ -555,14 +567,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
};
const createNewFlow = (flowData, flow) => ({
const createNewFlow = (
flowData: { data: ReactFlowJsonObject | null; description: string },
flow: FlowType
) => ({
description: flowData.description,
name: flow?.name ?? getRandomName(),
data: flowData.data,
id: "",
});
const addFlowToLocalState = (newFlow) => {
const addFlowToLocalState = (newFlow: FlowType) => {
setFlows((prevState) => {
return [...prevState, newFlow];
});
@ -613,7 +628,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
} catch (err) {
setErrorData(err);
setErrorData(err as errorsVarType);
}
}

View file

@ -1,14 +1,21 @@
import { createContext, ReactNode, useEffect, useState } from "react";
import { Node } from "reactflow";
import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { Node, ReactFlowInstance } from "reactflow";
import { getAll } from "../controllers/API";
import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext";
import { alertContext } from "./alertContext";
//context to share types adn functions from nodes to flow
const initialValue: typesContextType = {
reactFlowInstance: null,
setReactFlowInstance: () => {},
setReactFlowInstance: (newState: ReactFlowInstance) => {},
deleteNode: () => {},
types: {},
setTypes: () => {},
@ -22,13 +29,15 @@ export const typesContext = createContext<typesContextType>(initialValue);
export function TypesProvider({ children }: { children: ReactNode }) {
const [types, setTypes] = useState({});
const [reactFlowInstance, setReactFlowInstance] = useState(null);
const [reactFlowInstance, setReactFlowInstance] =
useState<ReactFlowInstance | null>(null);
const [templates, setTemplates] = useState({});
const [data, setData] = useState({});
const { setLoading } = useContext(alertContext);
useEffect(() => {
let delay = 1000; // Start delay of 1 second
let intervalId = null;
let intervalId: NodeJS.Timer;
let retryCount = 0; // Count of retry attempts
const maxRetryCount = 5; // Max retry attempts
@ -40,6 +49,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
const result = await getAll();
// Make sure to only update the state if the component is still mounted.
if (isMounted) {
setLoading(false);
setData(result.data);
setTemplates(
Object.keys(result.data).reduce((acc, curr) => {
@ -69,7 +79,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
);
}
// Clear the interval if successful.
clearInterval(intervalId);
clearInterval(intervalId!);
} catch (error) {
console.error("An error has occurred while fetching types.");
}
@ -77,23 +87,22 @@ export function TypesProvider({ children }: { children: ReactNode }) {
// Start the initial interval.
intervalId = setInterval(getTypes, delay);
return () => {
// This will clear the interval when the component unmounts, or when the dependencies of the useEffect hook change.
clearInterval(intervalId);
clearInterval(intervalId!);
// Indicate that the component has been unmounted.
isMounted = false;
};
}, []);
function deleteNode(idx: string) {
reactFlowInstance.setNodes(
reactFlowInstance.getNodes().filter((n: Node) => n.id !== idx)
reactFlowInstance!.setNodes(
reactFlowInstance!.getNodes().filter((node: Node) => node.id !== idx)
);
reactFlowInstance.setEdges(
reactFlowInstance
reactFlowInstance!.setEdges(
reactFlowInstance!
.getEdges()
.filter((ns) => ns.source !== idx && ns.target !== idx)
.filter((edge) => edge.source !== idx && edge.target !== idx)
);
}
return (

View file

@ -6,26 +6,15 @@ import {
useEffect,
useState,
} from "react";
import { Edge, Node, useReactFlow } from "reactflow";
import { useReactFlow } from "reactflow";
import {
HistoryItem,
UseUndoRedoOptions,
undoRedoContextType,
} from "../types/typesContext";
import { isWrappedWithClass } from "../utils/utils";
import { TabsContext } from "./tabsContext";
type undoRedoContextType = {
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
};
type UseUndoRedoOptions = {
maxHistorySize: number;
enableShortcuts: boolean;
};
type HistoryItem = {
nodes: Node[];
edges: Edge[];
};
const initialValue = {
undo: () => {},
redo: () => {},
@ -45,14 +34,18 @@ export function UndoRedoProvider({ children }) {
const [past, setPast] = useState<HistoryItem[][]>(flows.map(() => []));
const [future, setFuture] = useState<HistoryItem[][]>(flows.map(() => []));
const [tabIndex, setTabIndex] = useState(
flows.findIndex((f) => f.id === tabId)
flows.findIndex((flow) => flow.id === tabId)
);
useEffect(() => {
// whenever the flows variable changes, we need to add one array to the past and future states
setPast((old) => flows.map((f, i) => (old[i] ? old[i] : [])));
setFuture((old) => flows.map((f, i) => (old[i] ? old[i] : [])));
setTabIndex(flows.findIndex((f) => f.id === tabId));
setPast((old) =>
flows.map((flow, index) => (old[index] ? old[index] : []))
);
setFuture((old) =>
flows.map((flow, index) => (old[index] ? old[index] : []))
);
setTabIndex(flows.findIndex((flow) => flow.id === tabId));
}, [flows, tabId]);
const { setNodes, setEdges, getNodes, getEdges } = useReactFlow();

View file

@ -1,6 +1,5 @@
import axios, { AxiosError, AxiosInstance } from "axios";
import { useContext, useEffect, useRef } from "react";
import { URL_EXCLUDED_FROM_ERROR_RETRIES } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
// Create a new Axios instance
@ -8,7 +7,7 @@ const api: AxiosInstance = axios.create({
baseURL: "",
});
function ApiInterceptor() {
function ApiInterceptor(): null {
const retryCounts = useRef([]);
const { setErrorData } = useContext(alertContext);
@ -16,32 +15,31 @@ function ApiInterceptor() {
const interceptor = api.interceptors.response.use(
(response) => response,
async (error: AxiosError) => {
if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
return Promise.reject(error);
}
let retryCount = 0;
while (retryCount < 4) {
await sleep(5000); // Sleep for 5 seconds
retryCount++;
try {
const response = await axios.request(error.config);
return response;
} catch (error) {
if (retryCount === 3) {
setErrorData({
title: "There was an error on web connection, please: ",
list: [
"Refresh the page",
"Use a new flow tab",
"Check if the backend is up",
"Endpoint: " + error.config?.url,
],
});
return Promise.reject(error);
}
}
}
// if (URL_EXCLUDED_FROM_ERROR_RETRIES.includes(error.config?.url)) {
// return Promise.reject(error);
// }
// let retryCount = 0;
// while (retryCount < 4) {
// await sleep(5000); // Sleep for 5 seconds
// retryCount++;
// try {
// const response = await axios.request(error.config);
// return response;
// } catch (error) {
// if (retryCount === 3) {
// setErrorData({
// title: "There was an error on web connection, please: ",
// list: [
// "Refresh the page",
// "Use a new flow tab",
// "Check if the backend is up",
// "Endpoint: " + error.config?.url,
// ],
// });
// return Promise.reject(error);
// }
// }
// }
}
);
@ -55,7 +53,7 @@ function ApiInterceptor() {
}
// Function to sleep for a given duration in milliseconds
function sleep(ms) {
function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View file

@ -23,7 +23,7 @@ export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
const GITHUB_API_URL = "https://api.github.com";
export async function getRepoStars(owner, repo) {
export async function getRepoStars(owner: string, repo: string) {
try {
const response = await api.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
return response.data.stargazers_count;
@ -100,7 +100,7 @@ export async function getExamples(): Promise<FlowType[]> {
export async function saveFlowToDatabase(newFlow: {
name: string;
id: string;
data: ReactFlowJsonObject;
data: ReactFlowJsonObject | null;
description: string;
style?: FlowStyleType;
}): Promise<FlowType> {
@ -179,7 +179,7 @@ export async function downloadFlowsFromDatabase() {
}
}
export async function uploadFlowsToDatabase(flows) {
export async function uploadFlowsToDatabase(flows: FormData) {
try {
const response = await api.post(`/api/v1/flows/upload/`, flows);

View file

@ -1,9 +1,9 @@
import React, { forwardRef } from "react";
import { ReactComponent as PowerPointSVG } from "./PowerPoint.svg";
import SvgPowerPoint from "./PowerPoint";
export const PowerPointIcon = forwardRef<
SVGSVGElement,
React.PropsWithChildren<{}>
>((props, ref) => {
return <PowerPointSVG ref={ref} {...props} />;
return <SvgPowerPoint ref={ref} {...props} />;
});

View file

@ -15,13 +15,16 @@ import CodeTabsComponent from "../../components/codeTabsComponent";
import IconComponent from "../../components/genericIconComponent";
import { EXPORT_CODE_DIALOG } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
import { FlowType } from "../../types/flow/index";
import { TemplateVariableType } from "../../types/api";
import { tweakType, uniqueTweakType } from "../../types/components";
import { FlowType, NodeType } from "../../types/flow/index";
import { buildTweaks } from "../../utils/reactflowUtils";
import {
getCurlCode,
getPythonApiCode,
getPythonCode,
getWidgetCode,
tabsArray,
} from "../../utils/utils";
import BaseModal from "../baseModal";
@ -40,47 +43,22 @@ const ApiModal = forwardRef(
) => {
const [open, setOpen] = useState(false);
const [activeTab, setActiveTab] = useState("0");
const tweak = useRef([]);
const tweaksList = useRef([]);
const tweak = useRef<tweakType>([]);
const tweaksList = useRef<string[]>([]);
const { setTweak, getTweak, tabsState } = useContext(TabsContext);
const pythonApiCode = getPythonApiCode(flow, tweak.current, tabsState);
const curl_code = getCurlCode(flow, tweak.current, tabsState);
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, tabsState);
const tweaksCode = buildTweaks(flow);
const [tabs, setTabs] = useState([
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: curl_code,
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: pythonCode,
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: widgetCode,
},
]);
const codesArray = [
curl_code,
pythonApiCode,
pythonCode,
widgetCode,
pythonCode,
];
const [tabs, setTabs] = useState(tabsArray(codesArray, 0));
function startState() {
tweak.current = [];
@ -89,7 +67,7 @@ const ApiModal = forwardRef(
}
useEffect(() => {
if (flow["data"]["nodes"].length == 0) {
if (flow["data"]!["nodes"].length == 0) {
startState();
} else {
tweak.current = [];
@ -101,102 +79,31 @@ const ApiModal = forwardRef(
if (Object.keys(tweaksCode).length > 0) {
setActiveTab("0");
setTabs([
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: curl_code,
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
language: "py",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: pythonCode,
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: widgetCode,
},
{
name: "Tweaks",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: pythonCode,
},
]);
setTabs(tabsArray(codesArray, 1));
} else {
setTabs([
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: curl_code,
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: pythonApiCode,
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: pythonCode,
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: widgetCode,
},
]);
setTabs(tabsArray(codesArray, 1));
}
}, [flow["data"]["nodes"], open]);
}, [flow["data"]!["nodes"], open]);
function filterNodes() {
let arrNodesWithValues = [];
let arrNodesWithValues: string[] = [];
flow["data"]["nodes"].forEach((t) => {
Object.keys(t["data"]["node"]["template"])
flow["data"]!["nodes"].forEach((node) => {
Object.keys(node["data"]["node"]["template"])
.filter(
(n) =>
n.charAt(0) !== "_" &&
t.data.node.template[n].show &&
(t.data.node.template[n].type === "str" ||
t.data.node.template[n].type === "bool" ||
t.data.node.template[n].type === "float" ||
t.data.node.template[n].type === "code" ||
t.data.node.template[n].type === "prompt" ||
t.data.node.template[n].type === "file" ||
t.data.node.template[n].type === "int")
(templateField) =>
templateField.charAt(0) !== "_" &&
node.data.node.template[templateField].show &&
(node.data.node.template[templateField].type === "str" ||
node.data.node.template[templateField].type === "bool" ||
node.data.node.template[templateField].type === "float" ||
node.data.node.template[templateField].type === "code" ||
node.data.node.template[templateField].type === "prompt" ||
node.data.node.template[templateField].type === "file" ||
node.data.node.template[templateField].type === "int")
)
.map((n, i) => {
arrNodesWithValues.push(t["id"]);
arrNodesWithValues.push(node["id"]);
});
});
@ -204,11 +111,15 @@ const ApiModal = forwardRef(
return self.indexOf(value) === index;
});
}
function buildTweakObject(tw, changes, template) {
if (template.type === "float") {
function buildTweakObject(
tw: string,
changes: string | string[] | boolean | number,
template: TemplateVariableType
) {
if (typeof changes === "string" && template.type === "float") {
changes = parseFloat(changes);
}
if (template.type === "int") {
if (typeof changes === "string" && template.type === "int") {
changes = parseInt(changes);
}
if (template.list === true && Array.isArray(changes)) {
@ -220,7 +131,7 @@ const ApiModal = forwardRef(
);
if (existingTweak) {
existingTweak[tw][template["name"]] = changes;
existingTweak[tw][template["name"]] = changes as string;
if (existingTweak[tw][template["name"]] == template.value) {
tweak.current.forEach((element) => {
@ -237,7 +148,7 @@ const ApiModal = forwardRef(
[tw]: {
[template["name"]]: changes,
},
};
} as uniqueTweakType;
tweak.current.push(newTweak);
}
@ -246,15 +157,15 @@ const ApiModal = forwardRef(
const pythonCode = getPythonCode(flow, tweak.current, tabsState);
const widgetCode = getWidgetCode(flow, tabsState);
tabs[0].code = curl_code;
tabs[1].code = pythonApiCode;
tabs[2].code = pythonCode;
tabs[3].code = widgetCode;
tabs![0].code = curl_code;
tabs![1].code = pythonApiCode;
tabs![2].code = pythonCode;
tabs![3].code = widgetCode;
setTweak(tweak.current);
}
function buildContent(value) {
function buildContent(value: string) {
const htmlContent = (
<div className="w-[200px]">
<span>{value != null && value != "" ? value : "None"}</span>
@ -263,7 +174,11 @@ const ApiModal = forwardRef(
return htmlContent;
}
function getValue(value, node, template) {
function getValue(
value: string,
node: NodeType,
template: TemplateVariableType
) {
let returnValue = value ?? "";
if (getTweak.length > 0) {
@ -299,7 +214,7 @@ const ApiModal = forwardRef(
<BaseModal.Content>
<CodeTabsComponent
flow={flow}
tabs={tabs}
tabs={tabs!}
activeTab={activeTab}
setActiveTab={setActiveTab}
tweaks={{

View file

@ -0,0 +1,66 @@
import { useState } from "react";
import { Button } from "../../components/ui/button";
import { ConfirmationModalType } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal";
export default function ConfirmationModal({
title,
titleHeader,
modalContent,
modalContentTitle,
cancelText,
confirmationText,
children,
icon,
data,
index,
onConfirm,
}: ConfirmationModalType) {
const Icon: any = nodeIconsLucide[icon];
const [open, setOpen] = useState(false);
return (
<BaseModal size="x-small" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon
name="icon"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
{modalContentTitle != "" && (
<>
<strong>{modalContentTitle}</strong>
<br></br>
</>
)}
<span>{modalContent}</span>
</BaseModal.Content>
<BaseModal.Footer>
<Button
className="ml-3"
onClick={() => {
setOpen(false);
onConfirm(index, data);
}}
>
{confirmationText}
</Button>
<Button
variant="outline"
onClick={() => {
setOpen(false);
}}
>
{cancelText}
</Button>
</BaseModal.Footer>
</BaseModal>
);
}

View file

@ -25,6 +25,7 @@ import { limitScrollFieldsModal } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
import { typesContext } from "../../contexts/typesContext";
import { NodeDataType } from "../../types/flow";
import { TabsState } from "../../types/tabs";
import { classNames } from "../../utils/utils";
import BaseModal from "../baseModal";
@ -49,21 +50,26 @@ const EditNodeModal = forwardRef(
const { reactFlowInstance } = useContext(typesContext);
let disabled =
reactFlowInstance?.getEdges().some((e) => e.targetHandle === data.id) ??
false;
reactFlowInstance
?.getEdges()
.some((edge) => edge.targetHandle === data.id) ?? false;
function changeAdvanced(n) {
function changeAdvanced(templateParam: string): void {
setMyData((old) => {
let newData = cloneDeep(old);
newData.node.template[n].advanced = !newData.node.template[n].advanced;
newData.node!.template[templateParam].advanced =
!newData.node!.template[templateParam].advanced;
return newData;
});
}
const handleOnNewValue = (newValue: any, name) => {
const handleOnNewValue = (
newValue: string | string[] | boolean,
name: string
) => {
setMyData((old) => {
let newData = cloneDeep(old);
newData.node.template[name].value = newValue;
newData.node!.template[name].value = newValue;
return newData;
});
};
@ -75,7 +81,7 @@ const EditNodeModal = forwardRef(
return (
<BaseModal size="large-h-full" open={modalOpen} setOpen={setModalOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={myData.node?.description}>
<BaseModal.Header description={myData.node?.description!}>
<span className="pr-2">{myData.type}</span>
<Badge variant="secondary">ID: {myData.id}</Badge>
</BaseModal.Header>
@ -110,53 +116,67 @@ const EditNodeModal = forwardRef(
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(myData.node.template)
{Object.keys(myData.node!.template)
.filter(
(t) =>
t.charAt(0) !== "_" &&
myData.node.template[t].show &&
(myData.node.template[t].type === "str" ||
myData.node.template[t].type === "bool" ||
myData.node.template[t].type === "float" ||
myData.node.template[t].type === "code" ||
myData.node.template[t].type === "prompt" ||
myData.node.template[t].type === "file" ||
myData.node.template[t].type === "int")
(templateParam) =>
templateParam.charAt(0) !== "_" &&
myData.node?.template[templateParam].show &&
(myData.node.template[templateParam].type ===
"str" ||
myData.node.template[templateParam].type ===
"bool" ||
myData.node.template[templateParam].type ===
"float" ||
myData.node.template[templateParam].type ===
"code" ||
myData.node.template[templateParam].type ===
"prompt" ||
myData.node.template[templateParam].type ===
"file" ||
myData.node.template[templateParam].type ===
"int")
)
.map((n, i) => (
<TableRow key={i} className="h-10">
.map((templateParam, index) => (
<TableRow key={index} className="h-10">
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
{myData.node.template[n].name
? myData.node.template[n].name
: myData.node.template[n].display_name}
{myData.node?.template[templateParam].name
? myData.node.template[templateParam].name
: myData.node?.template[templateParam]
.display_name}
</TableCell>
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
{myData.node.template[n].type === "str" &&
!myData.node.template[n].options ? (
{myData.node?.template[templateParam].type ===
"str" &&
!myData.node.template[templateParam].options ? (
<div className="mx-auto">
{myData.node.template[n].list ? (
{myData.node.template[templateParam].list ? (
<InputListComponent
editNode={true}
disabled={disabled}
value={
!myData.node.template[n].value ||
myData.node.template[n].value === ""
!myData.node.template[templateParam]
.value ||
myData.node.template[templateParam]
.value === ""
? [""]
: myData.node.template[n].value
: myData.node.template[templateParam]
.value
}
onChange={(t: string[]) => {
handleOnNewValue(t, n);
onChange={(value: string[]) => {
handleOnNewValue(value, templateParam);
}}
/>
) : myData.node.template[n].multiline ? (
) : myData.node.template[templateParam]
.multiline ? (
<TextAreaComponent
disabled={disabled}
editNode={true}
value={
myData.node.template[n].value ?? ""
myData.node.template[templateParam]
.value ?? ""
}
onChange={(t: string) => {
handleOnNewValue(t, n);
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
/>
) : (
@ -164,105 +184,142 @@ const EditNodeModal = forwardRef(
editNode={true}
disabled={disabled}
password={
myData.node.template[n].password ??
false
myData.node.template[templateParam]
.password ?? false
}
value={
myData.node.template[n].value ?? ""
myData.node.template[templateParam]
.value ?? ""
}
onChange={(t) => {
handleOnNewValue(t, n);
onChange={(value) => {
handleOnNewValue(value, templateParam);
}}
/>
)}
</div>
) : myData.node.template[n].type === "bool" ? (
) : myData.node?.template[templateParam].type ===
"bool" ? (
<div className="ml-auto">
{" "}
<ToggleShadComponent
disabled={disabled}
enabled={myData.node.template[n].value}
setEnabled={(t) => {
handleOnNewValue(t, n);
enabled={
myData.node.template[templateParam].value
}
setEnabled={(isEnabled) => {
handleOnNewValue(
isEnabled,
templateParam
);
}}
size="small"
/>
</div>
) : myData.node.template[n].type === "float" ? (
) : myData.node?.template[templateParam].type ===
"float" ? (
<div className="mx-auto">
<FloatComponent
disabled={disabled}
editNode={true}
value={myData.node.template[n].value ?? ""}
onChange={(t) => {
handleOnNewValue(t, n);
value={
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value) => {
handleOnNewValue(value, templateParam);
}}
/>
</div>
) : myData.node.template[n].type === "str" &&
myData.node.template[n].options ? (
) : myData.node?.template[templateParam].type ===
"str" &&
myData.node.template[templateParam].options ? (
<div className="mx-auto">
<Dropdown
numberOfOptions={nodeLength}
editNode={true}
options={myData.node.template[n].options}
onSelect={(t) => handleOnNewValue(t, n)}
options={
myData.node.template[templateParam]
.options
}
onSelect={(value) =>
handleOnNewValue(value, templateParam)
}
value={
myData.node.template[n].value ??
"Choose an option"
myData.node.template[templateParam]
.value ?? "Choose an option"
}
></Dropdown>
</div>
) : myData.node.template[n].type === "int" ? (
) : myData.node?.template[templateParam].type ===
"int" ? (
<div className="mx-auto">
<IntComponent
disabled={disabled}
editNode={true}
value={myData.node.template[n].value ?? ""}
onChange={(t) => {
handleOnNewValue(t, n);
value={
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value) => {
handleOnNewValue(value, templateParam);
}}
/>
</div>
) : myData.node.template[n].type === "file" ? (
) : myData.node?.template[templateParam].type ===
"file" ? (
<div className="mx-auto">
<InputFileComponent
editNode={true}
disabled={disabled}
value={myData.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
value={
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
fileTypes={
myData.node.template[n].fileTypes
myData.node.template[templateParam]
.fileTypes
}
suffixes={myData.node.template[n].suffixes}
onFileChange={(t: string) => {
data.node.template[n].file_path = t;
suffixes={
myData.node.template[templateParam]
.suffixes
}
onFileChange={(filePath: string) => {
data.node!.template[
templateParam
].file_path = filePath;
}}
></InputFileComponent>
</div>
) : myData.node.template[n].type === "prompt" ? (
) : myData.node?.template[templateParam].type ===
"prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
field_name={n}
field_name={templateParam}
editNode={true}
disabled={disabled}
nodeClass={myData.node}
setNodeClass={(nodeClass) => {
myData.node = nodeClass;
}}
value={myData.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
value={
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
/>
</div>
) : myData.node.template[n].type === "code" ? (
) : myData.node?.template[templateParam].type ===
"code" ? (
<div className="mx-auto">
<CodeAreaComponent
dynamic={
data.node.template[n].dynamic ?? false
data.node!.template[templateParam]
.dynamic ?? false
}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
@ -270,13 +327,17 @@ const EditNodeModal = forwardRef(
nodeClass={data.node}
disabled={disabled}
editNode={true}
value={myData.node.template[n].value ?? ""}
onChange={(t: string) => {
handleOnNewValue(t, n);
value={
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
/>
</div>
) : myData.node.template[n].type === "Any" ? (
) : myData.node?.template[templateParam].type ===
"Any" ? (
"-"
) : (
<div className="hidden"></div>
@ -285,8 +346,13 @@ const EditNodeModal = forwardRef(
<TableCell className="p-0 text-right">
<div className="items-center text-center">
<ToggleShadComponent
enabled={!myData.node.template[n].advanced}
setEnabled={(e) => changeAdvanced(n)}
enabled={
!myData.node?.template[templateParam]
.advanced
}
setEnabled={(e) =>
changeAdvanced(templateParam)
}
disabled={disabled}
size="small"
/>
@ -307,7 +373,8 @@ const EditNodeModal = forwardRef(
className="mt-3"
onClick={() => {
setData(cloneDeep(myData)); //saves data with actual state of modal
setTabsState((prev) => {
//@ts-ignore
setTabsState((prev: TabsState) => {
return {
...prev,
[tabId]: {

View file

@ -1,179 +0,0 @@
import { useState } from "react";
import CodeAreaComponent from "../../../../components/codeAreaComponent";
import Dropdown from "../../../../components/dropdownComponent";
import FloatComponent from "../../../../components/floatComponent";
import InputComponent from "../../../../components/inputComponent";
import InputFileComponent from "../../../../components/inputFileComponent";
import InputListComponent from "../../../../components/inputListComponent";
import IntComponent from "../../../../components/intComponent";
import PromptAreaComponent from "../../../../components/promptComponent";
import TextAreaComponent from "../../../../components/textAreaComponent";
import ToggleComponent from "../../../../components/toggleComponent";
import { classNames } from "../../../../utils/utils";
export default function ModalField({
data,
title,
required,
name,
type,
index,
}) {
const [enabled, setEnabled] = useState(
data.node.template[name]?.value ?? false
);
const display =
type === "str" ||
type === "int" ||
type === "prompt" ||
type === "bool" ||
type === "float" ||
type === "file" ||
type === "code";
return (
<div
className={classNames(
"flex w-full flex-row items-center justify-between",
display ? "" : "hidden",
Object.keys(data.node.template).filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].advanced &&
data.node.template[t].show
).length -
1 ===
index
? "pb-4"
: ""
)}
>
{display && (
<div>
<span className="mx-2">{title}</span>
<span className="text-status-red">{required ? " *" : ""}</span>
</div>
)}
{type === "str" && !data.node.template[name].options ? (
<div className="w-1/2">
{data.node.template[name].list ? (
<InputListComponent
disabled={false}
value={
!data.node.template[name].value ||
data.node.template[name].value === ""
? [""]
: data.node.template[name].value
}
onChange={(t: string[]) => {
data.node.template[name].value = t;
}}
/>
) : data.node.template[name].multiline ? (
<TextAreaComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
) : (
<InputComponent
disabled={false}
password={data.node.template[name].password ?? false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
)}
</div>
) : type === "bool" ? (
<div className="ml-auto">
{" "}
<ToggleComponent
disabled={false}
enabled={enabled}
setEnabled={(t) => {
data.node.template[name].value = t;
setEnabled(t);
}}
size="small"
/>
</div>
) : type === "float" ? (
<div className="w-1/2">
<FloatComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
</div>
) : type === "str" && data.node.template[name].options ? (
<div className="w-1/2">
<Dropdown
options={data.node.template[name].options}
onSelect={(newValue) => (data.node.template[name].value = newValue)}
value={data.node.template[name].value ?? "Choose an option"}
></Dropdown>
</div>
) : type === "int" ? (
<div className="w-1/2">
<IntComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t) => {
data.node.template[name].value = t;
}}
/>
</div>
) : type === "file" ? (
<div className="w-1/2">
<InputFileComponent
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
fileTypes={data.node.template[name].fileTypes}
suffixes={data.node.template[name].suffixes}
onFileChange={(t: string) => {
data.node.template[name].file_path = t;
}}
></InputFileComponent>
</div>
) : type === "prompt" ? (
<div className="w-1/2">
<PromptAreaComponent
field_name={name}
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
</div>
) : type === "code" ? (
<div className="w-1/2">
<CodeAreaComponent
dynamic={data.node.template[name].dynamic ?? false}
setNodeClass={(nodeClass) => {
data.node = nodeClass;
}}
nodeClass={data.node}
disabled={false}
value={data.node.template[name].value ?? ""}
onChange={(t: string) => {
data.node.template[name].value = t;
}}
/>
</div>
) : (
<div className="hidden"></div>
)}
</div>
);
}

View file

@ -1,144 +0,0 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, useContext, useRef, useState } from "react";
import IconComponent from "../../components/genericIconComponent";
import { limitScrollFieldsModal } from "../../constants/constants";
import { typesContext } from "../../contexts/typesContext";
import { NodeDataType } from "../../types/flow";
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
import { classNames, toTitleCase } from "../../utils/utils";
import ModalField from "./components/ModalField";
export default function NodeModal({ data }: { data: NodeDataType }) {
const [modalOpen, setModalOpen] = useState(false);
const { types } = useContext(typesContext);
const ref = useRef();
// any to avoid type conflict
const Icon: any = nodeIconsLucide[types[data.type]];
return (
<Dialog
open={modalOpen}
as="div"
className="relative z-10"
onClose={setModalOpen}
initialFocus={ref}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="node-modal-div" />
</Transition.Child>
<div className="node-modal-dialog-arrangement">
<div className="node-modal-dialog-div">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="node-modal-dialog-panel">
<div className=" node-modal-dialog-panel-div">
<button
type="button"
className="node-modal-dialog-button"
onClick={() => {
setModalOpen(false);
}}
>
<span className="sr-only">Close</span>
<IconComponent
name="X"
className="h-6 w-6"
aria-hidden="true"
/>
</button>
</div>
<div className="node-modal-dialog-icon-div">
<div className="node-modal-icon-arrangement">
<Icon
strokeWidth={1.5}
className="node-modal-icon"
style={{
color: nodeColors[types[data.type]] ?? nodeColors.unknown,
}}
/>
<div className="node-modal-title-div">
<Dialog.Title as="h3" className="node-modal-title">
{data.type}
</Dialog.Title>
</div>
</div>
<div className="node-modal-template-div">
<div className="flex-max-width h-[445px]">
<div
className={classNames(
"node-modal-template",
Object.keys(data.node.template).filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].advanced &&
data.node.template[t].show
).length > limitScrollFieldsModal
? "overflow-scroll overflow-x-hidden custom-scroll"
: "overflow-hidden"
)}
>
<div className="node-modal-template-column">
{Object.keys(data.node.template)
.filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].advanced &&
data.node.template[t].show
)
.map((t: string, idx) => {
return (
<ModalField
key={idx}
data={data}
title={
data.node.template[t].display_name
? data.node.template[t].display_name
: data.node.template[t].name
? toTitleCase(data.node.template[t].name)
: toTitleCase(t)
}
required={data.node.template[t].required}
name={t}
type={data.node.template[t].type}
index={idx}
/>
);
})}
</div>
</div>
</div>
</div>
<div className="node-modal-button-box">
<button
type="button"
className="node-modal-button"
onClick={() => {
setModalOpen(false);
}}
>
Done
</button>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
);
}

View file

@ -0,0 +1,241 @@
import * as Form from "@radix-ui/react-form";
import { useEffect, useState } from "react";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { UserManagementType } from "../../types/components";
import { nodeIconsLucide } from "../../utils/styleUtils";
import BaseModal from "../baseModal";
export default function UserManagementModal({
title,
titleHeader,
cancelText,
confirmationText,
children,
icon,
data,
index,
onConfirm,
}: UserManagementType) {
const Icon: any = nodeIconsLucide[icon];
const [open, setOpen] = useState(false);
const [password, setPassword] = useState(data?.password ?? "");
const [username, setUserName] = useState(data?.user ?? "");
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
useEffect(() => {
if (!data) {
resetForm();
}
}, [data, open]);
function resetForm() {
setPassword("");
setUserName("");
setConfirmPassword("");
}
return (
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{children}</BaseModal.Trigger>
<BaseModal.Header description={titleHeader}>
<span className="pr-2">{title}</span>
<Icon
name="icon"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<Form.Root
onSubmit={(event) => {
if (password !== confirmPassword) {
event.preventDefault();
return;
}
const data = Object.fromEntries(new FormData(event.currentTarget));
resetForm();
onConfirm(index ?? -1, data);
setOpen(false);
event.preventDefault();
}}
>
<div className="grid gap-5">
<Form.Field name="username">
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Username{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
</div>
<Form.Control asChild>
<input
onChange={(input) => {
setUserName(input.target.value);
}}
value={username}
className="primary-input"
required
placeholder="Username"
/>
</Form.Control>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your username
</Form.Message>
</Form.Field>
<div className="flex flex-row">
<div className="mr-3 basis-1/2">
<Form.Field
name="password"
serverInvalid={password != confirmPassword}
>
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Password{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
</div>
<InputComponent
onChange={(input) => {
setPassword(input);
}}
value={password}
password={true}
isForm
className="primary-input"
required
placeholder="Password"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please enter a password
</Form.Message>
{password != confirmPassword && (
<Form.Message className="field-invalid">
Passwords do not match
</Form.Message>
)}
</Form.Field>
</div>
<div className="basis-1/2">
<Form.Field
name="confirmpassword"
serverInvalid={password != confirmPassword}
>
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Confirm password{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
</div>
<InputComponent
onChange={(input) => {
setConfirmPassword(input);
}}
value={confirmPassword}
password={true}
isForm
className="primary-input"
required
placeholder="Confirm your password"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please confirm your password
</Form.Message>
</Form.Field>
</div>
</div>
{/*
<Form.Field name="email">
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Email <span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Message className="field-invalid" match="valueMissing">
Please enter your email
</Form.Message>
<Form.Message className="field-invalid" match="typeMismatch">
Please provide a valid email
</Form.Message>
</div>
<Form.Control asChild>
<input className="primary-input" type="email" required />
</Form.Control>
</Form.Field> */}
{/*
<Form.Field name="birth">
<div
style={{
display: "flex",
alignItems: "baseline",
justifyContent: "space-between",
}}
>
<Form.Label className="data-[invalid]:label-invalid">
Date of birth{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Message className="field-invalid" match="valueMissing">
Please enter your date of birth
</Form.Message>
</div>
<Form.Control asChild>
<input
type="date"
className="primary-input"
required
max={new Date().toISOString().split("T")[0]}
/>
</Form.Control>
</Form.Field> */}
</div>
<div className="float-right">
<Form.Submit asChild>
<Button className="mr-3 mt-8">{confirmationText}</Button>
</Form.Submit>
<Button
variant="outline"
onClick={() => {
setOpen(false);
}}
>
{cancelText}
</Button>
</div>
</Form.Root>
</BaseModal.Content>
</BaseModal>
);
}

View file

@ -9,6 +9,7 @@ import {
DialogTitle,
DialogTrigger,
} from "../../components/ui/dialog";
import { modalHeaderType } from "../../types/components";
type ContentProps = { children: ReactNode };
type HeaderProps = { children: ReactNode; description: string };
@ -22,10 +23,10 @@ const Trigger: React.FC<ContentProps> = ({ children }) => {
return <>{children}</>;
};
const Header: React.FC<{ children: ReactNode; description: string }> = ({
const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
children,
description,
}) => {
}: modalHeaderType): JSX.Element => {
return (
<DialogHeader>
<DialogTitle className="flex items-center">{children}</DialogTitle>
@ -47,7 +48,15 @@ interface BaseModalProps {
open?: boolean;
setOpen?: (open: boolean) => void;
disable?: boolean;
size?: "smaller" | "small" | "medium" | "large" | "large-h-full";
size?:
| "x-small"
| "smaller"
| "small"
| "medium"
| "large"
| "large-h-full"
| "small-h-full"
| "medium-h-full";
}
function BaseModal({
open,
@ -73,6 +82,10 @@ function BaseModal({
let height: string;
switch (size) {
case "x-small":
minWidth = "min-w-[20vw]";
height = "h-[10vh]";
break;
case "smaller":
minWidth = "min-w-[40vw]";
height = "h-[27vh]";
@ -81,10 +94,16 @@ function BaseModal({
minWidth = "min-w-[40vw]";
height = "h-[40vh]";
break;
case "small-h-full":
minWidth = "min-w-[40vw]";
break;
case "medium":
minWidth = "min-w-[60vw]";
height = "h-[60vh]";
break;
case "medium-h-full":
minWidth = "min-w-[60vw]";
break;
case "large":
minWidth = "min-w-[80vw]";
height = "h-[80vh]";
@ -108,7 +127,9 @@ function BaseModal({
{triggerChild}
</DialogTrigger>
<DialogContent className={minWidth}>
{headerChild}
<div className="truncate-doubleline word-break-break-word">
{headerChild}
</div>
<div className={`mt-2 flex flex-col ${height} w-full `}>
{ContentChild}
</div>

View file

@ -4,7 +4,7 @@ import "ace-builds/src-noconflict/mode-python";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-twilight";
// import "ace-builds/webpack-resolver";
import { ReactNode, useContext, useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import AceEditor from "react-ace";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
@ -13,7 +13,7 @@ import { alertContext } from "../../contexts/alertContext";
import { darkContext } from "../../contexts/darkContext";
import { typesContext } from "../../contexts/typesContext";
import { postCustomComponent, postValidateCode } from "../../controllers/API";
import { APIClassType } from "../../types/api";
import { codeAreaModalPropsType } from "../../types/components";
import BaseModal from "../baseModal";
export default function CodeAreaModal({
@ -23,27 +23,20 @@ export default function CodeAreaModal({
setNodeClass,
children,
dynamic,
}: {
setValue: (value: string) => void;
value: string;
nodeClass?: APIClassType;
children: ReactNode;
setNodeClass?: (Class: APIClassType) => void;
dynamic?: boolean;
}) {
}: codeAreaModalPropsType): JSX.Element {
const [code, setCode] = useState(value);
const { dark } = useContext(darkContext);
const { reactFlowInstance } = useContext(typesContext);
const [height, setHeight] = useState(null);
const [height, setHeight] = useState<string | null>(null);
const { setErrorData, setSuccessData } = useContext(alertContext);
const [error, setError] = useState<{
detail: { error: string; traceback: string };
}>(null);
detail: { error: string | undefined; traceback: string | undefined };
} | null>(null);
useEffect(() => {
// if nodeClass.template has more fields other than code and dynamic is true
// do not run handleClick
if (dynamic && Object.keys(nodeClass.template).length > 2) {
if (dynamic && Object.keys(nodeClass!.template).length > 2) {
return;
}
processCode();
@ -90,7 +83,7 @@ export default function CodeAreaModal({
}
function processDynamicField() {
postCustomComponent(code, nodeClass)
postCustomComponent(code, nodeClass!)
.then((apiReturn) => {
const { data } = apiReturn;
if (data) {
@ -180,8 +173,8 @@ export default function CodeAreaModal({
<h1 className="text-lg text-destructive">
{error?.detail?.error}
</h1>
<div className="ml-2 w-full break-all text-sm text-status-red">
<pre className="w-full whitespace-pre-wrap break-all">
<div className="ml-2 w-full text-sm text-status-red word-break-break-word">
<pre className="w-full word-break-break-word">
{error?.detail?.traceback}
</pre>
</div>

View file

@ -1,4 +1,4 @@
import { ReactNode, forwardRef, useContext, useState } from "react";
import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
@ -8,75 +8,77 @@ import { TabsContext } from "../../contexts/tabsContext";
import { removeApiKeys } from "../../utils/reactflowUtils";
import BaseModal from "../baseModal";
const ExportModal = forwardRef((props: { children: ReactNode }, ref) => {
const { flows, tabId, updateFlow, downloadFlow, saveFlow } =
useContext(TabsContext);
const [checked, setChecked] = useState(false);
const [name, setName] = useState(flows.find((f) => f.id === tabId).name);
const [invalidName, setInvalidName] = useState(false);
const [description, setDescription] = useState(
flows.find((f) => f.id === tabId).description
);
const [open, setOpen] = useState(false);
return (
<BaseModal size="smaller" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{props.children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
<span className="pr-2">Export</span>
<IconComponent
name="Download"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
invalidName={invalidName}
setInvalidName={setInvalidName}
name={name}
description={description}
flows={flows}
tabId={tabId}
setName={setName}
setDescription={setDescription}
updateFlow={updateFlow}
/>
<div className="mt-3 flex items-center space-x-2">
<Checkbox
id="terms"
onCheckedChange={(event: boolean) => {
setChecked(event);
}}
/>
<label htmlFor="terms" className="export-modal-save-api text-sm ">
Save with my API keys
</label>
</div>
</BaseModal.Content>
const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => {
const { flows, tabId, updateFlow, downloadFlow } = useContext(TabsContext);
const [checked, setChecked] = useState(false);
const flow = flows.find((f) => f.id === tabId);
useEffect(() => {
setName(flow.name);
setDescription(flow.description);
}, [flow.name, flow.description]);
const [name, setName] = useState(flow.name);
const [description, setDescription] = useState(flow.description);
const [open, setOpen] = useState(false);
<BaseModal.Footer>
<Button
onClick={() => {
if (checked)
downloadFlow(
flows.find((f) => f.id === tabId),
name,
description
);
else
downloadFlow(
removeApiKeys(flows.find((f) => f.id === tabId)),
name,
description
);
setOpen(false);
}}
type="submit"
>
Download Flow
</Button>
</BaseModal.Footer>
</BaseModal>
);
});
return (
<BaseModal size="smaller" open={open} setOpen={setOpen}>
<BaseModal.Trigger>{props.children}</BaseModal.Trigger>
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
<span className="pr-2">Export</span>
<IconComponent
name="Download"
className="h-6 w-6 pl-1 text-foreground"
aria-hidden="true"
/>
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
name={name}
description={description}
flows={flows}
tabId={tabId}
setName={setName}
setDescription={setDescription}
updateFlow={updateFlow}
/>
<div className="mt-3 flex items-center space-x-2">
<Checkbox
id="terms"
onCheckedChange={(event: boolean) => {
setChecked(event);
}}
/>
<label htmlFor="terms" className="export-modal-save-api text-sm ">
Save with my API keys
</label>
</div>
</BaseModal.Content>
<BaseModal.Footer>
<Button
onClick={() => {
if (checked)
downloadFlow(
flows.find((flow) => flow.id === tabId)!,
name!,
description
);
else
downloadFlow(
removeApiKeys(flows.find((flow) => flow.id === tabId)!),
name!,
description
);
setOpen(false);
}}
type="submit"
>
Download Flow
</Button>
</BaseModal.Footer>
</BaseModal>
);
}
);
export default ExportModal;

View file

@ -1,35 +1,33 @@
import { useContext, useRef, useState } from "react";
import { useContext, useEffect, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
import { alertContext } from "../../contexts/alertContext";
import { TabsContext } from "../../contexts/tabsContext";
import { FlowSettingsPropsType } from "../../types/components";
import BaseModal from "../baseModal";
export default function FlowSettingsModal({
open,
setOpen,
}: {
open: boolean;
setOpen: (open: boolean) => void;
}) {
const { setErrorData, setSuccessData } = useContext(alertContext);
const ref = useRef();
const { flows, tabId, updateFlow, setTabsState, saveFlow } =
useContext(TabsContext);
const maxLength = 50;
const [name, setName] = useState(flows.find((f) => f.id === tabId).name);
const [description, setDescription] = useState(
flows.find((f) => f.id === tabId).description
);
}: FlowSettingsPropsType): JSX.Element {
const { setSuccessData } = useContext(alertContext);
const { flows, tabId, updateFlow, saveFlow } = useContext(TabsContext);
const flow = flows.find((f) => f.id === tabId);
useEffect(() => {
setName(flow.name);
setDescription(flow.description);
}, [flow.name, flow.description]);
const [name, setName] = useState(flow.name);
const [description, setDescription] = useState(flow.description);
const [invalidName, setInvalidName] = useState(false);
function handleClick() {
let savedFlow = flows.find((f) => f.id === tabId);
savedFlow.name = name;
savedFlow.description = description;
saveFlow(savedFlow);
function handleClick(): void {
let savedFlow = flows.find((flow) => flow.id === tabId);
savedFlow!.name = name;
savedFlow!.description = description;
saveFlow(savedFlow!);
setSuccessData({ title: "Changes saved successfully" });
setOpen(false);
}
@ -49,7 +47,6 @@ export default function FlowSettingsModal({
tabId={tabId}
setName={setName}
setDescription={setDescription}
updateFlow={updateFlow}
/>
</BaseModal.Content>

View file

@ -1,6 +1,7 @@
import { useEffect } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { Textarea } from "../../../components/ui/textarea";
import { chatInputType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
export default function ChatInput({
@ -10,7 +11,7 @@ export default function ChatInput({
setChatValue,
inputRef,
noInput,
}) {
}: chatInputType): JSX.Element {
useEffect(() => {
if (!lockChat && inputRef.current) {
inputRef.current.focus();
@ -46,8 +47,8 @@ export default function ChatInput({
}`,
}}
value={lockChat ? "Thinking..." : chatValue}
onChange={(e) => {
setChatValue(e.target.value);
onChange={(event): void => {
setChatValue(event.target.value);
}}
className={classNames(
lockChat
@ -75,7 +76,7 @@ export default function ChatInput({
: "bg-chat-send text-background"
)}
disabled={lockChat}
onClick={() => sendMessage()}
onClick={(): void => sendMessage()}
>
{lockChat ? (
<IconComponent

View file

@ -3,16 +3,12 @@ import { useState } from "react";
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
import { programmingLanguages } from "../../../../constants/constants";
import { Props } from "../../../../types/components";
interface Props {
language: string;
value: string;
}
export function CodeBlock({ language, value }) {
export function CodeBlock({ language, value }: Props): JSX.Element {
const [isCopied, setIsCopied] = useState<Boolean>(false);
const copyToClipboard = () => {
const copyToClipboard = (): void => {
if (!navigator.clipboard || !navigator.clipboard.writeText) {
return;
}
@ -25,7 +21,7 @@ export function CodeBlock({ language, value }) {
}, 2000);
});
};
const downloadAsFile = () => {
const downloadAsFile = (): void => {
const fileExtension = programmingLanguages[language] || ".file";
const suggestedFileName = `${"generated-code"}${fileExtension}`;
const fileName = window.prompt("enter file name", suggestedFileName);

View file

@ -9,18 +9,15 @@ import Robot from "../../../assets/robot.png";
import SanitizedHTMLWrapper from "../../../components/SanitizedHTMLWrapper";
import CodeTabsComponent from "../../../components/codeTabsComponent";
import IconComponent from "../../../components/genericIconComponent";
import { ChatMessageType } from "../../../types/chat";
import { chatMessagePropsType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
import FileCard from "../fileComponent";
export default function ChatMessage({
chat,
lockChat,
lastMessage,
}: {
chat: ChatMessageType;
lockChat: boolean;
lastMessage: boolean;
}) {
}: chatMessagePropsType): JSX.Element {
const convert = new Convert({ newline: true });
const [hidden, setHidden] = useState(true);
const template = chat.template;
@ -57,7 +54,7 @@ export default function ChatMessage({
<div className="form-modal-chat-text">
{hidden && chat.thought && chat.thought !== "" && (
<div
onClick={() => setHidden((prev) => !prev)}
onClick={(): void => setHidden((prev) => !prev)}
className="form-modal-chat-icon-div"
>
<IconComponent
@ -88,8 +85,8 @@ export default function ChatMessage({
<ReactMarkdown
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeMathjax]}
className="markdown prose inline-block break-words text-primary dark:prose-invert
sm:w-[30vw] sm:max-w-[30vw] lg:w-[40vw] lg:max-w-[40vw]"
className="markdown prose min-w-full text-primary word-break-break-word
dark:prose-invert"
components={{
pre({ node, ...props }) {
return <>{props.children}</>;
@ -187,12 +184,12 @@ export default function ChatMessage({
}
/>
</button>
<span className="prose inline-block break-words text-primary dark:prose-invert">
<span className="prose text-primary word-break-break-word dark:prose-invert">
{promptOpen
? template?.split("\n")?.map((line, index) => {
const regex = /{([^}]+)}/g;
let match;
let parts = [];
let parts: Array<JSX.Element | string> = [];
let lastIndex = 0;
while ((match = regex.exec(line)) !== null) {
// Push text up to the match

View file

@ -1,9 +1,14 @@
import * as base64js from "base64-js";
import { useState } from "react";
import IconComponent from "../../../components/genericIconComponent";
import { fileCardPropsType } from "../../../types/components";
export default function FileCard({ fileName, content, fileType }) {
const handleDownload = () => {
export default function FileCard({
fileName,
content,
fileType,
}: fileCardPropsType): JSX.Element {
const handleDownload = (): void => {
const byteArray = new Uint8Array(base64js.toByteArray(content));
const blob = new Blob([byteArray], { type: "application/octet-stream" });
const url = URL.createObjectURL(blob);
@ -16,10 +21,10 @@ export default function FileCard({ fileName, content, fileType }) {
URL.revokeObjectURL(url);
};
const [isHovered, setIsHovered] = useState(false);
function handleMouseEnter() {
function handleMouseEnter(): void {
setIsHovered(true);
}
function handleMouseLeave() {
function handleMouseLeave(): void {
setIsHovered(false);
}
@ -55,7 +60,7 @@ export default function FileCard({ fileName, content, fileType }) {
return (
<button onClick={handleDownload} className="file-card-modal-button">
<div className="file-card-modal-div">
{" "}
ooooooooooooooo{" "}
{fileType === "image" ? (
<img
src={`data:image/png;base64,${content}`}

View file

@ -24,6 +24,7 @@ import {
import { Textarea } from "../../components/ui/textarea";
import { CHAT_FORM_DIALOG_SUBTITLE } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
import { TabsState } from "../../types/tabs";
import { validateNodes } from "../../utils/reactflowUtils";
export default function FormModal({
@ -34,7 +35,7 @@ export default function FormModal({
open: boolean;
setOpen: (open: boolean) => void;
flow: FlowType;
}) {
}): JSX.Element {
const { tabsState, setTabsState } = useContext(TabsContext);
const [chatValue, setChatValue] = useState(() => {
try {
@ -45,11 +46,11 @@ export default function FormModal({
const inputKeys = formKeysData.input_keys;
const handleKeys = formKeysData.handle_keys;
const keyToUse = Object.keys(inputKeys).find(
(k) => !handleKeys.some((j) => j === k) && inputKeys[k] === ""
const keyToUse = Object.keys(inputKeys!).find(
(key) => !handleKeys?.some((j) => j === key) && inputKeys![key] === ""
);
return inputKeys[keyToUse];
return inputKeys![keyToUse!];
} catch (error) {
console.error(error);
// return a sensible default or `undefined` if no default is possible
@ -63,18 +64,22 @@ export default function FormModal({
const ws = useRef<WebSocket | null>(null);
const [lockChat, setLockChat] = useState(false);
const isOpen = useRef(open);
const messagesRef = useRef(null);
const messagesRef = useRef<HTMLDivElement | null>(null);
const id = useRef(flow.id);
const tabsStateFlowId = tabsState[flow.id];
const tabsStateFlowIdFormKeysData = tabsStateFlowId.formKeysData;
const [chatKey, setChatKey] = useState(
Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
(k) =>
!tabsState[flow.id].formKeysData.handle_keys.some((j) => j === k) &&
tabsState[flow.id].formKeysData.input_keys[k] === ""
)
);
const [chatKey, setChatKey] = useState(() => {
if (tabsState[flow.id]?.formKeysData?.input_keys) {
return Object.keys(tabsState[flow.id].formKeysData.input_keys!).find(
(key) =>
!tabsState[flow.id].formKeysData.handle_keys!.some(
(j) => j === key
) && tabsState[flow.id].formKeysData.input_keys![key] === ""
);
}
// TODO: return a sensible default
return "";
});
useEffect(() => {
if (messagesRef.current) {
messagesRef.current.scrollTop = messagesRef.current.scrollHeight;
@ -146,7 +151,7 @@ export default function FormModal({
});
}
function handleOnClose(event: CloseEvent) {
function handleOnClose(event: CloseEvent): void {
if (isOpen.current) {
setErrorData({ title: event.reason });
setTimeout(() => {
@ -156,8 +161,12 @@ export default function FormModal({
}
}
function getWebSocketUrl(chatId, isDevelopment = false) {
const isSecureProtocol = window.location.protocol === "https:";
function getWebSocketUrl(
chatId: string,
isDevelopment: boolean = false
): string {
const isSecureProtocol =
window.location.protocol === "https:" || window.location.port === "443";
const webSocketProtocol = isSecureProtocol ? "wss" : "ws";
const host = isDevelopment ? "localhost:7860" : window.location.host;
const chatEndpoint = `/api/v1/chat/${chatId}`;
@ -208,7 +217,7 @@ export default function FormModal({
});
}
if (data.type === "start") {
addChatHistory("", false, chatKey);
addChatHistory("", false, chatKey!);
isStream = true;
}
if (data.type === "end") {
@ -237,7 +246,7 @@ export default function FormModal({
}
}
function connectWS() {
function connectWS(): void {
try {
const urlWs = getWebSocketUrl(
id.current,
@ -304,15 +313,15 @@ export default function FormModal({
// do not add connectWS on dependencies array
}, [lockChat]);
async function sendAll(data: sendAllProps) {
async function sendAll(data: sendAllProps): Promise<void> {
try {
if (ws) {
ws.current.send(JSON.stringify(data));
ws.current?.send(JSON.stringify(data));
}
} catch (error) {
setErrorData({
title: "There was an error sending the message",
list: [error.message],
list: [(error as { message: string }).message],
});
setChatValue(data.inputs);
connectWS();
@ -323,7 +332,7 @@ export default function FormModal({
if (ref.current) ref.current.scrollIntoView({ behavior: "smooth" });
}, [chatHistory]);
const ref = useRef(null);
const ref = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (open && ref.current) {
@ -331,30 +340,31 @@ export default function FormModal({
}
}, [open]);
function sendMessage() {
let nodeValidationErrors = validateNodes(reactFlowInstance);
function sendMessage(): void {
let nodeValidationErrors = validateNodes(reactFlowInstance!);
if (nodeValidationErrors.length === 0) {
setLockChat(true);
let inputs = tabsState[id.current].formKeysData.input_keys;
setChatValue("");
const message = inputs;
addChatHistory(
message,
message!,
true,
chatKey,
chatKey!,
tabsState[flow.id].formKeysData.template
);
sendAll({
...reactFlowInstance.toObject(),
inputs: inputs,
...reactFlowInstance?.toObject()!,
inputs: inputs!,
chatHistory,
name: flow.name,
description: flow.description,
});
setTabsState((old) => {
//@ts-ignore
setTabsState((old: TabsState) => {
if (!chatKey) return old;
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[chatKey] = "";
newTabsState[id.current].formKeysData.input_keys![chatKey] = "";
return newTabsState;
});
} else {
@ -364,18 +374,18 @@ export default function FormModal({
});
}
}
function clearChat() {
function clearChat(): void {
setChatHistory([]);
ws.current.send(JSON.stringify({ clear_history: true }));
ws.current?.send(JSON.stringify({ clear_history: true }));
if (lockChat) setLockChat(false);
}
function handleOnCheckedChange(checked: boolean, i: string) {
if (checked === true) {
setChatKey(i);
setChatValue(tabsState[flow.id].formKeysData.input_keys[i]);
setChatValue(tabsState[flow.id].formKeysData.input_keys![i]);
} else {
setChatKey(null);
setChatKey(null!);
setChatValue("");
}
}
@ -419,97 +429,107 @@ export default function FormModal({
</div>
</div>
{Object.keys(tabsState[id.current].formKeysData.input_keys).map(
(i, k) => (
<div className="file-component-accordion-div" key={k}>
{tabsState[id.current]?.formKeysData?.input_keys
? Object.keys(
tabsState[id.current].formKeysData.input_keys!
).map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{key}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<ToggleShadComponent
enabled={chatKey === key}
setEnabled={(value) =>
handleOnCheckedChange(value, key)
}
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys!.some(
(t) => t === key
)}
/>
</div>
</div>
}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
{tabsState[id.current].formKeysData.handle_keys!.some(
(t) => t === key
) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
className="custom-scroll"
value={
tabsState[id.current].formKeysData.input_keys![
key
]
}
onChange={(e) => {
//@ts-ignore
setTabsState((old: TabsState) => {
let newTabsState = _.cloneDeep(old);
newTabsState[
id.current
].formKeysData.input_keys![key] =
e.target.value;
return newTabsState;
});
}}
disabled={chatKey === key}
placeholder="Enter text..."
></Textarea>
</div>
</AccordionComponent>
</div>
))
: null}
{tabsState[id.current].formKeysData.memory_keys!.map(
(key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{i}
{key}
</Badge>
<div
className="-mb-1"
onClick={(event) => {
event.stopPropagation();
}}
>
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === i}
setEnabled={(value) =>
handleOnCheckedChange(value, i)
}
enabled={chatKey === key}
setEnabled={() => {}}
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys.some((t) => t === i)}
disabled={true}
/>
</div>
</div>
}
key={k}
keyValue={i}
key={index}
keyValue={key}
>
<div className="file-component-tab-column">
{tabsState[id.current].formKeysData.handle_keys.some(
(t) => t === i
) && (
<div className="font-normal text-muted-foreground ">
Source: Component
</div>
)}
<Textarea
className="custom-scroll"
value={
tabsState[id.current].formKeysData.input_keys[i]
}
onChange={(e) => {
setTabsState((old) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[
i
] = e.target.value;
return newTabsState;
});
}}
disabled={chatKey === i}
placeholder="Enter text..."
></Textarea>
<div className="font-normal text-muted-foreground ">
Source: Memory
</div>
</div>
</AccordionComponent>
</div>
)
)}
{tabsState[id.current].formKeysData.memory_keys.map((i, k) => (
<div className="file-component-accordion-div" key={k}>
<AccordionComponent
trigger={
<div className="file-component-badge-div">
<Badge variant="gray" size="md">
{i}
</Badge>
<div className="-mb-1">
<ToggleShadComponent
enabled={chatKey === i}
setEnabled={() => {}}
size="small"
disabled={true}
/>
</div>
</div>
}
key={k}
keyValue={i}
>
<div className="file-component-tab-column">
<div className="font-normal text-muted-foreground ">
Source: Memory
</div>
</div>
</AccordionComponent>
</div>
))}
</div>
<div className="eraser-column-arrangement">
<div className="eraser-size">
@ -529,14 +549,14 @@ export default function FormModal({
</div>
<div ref={messagesRef} className="chat-message-div">
{chatHistory.length > 0 ? (
chatHistory.map((c, i) => (
chatHistory.map((chat, index) => (
<ChatMessage
lockChat={lockChat}
chat={c}
chat={chat}
lastMessage={
chatHistory.length - 1 === i ? true : false
chatHistory.length - 1 === index ? true : false
}
key={i}
key={index}
/>
))
) : (
@ -573,10 +593,11 @@ export default function FormModal({
sendMessage={sendMessage}
setChatValue={(value) => {
setChatValue(value);
setTabsState((old) => {
//@ts-ignore
setTabsState((old: TabsState) => {
let newTabsState = _.cloneDeep(old);
newTabsState[id.current].formKeysData.input_keys[
chatKey
newTabsState[id.current].formKeysData.input_keys![
chatKey!
] = value;
return newTabsState;
});

View file

@ -1,4 +1,4 @@
import { ReactNode, useContext, useEffect, useRef, useState } from "react";
import { useContext, useEffect, useRef, useState } from "react";
import SanitizedHTMLWrapper from "../../components/SanitizedHTMLWrapper";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
@ -15,7 +15,8 @@ import {
import { TypeModal } from "../../constants/enums";
import { alertContext } from "../../contexts/alertContext";
import { postValidatePrompt } from "../../controllers/API";
import { APIClassType } from "../../types/api";
import { genericModalPropsType } from "../../types/components";
import { handleKeyDown } from "../../utils/reactflowUtils";
import {
classNames,
getRandomKeyByssmm,
@ -33,39 +34,29 @@ export default function GenericModal({
nodeClass,
setNodeClass,
children,
}: {
field_name?: string;
setValue: (value: string) => void;
value: string;
buttonText: string;
modalTitle: string;
type: number;
children: ReactNode;
nodeClass?: APIClassType;
setNodeClass?: (Class: APIClassType) => void;
}) {
}: genericModalPropsType): JSX.Element {
const [myButtonText] = useState(buttonText);
const [myModalTitle] = useState(modalTitle);
const [myModalType] = useState(type);
const [inputValue, setInputValue] = useState(value);
const [isEdit, setIsEdit] = useState(true);
const [wordsHighlight, setWordsHighlight] = useState([]);
const [wordsHighlight, setWordsHighlight] = useState<string[]>([]);
const { setErrorData, setSuccessData, setNoticeData } =
useContext(alertContext);
const ref = useRef();
const divRef = useRef(null);
const divRefPrompt = useRef(null);
function checkVariables(valueToCheck) {
function checkVariables(valueToCheck: string): void {
const regex = /\{([^{}]+)\}/g;
const matches = [];
const matches: string[] = [];
let match;
while ((match = regex.exec(valueToCheck))) {
matches.push(`{${match[1]}}`);
}
let invalid_chars = [];
let fixed_variables = [];
let invalid_chars: string[] = [];
let fixed_variables: string[] = [];
let input_variables = matches;
for (let variable of input_variables) {
let new_var = variable;
@ -106,7 +97,7 @@ export default function GenericModal({
.replace(regexHighlight, varHighlightHTML({ name: "$1" }))
.replace(/\n/g, "<br />");
const TextAreaContentView = () => {
const TextAreaContentView = (): JSX.Element => {
return (
<SanitizedHTMLWrapper
className={getClassByNumberLength()}
@ -119,7 +110,7 @@ export default function GenericModal({
);
};
function getClassByNumberLength() {
function getClassByNumberLength(): string {
let sumOfCaracteres: number = 0;
wordsHighlight.forEach((element) => {
sumOfCaracteres = sumOfCaracteres + element.replace(/[{}]/g, "").length;
@ -129,8 +120,8 @@ export default function GenericModal({
: "code-nohighlight";
}
function validatePrompt(closeModal: boolean) {
postValidatePrompt(field_name, inputValue, nodeClass)
function validatePrompt(closeModal: boolean): void {
postValidatePrompt(field_name, inputValue, nodeClass!)
.then((apiReturn) => {
if (apiReturn.data) {
let inputVariables = apiReturn.data.input_variables ?? [];
@ -144,7 +135,7 @@ export default function GenericModal({
setSuccessData({
title: "Prompt is ready",
});
setNodeClass(apiReturn.data?.frontend_node);
setNodeClass!(apiReturn.data?.frontend_node);
setModalOpen(closeModal);
setValue(inputValue);
}
@ -208,23 +199,30 @@ export default function GenericModal({
setIsEdit(false);
}}
autoFocus
onChange={(e) => {
setInputValue(e.target.value);
checkVariables(e.target.value);
onChange={(event) => {
setInputValue(event.target.value);
checkVariables(event.target.value);
}}
placeholder="Type message here."
onKeyDown={(e) => {
handleKeyDown(e, inputValue, "");
}}
/>
) : type === TypeModal.PROMPT && !isEdit ? (
<TextAreaContentView />
) : type !== TypeModal.PROMPT ? (
<Textarea
//@ts-ignore
ref={ref}
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
onChange={(event) => {
setInputValue(event.target.value);
}}
placeholder="Type message here."
onKeyDown={(e) => {
handleKeyDown(e, value, "");
}}
/>
) : (
<></>

View file

@ -1,4 +1,4 @@
import { ReactNode } from "react";
import { buttonBoxPropsType } from "../../../types/components";
import { classNames } from "../../../utils/utils";
export default function ButtonBox({
@ -10,16 +10,7 @@ export default function ButtonBox({
textColor,
deactivate,
size,
}: {
onClick: () => void;
title: string;
description: string;
icon: ReactNode;
bgColor: string;
textColor: string;
deactivate?: boolean;
size: "small" | "medium" | "big";
}) {
}: buttonBoxPropsType): JSX.Element {
let bigCircle: string;
let smallCircle: string;
let titleFontSize: string;
@ -96,7 +87,7 @@ export default function ButtonBox({
<div className="mb-auto mt-auto w-full">
<h3
className={classNames(
"w-full break-words font-semibold text-background truncate-multiline",
"w-full font-semibold text-background truncate-multiline word-break-break-word",
titleFontSize,
marginTop
)}

View file

@ -23,7 +23,7 @@ import { FlowType } from "../../types/flow";
import { classNames } from "../../utils/utils";
import ButtonBox from "./buttonBox";
export default function ImportModal() {
export default function ImportModal(): JSX.Element {
const [open, setOpen] = useState(true);
const { setErrorData } = useContext(alertContext);
const ref = useRef();
@ -32,7 +32,7 @@ export default function ImportModal() {
const [examples, setExamples] = useState<FlowType[]>([]);
const { uploadFlow, addFlow } = useContext(TabsContext);
function handleExamples() {
function handleExamples(): void {
setLoadingExamples(true);
getExamples()
.then((result) => {

View file

@ -0,0 +1,31 @@
import { useNavigate } from "react-router-dom";
import { Button } from "../../../components/ui/button";
import { Input } from "../../../components/ui/input";
export default function LoginAdminPage() {
const navigate = useNavigate();
function loginAdmin() {
navigate("/admin/");
}
return (
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-6 text-2xl font-semibold text-primary">Admin</span>
<Input className="bg-background" placeholder="Email address" />
<Input className="bg-background" placeholder="Password" />
<Button
onClick={() => {
loginAdmin();
}}
variant="default"
className="w-full"
>
Login
</Button>
</div>
</div>
);
}

View file

@ -0,0 +1,401 @@
import _ from "lodash";
import { X } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import PaginatorComponent from "../../components/PaginatorComponent";
import ShadTooltip from "../../components/ShadTooltipComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "../../components/ui/table";
import ConfirmationModal from "../../modals/ConfirmationModal";
import UserManagementModal from "../../modals/UserManagementModal";
export default function AdminPage() {
const [inputValue, setInputValue] = useState("");
const [size, setPageSize] = useState(10);
const [index, setPageIndex] = useState(1);
const userList = useRef([
{
user: generateRandomString(50),
email: generateRandomString(50) + "@example.com",
password: generateRandomString(50),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
{
user: generateRandomString(8),
email: generateRandomString(10) + "@example.com",
password: generateRandomString(12),
register_date: generateRandomDate(),
},
]);
const [filterUserList, setFilterUserList] = useState(userList.current);
function generateRandomString(length) {
const characters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
let result = "";
for (let i = 0; i < length; i++) {
const randomIndex = Math.floor(Math.random() * characters.length);
result += characters.charAt(randomIndex);
}
return result;
}
function generateRandomDate() {
const start = new Date(2010, 0, 1);
const end = new Date();
const randomTimestamp =
start.getTime() + Math.random() * (end.getTime() - start.getTime());
const randomDate = new Date(randomTimestamp);
const options = { year: "numeric", month: "short", day: "numeric" };
return randomDate.toLocaleDateString("en-US");
}
const [editUser, setEditUser] = useState(-1);
const [editedUser, setEditedUser] = useState("");
const [modalEditOpen, setModalEditOpen] = useState(false);
const [modalDeleteOpen, setModalDeleteOpen] = useState(false);
useEffect(() => {
resetFilter();
}, []);
const handleInputChange = (event, index) => {
const user = _.cloneDeepWith(userList.current);
user[index].password = event.target.value;
userList.current = user;
const userFilter = _.cloneDeepWith(filterUserList);
userFilter[index].password = event.target.value;
setFilterUserList(userFilter);
setEditedUser(event.target.value);
};
function handleChangePagination(pageIndex: number, pageSize: number) {
setPageIndex(pageIndex);
setPageSize(pageSize);
const startIndex = (pageIndex - 1) * pageSize;
const endIndex = startIndex + pageSize;
const newList = userList.current.slice(startIndex, endIndex);
setFilterUserList(newList);
}
function resetFilter() {
setFilterUserList(userList.current);
setPageIndex(1);
setPageSize(10);
const startIndex = (index - 1) * size;
const endIndex = index + size - 1;
const newList = userList.current.slice(startIndex, endIndex);
console.log(userList.current);
setFilterUserList(newList);
}
function handleFilterUsers(input: string) {
setInputValue(input);
if (input === "") {
resetFilter();
} else {
const filteredList = userList.current.filter(
(user) =>
user.user.toLowerCase().includes(input.toLowerCase()) ||
user.email.toLowerCase().includes(input.toLowerCase())
);
setFilterUserList(filteredList);
}
}
function handleDeleteUser(index) {
const user = _.cloneDeepWith(userList.current);
user.splice(index, 1);
userList.current = user;
const userFilter = _.cloneDeepWith(filterUserList);
userFilter.splice(index, 1);
setFilterUserList(userFilter);
resetFilter();
}
function handleEditUser(index, user) {
const newUser = _.cloneDeepWith(userList.current);
newUser[index].password = user.password;
newUser[index].user = user.username;
userList.current = newUser;
resetFilter();
}
function handleNewUser(user) {
const newUser = {
user: user.username,
email: generateRandomString(50) + "@example.com",
password: user.password,
register_date: generateRandomDate(),
};
userList.current.unshift(newUser);
console.log(userList.current);
resetFilter();
}
return (
<>
<div className="main-page-panel">
<div className="m-auto flex h-full flex-row justify-center">
<div className="basis-5/6">
<div className="m-auto flex h-full flex-col space-y-8 p-8 ">
<div className="flex items-center justify-between space-y-2">
<div>
<h2 className="text-2xl font-bold tracking-tight">
Welcome back!
</h2>
<p className="text-muted-foreground">
Here&apos;s a list of all users!
</p>
</div>
<div className="flex items-center space-x-2"></div>
</div>
{userList.current.length === 0 && (
<>
<div className="flex items-center justify-between">
<h2>There's no users left :)</h2>
</div>
</>
)}
{userList.current.length > 0 && (
<>
<div className="flex items-center justify-between">
<div className="flex flex-1 items-center space-x-2">
<Input
value={inputValue}
placeholder="Filter users..."
className="h-8 w-[150px] lg:w-[250px]"
onChange={(e) => handleFilterUsers(e.target.value)}
/>
{inputValue.length > 0 && (
<Button
onClick={() => {
resetFilter();
setInputValue("");
}}
variant="ghost"
className="h-8 px-2 lg:px-3"
>
Reset
<X className="ml-2 h-4 w-4" />
</Button>
)}
</div>
<div>
<UserManagementModal
title="New User"
titleHeader={"Add a new user"}
cancelText="Cancel"
confirmationText="Save"
icon={"UserPlus2"}
onConfirm={(index, user) => {
handleNewUser(user);
}}
>
<Button>New User</Button>
</UserManagementModal>
</div>
</div>
<div
className="overflow-scroll overflow-x-hidden rounded-md border-2 bg-muted
custom-scroll"
>
<Table className="table-fixed bg-muted outline-1 ">
<TableHeader>
<TableRow>
<TableHead className="h-10">User</TableHead>
<TableHead className="h-10">Password</TableHead>
<TableHead className="h-10 w-[100px] text-right"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{filterUserList.map((user, index) => (
<TableRow key={user.user}>
<TableCell className="truncate py-2 font-medium">
{user.user}
</TableCell>
<TableCell className="truncate py-2">
{user.password}
</TableCell>
<TableCell className="flex w-[100px] py-2 text-right">
<div className="flex">
<UserManagementModal
title="Edit"
titleHeader={`${user.user}`}
cancelText="Cancel"
confirmationText="Edit"
icon={"UserPlus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleEditUser(index, user);
}}
>
<ShadTooltip content="Edit" side="top">
<IconComponent
name="Pencil"
className="h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</UserManagementModal>
<ConfirmationModal
title="Delete"
titleHeader="Delete User"
modalContentTitle="Attention!"
modalContent="Are you sure you want to delete this user? This action cannot be undone."
cancelText="Cancel"
confirmationText="Delete"
icon={"UserMinus2"}
data={user}
index={index}
onConfirm={(index, user) => {
handleDeleteUser(index);
}}
>
<ShadTooltip content="Delete" side="top">
<IconComponent
name="Trash2"
className="ml-2 h-4 w-4 cursor-pointer"
/>
</ShadTooltip>
</ConfirmationModal>
</div>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<PaginatorComponent
pageIndex={index}
pageSize={size}
totalRowsCount={filterUserList.length}
paginate={(pageIndex, pageSize) => {
handleChangePagination(pageSize, pageIndex);
}}
></PaginatorComponent>
</>
)}
</div>
</div>
</div>
</div>
</>
);
}

View file

@ -6,9 +6,10 @@ import { TabsContext } from "../../contexts/tabsContext";
import { useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { getExamples } from "../../controllers/API";
import { FlowType } from "../../types/flow";
export default function CommunityPage() {
export default function CommunityPage(): JSX.Element {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow } =
useContext(TabsContext);
@ -21,7 +22,7 @@ export default function CommunityPage() {
const [examples, setExamples] = useState<FlowType[]>([]);
// Show community examples on screen
function handleExamples() {
function handleExamples(): void {
setLoadingExamples(true);
getExamples()
.then((result) => {
@ -42,61 +43,65 @@ export default function CommunityPage() {
handleExamples();
}, []);
return (
<div className="community-page-arrangement">
<div className="community-page-nav-arrangement">
<span className="community-page-nav-title">
<IconComponent name="Users2" className="w-6" />
Community Examples
<>
<Header />
<div className="community-page-arrangement">
<div className="community-page-nav-arrangement">
<span className="community-page-nav-title">
<IconComponent name="Users2" className="w-6" />
Community Examples
</span>
<div className="community-page-nav-button">
<a
href="https://github.com/logspace-ai/langflow_examples"
target="_blank"
rel="noreferrer"
>
<Button variant="primary">
<IconComponent
name="GithubIcon"
className="main-page-nav-button"
/>
Add Your Example
</Button>
</a>
</div>
</div>
<span className="community-page-description-text">
Discover and learn from shared examples by the Langflow community. We
welcome new example contributions that can help our community explore
new and powerful features.
</span>
<div className="community-page-nav-button">
<a
href="https://github.com/logspace-ai/langflow_examples"
target="_blank"
rel="noreferrer"
>
<Button variant="primary">
<IconComponent
name="GithubIcon"
className="main-page-nav-button"
<div className="community-pages-flows-panel">
{!loadingExamples &&
examples.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
onClick={() => {
addFlow(flow, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
name="GitFork"
className="main-page-nav-button"
/>
Fork Example
</Button>
}
/>
Add Your Example
</Button>
</a>
))}
</div>
</div>
<span className="community-page-description-text">
Discover and learn from shared examples by the Langflow community. We
welcome new example contributions that can help our community explore
new and powerful features.
</span>
<div className="community-pages-flows-panel">
{!loadingExamples &&
examples.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
onClick={() => {
addFlow(flow, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent
name="GitFork"
className="main-page-nav-button"
/>
Fork Example
</Button>
}
/>
))}
</div>
</div>
</>
);
}

View file

@ -6,7 +6,7 @@ const ConnectionLineComponent = ({
toX,
toY,
connectionLineStyle = {}, // provide a default value for connectionLineStyle
}: ConnectionLineComponentProps) => {
}: ConnectionLineComponentProps): JSX.Element => {
return (
<g>
<path

View file

@ -6,7 +6,7 @@ export default function DisclosureComponent({
button: { title, Icon, buttons = [] },
children,
openDisc,
}: DisclosureComponentType) {
}: DisclosureComponentType): JSX.Element {
return (
<Disclosure as="div" key={title}>
{({ open }) => (
@ -18,9 +18,9 @@ export default function DisclosureComponent({
<span className="components-disclosure-title">{title}</span>
</div>
<div className="components-disclosure-div">
{buttons.map((x, index) => (
<button key={index} onClick={x.onClick}>
{x.Icon}
{buttons.map((btn, index) => (
<button key={index} onClick={btn.onClick}>
{btn.Icon}
</button>
))}
<div>

View file

@ -1,11 +1,19 @@
import _ from "lodash";
import { useCallback, useContext, useEffect, useRef, useState } from "react";
import {
MouseEvent,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from "react";
import ReactFlow, {
Background,
Connection,
Controls,
Edge,
EdgeChange,
Node,
NodeChange,
NodeDragHandler,
OnEdgesDelete,
@ -26,6 +34,7 @@ import { typesContext } from "../../../../contexts/typesContext";
import { undoRedoContext } from "../../../../contexts/undoRedoContext";
import { APIClassType } from "../../../../types/api";
import { FlowType, NodeType, targetHandleType } from "../../../../types/flow";
import { TabsState } from "../../../../types/tabs";
import {
isValidConnection,
scapeJSONParse,
@ -38,7 +47,13 @@ const nodeTypes = {
genericNode: GenericNode,
};
export default function Page({ flow }: { flow: FlowType }) {
export default function Page({
flow,
view,
}: {
flow: FlowType;
view?: boolean;
}): JSX.Element {
let {
updateFlow,
uploadFlow,
@ -54,13 +69,13 @@ export default function Page({ flow }: { flow: FlowType }) {
} = useContext(TabsContext);
const { types, reactFlowInstance, setReactFlowInstance, templates } =
useContext(typesContext);
const reactFlowWrapper = useRef(null);
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const { takeSnapshot } = useContext(undoRedoContext);
const [position, setPosition] = useState({ x: 0, y: 0 });
const [lastSelection, setLastSelection] =
useState<OnSelectionChangeParams>(null);
useState<OnSelectionChangeParams | null>(null);
useEffect(() => {
// this effect is used to attach the global event handlers
@ -81,10 +96,10 @@ export default function Page({ flow }: { flow: FlowType }) {
lastCopiedSelection
) {
event.preventDefault();
let bounds = reactFlowWrapper.current.getBoundingClientRect();
let bounds = reactFlowWrapper.current?.getBoundingClientRect();
paste(lastCopiedSelection, {
x: position.x - bounds.left,
y: position.y - bounds.top,
x: position.x - bounds!.left,
y: position.y - bounds!.top,
});
}
if (
@ -144,13 +159,14 @@ export default function Page({ flow }: { flow: FlowType }) {
}, [setExtraComponent, setExtraNavigation]);
const onEdgesChangeMod = useCallback(
(s: EdgeChange[]) => {
onEdgesChange(s);
setNodes((x) => {
let newX = _.cloneDeep(x);
(change: EdgeChange[]) => {
onEdgesChange(change);
setNodes((node) => {
let newX = _.cloneDeep(node);
return newX;
});
setTabsState((prev) => {
//@ts-ignore
setTabsState((prev: TabsState) => {
return {
...prev,
[tabId]: {
@ -164,9 +180,10 @@ export default function Page({ flow }: { flow: FlowType }) {
);
const onNodesChangeMod = useCallback(
(s: NodeChange[]) => {
onNodesChange(s);
setTabsState((prev) => {
(change: NodeChange[]) => {
onNodesChange(change);
//@ts-ignore
setTabsState((prev: TabsState) => {
return {
...prev,
[tabId]: {
@ -188,19 +205,19 @@ export default function Page({ flow }: { flow: FlowType }) {
...params,
style: { stroke: "#555" },
className:
((scapeJSONParse(params.targetHandle) as targetHandleType)
((scapeJSONParse(params.targetHandle!) as targetHandleType)
.type === "Text"
? "stroke-foreground "
: "stroke-foreground ") + " stroke-connection",
animated:
(scapeJSONParse(params.targetHandle) as targetHandleType).type ===
"Text",
(scapeJSONParse(params.targetHandle!) as targetHandleType)
.type === "Text",
},
eds
)
);
setNodes((x) => {
let newX = _.cloneDeep(x);
setNodes((node) => {
let newX = _.cloneDeep(node);
return newX;
});
},
@ -225,7 +242,7 @@ export default function Page({ flow }: { flow: FlowType }) {
const onDragOver = useCallback((event: React.DragEvent) => {
event.preventDefault();
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
if (event.dataTransfer.types.some((types) => types === "nodedata")) {
event.dataTransfer.dropEffect = "move";
} else {
event.dataTransfer.dropEffect = "copy";
@ -235,12 +252,12 @@ export default function Page({ flow }: { flow: FlowType }) {
const onDrop = useCallback(
(event: React.DragEvent) => {
event.preventDefault();
if (event.dataTransfer.types.some((t) => t === "nodedata")) {
if (event.dataTransfer.types.some((types) => types === "nodedata")) {
takeSnapshot();
// Get the current bounds of the ReactFlow wrapper element
const reactflowBounds =
reactFlowWrapper.current.getBoundingClientRect();
reactFlowWrapper.current?.getBoundingClientRect();
// Extract the data from the drag event and parse it as a JSON object
let data: { type: string; node?: APIClassType } = JSON.parse(
@ -249,9 +266,9 @@ export default function Page({ flow }: { flow: FlowType }) {
// If data type is not "chatInput" or if there are no "chatInputNode" nodes present in the ReactFlow instance, create a new node
// Calculate the position where the node should be created
const position = reactFlowInstance.project({
x: event.clientX - reactflowBounds.left,
y: event.clientY - reactflowBounds.top,
const position = reactFlowInstance!.project({
x: event.clientX - reactflowBounds!.left,
y: event.clientY - reactflowBounds!.top,
});
// Generate a unique node ID
@ -268,7 +285,6 @@ export default function Page({ flow }: { flow: FlowType }) {
data: {
...data,
id: newId,
value: null,
},
};
} else {
@ -280,16 +296,15 @@ export default function Page({ flow }: { flow: FlowType }) {
data: {
...data,
id: newId,
value: null,
},
};
// Add the new node to the list of nodes in state
}
setNodes((nds) => nds.concat(newNode));
} else if (event.dataTransfer.types.some((t) => t === "Files")) {
} else if (event.dataTransfer.types.some((types) => types === "Files")) {
takeSnapshot();
uploadFlow(false, event.dataTransfer.files.item(0));
uploadFlow(false, event.dataTransfer.files.item(0)!);
}
},
// Specify dependencies for useCallback
@ -305,11 +320,14 @@ export default function Page({ flow }: { flow: FlowType }) {
}, []);
const onDelete = useCallback(
(mynodes) => {
(mynodes: Node[]) => {
takeSnapshot();
setEdges(
edges.filter(
(ns) => !mynodes.some((n) => ns.source === n.id || ns.target === n.id)
(edge) =>
!mynodes.some(
(node) => edge.source === node.id || edge.target === node.id
)
)
);
},
@ -322,7 +340,7 @@ export default function Page({ flow }: { flow: FlowType }) {
const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, reactFlowInstance)) {
if (isValidConnection(newConnection, reactFlowInstance!)) {
edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
@ -330,11 +348,10 @@ export default function Page({ flow }: { flow: FlowType }) {
[reactFlowInstance, setEdges]
);
const onEdgeUpdateEnd = useCallback((_, edge) => {
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
if (!edgeUpdateSuccessful.current) {
setEdges((eds) => eds.filter((e) => e.id !== edge.id));
setEdges((eds) => eds.filter((edg) => edg.id !== edge.id));
}
edgeUpdateSuccessful.current = true;
}, []);
@ -343,7 +360,7 @@ export default function Page({ flow }: { flow: FlowType }) {
const onSelectionEnd = useCallback(() => {
setSelectionEnded(true);
}, []);
const onSelectionStart = useCallback((event) => {
const onSelectionStart = useCallback((event: MouseEvent) => {
event.preventDefault();
setSelectionEnded(false);
}, []);
@ -357,13 +374,16 @@ export default function Page({ flow }: { flow: FlowType }) {
}
}, [selectionEnded, lastSelection]);
const onSelectionChange = useCallback((flow) => {
setLastSelection(flow);
}, []);
const onSelectionChange = useCallback(
(flow: OnSelectionChangeParams): void => {
setLastSelection(flow);
},
[]
);
return (
<div className="flex h-full overflow-hidden">
<ExtraSidebar />
{!view && <ExtraSidebar />}
{/* Main area */}
<main className="flex flex-1">
{/* Primary column */}
@ -386,7 +406,6 @@ export default function Page({ flow }: { flow: FlowType }) {
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
disableKeyboardA11y={true}
onLoad={setReactFlowInstance}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
@ -405,14 +424,21 @@ export default function Page({ flow }: { flow: FlowType }) {
className="theme-attribution"
minZoom={0.01}
maxZoom={8}
zoomOnScroll={!view}
zoomOnPinch={!view}
panOnDrag={!view}
>
<Background className="" />
<Controls
className="bg-muted fill-foreground stroke-foreground text-primary
{!view && (
<Controls
className="bg-muted fill-foreground stroke-foreground text-primary
[&>button]:border-b-border hover:[&>button]:bg-border"
></Controls>
></Controls>
)}
</ReactFlow>
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
{!view && (
<Chat flow={flow} reactFlowInstance={reactFlowInstance!} />
)}
</div>
) : (
<></>

View file

@ -17,7 +17,7 @@ import {
import { classNames } from "../../../../utils/utils";
import DisclosureComponent from "../DisclosureComponent";
export default function ExtraSidebar() {
export default function ExtraSidebar(): JSX.Element {
const { data, templates } = useContext(typesContext);
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } =
useContext(TabsContext);
@ -28,7 +28,7 @@ export default function ExtraSidebar() {
function onDragStart(
event: React.DragEvent<any>,
data: { type: string; node?: APIClassType }
) {
): void {
//start drag event
var crt = event.currentTarget.cloneNode(true);
crt.style.position = "absolute";
@ -56,10 +56,10 @@ export default function ExtraSidebar() {
return ret;
});
}
const flow = flows.find((f) => f.id === tabId);
const flow = flows.find((flow) => flow.id === tabId);
useEffect(() => {
// show components with error on load
let errors = [];
let errors: string[] = [];
Object.keys(templates).forEach((component) => {
if (templates[component].error) {
errors.push(component);
@ -120,7 +120,7 @@ export default function ExtraSidebar() {
"extra-side-bar-buttons " + (isPending ? "" : "button-disable")
}
onClick={(event) => {
saveFlow(flow);
saveFlow(flow!);
setSuccessData({ title: "Changes saved successfully" });
}}
>
@ -143,10 +143,10 @@ export default function ExtraSidebar() {
id="search"
placeholder="Search"
className="nopan nodrag noundo nocopy input-search"
onChange={(e) => {
handleSearchInput(e.target.value);
onChange={(event) => {
handleSearchInput(event.target.value);
// Set search input state
setSearch(e.target.value);
setSearch(event.target.value);
}}
/>
<div className="search-icon">
@ -161,42 +161,43 @@ export default function ExtraSidebar() {
<div className="side-bar-components-div-arrangement">
{Object.keys(dataFilter)
.sort()
.map((d: keyof APIObjectType, i) =>
Object.keys(dataFilter[d]).length > 0 ? (
.map((SBSectionName: keyof APIObjectType, index) =>
Object.keys(dataFilter[SBSectionName]).length > 0 ? (
<DisclosureComponent
openDisc={search.length == 0 ? false : true}
key={i}
key={index}
button={{
title: nodeNames[d] ?? nodeNames.unknown,
Icon: nodeIconsLucide[d] ?? nodeIconsLucide.unknown,
title: nodeNames[SBSectionName] ?? nodeNames.unknown,
Icon:
nodeIconsLucide[SBSectionName] ?? nodeIconsLucide.unknown,
}}
>
<div className="side-bar-components-gap">
{Object.keys(dataFilter[d])
{Object.keys(dataFilter[SBSectionName])
.sort()
.map((t: string, k) => (
.map((SBItemName: string, index) => (
<ShadTooltip
content={data[d][t].display_name}
content={data[SBSectionName][SBItemName].display_name}
side="right"
key={k}
key={index}
>
<div key={k} data-tooltip-id={t}>
<div key={index} data-tooltip-id={SBItemName}>
<div
draggable={!data[d][t].error}
draggable={!data[SBSectionName][SBItemName].error}
className={
"side-bar-components-border bg-background" +
(data[d][t].error
(data[SBSectionName][SBItemName].error
? " cursor-not-allowed select-none"
: "")
}
style={{
borderLeftColor:
nodeColors[d] ?? nodeColors.unknown,
nodeColors[SBSectionName] ?? nodeColors.unknown,
}}
onDragStart={(event) =>
onDragStart(event, {
type: t,
node: data[d][t],
type: SBItemName,
node: data[SBSectionName][SBItemName],
})
}
onDragEnd={() => {
@ -209,7 +210,7 @@ export default function ExtraSidebar() {
>
<div className="side-bar-components-div-form">
<span className="side-bar-components-text">
{data[d][t].display_name}
{data[SBSectionName][SBItemName].display_name}
</span>
<IconComponent
name="Menu"
@ -223,7 +224,7 @@ export default function ExtraSidebar() {
</div>
</DisclosureComponent>
) : (
<div key={i}></div>
<div key={index}></div>
)
)}
</div>

View file

@ -4,22 +4,27 @@ import ShadTooltip from "../../../../components/ShadTooltipComponent";
import IconComponent from "../../../../components/genericIconComponent";
import { TabsContext } from "../../../../contexts/tabsContext";
import EditNodeModal from "../../../../modals/EditNodeModal";
import { nodeToolbarPropsType } from "../../../../types/components";
import { classNames } from "../../../../utils/utils";
export default function NodeToolbarComponent({ data, setData, deleteNode }) {
export default function NodeToolbarComponent({
data,
setData,
deleteNode,
}: nodeToolbarPropsType): JSX.Element {
const [nodeLength, setNodeLength] = useState(
Object.keys(data.node.template).filter(
(t) =>
t.charAt(0) !== "_" &&
data.node.template[t].show &&
(data.node.template[t].type === "str" ||
data.node.template[t].type === "bool" ||
data.node.template[t].type === "float" ||
data.node.template[t].type === "code" ||
data.node.template[t].type === "prompt" ||
data.node.template[t].type === "file" ||
data.node.template[t].type === "Any" ||
data.node.template[t].type === "int")
Object.keys(data.node!.template).filter(
(templateField) =>
templateField.charAt(0) !== "_" &&
data.node?.template[templateField].show &&
(data.node.template[templateField].type === "str" ||
data.node.template[templateField].type === "bool" ||
data.node.template[templateField].type === "float" ||
data.node.template[templateField].type === "code" ||
data.node.template[templateField].type === "prompt" ||
data.node.template[templateField].type === "file" ||
data.node.template[templateField].type === "Any" ||
data.node.template[templateField].type === "int")
).length
);
@ -55,8 +60,8 @@ export default function NodeToolbarComponent({ data, setData, deleteNode }) {
{
x: 50,
y: 10,
paneX: reactFlowInstance.getNode(data.id).position.x,
paneY: reactFlowInstance.getNode(data.id).position.y,
paneX: reactFlowInstance.getNode(data.id)?.position.x,
paneY: reactFlowInstance.getNode(data.id)?.position.y,
}
);
}}
@ -67,23 +72,23 @@ export default function NodeToolbarComponent({ data, setData, deleteNode }) {
<ShadTooltip
content={
data.node.documentation === "" ? "Coming Soon" : "Documentation"
data.node?.documentation === "" ? "Coming Soon" : "Documentation"
}
side="top"
>
<a
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" +
(data.node.documentation === ""
(data.node?.documentation === ""
? " text-muted-foreground"
: " text-foreground")
)}
target="_blank"
rel="noopener noreferrer"
href={data.node.documentation}
href={data.node?.documentation}
// deactivate link if no documentation is provided
onClick={(event) => {
if (data.node.documentation === "") {
if (data.node?.documentation === "") {
event.preventDefault();
}
}}

View file

@ -1,16 +1,17 @@
import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import Header from "../../components/headerComponent";
import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API";
import Page from "./components/PageComponent";
export default function FlowPage() {
export default function FlowPage(): JSX.Element {
const { flows, tabId, setTabId } = useContext(TabsContext);
const { id } = useParams();
// Set flow tab id
useEffect(() => {
setTabId(id);
setTabId(id!);
}, [id]);
// Initialize state variable for the version
@ -22,20 +23,23 @@ export default function FlowPage() {
}, []);
return (
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page flow={flows.find((flow) => flow.id === tabId)} />
)}
<a
target={"_blank"}
href="https://logspace.ai/"
className="logspace-page-icon"
>
{version && <div className="mt-1">⛓️ Langflow v{version}</div>}
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
</a>
</div>
<>
<Header />
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page flow={flows.find((flow) => flow.id === tabId)!} />
)}
<a
target={"_blank"}
href="https://logspace.ai/"
className="logspace-page-icon"
>
{version && <div className="mt-1">⛓️ Langflow v{version}</div>}
<div className={version ? "mt-2" : "mt-1"}>Created by Logspace</div>
</a>
</div>
</>
);
}

View file

@ -2,10 +2,11 @@ import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import IconComponent from "../../components/genericIconComponent";
import Header from "../../components/headerComponent";
import { Button } from "../../components/ui/button";
import { USER_PROJECTS_HEADER } from "../../constants/constants";
import { TabsContext } from "../../contexts/tabsContext";
export default function HomePage() {
export default function HomePage(): JSX.Element {
const { flows, setTabId, downloadFlows, uploadFlows, addFlow, removeFlow } =
useContext(TabsContext);
@ -17,74 +18,77 @@ export default function HomePage() {
// Personal flows display
return (
<div className="main-page-panel">
<div className="main-page-nav-arrangement">
<span className="main-page-nav-title">
<IconComponent name="Home" className="w-6" />
{USER_PROJECTS_HEADER}
<>
<Header />
<div className="main-page-panel">
<div className="main-page-nav-arrangement">
<span className="main-page-nav-title">
<IconComponent name="Home" className="w-6" />
{USER_PROJECTS_HEADER}
</span>
<div className="button-div-style">
<Button
variant="primary"
onClick={() => {
downloadFlows();
}}
>
<IconComponent name="Download" className="main-page-nav-button" />
Download Collection
</Button>
<Button
variant="primary"
onClick={() => {
uploadFlows();
}}
>
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
addFlow(null!, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="button-div-style">
<Button
variant="primary"
onClick={() => {
downloadFlows();
}}
>
<IconComponent name="Download" className="main-page-nav-button" />
Download Collection
</Button>
<Button
variant="primary"
onClick={() => {
uploadFlows();
}}
>
<IconComponent name="Upload" className="main-page-nav-button" />
Upload Collection
</Button>
<Button
variant="primary"
onClick={() => {
addFlow(null, true).then((id) => {
navigate("/flow/" + id);
});
}}
>
<IconComponent name="Plus" className="main-page-nav-button" />
New Project
</Button>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
</div>
</div>
<span className="main-page-description-text">
Manage your personal projects. Download or upload your collection.
</span>
<div className="main-page-flows-display">
{flows.map((flow, idx) => (
<CardComponent
key={idx}
flow={flow}
id={flow.id}
button={
<Link to={"/flow/" + flow.id}>
<Button
variant="outline"
size="sm"
className="whitespace-nowrap "
>
<IconComponent
name="ExternalLink"
className="main-page-nav-button"
/>
Edit Flow
</Button>
</Link>
}
onDelete={() => {
removeFlow(flow.id);
}}
/>
))}
</div>
</div>
</>
);
}

View file

@ -0,0 +1,33 @@
import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { TabsContext } from "../../contexts/tabsContext";
import { getVersion } from "../../controllers/API";
import Page from "../FlowPage/components/PageComponent";
export default function ViewPage() {
const { flows, tabId, setTabId } = useContext(TabsContext);
const { id } = useParams();
// Set flow tab id
useEffect(() => {
setTabId(id);
}, [id]);
// Initialize state variable for the version
const [version, setVersion] = useState("");
useEffect(() => {
getVersion().then((data) => {
setVersion(data.version);
});
}, []);
return (
<div className="flow-page-positioning">
{flows.length > 0 &&
tabId !== "" &&
flows.findIndex((flow) => flow.id === tabId) !== -1 && (
<Page view flow={flows.find((flow) => flow.id === tabId)} />
)}
</div>
);
}

View file

@ -0,0 +1,59 @@
import { useState } from "react";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import BaseModal from "../../modals/baseModal";
export default function DeleteAccountPage() {
const [showConfirmation, setShowConfirmation] = useState(false);
const handleDeleteAccount = () => {
// Implement your account deletion logic here
// For example, make an API call to delete the account
// Upon successful deletion, you can redirect the user to another page
console.log("Account deleted!");
// Implement the logic to redirect the user after account deletion.
// For example, use react-router-dom's useHistory hook.
};
return (
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-4 text-center text-2xl font-semibold text-primary">
Delete your account
</span>
<Input className="bg-background" placeholder="Confirm password" />
<BaseModal
open={showConfirmation}
setOpen={setShowConfirmation}
size="x-small"
>
<BaseModal.Header description="This action is irreversible and will permanently erase all your data and information associated with the account. ">
<h3>Are you sure ?</h3>
</BaseModal.Header>
<BaseModal.Trigger>
<Button
variant="default"
className="w-full hover:bg-status-red"
onClick={() => setShowConfirmation(true)}
>
Delete account
</Button>
</BaseModal.Trigger>
<BaseModal.Content>
<div className="flex h-full w-full flex-col justify-end">
<Button
variant="default"
className="w-full hover:bg-status-red"
onClick={() => handleDeleteAccount()}
>
Delete account
</Button>
</div>
</BaseModal.Content>
</BaseModal>
</div>
</div>
);
}

View file

@ -0,0 +1,105 @@
import * as Form from "@radix-ui/react-form";
import { useState } from "react";
import { Link } from "react-router-dom";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CONTROL_LOGIN_STATE } from "../../constants/constants";
import {
inputHandlerEventType,
loginInputStateType,
} from "../../types/components";
export default function LoginPage(): JSX.Element {
const [inputState, setInputState] =
useState<loginInputStateType>(CONTROL_LOGIN_STATE);
const { password, username } = inputState;
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
return (
<Form.Root
onSubmit={(event) => {
if (password === "") {
event.preventDefault();
return;
}
const data = Object.fromEntries(new FormData(event.currentTarget));
event.preventDefault();
}}
className="h-full w-full"
>
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-6 text-2xl font-semibold text-primary">
Sign in to Langflow
</span>
<div className="mb-3 w-full">
<Form.Field name="username">
<Form.Label className="data-[invalid]:label-invalid">
Username <span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Control asChild>
<Input
onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } });
}}
value={username}
className="w-full"
required
placeholder="Username"
/>
</Form.Control>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your username
</Form.Message>
</Form.Field>
</div>
<div className="mb-3 w-full">
<Form.Field name="password">
<Form.Label className="data-[invalid]:label-invalid">
Password <span className="font-medium text-destructive">*</span>
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
required
placeholder="Password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please enter your password
</Form.Message>
</Form.Field>
</div>
<div className="w-full">
<Form.Submit asChild>
<Button className="mr-3 mt-6 w-full">Sign in</Button>
</Form.Submit>
</div>
<div className="w-full">
<Link to="">
<Button className="w-full" variant="outline">
Don't have an account?&nbsp;<b>Sign Up</b>
</Button>
</Link>
</div>
</div>
</div>
</Form.Root>
);
}

View file

@ -0,0 +1,137 @@
import * as Form from "@radix-ui/react-form";
import { FormEvent, useState } from "react";
import { Link } from "react-router-dom";
import InputComponent from "../../components/inputComponent";
import { Button } from "../../components/ui/button";
import { Input } from "../../components/ui/input";
import { CONTROL_INPUT_STATE } from "../../constants/constants";
import {
inputHandlerEventType,
signUpInputStateType,
} from "../../types/components";
export default function SignUp(): JSX.Element {
const [inputState, setInputState] =
useState<signUpInputStateType>(CONTROL_INPUT_STATE);
const { password, cnfPassword, username } = inputState;
function handleInput({
target: { name, value },
}: inputHandlerEventType): void {
setInputState((prev) => ({ ...prev, [name]: value }));
}
return (
<Form.Root
onSubmit={(event: FormEvent<HTMLFormElement>) => {
if (password === "") {
event.preventDefault();
return;
}
const data = Object.fromEntries(new FormData(event.currentTarget));
event.preventDefault();
}}
className="h-full w-full"
>
<div className="flex h-full w-full flex-col items-center justify-center bg-muted">
<div className="flex w-72 flex-col items-center justify-center gap-2">
<span className="mb-4 text-5xl">⛓️</span>
<span className="mb-6 text-2xl font-semibold text-primary">
Sign up to Langflow
</span>
<div className="mb-3 w-full">
<Form.Field name="username">
<Form.Label className="data-[invalid]:label-invalid">
Username <span className="font-medium text-destructive">*</span>
</Form.Label>
<Form.Control asChild>
<Input
onChange={({ target: { value } }) => {
handleInput({ target: { name: "username", value } });
}}
value={username}
className="w-full"
required
placeholder="Username"
/>
</Form.Control>
<Form.Message match="valueMissing" className="field-invalid">
Please enter your username
</Form.Message>
</Form.Field>
</div>
<div className="mb-3 w-full">
<Form.Field name="password" serverInvalid={password != cnfPassword}>
<Form.Label className="data-[invalid]:label-invalid">
Password <span className="font-medium text-destructive">*</span>
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "password", value } });
}}
value={password}
isForm
password={true}
required
placeholder="Password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please enter a password
</Form.Message>
{password != cnfPassword && (
<Form.Message className="field-invalid">
Passwords do not match
</Form.Message>
)}
</Form.Field>
</div>
<div className="w-full">
<Form.Field
name="confirmpassword"
serverInvalid={password != cnfPassword}
>
<Form.Label className="data-[invalid]:label-invalid">
Confirm your password{" "}
<span className="font-medium text-destructive">*</span>
</Form.Label>
<InputComponent
onChange={(value) => {
handleInput({ target: { name: "cnfPassword", value } });
}}
value={cnfPassword}
isForm
password={true}
required
placeholder="Confirm your password"
className="w-full"
/>
<Form.Message className="field-invalid" match="valueMissing">
Please confirm your password
</Form.Message>
</Form.Field>
</div>
<div className="w-full">
<Form.Submit asChild>
<Button className="mr-3 mt-6 w-full">Sign up</Button>
</Form.Submit>
</div>
<div className="w-full">
<Link to="/login">
<Button className="w-full" variant="outline">
Already have an account?&nbsp;<b>Sign in</b>
</Button>
</Link>
</div>
</div>
</div>
</Form.Root>
);
}

View file

@ -1,7 +1,12 @@
import { Route, Routes } from "react-router-dom";
import AdminPage from "./pages/AdminPage";
import LoginAdminPage from "./pages/AdminPage/LoginPage";
import CommunityPage from "./pages/CommunityPage";
import FlowPage from "./pages/FlowPage";
import HomePage from "./pages/MainPage";
import ViewPage from "./pages/ViewPage";
import DeleteAccountPage from "./pages/deleteAccountPage";
import LoginPage from "./pages/loginPage";
const Router = () => {
return (
@ -10,8 +15,19 @@ const Router = () => {
<Route path="/community" element={<CommunityPage />} />
<Route path="/flow/:id/">
<Route path="" element={<FlowPage />} />
<Route path="view" element={<ViewPage />} />
</Route>
<Route path="*" element={<HomePage />} />
<Route path="/login" element={<LoginPage />} />
{/* <Route path="/signup" element={<SignUp />} /> */}
<Route path="/login/admin" element={<LoginAdminPage />} />
<Route path="/admin" element={<AdminPage />} />
<Route path="/account">
<Route path="delete" element={<DeleteAccountPage />}></Route>
</Route>
</Routes>
);
};

View file

@ -192,6 +192,10 @@
@apply flex w-full;
}
.loading-page-panel {
@apply h-full w-full flex justify-center items-center bg-muted;
}
.main-page-panel {
@apply flex-max-width h-full flex-col overflow-auto bg-muted px-16;
}
@ -301,13 +305,13 @@
}
.error-build-message {
@apply mt-6 w-96 cursor-pointer rounded-md bg-error-background p-4 shadow-xl;
@apply mt-6 w-96 cursor-pointer rounded-md bg-error-background p-4 shadow-xl;
}
.error-build-message-circle {
@apply alert-icon text-status-red;
}
.error-build-text {
@apply text-error-foreground;
@apply text-error-foreground word-break-break-word;
}
.error-build-foreground {
@apply error-build-text alert-font-size;
@ -326,7 +330,7 @@
@apply alert-icon text-status-green;
}
.success-alert-message {
@apply alert-font-size text-success-foreground;
@apply word-break-break-word alert-font-size text-success-foreground;
}
.card-component-title-display {
@ -336,7 +340,7 @@
@apply flex h-7 w-7 items-center justify-center rounded-full text-2xl;
}
.card-component-title-size {
@apply inline-block w-full flex-1 break-words truncate-doubleline;
@apply w-full flex-1 word-break-break-word truncate-doubleline;
}
.card-component-delete-button {
@apply flex self-start;
@ -495,6 +499,12 @@
.header-github-link-box {
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-3 pr-0 shadow-sm;
}
.header-waitlist-link-box {
@apply inline-flex h-9 items-center justify-center rounded-md border border-input px-2 shadow-sm text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50 whitespace-nowrap;
}
.header-waitlist-link-box:hover {
@apply hover:bg-accent hover:text-accent-foreground;
}
.header-github-link {
@apply header-github-link-box text-sm font-medium text-muted-foreground ring-offset-background disabled:pointer-events-none disabled:opacity-50;
}
@ -544,7 +554,7 @@
@apply focus:outline-none focus:ring-1 focus:ring-primary focus:ring-offset-1;
}
.toggle-component-span {
@apply pointer-events-none relative inline-block h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out;
@apply pointer-events-none relative h-5 w-5 transform rounded-full shadow ring-0 transition duration-200 ease-in-out;
}
.toggle-component-second-span {
@apply absolute inset-0 flex h-full w-full items-center justify-center transition-opacity;
@ -590,13 +600,13 @@
@apply flex-max-width items-center text-start;
}
.chat-message-modal-text {
@apply relative inline-block w-full text-start text-sm font-normal text-muted-foreground;
@apply relative w-full text-start text-sm font-normal text-muted-foreground;
}
.chat-message-modal-icon-div {
@apply absolute -left-2 -top-1 cursor-pointer;
}
.chat-message-modal-thought {
@apply chat-message-modal-thought-cursor ml-3 inline-block h-full w-[95%] rounded-md border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground;
@apply chat-message-modal-thought-cursor ml-3 h-full w-[95%] rounded-md border border-ring bg-muted px-2 pb-3 pt-3 text-start text-muted-foreground;
}
.chat-message-modal-thought-cursor {
@apply cursor-pointer overflow-scroll scrollbar-hide;
@ -608,7 +618,7 @@
@apply mt-1 animate-pulse cursor-default;
}
.chat-message-modal-alert {
@apply inline-block px-3 text-start text-muted-foreground;
@apply px-3 text-start text-muted-foreground;
}
.file-card-modal-image-div {
@ -852,11 +862,11 @@
@apply bg-white text-primary;
}
.code-highlight {
@apply block max-h-[64vh] w-full overflow-y-hidden break-all border-0 px-3 py-2 text-sm outline-0;
@apply block max-h-[64vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
}
.code-nohighlight {
@apply block max-h-[70vh] w-full overflow-y-hidden break-all border-0 px-3 py-2 text-sm outline-0;
@apply block max-h-[70vh] w-full overflow-y-hidden word-break-break-word border-0 px-3 py-2 text-sm outline-0;
}
.form-modal-lockchat {
@apply form-input block w-full rounded-md border-border p-4 pr-16 custom-scroll focus:border-ring focus:ring-ring sm:text-sm;
@ -913,7 +923,7 @@
@apply rounded-md border border-ring/60;
}
.form-modal-chat-thought-size {
@apply inline-block h-full w-[95%];
@apply h-full w-[95%];
}
.form-modal-chat-thought {
@apply form-modal-chat-thought-border form-modal-chat-thought-size cursor-pointer overflow-scroll bg-background px-2 py-2 text-start text-primary scrollbar-hide;
@ -1019,4 +1029,12 @@
.chat-message-highlight {
@apply rounded-md bg-indigo-100 px-0.5 dark:bg-indigo-900;
}
.field-invalid{
@apply font-medium text-[0.8rem] text-destructive absolute
}
.label-invalid{
@apply text-destructive
}
}

View file

@ -1,12 +1,12 @@
export type ErrorAlertType = {
title: string;
list: Array<string>;
list: Array<string> | undefined;
id: string;
removeAlert: (id: string) => void;
};
export type NoticeAlertType = {
title: string;
link: string;
link: string | undefined;
id: string;
removeAlert: (id: string) => void;
};

View file

@ -1,6 +1,6 @@
import { Edge, Node, Viewport } from "reactflow";
//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 };
export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType };
export type APIKindType = { class: APIClassType; [key: string]: APIClassType };
export type APITemplateType = {
@ -17,7 +17,7 @@ export type APIClassType = {
beta?: boolean;
documentation: string;
error?: string;
[key: string]: Array<string> | string | APITemplateType | boolean;
[key: string]: Array<string> | string | APITemplateType | boolean | undefined;
};
export type TemplateVariableType = {
@ -37,7 +37,7 @@ export type sendAllProps = {
name: string;
description: string;
viewport: Viewport;
inputs: any;
inputs: { text?: string };
chatHistory: { message: string | object; isSend: boolean }[];
};

View file

@ -1,6 +1,8 @@
import { ReactElement, ReactNode } from "react";
import { APIClassType } from "../api";
import { NodeDataType } from "../flow/index";
import { ReactFlowJsonObject } from "reactflow";
import { APIClassType, APITemplateType, TemplateVariableType } from "../api";
import { ChatMessageType } from "../chat";
import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index";
import { typesContextType } from "../typesContext";
import { sourceHandleType, targetHandleType } from "./../flow/index";
export type InputComponentType = {
@ -8,14 +10,18 @@ export type InputComponentType = {
disabled?: boolean;
onChange: (value: string) => void;
password: boolean;
required?: boolean;
isForm?: boolean;
editNode?: boolean;
onChangePass?: (value: boolean | boolean) => void;
showPass?: boolean;
placeholder?: string;
className?: string;
};
export type ToggleComponentType = {
enabled: boolean;
setEnabled: (state: boolean) => void;
disabled: boolean;
disabled: boolean | undefined;
size: "small" | "medium" | "large";
};
export type DropDownComponentType = {
@ -33,12 +39,12 @@ export type ParameterComponentType = {
id: sourceHandleType | targetHandleType;
color: string;
left: boolean;
type: string;
type: string | undefined;
required?: boolean;
name?: string;
tooltipTitle: string;
tooltipTitle: string | undefined;
dataContext?: typesContextType;
optionalHandle?: Array<String>;
optionalHandle?: Array<String> | null;
info?: string;
};
export type InputListComponentType = {
@ -83,7 +89,7 @@ export type DisclosureComponentType = {
openDisc: boolean;
button: {
title: string;
Icon: any;
Icon: React.ElementType;
buttons?: {
Icon: ReactElement;
title: string;
@ -143,7 +149,7 @@ export type ShadTooltipProps = {
style?: string;
};
export type ShadToolTipType = {
content?: ReactNode;
content?: ReactNode | null;
side?: "top" | "right" | "bottom" | "left";
asChild?: boolean;
children?: ReactElement;
@ -165,10 +171,340 @@ export interface IVarHighlightType {
export type IconComponentProps = {
name: string;
className: string;
className?: string;
iconColor?: string;
};
export type InputProps = {
name: string | null;
description: string | null;
maxLength?: number;
flows: Array<{ id: string; name: string; description: string }>;
tabId: string;
invalidName?: boolean;
setName: (name: string) => void;
setDescription: (description: string) => void;
setInvalidName?: (invalidName: boolean) => void;
};
export type TooltipProps = {
selector: string;
content?: string;
disabled?: boolean;
htmlContent?: React.ReactNode;
className?: string; // This should use !impornant to override the default styles eg: '!bg-white'
position?: "top" | "right" | "bottom" | "left";
clickable?: boolean;
children: React.ReactNode;
delayShow?: number;
};
export type LoadingComponentProps = {
remSize: number;
};
export type ContentProps = { children: ReactNode };
export type HeaderProps = { children: ReactNode; description: string };
export interface languageMap {
[key: string]: string | undefined;
}
export type signUpInputStateType = {
password: string;
cnfPassword: string;
username: string;
};
export type inputHandlerEventType = {
target: {
value: string;
name: string;
};
};
export type PaginatorComponentType = {
pageSize: number;
pageIndex: number;
rowsCount?: number[];
totalRowsCount: number;
paginate: (pageIndex: number, pageSize: number) => void;
};
export type ConfirmationModalType = {
title: string;
titleHeader: string;
modalContent: string;
modalContentTitle: string;
cancelText: string;
confirmationText: string;
children: ReactElement;
icon: string;
data: any;
index: number;
onConfirm: (index, data) => void;
};
export type UserManagementType = {
title: string;
titleHeader: string;
cancelText: string;
confirmationText: string;
children: ReactElement;
icon: string;
data?: any;
index?: number;
onConfirm: (index, data) => void;
};
export type loginInputStateType = {
username: string;
password: string;
};
export type groupedObjType = {
family: string;
type: string;
};
type test = {
[char: string]: string;
};
export type tweakType = Array<{
[key: string]: {
[char: string]: string;
} & FlowStyleType;
}>;
export type uniqueTweakType = {
[key: string]: {
[char: string]: string;
} & FlowStyleType;
};
export type apiModalTweakType = {
current: Array<{
[key: string]: {
[char: string]: string | number;
};
}>;
};
export type nodeToolbarType = {
data: {
id: string;
type: string;
node: {
base_classes: string[];
description: string;
display_name: string;
documentation: string;
template: APITemplateType;
};
value: void;
};
deleteNode: (idx: string) => void;
openPopUp: (element: JSX.Element) => JSX.Element;
};
export type chatTriggerPropType = {
open: boolean;
isBuilt: boolean;
canOpen: boolean;
setOpen: (can: boolean) => void;
};
export type headerFlowsType = {
data: ReactFlowJsonObject | null;
description: string;
id: string;
name: string;
style?: FlowStyleType;
};
export type menuBarPropsType = {
flows: Array<headerFlowsType>;
tabId: string;
};
export type chatInputType = {
chatValue: string;
inputRef: {
current: any;
};
lockChat: boolean;
noInput: boolean;
sendMessage: () => void;
setChatValue: (value: string) => void;
};
export type editNodeToggleType = {
advanced?: boolean;
info?: string;
list: boolean;
multiline?: boolean;
name?: string;
password?: boolean;
placeholder?: string;
required: boolean;
show: boolean;
type: string;
};
export interface Props {
language: string;
value: string;
}
export type fileCardPropsType = {
fileName: string;
content: string;
fileType: string;
};
export type nodeToolbarPropsType = {
data: NodeDataType;
deleteNode: (idx: string) => void;
setData: (newState: NodeDataType) => void;
};
export type parsedDataType = {
id: string;
params: string;
progress: number;
valid: boolean;
};
export type SanitizedHTMLWrapperType = {
className: string;
content: string;
onClick: () => void;
suppressWarning?: boolean;
};
export type iconsType = {
[key: string]: React.ElementType;
};
export type modalHeaderType = {
children: ReactNode;
description: string | null;
};
export type codeAreaModalPropsType = {
setValue: (value: string) => void;
value: string;
nodeClass: APIClassType | undefined;
setNodeClass: (Class: APIClassType) => void | undefined;
children: ReactNode;
dynamic?: boolean;
};
export type chatMessagePropsType = {
chat: ChatMessageType;
lockChat: boolean;
lastMessage: boolean;
};
export type formModalPropsType = {
open: boolean;
setOpen: Function;
flow: FlowType;
};
export type genericModalPropsType = {
field_name?: string;
setValue: (value: string) => void;
value: string;
buttonText: string;
modalTitle: string;
type: number;
nodeClass?: APIClassType;
setNodeClass?: (Class: APIClassType) => void;
children: ReactNode;
};
export type buttonBoxPropsType = {
onClick: () => void;
title: string;
description: string;
icon: ReactNode;
bgColor: string;
textColor: string;
deactivate?: boolean;
size: "small" | "medium" | "big";
};
export type FlowSettingsPropsType = {
open: boolean;
setOpen: (open: boolean) => void;
};
export type groupDataType = {
[char: string]: string;
};
export type cardComponentPropsType = {
flow: FlowType;
id: string;
onDelete?: () => void;
button?: JSX.Element;
};
type tabsArrayType = {
code: string;
image: string;
language: string;
mode: string;
name: string;
description?: string;
};
type getValueNodeType = {
id: string;
node: NodeType;
type: string;
value: null;
};
type codeTabsFuncTempType = {
[key: string]: string | boolean;
};
export type codeTabsPropsType = {
flow?: FlowType;
tabs: Array<tabsArrayType>;
activeTab: string;
setActiveTab: (value: string) => void;
isMessage?: boolean;
tweaks?: {
tweak?: { current: tweakType };
tweaksList?: { current: Array<string> };
buildContent?: (value: string) => ReactNode;
getValue?: (
value: string,
node: NodeType,
template: TemplateVariableType
) => string;
buildTweakObject?: (
tw: string,
changes: string | string[] | boolean | number,
template: TemplateVariableType
) => string | void;
};
};
export type crashComponentPropsType = {
error: {
message: string;
stack: string;
};
resetErrorBoundary: (args) => void;
};
export type validationStatusType = {
id: string;
params: string;
progress: number;
valid: boolean;
};

View file

@ -0,0 +1,16 @@
export type AuthContextType = {
isAuthenticated: boolean;
accessToken: string | null;
login: (accessToken: string, refreshToken: string) => void;
logout: () => void;
refreshAccessToken: (refreshToken: string) => Promise<void>;
userData: userData | null;
setUserData: (userData: userData | null) => void;
};
export type userData = {
id: string;
name: string;
email: string;
role: string;
};

View file

@ -4,7 +4,7 @@ import { APIClassType } from "../api/index";
export type FlowType = {
name: string;
id: string;
data: ReactFlowJsonObject;
data: ReactFlowJsonObject | null;
description: string;
style?: FlowStyleType;
};
@ -14,11 +14,11 @@ export type NodeType = {
position: XYPosition;
data: NodeDataType;
};
export type NodeDataType = {
type: string;
node?: APIClassType;
id: string;
value: any;
};
// FlowStyleType is the type of the style object that is used to style the
// Flow card with an emoji and a color.

View file

@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from "react";
import { FlowType, TweaksType } from "../flow";
import { tweakType } from "../components";
import { FlowType } from "../flow";
export type TabsContextType = {
saveFlow: (flow: FlowType) => Promise<void>;
@ -8,7 +8,10 @@ export type TabsContextType = {
setTabId: (index: string) => void;
flows: Array<FlowType>;
removeFlow: (id: string) => void;
addFlow: (flowData?: FlowType, newProject?: boolean) => Promise<String>;
addFlow: (
flow?: FlowType,
newProject?: Boolean
) => Promise<String | undefined>;
updateFlow: (newFlow: FlowType) => void;
incrementNodeId: () => string;
downloadFlow: (
@ -24,15 +27,15 @@ export type TabsContextType = {
hardReset: () => void;
getNodeId: (nodeType: string) => string;
tabsState: TabsState;
setTabsState: Dispatch<SetStateAction<TabsState>>;
setTabsState: (state: TabsState) => void;
paste: (
selection: { nodes: any; edges: any },
position: { x: number; y: number; paneX?: number; paneY?: number }
) => void;
lastCopiedSelection: { nodes: any; edges: any };
lastCopiedSelection: { nodes: any; edges: any } | null;
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
setTweak: (tweak: TweaksType) => void;
getTweak: TweaksType[];
setTweak: (tweak: tweakType) => tweakType | void;
getTweak: tweakType;
};
export type TabsState = {
@ -46,3 +49,8 @@ export type TabsState = {
};
};
};
export type errorsVarType = {
title: string;
list?: Array<string>;
};

View file

@ -1,5 +1,6 @@
import { ReactFlowInstance } from "reactflow";
import { APIClassType } from "../api";
import { Edge, Node, ReactFlowInstance } from "reactflow";
import { AlertItemType } from "../alerts";
import { APIClassType, APIDataType } from "../api";
const types: { [char: string]: string } = {};
const template: { [char: string]: APIClassType } = {};
@ -7,12 +8,93 @@ const data: { [char: string]: string } = {};
export type typesContextType = {
reactFlowInstance: ReactFlowInstance | null;
setReactFlowInstance: any;
setReactFlowInstance: (newState: ReactFlowInstance) => void;
deleteNode: (idx: string) => void;
types: typeof types;
setTypes: (newState: {}) => void;
templates: typeof template;
setTemplates: (newState: {}) => void;
data: typeof data;
data: APIDataType;
setData: (newState: {}) => void;
};
export type alertContextType = {
errorData: { title: string; list?: Array<string> };
setErrorData: (newState: { title: string; list?: Array<string> }) => void;
errorOpen: boolean;
setErrorOpen: (newState: boolean) => void;
noticeData: { title: string; link?: string };
setNoticeData: (newState: { title: string; link?: string }) => void;
noticeOpen: boolean;
setNoticeOpen: (newState: boolean) => void;
successData: { title: string };
setSuccessData: (newState: { title: string }) => void;
successOpen: boolean;
setSuccessOpen: (newState: boolean) => void;
notificationCenter: boolean;
setNotificationCenter: (newState: boolean) => void;
notificationList: Array<AlertItemType>;
pushNotificationList: (Object: AlertItemType) => void;
clearNotificationList: () => void;
removeFromNotificationList: (index: string) => void;
loading: boolean;
setLoading: (newState: boolean) => void;
};
export type darkContextType = {
dark: {};
setDark: (newState: {}) => void;
};
export type locationContextType = {
current: Array<string>;
setCurrent: (newState: Array<string>) => void;
isStackedOpen: boolean;
setIsStackedOpen: (newState: boolean) => void;
showSideBar: boolean;
setShowSideBar: (newState: boolean) => void;
extraNavigation: {
title: string;
options?: Array<{
name: string;
href: string;
icon: React.ElementType;
children?: Array<JSX.Element>;
}>;
};
setExtraNavigation: (newState: {
title: string;
options?: Array<{
name: string;
href: string;
icon: React.ElementType;
children?: Array<JSX.Element>;
}>;
}) => void;
extraComponent: any;
setExtraComponent: (newState: JSX.Element) => void;
};
export type undoRedoContextType = {
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
};
export type UseUndoRedoOptions = {
maxHistorySize: number;
enableShortcuts: boolean;
};
export type UseUndoRedo = (options?: UseUndoRedoOptions) => {
undo: () => void;
redo: () => void;
takeSnapshot: () => void;
canUndo: boolean;
canRedo: boolean;
};
export type HistoryItem = {
nodes: Node[];
edges: Edge[];
};

View file

@ -1,5 +1,11 @@
import _ from "lodash";
import { Connection, Edge, ReactFlowInstance } from "reactflow";
import {
Connection,
Edge,
ReactFlowInstance,
ReactFlowJsonObject,
} from "reactflow";
import { specialCharsRegex } from "../constants/constants";
import { APITemplateType } from "../types/api";
import {
FlowType,
@ -23,7 +29,7 @@ export function cleanEdges({
const sourceNode = nodes.find((node) => node.id === edge.source);
const targetNode = nodes.find((node) => node.id === edge.target);
if (!sourceNode || !targetNode) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
newEdges = newEdges.filter((edg) => edg.id !== edge.id);
}
// check if the source and target handle still exists
if (sourceNode && targetNode) {
@ -34,10 +40,10 @@ export function cleanEdges({
scapeJSONParse(targetHandle);
const field = targetHandleObject.fieldName;
const id: targetHandleType = {
type: targetNode.data.node.template[field]?.type,
type: targetNode.data.node!.template[field]?.type,
fieldName: field,
id: targetNode.data.id,
inputTypes: targetNode.data.node.template[field]?.input_types,
inputTypes: targetNode.data.node!.template[field]?.input_types,
};
if (scapedJSONStringfy(id) !== targetHandle) {
newEdges = newEdges.filter((e) => e.id !== edge.id);
@ -46,7 +52,7 @@ export function cleanEdges({
if (sourceHandle) {
const id: sourceHandleType = {
id: sourceNode.data.id,
baseClasses: sourceNode.data.node.base_classes,
baseClasses: sourceNode.data.node!.base_classes,
dataType: sourceNode.data.type,
};
if (scapedJSONStringfy(id) !== sourceHandle) {
@ -63,8 +69,8 @@ export function isValidConnection(
{ source, target, sourceHandle, targetHandle }: Connection,
reactFlowInstance: ReactFlowInstance
) {
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle);
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
console.log(sourceHandleObject, targetHandleObject);
if (
targetHandleObject.inputTypes?.some(
@ -77,7 +83,7 @@ export function isValidConnection(
) ||
targetHandleObject.type === "str"
) {
let targetNode = reactFlowInstance?.getNode(target)?.data?.node;
let targetNode = reactFlowInstance?.getNode(target!)?.data?.node;
if (!targetNode) {
if (
!reactFlowInstance
@ -101,7 +107,7 @@ export function isValidConnection(
export function removeApiKeys(flow: FlowType): FlowType {
let cleanFLow = _.cloneDeep(flow);
cleanFLow.data.nodes.forEach((node) => {
cleanFLow.data!.nodes.forEach((node) => {
for (const key in node.data.node.template) {
if (node.data.node.template[key].password) {
node.data.node.template[key].value = "";
@ -134,53 +140,60 @@ export function updateTemplate(
return clonedObject;
}
export function updateIds(newFlow, getNodeId) {
export function updateIds(
newFlow: ReactFlowJsonObject,
getNodeId: (type: string) => string
) {
let idsMap = {};
newFlow.nodes.forEach((n: NodeType) => {
newFlow.nodes.forEach((node: NodeType) => {
// Generate a unique node ID
let newId = getNodeId(n.data.type);
idsMap[n.id] = newId;
n.id = newId;
n.data.id = newId;
let newId = getNodeId(node.data.type);
idsMap[node.id] = newId;
node.id = newId;
node.data.id = newId;
// Add the new node to the list of nodes in state
});
newFlow.edges.forEach((e: Edge) => {
e.source = idsMap[e.source];
e.target = idsMap[e.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(e.sourceHandle);
e.sourceHandle = scapedJSONStringfy({
newFlow.edges.forEach((edge: Edge) => {
edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target];
const sourceHandleObject: sourceHandleType = scapeJSONParse(
edge.sourceHandle!
);
edge.sourceHandle = scapedJSONStringfy({
...sourceHandleObject,
id: e.source,
id: edge.source,
});
const targetHandleObject: targetHandleType = scapeJSONParse(e.targetHandle);
e.targetHandle = scapedJSONStringfy({
const targetHandleObject: targetHandleType = scapeJSONParse(
edge.targetHandle!
);
edge.targetHandle = scapedJSONStringfy({
...targetHandleObject,
id: e.target,
id: edge.target,
});
e.id =
edge.id =
"reactflow__edge-" +
e.source +
e.sourceHandle +
edge.source +
edge.sourceHandle +
"-" +
e.target +
e.targetHandle;
edge.target +
edge.targetHandle;
});
}
export function buildTweaks(flow) {
return flow.data.nodes.reduce((acc, node) => {
export function buildTweaks(flow: FlowType) {
return flow.data!.nodes.reduce((acc, node) => {
acc[node.data.id] = {};
return acc;
}, {});
}
export function validateNode(
n: NodeType,
node: NodeType,
reactFlowInstance: ReactFlowInstance
): Array<string> {
if (!n.data?.node?.template || !Object.keys(n.data.node.template)) {
if (!node.data?.node?.template || !Object.keys(node.data.node.template)) {
return [
"We've noticed a potential issue with a node in the flow. Please review it and, if necessary, submit a bug report with your exported flow file. Thank you for your help!",
];
@ -189,7 +202,7 @@ export function validateNode(
const {
type,
node: { template },
} = n.data;
} = node.data;
return Object.keys(template).reduce(
(errors: Array<string>, t) =>
@ -202,10 +215,11 @@ export function validateNode(
!reactFlowInstance
.getEdges()
.some(
(e) =>
(scapeJSONParse(e.targetHandle) as targetHandleType)
(edge) =>
(scapeJSONParse(edge.targetHandle!) as targetHandleType)
.fieldName === t &&
(scapeJSONParse(e.targetHandle) as targetHandleType).id === n.id
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
node.id
)
? [
`${type} is missing ${
@ -261,25 +275,53 @@ export function updateEdgesHandleIds({
if (target && targetNode) {
let field = target.split("|")[1];
newTarget = {
type: targetNode.data.node.template[field].type,
type: targetNode.data.node!.template[field].type,
fieldName: field,
id: targetNode.data.id,
inputTypes: targetNode.data.node.template[field].input_types,
inputTypes: targetNode.data.node!.template[field].input_types,
};
}
if (source && sourceNode) {
newSource = {
dataType: sourceNode.data.type,
id: sourceNode.data.id,
baseClasses: sourceNode.data.node.base_classes,
baseClasses: sourceNode.data.node!.base_classes,
};
}
edge.sourceHandle = scapedJSONStringfy(newSource);
edge.targetHandle = scapedJSONStringfy(newTarget);
edge.sourceHandle = scapedJSONStringfy(newSource!);
edge.targetHandle = scapedJSONStringfy(newTarget!);
});
return newEdges;
}
export function handleKeyDown(
e: React.KeyboardEvent<HTMLInputElement>,
inputValue: string | string[] | null,
block: string
) {
console.log(e, inputValue, block);
//condition to fix bug control+backspace on Windows/Linux
if (
(typeof inputValue === "string" &&
(e.metaKey === true || e.ctrlKey === true) &&
e.key === "Backspace" &&
(inputValue === block ||
inputValue?.charAt(inputValue?.length - 1) === " " ||
specialCharsRegex.test(inputValue?.charAt(inputValue?.length - 1)))) ||
(navigator.userAgent.toUpperCase().includes("MAC") &&
e.ctrlKey === true &&
e.key === "Backspace")
) {
e.preventDefault();
e.stopPropagation();
}
if (e.ctrlKey === true && e.key === "Backspace" && inputValue === block) {
e.preventDefault();
e.stopPropagation();
}
}
export function getConnectedNodes(
edge: Edge,
nodes: Array<NodeType>

View file

@ -5,6 +5,8 @@ import {
ChevronDown,
ChevronLeft,
ChevronRight,
ChevronsLeft,
ChevronsRight,
ChevronsUpDown,
Circle,
Clipboard,
@ -44,6 +46,7 @@ import {
MoonIcon,
MoreHorizontal,
Paperclip,
Pencil,
Plus,
Redo,
Rocket,
@ -57,6 +60,8 @@ import {
Trash2,
Undo,
Upload,
UserMinus2,
UserPlus2,
Users2,
Variable,
Wand2,
@ -65,6 +70,7 @@ import {
XCircle,
Zap,
} from "lucide-react";
import { FaApple, FaGithub } from "react-icons/fa";
import { AirbyteIcon } from "../icons/Airbyte";
import { AnthropicIcon } from "../icons/Anthropic";
import { BingIcon } from "../icons/Bing";
@ -91,6 +97,7 @@ import SvgWikipedia from "../icons/Wikipedia/Wikipedia";
import SvgWolfram from "../icons/Wolfram/Wolfram";
import { HackerNewsIcon } from "../icons/hackerNews";
import { SupabaseIcon } from "../icons/supabase";
import { iconsType } from "../types/components";
export const gradients = [
"bg-gradient-to-br from-gray-800 via-rose-700 to-violet-900",
@ -172,7 +179,7 @@ export const nodeNames: { [char: string]: string } = {
unknown: "Other",
};
export const nodeIconsLucide = {
export const nodeIconsLucide: iconsType = {
Chroma: ChromaIcon,
AirbyteJSONLoader: AirbyteIcon,
Anthropic: AnthropicIcon,
@ -205,6 +212,7 @@ export const nodeIconsLucide = {
SupabaseVectorStore: SupabaseIcon,
VertexAI: VertexAIIcon,
ChatVertexAI: VertexAIIcon,
VertexAIEmbeddings: VertexAIIcon,
agents: Rocket,
WikipediaAPIWrapper: SvgWikipedia,
chains: Link,
@ -275,4 +283,11 @@ export const nodeIconsLucide = {
Upload,
MessageSquare,
MoreHorizontal,
UserMinus2,
UserPlus2,
Pencil,
ChevronsRight,
ChevronsLeft,
FaGithub,
FaApple,
};

View file

@ -1,20 +1,25 @@
import clsx, { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { ADJECTIVES, DESCRIPTIONS, NOUNS } from "../flow_constants";
import { IVarHighlightType } from "../types/components";
import { APIDataType, TemplateVariableType } from "../types/api";
import {
IVarHighlightType,
groupedObjType,
tweakType,
} from "../types/components";
import { FlowType, NodeType } from "../types/flow";
import { TabsState } from "../types/tabs";
import { buildTweaks } from "./reactflowUtils";
export function classNames(...classes: Array<string>) {
export function classNames(...classes: Array<string>): string {
return classes.filter(Boolean).join(" ");
}
export function cn(...inputs: ClassValue[]) {
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
export function toNormalCase(str: string) {
export function toNormalCase(str: string): string {
let result = str
.split("_")
.map((word, index) => {
@ -36,7 +41,7 @@ export function toNormalCase(str: string) {
.join(" ");
}
export function normalCaseToSnakeCase(str: string) {
export function normalCaseToSnakeCase(str: string): string {
return str
.split(" ")
.map((word, index) => {
@ -48,7 +53,7 @@ export function normalCaseToSnakeCase(str: string) {
.join("_");
}
export function toTitleCase(str: string) {
export function toTitleCase(str: string): string {
let result = str
.split("_")
.map((word, index) => {
@ -75,7 +80,7 @@ export function toTitleCase(str: string) {
}
export const upperCaseWords: string[] = ["llm", "uri"];
export function checkUpperWords(str: string) {
export function checkUpperWords(str: string): string {
const words = str.split(" ").map((word) => {
return upperCaseWords.includes(word.toLowerCase())
? word.toUpperCase()
@ -88,10 +93,23 @@ export function checkUpperWords(str: string) {
export const isWrappedWithClass = (event: any, className: string | undefined) =>
event.target.closest(`.${className}`);
export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
export function groupByFamily(
data: APIDataType,
baseClasses: string,
left: boolean,
flow?: NodeType[]
): groupedObjType[] {
const baseClassesSet = new Set(baseClasses.split("\n"));
let arrOfPossibleInputs = [];
let arrOfPossibleOutputs = [];
let arrOfPossibleInputs: Array<{
category: string;
nodes: string[];
full: boolean;
}> = [];
let arrOfPossibleOutputs: Array<{
category: string;
nodes: string[];
full: boolean;
}> = [];
let checkedNodes = new Map();
const excludeTypes = new Set([
"str",
@ -103,11 +121,14 @@ export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
"int",
]);
const checkBaseClass = (t: any) =>
t.type &&
t.show &&
((!excludeTypes.has(t.type) && baseClassesSet.has(t.type)) ||
(t.input_types && t.input_types.some((x) => baseClassesSet.has(x))));
const checkBaseClass = (template: TemplateVariableType) =>
template.type &&
template.show &&
((!excludeTypes.has(template.type) && baseClassesSet.has(template.type)) ||
(template.input_types &&
template.input_types.some((inputType) =>
baseClassesSet.has(inputType)
)));
if (flow) {
for (const node of flow) {
@ -116,27 +137,29 @@ export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
checkedNodes.set(nodeData.type, {
hasBaseClassInTemplate:
foundNode?.hasBaseClassInTemplate ||
Object.values(nodeData.node.template).some(checkBaseClass),
Object.values(nodeData.node!.template).some(checkBaseClass),
hasBaseClassInBaseClasses:
foundNode?.hasBaseClassInBaseClasses ||
nodeData.node.base_classes.some((t) => baseClassesSet.has(t)),
nodeData.node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass)
),
});
}
}
for (const [d, nodes] of Object.entries(data)) {
let tempInputs = [],
tempOutputs = [];
let tempInputs: string[] = [],
tempOutputs: string[] = [];
for (const [n, node] of Object.entries(nodes)) {
for (const [n, node] of Object.entries(nodes!)) {
let foundNode = checkedNodes.get(n);
if (!foundNode) {
foundNode = {
hasBaseClassInTemplate: Object.values(node.template).some(
hasBaseClassInTemplate: Object.values(node!.template).some(
checkBaseClass
),
hasBaseClassInBaseClasses: node.base_classes.some((t) =>
baseClassesSet.has(t)
hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass)
),
};
checkedNodes.set(n, foundNode);
@ -146,7 +169,7 @@ export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
if (foundNode.hasBaseClassInBaseClasses) tempOutputs.push(n);
}
const totalNodes = Object.keys(nodes).length;
const totalNodes = Object.keys(nodes!).length;
if (tempInputs.length)
arrOfPossibleInputs.push({
category: d,
@ -162,22 +185,22 @@ export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
}
return left
? arrOfPossibleOutputs.map((t) => ({
family: t.category,
type: t.full ? "" : t.nodes.join(", "),
? arrOfPossibleOutputs.map((output) => ({
family: output.category,
type: output.full ? "" : output.nodes.join(", "),
}))
: arrOfPossibleInputs.map((t) => ({
family: t.category,
type: t.full ? "" : t.nodes.join(", "),
: arrOfPossibleInputs.map((input) => ({
family: input.category,
type: input.full ? "" : input.nodes.join(", "),
}));
}
export function buildInputs(tabsState, id) {
export function buildInputs(tabsState: TabsState, id: string): string {
return tabsState &&
tabsState[id] &&
tabsState[id].formKeysData &&
tabsState[id].formKeysData.input_keys &&
Object.keys(tabsState[id].formKeysData.input_keys).length > 0
Object.keys(tabsState[id].formKeysData.input_keys!).length > 0
? JSON.stringify(tabsState[id].formKeysData.input_keys)
: '{"input": "message"}';
}
@ -233,7 +256,7 @@ export function varHighlightHTML({ name }: IVarHighlightType): string {
return html;
}
export function buildTweakObject(tweak) {
export function buildTweakObject(tweak: tweakType) {
tweak.forEach((el) => {
Object.keys(el).forEach((key) => {
for (let kp in el[key]) {
@ -243,11 +266,32 @@ export function buildTweakObject(tweak) {
}
});
});
const tweakString = JSON.stringify(tweak.at(-1), null, 2);
return tweakString;
}
/**
* Function to get Chat Input Field
* @param {FlowType} flow - The current flow.
* @param {TabsState} tabsState - The current tabs state.
* @returns {string} - The chat input field
*/
export function getChatInputField(flow: FlowType, tabsState?: TabsState) {
let chat_input_field = "text";
if (
tabsState &&
tabsState[flow.id] &&
tabsState[flow.id].formKeysData &&
tabsState[flow.id].formKeysData.input_keys
) {
chat_input_field = Object.keys(
tabsState[flow.id].formKeysData.input_keys!
)[0];
}
return chat_input_field;
}
/**
* Function to get the python code for the API
* @param {string} flowId - The id of the flow
@ -265,7 +309,7 @@ export function getPythonApiCode(
// node.data.id
// }
const tweaks = buildTweaks(flow);
const inputs = buildInputs(tabsState, flow.id);
const inputs = buildInputs(tabsState!, flow.id);
return `import requests
from typing import Optional
@ -317,7 +361,7 @@ export function getCurlCode(
): string {
const flowId = flow.id;
const tweaks = buildTweaks(flow);
const inputs = buildInputs(tabsState, flow.id);
const inputs = buildInputs(tabsState!, flow.id);
return `curl -X POST \\
${window.location.protocol}//${
@ -343,7 +387,7 @@ export function getPythonCode(
): string {
const flowName = flow.name;
const tweaks = buildTweaks(flow);
const inputs = buildInputs(tabsState, flow.id);
const inputs = buildInputs(tabsState!, flow.id);
return `from langflow import load_flow_from_json
TWEAKS = ${
tweak && tweak.length > 0
@ -364,7 +408,8 @@ flow(inputs)`;
export function getWidgetCode(flow: FlowType, tabsState?: TabsState): string {
const flowId = flow.id;
const flowName = flow.name;
const inputs = buildInputs(tabsState, flow.id);
const inputs = buildInputs(tabsState!, flow.id);
let chat_input_field = getChatInputField(flow, tabsState);
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@main/dist/build/static/js/bundle.min.js"></script>
@ -375,13 +420,90 @@ chat_input_field: Input key that you want the chat to send the user message with
window_title="${flowName}"
flow_id="${flowId}"
${
tabsState[flow.id] && tabsState[flow.id].formKeysData
tabsState![flow.id] && tabsState![flow.id].formKeysData
? `chat_inputs='${inputs}'
chat_input_field="${
Object.keys(tabsState[flow.id].formKeysData.input_keys)[0]
}"
chat_input_field="${chat_input_field}"
`
: ""
}host_url="http://localhost:7860"
}host_url="http://localhost:7860"
></langflow-chat>`;
}
export function tabsArray(codes: string[], method: number) {
if (!method) return;
if (method === 0) {
return [
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[0],
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: codes[1],
},
{
name: "Python Code",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[2],
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[3],
},
];
}
return [
{
name: "cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[0],
},
{
name: "Python API",
mode: "python",
image:
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
language: "py",
code: codes[1],
},
{
name: "Python Code",
mode: "python",
language: "py",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
code: codes[2],
},
{
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
mode: "html",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[3],
},
{
name: "Tweaks",
mode: "python",
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
language: "py",
code: codes[4],
},
];
}

Some files were not shown because too many files have changed in this diff Show more