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

This commit is contained in:
Gabriel Luiz Freitas Almeida 2023-08-16 19:26:06 -03:00
commit 97ad9889be
123 changed files with 6957 additions and 3229 deletions

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,6 @@ import ErrorAlert from "./alerts/error";
import NoticeAlert from "./alerts/notice";
import SuccessAlert from "./alerts/success";
import CrashErrorComponent from "./components/CrashErrorComponent";
import LoadingComponent from "./components/loadingComponent";
import { alertContext } from "./contexts/alertContext";
import { locationContext } from "./contexts/locationContext";
import { TabsContext } from "./contexts/tabsContext";
@ -48,6 +47,10 @@ export default function App() {
}>
>([]);
const isLoginPage = location.pathname.includes("login");
const isAdminPage = location.pathname.includes("admin");
const isSignUpPage = location.pathname.includes("signup");
// Use effect hook to update alertsList when a new alert is added
useEffect(() => {
// If there is an error alert open with data, add it to the alertsList
@ -135,15 +138,8 @@ export default function App() {
}}
FallbackComponent={CrashErrorComponent}
>
{loading ? (
<div className="loading-page-panel">
<LoadingComponent remSize={50} />
</div>
) : (
<>
<Router />
</>
)}
{!isLoginPage && !isSignUpPage && <Header />}
<Router />
</ErrorBoundary>
<div></div>
<div className="app-div" style={{ zIndex: 999 }}>

View file

@ -111,6 +111,7 @@ export default function ParameterComponent({
let groupedObj = groupByFamily(myData, tooltipTitle!, left, flow!);
if (groupedObj && groupedObj.length > 0) {
//@ts-ignore
//@ts-ignore
refHtml.current = groupedObj.map((item, index) => {
const Icon: any =

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 } 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,20 +159,20 @@ 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)
{Object.keys(data.node!.template)
.filter((templateField) => templateField.charAt(0) !== "_")
.map((templateField: string, idx) => (
<div key={idx}>
{data.node.template[templateField].show &&
!data.node.template[templateField].advanced ? (
{data.node!.template[templateField].show &&
!data.node!.template[templateField].advanced ? (
<ParameterComponent
key={
(data.node.template[templateField].input_types?.join(
(data.node!.template[templateField].input_types?.join(
";"
) ?? data.node.template[templateField].type) +
) ?? data.node!.template[templateField].type) +
"|" +
templateField +
"|" +
@ -178,39 +182,39 @@ export default function GenericNode({
setData={setData}
color={
nodeColors[
types[data.node.template[templateField].type]
types[data.node?.template[templateField].type!]
] ??
nodeColors[data.node.template[templateField].type] ??
nodeColors[data.node?.template[templateField].type!] ??
nodeColors.unknown
}
title={
data.node.template[templateField].display_name
data.node?.template[templateField].display_name
? data.node.template[templateField].display_name
: data.node.template[templateField].name
: data.node?.template[templateField].name
? toTitleCase(data.node.template[templateField].name)
: toTitleCase(templateField)
}
info={data.node.template[templateField].info}
info={data.node?.template[templateField].info}
name={templateField}
tooltipTitle={
data.node.template[templateField].input_types?.join(
data.node?.template[templateField].input_types?.join(
"\n"
) ?? data.node.template[templateField].type
) ?? data.node?.template[templateField].type
}
required={data.node.template[templateField].required}
required={data.node?.template[templateField].required}
id={
(data.node.template[templateField].input_types?.join(
(data.node?.template[templateField].input_types?.join(
";"
) ?? data.node.template[templateField].type) +
) ?? data.node?.template[templateField].type) +
"|" +
templateField +
"|" +
data.id
}
left={true}
type={data.node.template[templateField].type}
type={data.node?.template[templateField].type}
optionalHandle={
data.node.template[templateField].input_types
data.node?.template[templateField].input_types
}
/>
) : (
@ -220,25 +224,25 @@ 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"
)}
>
{" "}
</div>
<ParameterComponent
key={[data.type, data.id, ...data.node.base_classes].join("|")}
key={[data.type, data.id, ...data.node!.base_classes].join("|")}
data={data}
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")}
id={[data.type, data.id, ...data.node.base_classes].join("|")}
type={data.node.base_classes.join("|")}
tooltipTitle={data.node?.base_classes.join("\n")}
id={[data.type, data.id, ...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;

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,

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) {

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,8 +28,8 @@ export default function AccordionComponent({
return value;
}
function handleClick() {
value === "" ? setValue(keyValue) : setValue("");
function handleClick(): void {
value === "" ? setValue(keyValue!) : setValue("");
}
const handleKeyDown = (event) => {
@ -48,7 +48,7 @@ export default function AccordionComponent({
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,29 +34,22 @@ 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);
} else {
setInvalidName!(true);
}
setName(value);
setCurrentName(value);
};
const [currentName, setCurrentName] = useState(name);
const [currentDescription, setCurrentDescription] = useState(description);
useEffect(() => {
setCurrentName(name);
setCurrentDescription(description);
}, [name, description]);
const [desc, setDesc] = useState(
flows.find((flow) => flow.id === tabId)?.description
);
const handleDescriptionChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
flows.find((f) => f.id === tabId).description = event.target.value;
setCurrentDescription(flows.find((f) => f.id === tabId).description);
flows.find((flow) => flow.id === tabId)!.description = event.target.value;
setDesc(flows.find((flow) => flow.id === tabId)?.description);
setDescription(event.target.value);
};

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);
@ -91,7 +93,8 @@ export default function BuildTrigger({
// If the event is a log, log it
setSuccessData({ title: parsedData.log });
} else if (parsedData.input_keys !== undefined) {
setTabsState((old) => {
//@ts-ignore
setTabsState((old: TabsState) => {
return {
...old,
[flowId]: {
@ -120,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 &&

View file

@ -33,7 +33,7 @@ export default function CodeAreaComponent({
dynamic={dynamic}
value={myValue}
nodeClass={nodeClass}
setNodeClass={setNodeClass}
setNodeClass={setNodeClass!}
setValue={(value: string) => {
setMyValue(value);
onChange(value);

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);
@ -205,9 +192,9 @@ export default function CodeTabsComponent({
: "overflow-hidden"
)}
>
{data.map((node: any, index) => (
{data?.map((node: any, index) => (
<div className="px-3" key={index}>
{tweaks.tweaksList.current.includes(
{tweaks?.tweaksList!.current.includes(
node["data"]["id"]
) && (
<AccordionComponent
@ -290,14 +277,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
@ -310,7 +297,7 @@ export default function CodeTabsComponent({
templateField
].multiline ? (
<ShadTooltip
content={tweaks.buildContent(
content={tweaks.buildContent!(
node.data.node.template[
templateField
].value
@ -339,14 +326,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node
@ -384,14 +371,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
@ -417,14 +404,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = e;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
e,
node.data.node.template[
@ -440,7 +427,7 @@ export default function CodeTabsComponent({
templateField
].type === "file" ? (
<ShadTooltip
content={tweaks.buildContent(
content={tweaks.buildContent!(
!node.data.node.template[
templateField
].value ||
@ -508,14 +495,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
@ -544,14 +531,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
@ -596,14 +583,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
@ -617,7 +604,7 @@ export default function CodeTabsComponent({
templateField
].type === "prompt" ? (
<ShadTooltip
content={tweaks.buildContent(
content={tweaks.buildContent!(
!node.data.node.template[
templateField
].value ||
@ -651,14 +638,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
@ -673,8 +660,8 @@ export default function CodeTabsComponent({
templateField
].type === "code" ? (
<ShadTooltip
content={tweaks.buildContent(
tweaks.getValue(
content={tweaks.buildContent!(
tweaks.getValue!(
node.data.node.template[
templateField
].value,
@ -706,14 +693,14 @@ export default function CodeTabsComponent({
setData((old) => {
let newInputList =
cloneDeep(old);
newInputList[
newInputList![
index
].data.node.template[
templateField
].value = target;
return newInputList;
});
tweaks.buildTweakObject(
tweaks.buildTweakObject!(
node["data"]["id"],
target,
node.data.node.template[
@ -742,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

@ -8,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;

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);
@ -31,24 +31,12 @@ export default function Header() {
return (
<div className="header-arrangement">
<div className="header-start-display">
{tabId === "" || !tabId ? (
<div className="ml-2">
<a
href="https://www.langflow.org/"
target="_blank"
rel="noreferrer"
className="header-waitlist-link-box"
>
<span className="pr-1 text-2xl">⛓️</span>
<span>Join The Waitlist</span>
</a>
</div>
) : (
<Link to="/">
<span className="ml-4 text-2xl">⛓️</span>
</Link>
)}
<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,6 +1,6 @@
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";
@ -8,9 +8,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
@ -22,23 +26,47 @@ 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={(event) => {
onChange(event.target.value);
}}
onKeyDown={(e) => {
handleKeyDown(e, 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);
}}
/>
</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);
}}
/>
)}
{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,7 +49,7 @@ export default function InputFileComponent({
input.style.display = "none"; // Hidden from view
input.multiple = false; // Allow only one file selection
input.onchange = (event: Event) => {
input.onchange = (event: Event): void => {
setLoading(true);
// Get the selected file

View file

@ -11,7 +11,7 @@ export default function InputListComponent({
onChange,
disabled,
editNode = false,
}: InputListComponentType) {
}: InputListComponentType): JSX.Element {
useEffect(() => {
if (disabled) {
onChange([""]);

View file

@ -8,7 +8,7 @@ export default function IntComponent({
onChange,
disabled,
editNode = false,
}: FloatComponentType) {
}: FloatComponentType): JSX.Element {
const min = 0;
// Clear component state

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.
}
});

View file

@ -10,7 +10,7 @@ export default function TextAreaComponent({
onChange,
disabled,
editNode = false,
}: TextAreaComponentType) {
}: TextAreaComponentType): JSX.Element {
// Clear text area
useEffect(() => {
if (disabled) {

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) {

View file

@ -6,7 +6,7 @@ export default function ToggleShadComponent({
setEnabled,
disabled,
size,
}: ToggleComponentType) {
}: ToggleComponentType): JSX.Element {
let scaleX, scaleY;
switch (size) {
case "small":

View file

@ -23,14 +23,14 @@ export default function RenameLabel(props) {
});
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;

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

@ -509,5 +509,93 @@ export const URL_EXCLUDED_FROM_ERROR_RETRIES = [
"/api/v1/custom_component",
"/api/v1/validate/prompt",
];
export const CONTROL_INPUT_STATE = {
password: "",
cnfPassword: "",
username: "",
};
export const skipNodeUpdate = ["CustomComponent"];
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,32 +1,9 @@
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;
loading: boolean;
setLoading: (newState: boolean) => void;
};
//initial values to alertContextType
const initialValue: alertContextType = {
errorData: { title: "", list: [] },
@ -70,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);

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

@ -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,7 +7,7 @@ import {
useRef,
useState,
} from "react";
import { addEdge } from "reactflow";
import { Edge, Node, ReactFlowJsonObject, addEdge } from "reactflow";
import ShortUniqueId from "short-unique-id";
import { skipNodeUpdate } from "../constants/constants";
import {
@ -19,8 +19,9 @@ import {
uploadFlowsToDatabase,
} from "../controllers/API";
import { APIClassType, APITemplateType } from "../types/api";
import { FlowType, NodeType } from "../types/flow";
import { TabsContextType, TabsState } from "../types/tabs";
import { tweakType } from "../types/components";
import { FlowType, NodeDataType, NodeType } from "../types/flow";
import { TabsContextType, TabsState, errorsVarType } from "../types/tabs";
import {
addVersionToDuplicates,
updateIds,
@ -74,9 +75,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() {
@ -131,8 +135,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;
@ -145,7 +149,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
function processFlowEdges(flow) {
function processFlowEdges(flow: FlowType) {
if (!flow.data || !flow.data.edges) return;
flow.data.edges.forEach((edge) => {
edge.className = "";
@ -154,14 +158,14 @@ 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;
@ -182,7 +186,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(
@ -190,10 +194,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) {
edge.sourceHandle = edge.sourceHandle
.split("|")
?.split("|")
.slice(0, 2)
.concat(template["base_classes"])
.join("|");
@ -202,17 +206,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);
}
@ -243,7 +247,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
@ -295,10 +299,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
@ -321,10 +325,10 @@ export function TabsProvider({ children }: { children: ReactNode }) {
input.onchange = (event: Event) => {
// check if the file type is application/json
if (
(event.target as HTMLInputElement).files[0].type === "application/json"
(event.target as HTMLInputElement).files![0].type === "application/json"
) {
// get the file from the file input
const file = (event.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);
@ -355,15 +359,15 @@ 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((node) => {
let nodes: Node<NodeDataType>[] = reactFlowInstance!.getNodes();
let edges = reactFlowInstance!.getEdges();
selectionInstance.nodes.forEach((node: Node) => {
if (node.position.y < minimumY) {
minimumY = node.position.y;
}
@ -373,8 +377,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
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((node: NodeType) => {
// Generate a unique node ID
@ -386,8 +390,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
id: newId,
type: "genericNode",
position: {
x: insidePosition.x + node.position.x - minimumX,
y: insidePosition.y + node.position.y - minimumY,
x: insidePosition.x + node.position!.x - minimumX,
y: insidePosition.y + node.position!.y - minimumY,
},
data: {
..._.cloneDeep(node.data),
@ -400,19 +404,19 @@ export function TabsProvider({ children }: { children: ReactNode }) {
.map((node) => ({ ...node, selected: false }))
.concat({ ...newNode, selected: false });
});
reactFlowInstance.setNodes(nodes);
reactFlowInstance!.setNodes(nodes);
selectionInstance.edges.forEach((edge) => {
let source = idsMap[edge.source];
let target = idsMap[edge.target];
let sourceHandleSplitted = edge.sourceHandle.split("|");
let sourceHandleSplitted = edge.sourceHandle!.split("|");
let sourceHandle =
sourceHandleSplitted[0] +
"|" +
source +
"|" +
sourceHandleSplitted.slice(2).join("|");
let targetHandleSplitted = edge.targetHandle.split("|");
let targetHandleSplitted = edge.targetHandle!.split("|");
let targetHandle =
targetHandleSplitted.slice(0, -1).join("|") + "|" + target;
let id =
@ -440,21 +444,21 @@ export function TabsProvider({ children }: { children: ReactNode }) {
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);
@ -479,13 +483,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 : "";
@ -498,17 +502,17 @@ export function TabsProvider({ children }: { children: ReactNode }) {
return { data, description };
};
const updateEdges = (edges) => {
const updateEdges = (edges: Edge[]) => {
edges.forEach((edge) => {
edge.className =
(edge.targetHandle.split("|")[0] === "Text"
(edge.targetHandle!.split("|")[0] === "Text"
? "stroke-gray-800 "
: "stroke-gray-900 ") + " stroke-connection";
edge.animated = edge.targetHandle.split("|")[0] === "Text";
edge.animated = edge.targetHandle!.split("|")[0] === "Text";
});
};
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];
@ -520,8 +524,8 @@ export function TabsProvider({ children }: { children: ReactNode }) {
node.data.node.base_classes = template["base_classes"];
edges.forEach((edge) => {
if (edge.source === node.id) {
edge.sourceHandle = edge.sourceHandle
.split("|")
edge.sourceHandle = edge
.sourceHandle!.split("|")
.slice(0, 2)
.concat(template["base_classes"])
.join("|");
@ -536,14 +540,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];
});
@ -594,7 +601,7 @@ export function TabsProvider({ children }: { children: ReactNode }) {
});
}
} catch (err) {
setErrorData(err);
setErrorData(err as errorsVarType);
}
}

View file

@ -1,11 +1,5 @@
import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import { Node } from "reactflow";
import { createContext, ReactNode, useEffect, useState } from "react";
import { Node, ReactFlowInstance } from "reactflow";
import { getAll } from "../controllers/API";
import { APIKindType } from "../types/api";
import { typesContextType } from "../types/typesContext";
@ -15,7 +9,7 @@ import { alertContext } from "./alertContext";
const initialValue: typesContextType = {
reactFlowInstance: null,
setReactFlowInstance: () => {},
setReactFlowInstance: (newState: ReactFlowInstance) => {},
deleteNode: () => {},
types: {},
setTypes: () => {},
@ -29,14 +23,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
@ -78,7 +73,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.");
}
@ -86,21 +81,20 @@ 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((node: Node) => node.id !== idx)
reactFlowInstance!.setNodes(
reactFlowInstance!.getNodes().filter((node: Node) => node.id !== idx)
);
reactFlowInstance.setEdges(
reactFlowInstance
reactFlowInstance!.setEdges(
reactFlowInstance!
.getEdges()
.filter((edge) => edge.source !== idx && edge.target !== idx)
);

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: () => {},

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://docs.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,87 +79,16 @@ 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://docs.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://docs.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((node) => {
flow["data"]!["nodes"].forEach((node) => {
Object.keys(node["data"]["node"]["template"])
.filter(
(templateField) =>
@ -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";
@ -53,19 +54,22 @@ const EditNodeModal = forwardRef(
?.getEdges()
.some((edge) => edge.targetHandle === data.id) ?? false;
function changeAdvanced(templateParam) {
function changeAdvanced(templateParam: string): void {
setMyData((old) => {
let newData = cloneDeep(old);
newData.node.template[templateParam].advanced =
!newData.node.template[templateParam].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;
});
};
@ -77,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>
@ -112,11 +116,11 @@ const EditNodeModal = forwardRef(
</TableRow>
</TableHeader>
<TableBody className="p-0">
{Object.keys(myData.node.template)
{Object.keys(myData.node!.template)
.filter(
(templateParam) =>
templateParam.charAt(0) !== "_" &&
myData.node.template[templateParam].show &&
myData.node?.template[templateParam].show &&
(myData.node.template[templateParam].type ===
"str" ||
myData.node.template[templateParam].type ===
@ -135,13 +139,13 @@ const EditNodeModal = forwardRef(
.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[templateParam].name
{myData.node?.template[templateParam].name
? myData.node.template[templateParam].name
: myData.node.template[templateParam]
: myData.node?.template[templateParam]
.display_name}
</TableCell>
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
{myData.node.template[templateParam].type ===
{myData.node?.template[templateParam].type ===
"str" &&
!myData.node.template[templateParam].options ? (
<div className="mx-auto">
@ -171,7 +175,7 @@ const EditNodeModal = forwardRef(
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value: string) => {
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
/>
@ -193,7 +197,7 @@ const EditNodeModal = forwardRef(
/>
)}
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"bool" ? (
<div className="ml-auto">
{" "}
@ -211,7 +215,7 @@ const EditNodeModal = forwardRef(
size="small"
/>
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"float" ? (
<div className="mx-auto">
<FloatComponent
@ -226,7 +230,7 @@ const EditNodeModal = forwardRef(
}}
/>
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"str" &&
myData.node.template[templateParam].options ? (
<div className="mx-auto">
@ -246,7 +250,7 @@ const EditNodeModal = forwardRef(
}
></Dropdown>
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"int" ? (
<div className="mx-auto">
<IntComponent
@ -261,7 +265,7 @@ const EditNodeModal = forwardRef(
}}
/>
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"file" ? (
<div className="mx-auto">
<InputFileComponent
@ -271,7 +275,7 @@ const EditNodeModal = forwardRef(
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value: string) => {
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
fileTypes={
@ -283,13 +287,13 @@ const EditNodeModal = forwardRef(
.suffixes
}
onFileChange={(filePath: string) => {
data.node.template[
data.node!.template[
templateParam
].file_path = filePath;
}}
></InputFileComponent>
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"prompt" ? (
<div className="mx-auto">
<PromptAreaComponent
@ -304,17 +308,17 @@ const EditNodeModal = forwardRef(
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value: string) => {
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
/>
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"code" ? (
<div className="mx-auto">
<CodeAreaComponent
dynamic={
data.node.template[templateParam]
data.node!.template[templateParam]
.dynamic ?? false
}
setNodeClass={(nodeClass) => {
@ -327,12 +331,12 @@ const EditNodeModal = forwardRef(
myData.node.template[templateParam]
.value ?? ""
}
onChange={(value: string) => {
onChange={(value: string | string[]) => {
handleOnNewValue(value, templateParam);
}}
/>
</div>
) : myData.node.template[templateParam].type ===
) : myData.node?.template[templateParam].type ===
"Any" ? (
"-"
) : (
@ -343,7 +347,7 @@ const EditNodeModal = forwardRef(
<div className="items-center text-center">
<ToggleShadComponent
enabled={
!myData.node.template[templateParam]
!myData.node?.template[templateParam]
.advanced
}
setEnabled={(e) =>
@ -369,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,180 +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,
id,
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,151 +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}
id={
data.node.template[t].type +
"|" +
t +
"|" +
data.id
}
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) {

View file

@ -1,4 +1,4 @@
import { ReactNode, forwardRef, useContext, useEffect, useState } from "react";
import { ReactNode, forwardRef, useContext, useState } from "react";
import EditFlowSettings from "../../components/EditFlowSettingsComponent";
import IconComponent from "../../components/genericIconComponent";
import { Button } from "../../components/ui/button";
@ -8,78 +8,78 @@ 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 } = 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);
const [invalidName, setInvalidName] = 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);
}}
const ExportModal = forwardRef(
(props: { children: ReactNode }, ref): JSX.Element => {
const { flows, tabId, updateFlow, downloadFlow, saveFlow } =
useContext(TabsContext);
const [checked, setChecked] = useState(false);
const [name, setName] = useState(
flows.find((flow) => flow.id === tabId)?.name
);
const [invalidName, setInvalidName] = useState(false);
const [description, setDescription] = useState(
flows.find((flow) => flow.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"
/>
<label htmlFor="terms" className="export-modal-save-api text-sm ">
Save with my API keys
</label>
</div>
</BaseModal.Content>
</BaseModal.Header>
<BaseModal.Content>
<EditFlowSettings
invalidName={invalidName}
setInvalidName={setInvalidName}
name={name!}
description={description!}
flows={flows}
tabId={tabId}
setName={setName}
setDescription={setDescription}
/>
<div className="mt-3 flex items-center space-x-2">
<Checkbox
id="terms"
onCheckedChange={(event: boolean): void => {
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>
);
});
<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,34 @@
import { useContext, useEffect, useState } from "react";
import { useContext, 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 { setSuccessData } = useContext(alertContext);
}: FlowSettingsPropsType): JSX.Element {
const { setErrorData, setSuccessData } = useContext(alertContext);
const ref = useRef();
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 maxLength = 50;
const [name, setName] = useState(
flows.find((flow) => flow.id === tabId)!.name
);
const [description, setDescription] = useState(
flows.find((flow) => flow.id === tabId)!.description
);
const [invalidName, setInvalidName] = useState(false);
function handleClick() {
function handleClick(): void {
let savedFlow = flows.find((flow) => flow.id === tabId);
savedFlow.name = name;
savedFlow.description = description;
saveFlow(savedFlow);
savedFlow!.name = name;
savedFlow!.description = description;
saveFlow(savedFlow!);
setSuccessData({ title: "Changes saved successfully" });
setOpen(false);
}
@ -49,7 +48,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,7 +47,7 @@ export default function ChatInput({
}`,
}}
value={lockChat ? "Thinking..." : chatValue}
onChange={(event) => {
onChange={(event): void => {
setChatValue(event.target.value);
}}
className={classNames(
@ -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
@ -192,7 +189,7 @@ export default function ChatMessage({
? 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(
(key) => !handleKeys.some((j) => j === key) && inputKeys[key] === ""
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,16 +64,17 @@ 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(() => {
if (tabsState[flow.id]?.formKeysData?.input_keys) {
return Object.keys(tabsState[flow.id].formKeysData.input_keys).find(
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] === ""
!tabsState[flow.id].formKeysData.handle_keys!.some(
(j) => j === key
) && tabsState[flow.id].formKeysData.input_keys![key] === ""
);
}
// TODO: return a sensible default
@ -149,7 +151,7 @@ export default function FormModal({
});
}
function handleOnClose(event: CloseEvent) {
function handleOnClose(event: CloseEvent): void {
if (isOpen.current) {
setErrorData({ title: event.reason });
setTimeout(() => {
@ -159,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}`;
@ -211,7 +217,7 @@ export default function FormModal({
});
}
if (data.type === "start") {
addChatHistory("", false, chatKey);
addChatHistory("", false, chatKey!);
isStream = true;
}
if (data.type === "end") {
@ -240,7 +246,7 @@ export default function FormModal({
}
}
function connectWS() {
function connectWS(): void {
try {
const urlWs = getWebSocketUrl(
id.current,
@ -307,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();
@ -326,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) {
@ -334,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 {
@ -367,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("");
}
}
@ -424,7 +431,7 @@ export default function FormModal({
{tabsState[id.current]?.formKeysData?.input_keys
? Object.keys(
tabsState[id.current].formKeysData.input_keys
tabsState[id.current].formKeysData.input_keys!
).map((key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
@ -448,7 +455,7 @@ export default function FormModal({
size="small"
disabled={tabsState[
id.current
].formKeysData.handle_keys.some(
].formKeysData.handle_keys!.some(
(t) => t === key
)}
/>
@ -459,7 +466,7 @@ export default function FormModal({
keyValue={key}
>
<div className="file-component-tab-column">
{tabsState[id.current].formKeysData.handle_keys.some(
{tabsState[id.current].formKeysData.handle_keys!.some(
(t) => t === key
) && (
<div className="font-normal text-muted-foreground ">
@ -469,14 +476,18 @@ export default function FormModal({
<Textarea
className="custom-scroll"
value={
tabsState[id.current].formKeysData.input_keys[key]
tabsState[id.current].formKeysData.input_keys![
key
]
}
onChange={(e) => {
setTabsState((old) => {
//@ts-ignore
setTabsState((old: TabsState) => {
let newTabsState = _.cloneDeep(old);
newTabsState[
id.current
].formKeysData.input_keys[key] = e.target.value;
].formKeysData.input_keys![key] =
e.target.value;
return newTabsState;
});
}}
@ -488,7 +499,7 @@ export default function FormModal({
</div>
))
: null}
{tabsState[id.current].formKeysData.memory_keys.map(
{tabsState[id.current].formKeysData.memory_keys!.map(
(key, index) => (
<div className="file-component-accordion-div" key={index}>
<AccordionComponent
@ -582,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,8 +15,7 @@ import {
import { TypeModal } from "../../constants/enums";
import { alertContext } from "../../contexts/alertContext";
import { postValidatePrompt } from "../../controllers/API";
import { APIClassType } from "../../types/api";
import { handleKeyDown } from "../../utils/reactflowUtils";
import { genericModalPropsType } from "../../types/components";
import {
classNames,
getRandomKeyByssmm,
@ -34,39 +33,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;
@ -107,7 +96,7 @@ export default function GenericModal({
.replace(regexHighlight, varHighlightHTML({ name: "$1" }))
.replace(/\n/g, "<br />");
const TextAreaContentView = () => {
const TextAreaContentView = (): JSX.Element => {
return (
<SanitizedHTMLWrapper
className={getClassByNumberLength()}
@ -120,7 +109,7 @@ export default function GenericModal({
);
};
function getClassByNumberLength() {
function getClassByNumberLength(): string {
let sumOfCaracteres: number = 0;
wordsHighlight.forEach((element) => {
sumOfCaracteres = sumOfCaracteres + element.replace(/[{}]/g, "").length;
@ -130,8 +119,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 ?? [];
@ -145,7 +134,7 @@ export default function GenericModal({
setSuccessData({
title: "Prompt is ready",
});
setNodeClass(apiReturn.data?.frontend_node);
setNodeClass!(apiReturn.data?.frontend_node);
setModalOpen(closeModal);
setValue(inputValue);
}
@ -222,6 +211,7 @@ export default function GenericModal({
<TextAreaContentView />
) : type !== TypeModal.PROMPT ? (
<Textarea
//@ts-ignore
ref={ref}
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
value={inputValue}

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;

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

@ -9,7 +9,7 @@ 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);
@ -22,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) => {

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 }) => (

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 } from "../../../../types/flow";
import { TabsState } from "../../../../types/tabs";
import { isValidConnection } from "../../../../utils/reactflowUtils";
import { isWrappedWithClass } from "../../../../utils/utils";
import ConnectionLineComponent from "../ConnectionLineComponent";
@ -35,13 +44,7 @@ const nodeTypes = {
genericNode: GenericNode,
};
export default function Page({
flow,
view,
}: {
flow: FlowType;
view?: boolean;
}) {
export default function Page({ flow }: { flow: FlowType }): JSX.Element {
let {
updateFlow,
uploadFlow,
@ -57,13 +60,13 @@ export default function Page({
} = 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
@ -84,10 +87,10 @@ export default function Page({
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 (
@ -153,7 +156,8 @@ export default function Page({
let newX = _.cloneDeep(node);
return newX;
});
setTabsState((prev) => {
//@ts-ignore
setTabsState((prev: TabsState) => {
return {
...prev,
[tabId]: {
@ -169,7 +173,8 @@ export default function Page({
const onNodesChangeMod = useCallback(
(change: NodeChange[]) => {
onNodesChange(change);
setTabsState((prev) => {
//@ts-ignore
setTabsState((prev: TabsState) => {
return {
...prev,
[tabId]: {
@ -191,10 +196,10 @@ export default function Page({
...params,
style: { stroke: "#555" },
className:
(params.targetHandle.split("|")[0] === "Text"
(params.targetHandle?.split("|")[0] === "Text"
? "stroke-foreground "
: "stroke-foreground ") + " stroke-connection",
animated: params.targetHandle.split("|")[0] === "Text",
animated: params.targetHandle?.split("|")[0] === "Text",
},
eds
)
@ -240,7 +245,7 @@ export default function Page({
// 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 +254,9 @@ export default function Page({
// 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 +273,6 @@ export default function Page({
data: {
...data,
id: newId,
value: null,
},
};
} else {
@ -280,7 +284,6 @@ export default function Page({
data: {
...data,
id: newId,
value: null,
},
};
@ -289,7 +292,7 @@ export default function Page({
setNodes((nds) => nds.concat(newNode));
} 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,7 +308,7 @@ export default function Page({
}, []);
const onDelete = useCallback(
(mynodes) => {
(mynodes: Node[]) => {
takeSnapshot();
setEdges(
edges.filter(
@ -325,7 +328,7 @@ export default function Page({
const onEdgeUpdate = useCallback(
(oldEdge: Edge, newConnection: Connection) => {
if (isValidConnection(newConnection, reactFlowInstance)) {
if (isValidConnection(newConnection, reactFlowInstance!)) {
edgeUpdateSuccessful.current = true;
setEdges((els) => updateEdge(oldEdge, newConnection, els));
}
@ -333,11 +336,10 @@ export default function Page({
[reactFlowInstance, setEdges]
);
const onEdgeUpdateEnd = useCallback((_, edge) => {
const onEdgeUpdateEnd = useCallback((_, edge: Edge): void => {
if (!edgeUpdateSuccessful.current) {
setEdges((eds) => eds.filter((edg) => edg.id !== edge.id));
}
edgeUpdateSuccessful.current = true;
}, []);
@ -346,7 +348,7 @@ export default function Page({
const onSelectionEnd = useCallback(() => {
setSelectionEnded(true);
}, []);
const onSelectionStart = useCallback((event) => {
const onSelectionStart = useCallback((event: MouseEvent) => {
event.preventDefault();
setSelectionEnded(false);
}, []);
@ -360,9 +362,12 @@ export default function Page({
}
}, [selectionEnded, lastSelection]);
const onSelectionChange = useCallback((flow) => {
setLastSelection(flow);
}, []);
const onSelectionChange = useCallback(
(flow: OnSelectionChangeParams): void => {
setLastSelection(flow);
},
[]
);
return (
<div className="flex h-full overflow-hidden">
@ -389,7 +394,6 @@ export default function Page({
onEdgesChange={onEdgesChangeMod}
onConnect={onConnect}
disableKeyboardA11y={true}
onLoad={setReactFlowInstance}
onInit={setReactFlowInstance}
nodeTypes={nodeTypes}
onEdgeUpdate={onEdgeUpdate}
@ -420,9 +424,7 @@ export default function Page({
></Controls>
)}
</ReactFlow>
{!view && (
<Chat flow={flow} reactFlowInstance={reactFlowInstance} />
)}
<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";
@ -59,7 +59,7 @@ export default function ExtraSidebar() {
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" });
}}
>

View file

@ -4,14 +4,19 @@ 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(
Object.keys(data.node!.template).filter(
(templateField) =>
templateField.charAt(0) !== "_" &&
data.node.template[templateField].show &&
data.node?.template[templateField].show &&
(data.node.template[templateField].type === "str" ||
data.node.template[templateField].type === "bool" ||
data.node.template[templateField].type === "float" ||
@ -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,17 +1,16 @@
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
@ -23,23 +22,20 @@ export default function FlowPage() {
}, []);
return (
<>
<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>
</>
<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

@ -1,12 +1,11 @@
import { useContext, useEffect } from "react";
import { Link, useNavigate } from "react-router-dom";
import { CardComponent } from "../../components/cardComponent";
import { useNavigate } from "react-router-dom";
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);
@ -61,32 +60,36 @@ export default function HomePage() {
<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 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>
</>

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,8 +1,11 @@
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 (
@ -14,6 +17,16 @@ const Router = () => {
<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

@ -1029,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,20 +1,27 @@
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";
export type InputComponentType = {
value: string;
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 = {
@ -32,12 +39,12 @@ export type ParameterComponentType = {
id: string;
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 = {
@ -82,7 +89,7 @@ export type DisclosureComponentType = {
openDisc: boolean;
button: {
title: string;
Icon: any;
Icon: React.ElementType;
buttons?: {
Icon: ReactElement;
title: string;
@ -142,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;
@ -164,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,91 @@ 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;
};
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,6 +1,10 @@
import _ from "lodash";
import { Connection, Edge, ReactFlowInstance } from "reactflow";
import { specialCharsRegex } from "../constants/constants";
import {
Connection,
Edge,
ReactFlowInstance,
ReactFlowJsonObject,
} from "reactflow";
import { APITemplateType } from "../types/api";
import { FlowType, NodeType } from "../types/flow";
import { cleanEdgesType } from "../types/utils/reactflowUtils";
@ -25,8 +29,8 @@ export function cleanEdges({
if (targetHandle) {
const field = targetHandle.split("|")[1];
const id =
(targetNode.data.node.template[field]?.input_types?.join(";") ??
targetNode.data.node.template[field]?.type) +
(targetNode.data.node?.template[field]?.input_types?.join(";") ??
targetNode.data.node?.template[field]?.type) +
"|" +
field +
"|" +
@ -39,7 +43,7 @@ export function cleanEdges({
const id = [
sourceNode.data.type,
sourceNode.data.id,
...sourceNode.data.node.base_classes,
...sourceNode.data.node?.base_classes!,
].join("|");
if (id !== sourceHandle) {
newEdges = newEdges.filter((edg) => edg.id !== edge.id);
@ -56,21 +60,21 @@ export function isValidConnection(
) {
if (
targetHandle
.split("|")[0]
?.split("|")[0]
.split(";")
.some((target) => target === sourceHandle.split("|")[0]) ||
.some((target) => target === sourceHandle?.split("|")[0]) ||
sourceHandle
.split("|")
?.split("|")
.slice(2)
.some((target) =>
targetHandle
.split("|")[0]
?.split("|")[0]
.split(";")
.some((n) => n === target)
) ||
targetHandle.split("|")[0] === "str"
targetHandle?.split("|")[0] === "str"
) {
let targetNode = reactFlowInstance?.getNode(target)?.data?.node;
let targetNode = reactFlowInstance?.getNode(target!)?.data?.node;
if (!targetNode) {
if (
!reactFlowInstance
@ -80,11 +84,11 @@ export function isValidConnection(
return true;
}
} else if (
(!targetNode.template[targetHandle.split("|")[1]].list &&
(!targetNode.template[targetHandle?.split("|")[1]!].list &&
!reactFlowInstance
.getEdges()
.find((e) => e.targetHandle === targetHandle)) ||
targetNode.template[targetHandle.split("|")[1]].list
targetNode.template[targetHandle?.split("|")[1]!].list
) {
return true;
}
@ -94,7 +98,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 = "";
@ -127,7 +131,10 @@ export function updateTemplate(
return clonedObject;
}
export function updateIds(newFlow, getNodeId) {
export function updateIds(
newFlow: ReactFlowJsonObject,
getNodeId: (type: string) => string
) {
let idsMap = {};
newFlow.nodes.forEach((node: NodeType) => {
@ -142,14 +149,14 @@ export function updateIds(newFlow, getNodeId) {
newFlow.edges.forEach((edge) => {
edge.source = idsMap[edge.source];
edge.target = idsMap[edge.target];
let sourceHandleSplitted = edge.sourceHandle.split("|");
let sourceHandleSplitted = edge.sourceHandle!.split("|");
edge.sourceHandle =
sourceHandleSplitted[0] +
"|" +
edge.source +
"|" +
sourceHandleSplitted.slice(2).join("|");
let targetHandleSplitted = edge.targetHandle.split("|");
let targetHandleSplitted = edge.targetHandle!.split("|");
edge.targetHandle =
targetHandleSplitted.slice(0, -1).join("|") + "|" + edge.target;
edge.id =
@ -162,8 +169,8 @@ export function updateIds(newFlow, getNodeId) {
});
}
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;
}, {});
@ -196,7 +203,7 @@ export function validateNode(
.getEdges()
.some(
(edge) =>
edge.targetHandle.split("|")[1] === t &&
edge.targetHandle?.split("|")[1] === t &&
edge.targetHandle.split("|")[2] === node.id
)
? [

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,7 +121,7 @@ export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
"int",
]);
const checkBaseClass = (template: any) =>
const checkBaseClass = (template: TemplateVariableType) =>
template.type &&
template.show &&
((!excludeTypes.has(template.type) && baseClassesSet.has(template.type)) ||
@ -119,10 +137,10 @@ 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((baseClass) =>
nodeData.node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass)
),
});
@ -130,17 +148,17 @@ export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
}
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((baseClass) =>
hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) =>
baseClassesSet.has(baseClass)
),
};
@ -151,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,
@ -177,12 +195,12 @@ export function groupByFamily(data, baseClasses, left, flow?: NodeType[]) {
}));
}
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"}';
}
@ -238,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]) {
@ -248,7 +266,6 @@ export function buildTweakObject(tweak) {
}
});
});
const tweakString = JSON.stringify(tweak.at(-1), null, 2);
return tweakString;
}
@ -263,12 +280,13 @@ 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
tabsState[flow.id].formKeysData.input_keys!
)[0];
}
return chat_input_field;
@ -291,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
@ -343,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}//${
@ -369,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
@ -390,7 +408,7 @@ 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>
@ -402,7 +420,7 @@ 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="${chat_input_field}"
`
@ -410,3 +428,82 @@ chat_input_field: Input key that you want the chat to send the user message with
}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],
},
];
}

View file

@ -6,7 +6,7 @@
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": false,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",

View file

@ -6,6 +6,9 @@ const apiRoutes = ["^/api/v1/", "/health"];
// Use environment variable to determine the target.
const target = process.env.VITE_PROXY_TARGET || "http://127.0.0.1:7860";
// Use environment variable to determine the UI server port
const port = process.env.VITE_PORT || 3000;
const proxyTargets = apiRoutes.reduce((proxyObj, route) => {
proxyObj[route] = {
target: target,
@ -22,7 +25,7 @@ export default defineConfig(() => {
},
plugins: [react(), svgr()],
server: {
port: 3000,
port: port,
proxy: {
...proxyTargets,
},