Merge branch 'dev' into python_custom_node_component
This commit is contained in:
commit
02ca9739a0
28 changed files with 528 additions and 458 deletions
|
|
@ -1,14 +1,17 @@
|
|||
[tool.poetry]
|
||||
name = "langflow"
|
||||
version = "0.3.2"
|
||||
version = "0.3.3"
|
||||
description = "A Python package with a built-in web application"
|
||||
authors = ["Logspace <contact@logspace.ai>"]
|
||||
maintainers = [
|
||||
"Carlos Coelho <carlos@logspace.ai>",
|
||||
"Cristhian Zanforlin <cristhian.lousa@gmail.com>",
|
||||
"Gabriel Almeida <gabriel@logspace.ai>",
|
||||
"Gustavo Schaedler <gustavopoa@gmail.com>",
|
||||
"Igor Carvalho <igorr.ackerman@gmail.com>",
|
||||
"Lucas Eduoli <lucaseduoli@gmail.com>",
|
||||
"Otávio Anovazzi <otavio2204@gmail.com>",
|
||||
"Rodrigo Nader <rodrigo@logspace.ai>",
|
||||
]
|
||||
repository = "https://github.com/logspace-ai/langflow"
|
||||
license = "MIT"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ FORCE_SHOW_FIELDS = [
|
|||
"headers",
|
||||
"max_value_length",
|
||||
"max_tokens",
|
||||
"google_cse_id",
|
||||
]
|
||||
|
||||
DEFAULT_PROMPT = """
|
||||
|
|
|
|||
55
src/frontend/package-lock.json
generated
55
src/frontend/package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
|||
"@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-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
|
|
@ -29,6 +30,7 @@
|
|||
"@tabler/icons-react": "^2.18.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@types/axios": "^0.14.0",
|
||||
"accordion": "^3.0.2",
|
||||
"ace-builds": "^1.16.0",
|
||||
"add": "^2.0.6",
|
||||
|
|
@ -143,6 +145,8 @@
|
|||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.22.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.6.tgz",
|
||||
"integrity": "sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
|
|
@ -1460,6 +1464,43 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.6.tgz",
|
||||
"integrity": "sha512-cZ4defGpkZ0qTRtlIBzJLSzL6ht7ofhhW4i1+pkemjV1IKXm0wgCRnee154qlV6r9Ttunmh2TNZhMfV2bavUyA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.1",
|
||||
"@radix-ui/react-context": "1.0.1",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.4",
|
||||
"@radix-ui/react-focus-guards": "1.0.1",
|
||||
"@radix-ui/react-focus-scope": "1.0.3",
|
||||
"@radix-ui/react-id": "1.0.1",
|
||||
"@radix-ui/react-popper": "1.1.2",
|
||||
"@radix-ui/react-portal": "1.0.3",
|
||||
"@radix-ui/react-presence": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-slot": "1.0.2",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.1.2",
|
||||
"license": "MIT",
|
||||
|
|
@ -2610,6 +2651,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/axios": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
|
||||
"integrity": "sha512-KqQnQbdYE54D7oa/UmYVMZKq7CO4l8DEENzOKc4aBRwxCXSlJXGz83flFx5L7AWrOQnmuN3kVsRdt+GZPPjiVQ==",
|
||||
"deprecated": "This is a stub types definition for axios (https://github.com/mzabriskie/axios). axios provides its own type definitions, so you don't need @types/axios installed!",
|
||||
"dependencies": {
|
||||
"axios": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cacheable-request": {
|
||||
"version": "6.0.3",
|
||||
"dev": true,
|
||||
|
|
@ -9533,8 +9583,9 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"license": "MIT",
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"@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-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
|
|
@ -24,6 +25,7 @@
|
|||
"@tabler/icons-react": "^2.18.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/line-clamp": "^0.4.4",
|
||||
"@types/axios": "^0.14.0",
|
||||
"accordion": "^3.0.2",
|
||||
"ace-builds": "^1.16.0",
|
||||
"add": "^2.0.6",
|
||||
|
|
|
|||
|
|
@ -1,66 +1,72 @@
|
|||
import { useContext, useRef } from "react";
|
||||
import { useContext, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "../../components/ui/popover";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { AlertDropdownType } from "../../types/alerts";
|
||||
import { useOnClickOutside } from "../hooks/useOnClickOutside";
|
||||
import SingleAlert from "./components/singleAlertComponent";
|
||||
|
||||
export default function AlertDropdown({}: AlertDropdownType) {
|
||||
const { closePopUp } = useContext(PopUpContext);
|
||||
const componentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Use the custom hook
|
||||
useOnClickOutside(componentRef, () => {
|
||||
closePopUp();
|
||||
});
|
||||
|
||||
export default function AlertDropdown({ children }: AlertDropdownType) {
|
||||
const {
|
||||
notificationList,
|
||||
clearNotificationList,
|
||||
removeFromNotificationList,
|
||||
setNotificationCenter,
|
||||
} = useContext(alertContext);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={componentRef}
|
||||
className="z-10 flex h-[500px] w-[400px] flex-col overflow-hidden rounded-md bg-muted px-2 py-3 pb-4 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={(k) => {
|
||||
setOpen(k);
|
||||
if (k) setNotificationCenter(false);
|
||||
}}
|
||||
>
|
||||
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
|
||||
Notifications
|
||||
<div className="flex gap-3 pr-3 ">
|
||||
<button
|
||||
className="text-foreground hover:text-status-red"
|
||||
onClick={() => {
|
||||
closePopUp();
|
||||
setTimeout(clearNotificationList, 100);
|
||||
}}
|
||||
>
|
||||
<IconComponent name="Trash2" className="h-[1.1rem] w-[1.1rem]" />
|
||||
</button>
|
||||
<button
|
||||
className="text-foreground hover:text-status-red"
|
||||
onClick={closePopUp}
|
||||
>
|
||||
<IconComponent name="X" className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-high-foreground mt-3 flex h-full w-full flex-col overflow-y-scroll scrollbar-hide">
|
||||
{notificationList.length !== 0 ? (
|
||||
notificationList.map((alertItem, index) => (
|
||||
<SingleAlert
|
||||
key={alertItem.id}
|
||||
dropItem={alertItem}
|
||||
removeAlert={removeFromNotificationList}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center pb-16 text-ring">
|
||||
No new notifications
|
||||
<PopoverTrigger>{children}</PopoverTrigger>
|
||||
<PopoverContent className="flex h-[500px] w-[500px] flex-col">
|
||||
<div className="text-md flex flex-row justify-between pl-3 font-medium text-foreground">
|
||||
Notifications
|
||||
<div className="flex gap-3 pr-3 ">
|
||||
<button
|
||||
className="text-foreground hover:text-status-red"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
setTimeout(clearNotificationList, 100);
|
||||
}}
|
||||
>
|
||||
<IconComponent name="Trash2" className="h-[1.1rem] w-[1.1rem]" />
|
||||
</button>
|
||||
<button
|
||||
className="text-foreground hover:text-status-red"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<IconComponent name="X" className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-high-foreground mt-3 flex h-full w-full flex-col overflow-y-scroll scrollbar-hide">
|
||||
{notificationList.length !== 0 ? (
|
||||
notificationList.map((alertItem, index) => (
|
||||
<SingleAlert
|
||||
key={alertItem.id}
|
||||
dropItem={alertItem}
|
||||
removeAlert={removeFromNotificationList}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className="flex h-full w-full items-center justify-center pb-16 text-ring">
|
||||
No new notifications
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect } from "react";
|
||||
import { FloatComponentType } from "../../types/components";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export default function FloatComponent({
|
||||
value,
|
||||
|
|
@ -19,8 +20,8 @@ export default function FloatComponent({
|
|||
}, [disabled, onChange]);
|
||||
|
||||
return (
|
||||
<div className={"w-full " + (disabled ? "float-component-pointer" : "")}>
|
||||
<input
|
||||
<div className="w-full">
|
||||
<Input
|
||||
type="number"
|
||||
step={step}
|
||||
min={min}
|
||||
|
|
@ -34,12 +35,8 @@ export default function FloatComponent({
|
|||
}}
|
||||
max={max}
|
||||
value={value ?? ""}
|
||||
className={
|
||||
"nopan nodrag noundo nocopy " +
|
||||
(editNode
|
||||
? "input-edit-node"
|
||||
: "input-primary" + (disabled ? " input-disable " : ""))
|
||||
}
|
||||
disabled={disabled}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
placeholder={
|
||||
editNode ? "Number 0 to 1" : "Type a number from zero to one"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
import { useContext, useEffect, useState } from "react";
|
||||
import { FaDiscord, FaGithub, FaTwitter } from "react-icons/fa";
|
||||
import { Link, useLocation, useParams } from "react-router-dom";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import AlertDropdown from "../../alerts/alertDropDown";
|
||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { darkContext } from "../../contexts/darkContext";
|
||||
import { PopUpContext } from "../../contexts/popUpContext";
|
||||
import { TabsContext } from "../../contexts/tabsContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { getRepoStars } from "../../controllers/API";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Button } from "../ui/button";
|
||||
|
|
@ -15,14 +13,9 @@ import { Separator } from "../ui/separator";
|
|||
import MenuBar from "./components/menuBar";
|
||||
|
||||
export default function Header() {
|
||||
const { flows, addFlow, tabId } = useContext(TabsContext);
|
||||
const { openPopUp } = useContext(PopUpContext);
|
||||
const { templates } = useContext(typesContext);
|
||||
const { id } = useParams();
|
||||
const AlertWidth = 384;
|
||||
const { flows, tabId } = useContext(TabsContext);
|
||||
const { dark, setDark } = useContext(darkContext);
|
||||
const { notificationCenter, setNotificationCenter, setErrorData } =
|
||||
useContext(alertContext);
|
||||
const { notificationCenter } = useContext(alertContext);
|
||||
const location = useLocation();
|
||||
|
||||
const [stars, setStars] = useState(null);
|
||||
|
|
@ -111,33 +104,18 @@ export default function Header() {
|
|||
<IconComponent name="MoonIcon" className="side-bar-button-size" />
|
||||
)}
|
||||
</button>
|
||||
<button
|
||||
className="extra-side-bar-save-disable relative"
|
||||
onClick={(event: React.MouseEvent<HTMLElement>) => {
|
||||
setNotificationCenter(false);
|
||||
const { top, left } = (
|
||||
event.target as Element
|
||||
).getBoundingClientRect();
|
||||
openPopUp(
|
||||
<>
|
||||
<div
|
||||
className="absolute z-10"
|
||||
style={{ top: top + 40, left: left - AlertWidth }}
|
||||
>
|
||||
<AlertDropdown />
|
||||
</div>
|
||||
<div className="header-notifications-box"></div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{notificationCenter && <div className="header-notifications"></div>}
|
||||
<IconComponent
|
||||
name="Bell"
|
||||
className="side-bar-button-size"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
<AlertDropdown>
|
||||
<div className="extra-side-bar-save-disable relative">
|
||||
{notificationCenter && (
|
||||
<div className="header-notifications"></div>
|
||||
)}
|
||||
<IconComponent
|
||||
name="Bell"
|
||||
className="side-bar-button-size"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</AlertDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { InputComponentType } from "../../types/components";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export default function InputComponent({
|
||||
value,
|
||||
|
|
@ -19,16 +20,15 @@ export default function InputComponent({
|
|||
}, [disabled, onChange]);
|
||||
|
||||
return (
|
||||
<div className={disabled ? "input-component-div" : "relative"}>
|
||||
<input
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
className={classNames(
|
||||
disabled ? " input-disable " : "",
|
||||
password && !pwdVisible && value !== "" ? " text-clip password " : "",
|
||||
editNode ? " input-edit-node " : " input-primary ",
|
||||
editNode ? " input-edit-node " : "",
|
||||
password && editNode ? "pr-8" : "",
|
||||
password && !editNode ? "pr-10" : "",
|
||||
"nopan nodrag noundo nocopy"
|
||||
password && !editNode ? "pr-10" : ""
|
||||
)}
|
||||
placeholder={password && editNode ? "Key" : "Type something..."}
|
||||
onChange={(e) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import { useEffect } from "react";
|
|||
import { InputListComponentType } from "../../types/components";
|
||||
|
||||
import _ from "lodash";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export default function InputListComponent({
|
||||
value,
|
||||
|
|
@ -18,23 +20,19 @@ export default function InputListComponent({
|
|||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
(disabled ? "pointer-events-none cursor-not-allowed" : "") +
|
||||
className={classNames(
|
||||
value.length > 1 && editNode ? "my-1" : "",
|
||||
"flex flex-col gap-3"
|
||||
}
|
||||
)}
|
||||
>
|
||||
{value.map((i, idx) => {
|
||||
return (
|
||||
<div key={idx} className="flex w-full gap-3">
|
||||
<input
|
||||
<Input
|
||||
disabled={disabled}
|
||||
type="text"
|
||||
value={i}
|
||||
className={
|
||||
"nopan nodrag noundo nocopy " +
|
||||
(editNode
|
||||
? "input-edit-node "
|
||||
: "input-primary " + (disabled ? "input-disable" : ""))
|
||||
}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
placeholder="Type something..."
|
||||
onChange={(e) => {
|
||||
let newInputList = _.cloneDeep(value);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect } from "react";
|
||||
import { FloatComponentType } from "../../types/components";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export default function IntComponent({
|
||||
value,
|
||||
|
|
@ -17,13 +18,8 @@ export default function IntComponent({
|
|||
}, [disabled, onChange]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"w-full " +
|
||||
(disabled ? "pointer-events-none w-full cursor-not-allowed" : "")
|
||||
}
|
||||
>
|
||||
<input
|
||||
<div className="w-full">
|
||||
<Input
|
||||
onKeyDown={(event) => {
|
||||
if (
|
||||
event.key !== "Backspace" &&
|
||||
|
|
@ -51,12 +47,8 @@ export default function IntComponent({
|
|||
}
|
||||
}}
|
||||
value={value ?? ""}
|
||||
className={
|
||||
"nopan nodrag noundo nocopy " +
|
||||
(editNode
|
||||
? " input-edit-node "
|
||||
: " input-primary " + (disabled ? " input-disable" : ""))
|
||||
}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
disabled={disabled}
|
||||
placeholder={editNode ? "Integer number" : "Type an integer number"}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { TypeModal } from "../../constants/enums";
|
|||
import GenericModal from "../../modals/genericModal";
|
||||
import { TextAreaComponentType } from "../../types/components";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Input } from "../ui/input";
|
||||
|
||||
export default function TextAreaComponent({
|
||||
value,
|
||||
|
|
@ -18,42 +19,36 @@ export default function TextAreaComponent({
|
|||
}, [disabled]);
|
||||
|
||||
return (
|
||||
<div className={disabled ? "pointer-events-none w-full " : " w-full"}>
|
||||
<div className="flex w-full items-center">
|
||||
<input
|
||||
<div className="flex w-full items-center">
|
||||
<Input
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
className={editNode ? "input-edit-node" : ""}
|
||||
placeholder={"Type something..."}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<GenericModal
|
||||
type={TypeModal.TEXT}
|
||||
buttonText="Finishing Editing"
|
||||
modalTitle="Edit Text"
|
||||
value={value}
|
||||
className={
|
||||
(editNode
|
||||
? " input-edit-node "
|
||||
: " input-primary " + (disabled ? " input-disable" : "")) +
|
||||
" nopan nodrag noundo nocopy w-full"
|
||||
}
|
||||
placeholder={"Type something..."}
|
||||
onChange={(e) => {
|
||||
onChange(e.target.value);
|
||||
setValue={(t: string) => {
|
||||
onChange(t);
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<GenericModal
|
||||
type={TypeModal.TEXT}
|
||||
buttonText="Finishing Editing"
|
||||
modalTitle="Edit Text"
|
||||
value={value}
|
||||
setValue={(t: string) => {
|
||||
onChange(t);
|
||||
}}
|
||||
>
|
||||
{!editNode && (
|
||||
<IconComponent
|
||||
name="ExternalLink"
|
||||
className={
|
||||
"icons-parameters-comp" +
|
||||
(disabled ? " text-ring" : " hover:text-accent-foreground")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</GenericModal>
|
||||
</div>
|
||||
>
|
||||
{!editNode && (
|
||||
<IconComponent
|
||||
name="ExternalLink"
|
||||
className={
|
||||
"icons-parameters-comp" +
|
||||
(disabled ? " text-ring" : " hover:text-accent-foreground")
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</GenericModal>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,10 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"nopan nodrag noundo nocopy flex h-10 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
className={cn("nopan nodrag noundo nocopy input-primary", className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
|||
30
src/frontend/src/components/ui/popover.tsx
Normal file
30
src/frontend/src/components/ui/popover.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
"use client";
|
||||
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const Popover = PopoverPrimitive.Root;
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
));
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent };
|
||||
|
|
@ -8,10 +8,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
className={cn("nopan nodrag noundo nocopy textarea-primary", className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { SSEProvider } from "./SSEContext";
|
|||
import { AlertProvider } from "./alertContext";
|
||||
import { DarkProvider } from "./darkContext";
|
||||
import { LocationProvider } from "./locationContext";
|
||||
import PopUpProvider from "./popUpContext";
|
||||
import { TabsProvider } from "./tabsContext";
|
||||
import { TypesProvider } from "./typesContext";
|
||||
import { UndoRedoProvider } from "./undoRedoContext";
|
||||
|
|
@ -22,9 +21,7 @@ export default function ContextWrapper({ children }: { children: ReactNode }) {
|
|||
<AlertProvider>
|
||||
<SSEProvider>
|
||||
<TabsProvider>
|
||||
<UndoRedoProvider>
|
||||
<PopUpProvider>{children}</PopUpProvider>
|
||||
</UndoRedoProvider>
|
||||
<UndoRedoProvider>{children}</UndoRedoProvider>
|
||||
</TabsProvider>
|
||||
</SSEProvider>
|
||||
</AlertProvider>
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
import React, { createContext, useState } from "react";
|
||||
|
||||
// context to set JSX element on the DOM
|
||||
export const PopUpContext = createContext({
|
||||
openPopUp: (popUpElement: JSX.Element) => {},
|
||||
closePopUp: () => {},
|
||||
setCloseEdit: (value: string) => {},
|
||||
closeEdit: "",
|
||||
});
|
||||
|
||||
interface PopUpProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const PopUpProvider = ({ children }: PopUpProviderProps) => {
|
||||
const [popUpElements, setPopUpElements] = useState<JSX.Element[]>([]);
|
||||
|
||||
const openPopUp = (element: JSX.Element) => {
|
||||
setPopUpElements((prevPopUps) => [element, ...prevPopUps]);
|
||||
};
|
||||
|
||||
const closePopUp = () => {
|
||||
setPopUpElements((prevPopUps) => prevPopUps.slice(1));
|
||||
};
|
||||
|
||||
const [closeEdit, setCloseEdit] = useState("");
|
||||
|
||||
return (
|
||||
<PopUpContext.Provider
|
||||
value={{ openPopUp, closePopUp, closeEdit, setCloseEdit }}
|
||||
>
|
||||
{children}
|
||||
{popUpElements[0]}
|
||||
</PopUpContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default PopUpProvider;
|
||||
|
|
@ -71,18 +71,7 @@ export function TypesProvider({ children }: { children: ReactNode }) {
|
|||
// Clear the interval if successful.
|
||||
clearInterval(intervalId);
|
||||
} catch (error) {
|
||||
retryCount++;
|
||||
// On error, double the delay for the next attempt up to a maximum.
|
||||
delay = Math.min(30000, delay * 2);
|
||||
// Log errors but don't do anything else - the function will try again on the next interval.
|
||||
console.error(error);
|
||||
// Clear the old interval and start a new one with the new delay.
|
||||
if (retryCount <= maxRetryCount) {
|
||||
clearInterval(intervalId);
|
||||
intervalId = setInterval(getTypes, delay);
|
||||
} else {
|
||||
console.error("Max retry attempts reached. Stopping retries.");
|
||||
}
|
||||
console.error("An error has occurred while fetching types.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
58
src/frontend/src/controllers/API/api.tsx
Normal file
58
src/frontend/src/controllers/API/api.tsx
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import axios, { AxiosError, AxiosInstance } from "axios";
|
||||
import { useContext, useEffect, useRef } from "react";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
|
||||
// Create a new Axios instance
|
||||
const api: AxiosInstance = axios.create({
|
||||
baseURL: "",
|
||||
});
|
||||
|
||||
function ApiInterceptor() {
|
||||
const retryCounts = useRef([]);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
|
||||
useEffect(() => {
|
||||
const interceptor = api.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
// Clean up the interceptor when the component unmounts
|
||||
api.interceptors.response.eject(interceptor);
|
||||
};
|
||||
}, [retryCounts]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Function to sleep for a given duration in milliseconds
|
||||
function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export { ApiInterceptor, api };
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import axios, { AxiosResponse } from "axios";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { ReactFlowJsonObject } from "reactflow";
|
||||
import { api } from "../../controllers/API/api";
|
||||
import { APIObjectType, sendAllProps } from "../../types/api/index";
|
||||
import { FlowStyleType, FlowType } from "../../types/flow";
|
||||
import {
|
||||
|
|
@ -17,16 +18,14 @@ import {
|
|||
* @returns {Promise<AxiosResponse<APIObjectType>>} A promise that resolves to an AxiosResponse containing all the objects.
|
||||
*/
|
||||
export async function getAll(): Promise<AxiosResponse<APIObjectType>> {
|
||||
return await axios.get(`/api/v1/all`);
|
||||
return await api.get(`/api/v1/all`);
|
||||
}
|
||||
|
||||
const GITHUB_API_URL = "https://api.github.com";
|
||||
|
||||
export async function getRepoStars(owner, repo) {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`${GITHUB_API_URL}/repos/${owner}/${repo}`
|
||||
);
|
||||
const response = await api.get(`${GITHUB_API_URL}/repos/${owner}/${repo}`);
|
||||
return response.data.stargazers_count;
|
||||
} catch (error) {
|
||||
console.error("Error fetching repository data:", error);
|
||||
|
|
@ -41,13 +40,13 @@ export async function getRepoStars(owner, repo) {
|
|||
* @returns {AxiosResponse<any>} The API response.
|
||||
*/
|
||||
export async function sendAll(data: sendAllProps) {
|
||||
return await axios.post(`/api/v1/predict`, data);
|
||||
return await api.post(`/api/v1/predict`, data);
|
||||
}
|
||||
|
||||
export async function postValidateCode(
|
||||
code: string
|
||||
): Promise<AxiosResponse<errorsTypeAPI>> {
|
||||
return await axios.post("/api/v1/validate/code", { code });
|
||||
return await api.post("/api/v1/validate/code", { code });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -62,7 +61,7 @@ export async function postValidatePrompt(
|
|||
template: string,
|
||||
frontend_node: APIClassType
|
||||
): Promise<AxiosResponse<PromptTypeAPI>> {
|
||||
return await axios.post("/api/v1/validate/prompt", {
|
||||
return await api.post("/api/v1/validate/prompt", {
|
||||
name: name,
|
||||
template: template,
|
||||
frontend_node: frontend_node,
|
||||
|
|
@ -77,14 +76,14 @@ export async function postValidatePrompt(
|
|||
export async function getExamples(): Promise<FlowType[]> {
|
||||
const url =
|
||||
"https://api.github.com/repos/logspace-ai/langflow_examples/contents/examples?ref=main";
|
||||
const response = await axios.get(url);
|
||||
const response = await api.get(url);
|
||||
|
||||
const jsonFiles = response.data.filter((file: any) => {
|
||||
return file.name.endsWith(".json");
|
||||
});
|
||||
|
||||
const contentsPromises = jsonFiles.map(async (file: any) => {
|
||||
const contentResponse = await axios.get(file.download_url);
|
||||
const contentResponse = await api.get(file.download_url);
|
||||
return contentResponse.data;
|
||||
});
|
||||
|
||||
|
|
@ -106,11 +105,12 @@ export async function saveFlowToDatabase(newFlow: {
|
|||
style?: FlowStyleType;
|
||||
}): Promise<FlowType> {
|
||||
try {
|
||||
const response = await axios.post("/api/v1/flows/", {
|
||||
const response = await api.post("/api/v1/flows/", {
|
||||
name: newFlow.name,
|
||||
data: newFlow.data,
|
||||
description: newFlow.description,
|
||||
});
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ export async function updateFlowInDatabase(
|
|||
updatedFlow: FlowType
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await axios.patch(`/api/v1/flows/${updatedFlow.id}`, {
|
||||
const response = await api.patch(`/api/v1/flows/${updatedFlow.id}`, {
|
||||
name: updatedFlow.name,
|
||||
data: updatedFlow.data,
|
||||
description: updatedFlow.description,
|
||||
|
|
@ -155,7 +155,7 @@ export async function updateFlowInDatabase(
|
|||
*/
|
||||
export async function readFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await axios.get("/api/v1/flows/");
|
||||
const response = await api.get("/api/v1/flows/");
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -168,7 +168,7 @@ export async function readFlowsFromDatabase() {
|
|||
|
||||
export async function downloadFlowsFromDatabase() {
|
||||
try {
|
||||
const response = await axios.get("/api/v1/flows/download/");
|
||||
const response = await api.get("/api/v1/flows/download/");
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ export async function downloadFlowsFromDatabase() {
|
|||
|
||||
export async function uploadFlowsToDatabase(flows) {
|
||||
try {
|
||||
const response = await axios.post(`/api/v1/flows/upload/`, flows);
|
||||
const response = await api.post(`/api/v1/flows/upload/`, flows);
|
||||
|
||||
if (response.status !== 201) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
|
|
@ -202,7 +202,7 @@ export async function uploadFlowsToDatabase(flows) {
|
|||
*/
|
||||
export async function deleteFlowFromDatabase(flowId: string) {
|
||||
try {
|
||||
const response = await axios.delete(`/api/v1/flows/${flowId}`);
|
||||
const response = await api.delete(`/api/v1/flows/${flowId}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -222,7 +222,7 @@ export async function deleteFlowFromDatabase(flowId: string) {
|
|||
*/
|
||||
export async function getFlowFromDatabase(flowId: number) {
|
||||
try {
|
||||
const response = await axios.get(`/api/v1/flows/${flowId}`);
|
||||
const response = await api.get(`/api/v1/flows/${flowId}`);
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -241,7 +241,7 @@ export async function getFlowFromDatabase(flowId: number) {
|
|||
*/
|
||||
export async function getFlowStylesFromDatabase() {
|
||||
try {
|
||||
const response = await axios.get("/api/v1/flow_styles/");
|
||||
const response = await api.get("/api/v1/flow_styles/");
|
||||
if (response.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
|
@ -261,7 +261,7 @@ export async function getFlowStylesFromDatabase() {
|
|||
*/
|
||||
export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
||||
try {
|
||||
const response = await axios.post("/api/v1/flow_styles/", flowStyle, {
|
||||
const response = await api.post("/api/v1/flow_styles/", flowStyle, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
|
|
@ -284,7 +284,7 @@ export async function saveFlowStyleToDatabase(flowStyle: FlowStyleType) {
|
|||
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the version information.
|
||||
*/
|
||||
export async function getVersion() {
|
||||
const respnose = await axios.get("/api/v1/version");
|
||||
const respnose = await api.get("/api/v1/version");
|
||||
return respnose.data;
|
||||
}
|
||||
|
||||
|
|
@ -294,7 +294,7 @@ export async function getVersion() {
|
|||
* @returns {Promise<AxiosResponse<any>>} A promise that resolves to an AxiosResponse containing the health status.
|
||||
*/
|
||||
export async function getHealth() {
|
||||
return await axios.get("/health"); // Health is the only endpoint that doesn't require /api/v1
|
||||
return await api.get("/health"); // Health is the only endpoint that doesn't require /api/v1
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -306,7 +306,7 @@ export async function getHealth() {
|
|||
export async function getBuildStatus(
|
||||
flowId: string
|
||||
): Promise<BuildStatusTypeAPI> {
|
||||
return await axios.get(`/api/v1/build/${flowId}/status`);
|
||||
return await api.get(`/api/v1/build/${flowId}/status`);
|
||||
}
|
||||
|
||||
//docs for postbuildinit
|
||||
|
|
@ -319,7 +319,7 @@ export async function getBuildStatus(
|
|||
export async function postBuildInit(
|
||||
flow: FlowType
|
||||
): Promise<AxiosResponse<InitTypeAPI>> {
|
||||
return await axios.post(`/api/v1/build/init/${flow.id}`, flow);
|
||||
return await api.post(`/api/v1/build/init/${flow.id}`, flow);
|
||||
}
|
||||
|
||||
// fetch(`/upload/${id}`, {
|
||||
|
|
@ -337,7 +337,7 @@ export async function uploadFile(
|
|||
): Promise<AxiosResponse<UploadFileTypeAPI>> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return await axios.post(`/api/v1/upload/${id}`, formData);
|
||||
return await api.post(`/api/v1/upload/${id}`, formData);
|
||||
}
|
||||
|
||||
export async function postCustomComponent(
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ import App from "./App";
|
|||
import ContextWrapper from "./contexts";
|
||||
import reportWebVitals from "./reportWebVitals";
|
||||
|
||||
import "./index.css";
|
||||
import { ApiInterceptor } from "./controllers/API/api";
|
||||
import "./style/applies.css";
|
||||
import "./style/classes.css";
|
||||
import "./style/index.css";
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById("root") as HTMLElement
|
||||
|
|
@ -13,6 +16,7 @@ root.render(
|
|||
<ContextWrapper>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<ApiInterceptor />
|
||||
</BrowserRouter>
|
||||
</ContextWrapper>
|
||||
);
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue