Merge remote-tracking branch 'origin/dev' into chatImg
This commit is contained in:
commit
8b1234f6ca
598 changed files with 26867 additions and 22399 deletions
710
src/frontend/harFiles/example.har
Normal file
710
src/frontend/harFiles/example.har
Normal file
File diff suppressed because one or more lines are too long
32
src/frontend/package-lock.json
generated
32
src/frontend/package-lock.json
generated
|
|
@ -26,6 +26,7 @@
|
|||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"@tabler/icons-react": "^2.32.0",
|
||||
"@tailwindcss/forms": "^0.5.6",
|
||||
|
|
@ -40,6 +41,7 @@
|
|||
"class-variance-authority": "^0.6.1",
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"dompurify": "^3.0.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"esbuild": "^0.17.19",
|
||||
|
|
@ -2761,6 +2763,31 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-toggle": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.0.3.tgz",
|
||||
"integrity": "sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.1",
|
||||
"@radix-ui/react-primitive": "1.0.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.1"
|
||||
},
|
||||
"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-tooltip": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.7.tgz",
|
||||
|
|
@ -5670,6 +5697,11 @@
|
|||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/debounce-promise": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/debounce-promise/-/debounce-promise-3.1.2.tgz",
|
||||
"integrity": "sha512-rZHcgBkbYavBeD9ej6sP56XfG53d51CD4dnaw989YX/nZ/ZJfgRx/9ePKmTNiUiyQvh4mtrMoS3OAWW+yoYtpg=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toggle": "^1.0.3",
|
||||
"@radix-ui/react-tooltip": "^1.0.6",
|
||||
"@tabler/icons-react": "^2.32.0",
|
||||
"@tailwindcss/forms": "^0.5.6",
|
||||
|
|
@ -35,6 +36,7 @@
|
|||
"class-variance-authority": "^0.6.1",
|
||||
"clsx": "^1.2.1",
|
||||
"cmdk": "^1.0.0",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"dompurify": "^3.0.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"esbuild": "^0.17.19",
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
|||
export default defineConfig({
|
||||
testDir: "./tests",
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
fullyParallel: false,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
|
|
@ -52,18 +52,18 @@ export default defineConfig({
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "firefox",
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
launchOptions: {
|
||||
firefoxUserPrefs: {
|
||||
"dom.events.asyncClipboard.readText": true,
|
||||
"dom.events.testing.asyncClipboard": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// {
|
||||
// name: "firefox",
|
||||
// use: {
|
||||
// ...devices["Desktop Firefox"],
|
||||
// launchOptions: {
|
||||
// firefoxUserPrefs: {
|
||||
// "dom.events.asyncClipboard.readText": true,
|
||||
// "dom.events.testing.asyncClipboard": true,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
],
|
||||
webServer: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import axios from "axios";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { ErrorBoundary } from "react-error-boundary";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
|
@ -15,6 +16,8 @@ import {
|
|||
} from "./constants/constants";
|
||||
import { AuthContext } from "./contexts/authContext";
|
||||
import { autoLogin, getGlobalVariables, getHealth } from "./controllers/API";
|
||||
import { setupAxiosDefaults } from "./controllers/API/utils";
|
||||
import useTrackLastVisitedPath from "./hooks/use-track-last-visited-path";
|
||||
import Router from "./routes";
|
||||
import useAlertStore from "./stores/alertStore";
|
||||
import { useDarkStore } from "./stores/darkStore";
|
||||
|
|
@ -24,6 +27,8 @@ import { useGlobalVariablesStore } from "./stores/globalVariablesStore/globalVar
|
|||
import { useStoreStore } from "./stores/storeStore";
|
||||
import { useTypesStore } from "./stores/typesStore";
|
||||
export default function App() {
|
||||
useTrackLastVisitedPath();
|
||||
|
||||
const removeFromTempNotificationList = useAlertStore(
|
||||
(state) => state.removeFromTempNotificationList,
|
||||
);
|
||||
|
|
@ -104,7 +109,6 @@ export default function App() {
|
|||
const fetchAllData = async () => {
|
||||
setTimeout(async () => {
|
||||
await Promise.all([refreshStars(), refreshVersion(), fetchData()]);
|
||||
getFoldersApi();
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
|
|
@ -112,6 +116,8 @@ export default function App() {
|
|||
return new Promise<void>(async (resolve, reject) => {
|
||||
if (isAuthenticated) {
|
||||
try {
|
||||
await setupAxiosDefaults();
|
||||
await getFoldersApi();
|
||||
await getTypes();
|
||||
await refreshFlows();
|
||||
const res = await getGlobalVariables();
|
||||
|
|
@ -208,7 +214,7 @@ export default function App() {
|
|||
<div className="flex flex-col-reverse" style={{ zIndex: 999 }}>
|
||||
{tempNotificationList.map((alert) => (
|
||||
<div key={alert.id}>
|
||||
{alert.type === "error" && (
|
||||
{alert.type === "error" ? (
|
||||
<ErrorAlert
|
||||
key={alert.id}
|
||||
title={alert.title}
|
||||
|
|
@ -216,14 +222,7 @@ export default function App() {
|
|||
id={alert.id}
|
||||
removeAlert={removeAlert}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="z-40 flex flex-col-reverse">
|
||||
{tempNotificationList.map((alert) => (
|
||||
<div key={alert.id}>
|
||||
{alert.type === "notice" ? (
|
||||
) : alert.type === "notice" ? (
|
||||
<NoticeAlert
|
||||
key={alert.id}
|
||||
title={alert.title}
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ export default function AlertDropdown({
|
|||
}: AlertDropdownType): JSX.Element {
|
||||
const notificationList = useAlertStore((state) => state.notificationList);
|
||||
const clearNotificationList = useAlertStore(
|
||||
(state) => state.clearNotificationList
|
||||
(state) => state.clearNotificationList,
|
||||
);
|
||||
const removeFromNotificationList = useAlertStore(
|
||||
(state) => state.removeFromNotificationList
|
||||
(state) => state.removeFromNotificationList,
|
||||
);
|
||||
const setNotificationCenter = useAlertStore(
|
||||
(state) => state.setNotificationCenter
|
||||
(state) => state.setNotificationCenter,
|
||||
);
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
|
@ -36,7 +36,7 @@ export default function AlertDropdown({
|
|||
}}
|
||||
>
|
||||
<PopoverTrigger>{children}</PopoverTrigger>
|
||||
<PopoverContent className="nocopy nopan nodelete nodrag noundo flex h-[500px] w-[500px] flex-col">
|
||||
<PopoverContent className="nocopy nowheel nopan nodelete nodrag noundo 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 ">
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export default function ErrorAlert({
|
|||
removeAlert(id);
|
||||
}, 500);
|
||||
}}
|
||||
className="error-build-message nocopy nopan nodelete nodrag noundo"
|
||||
className="error-build-message nocopy nowheel nopan nodelete nodrag noundo"
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ export default function NoticeAlert({
|
|||
setShow(false);
|
||||
removeAlert(id);
|
||||
}}
|
||||
className="nocopy nopan nodelete nodrag noundo mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
|
||||
className="nocopy nowheel nopan nodelete nodrag noundo mt-6 w-96 rounded-md bg-info-background p-4 shadow-xl"
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ export default function SuccessAlert({
|
|||
setShow(false);
|
||||
removeAlert(id);
|
||||
}}
|
||||
className="success-alert nocopy nopan nodelete nodrag noundo"
|
||||
className="success-alert nocopy nowheel nopan nodelete nodrag noundo"
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
|
|
|
|||
|
|
@ -6,10 +6,13 @@ import {
|
|||
AccordionTrigger,
|
||||
} from "../../components/ui/accordion";
|
||||
import { AccordionComponentType } from "../../types/components";
|
||||
import { cn } from "../../utils/utils";
|
||||
import ShadTooltip from "../shadTooltipComponent";
|
||||
|
||||
export default function AccordionComponent({
|
||||
trigger,
|
||||
children,
|
||||
disabled,
|
||||
open = [],
|
||||
keyValue,
|
||||
sideBar,
|
||||
|
|
@ -29,7 +32,9 @@ export default function AccordionComponent({
|
|||
}
|
||||
|
||||
function handleClick(): void {
|
||||
value === "" ? setValue(keyValue!) : setValue("");
|
||||
if (!disabled) {
|
||||
value === "" ? setValue(keyValue!) : setValue("");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -38,16 +43,18 @@ export default function AccordionComponent({
|
|||
type="single"
|
||||
className="w-full"
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
onValueChange={!disabled ? setValue : () => {}}
|
||||
>
|
||||
<AccordionItem value={keyValue!} className="border-b">
|
||||
<AccordionTrigger
|
||||
onClick={() => {
|
||||
handleClick();
|
||||
}}
|
||||
className={
|
||||
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3"
|
||||
}
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
sideBar ? "w-full bg-muted px-[0.75rem] py-[0.5rem]" : "ml-3",
|
||||
disabled ? "cursor-not-allowed" : "cursor-pointer",
|
||||
)}
|
||||
>
|
||||
{trigger}
|
||||
</AccordionTrigger>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { useTypesStore } from "../../stores/typesStore";
|
|||
import { ResponseErrorDetailAPI } from "../../types/api";
|
||||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
import InputComponent from "../inputComponent";
|
||||
import { Button } from "../ui/button";
|
||||
import { Input } from "../ui/input";
|
||||
import { Label } from "../ui/label";
|
||||
import { Textarea } from "../ui/textarea";
|
||||
|
|
@ -65,12 +64,17 @@ export default function AddNewVariableButton({ children }): JSX.Element {
|
|||
let responseError = error as ResponseErrorDetailAPI;
|
||||
setErrorData({
|
||||
title: "Error creating variable",
|
||||
list: [responseError.response.data.detail ?? "Unknown error"],
|
||||
list: [responseError?.response?.data?.detail ?? "Unknown error"],
|
||||
});
|
||||
});
|
||||
}
|
||||
return (
|
||||
<BaseModal open={open} setOpen={setOpen} size="x-small">
|
||||
<BaseModal
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
size="x-small"
|
||||
onSubmit={handleSaveVariable}
|
||||
>
|
||||
<BaseModal.Header
|
||||
description={
|
||||
"This variable will be encrypted and will be available for you to use in any of your projects."
|
||||
|
|
@ -137,9 +141,9 @@ export default function AddNewVariableButton({ children }): JSX.Element {
|
|||
></InputComponent>
|
||||
</div>
|
||||
</BaseModal.Content>
|
||||
<BaseModal.Footer>
|
||||
<Button onClick={handleSaveVariable}>Save Variable</Button>
|
||||
</BaseModal.Footer>
|
||||
<BaseModal.Footer
|
||||
submit={{ label: "Save Variable", dataTestId: "save-variable-btn" }}
|
||||
/>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export default function sortByName(stringList: string[]): string[] {
|
||||
return stringList.sort((a, b) => a.localeCompare(b));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { storeComponent } from "../../../../types/store";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import ForwardedIconComponent from "../../../genericIconComponent";
|
||||
import ShadTooltip from "../../../shadTooltipComponent";
|
||||
import { Card, CardHeader, CardTitle } from "../../../ui/card";
|
||||
|
||||
export default function DragCardComponent({ data }: { data: storeComponent }) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export function convertTestName(name: string): string {
|
||||
return name.replace(/ /g, "-").toLowerCase();
|
||||
return name.replace(/ /g, "-").toLowerCase();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -841,9 +841,7 @@ export default function CodeTabsComponent({
|
|||
node.data.node!.template[
|
||||
templateField
|
||||
].value?.toString() === "{}"
|
||||
? {
|
||||
// yourkey: "value",
|
||||
}
|
||||
? {}
|
||||
: node.data.node!
|
||||
.template[
|
||||
templateField
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@ export default function DictComponent({
|
|||
editNode = false,
|
||||
id = "",
|
||||
}: DictComponentType): JSX.Element {
|
||||
// Create a reference to the value
|
||||
const ref = useRef(value);
|
||||
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
onChange({});
|
||||
|
|
@ -19,15 +22,14 @@ export default function DictComponent({
|
|||
}, [disabled]);
|
||||
|
||||
useEffect(() => {
|
||||
if (value) onChange(value);
|
||||
// Update the reference value
|
||||
ref.current = value;
|
||||
}, [value]);
|
||||
|
||||
const ref = useRef(value);
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
value.length > 1 && editNode ? "my-1" : "",
|
||||
"flex flex-col gap-3"
|
||||
"flex flex-col gap-3",
|
||||
)}
|
||||
>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,11 +9,14 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
name,
|
||||
invalidNameList,
|
||||
description,
|
||||
endpointName,
|
||||
maxLength = 50,
|
||||
setName,
|
||||
setDescription,
|
||||
setEndpointName,
|
||||
}: InputProps): JSX.Element => {
|
||||
const [isMaxLength, setIsMaxLength] = useState(false);
|
||||
const [isEndpointNameValid, setIsEndpointNameValid] = useState(true);
|
||||
|
||||
const handleNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
|
|
@ -29,6 +32,18 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
setDescription!(event.target.value);
|
||||
};
|
||||
|
||||
const handleEndpointNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
// Validate the endpoint name
|
||||
// use this regex r'^[a-zA-Z0-9_-]+$'
|
||||
const isValid =
|
||||
(/^[a-zA-Z0-9_-]+$/.test(event.target.value) &&
|
||||
event.target.value.length <= maxLength) ||
|
||||
// empty is also valid
|
||||
event.target.value.length === 0;
|
||||
setIsEndpointNameValid(isValid);
|
||||
setEndpointName!(event.target.value);
|
||||
};
|
||||
|
||||
//this function is necessary to select the text when double clicking, this was not working with the onFocus event
|
||||
const handleFocus = (event) => event.target.select();
|
||||
|
||||
|
|
@ -84,13 +99,39 @@ export const EditFlowSettings: React.FC<InputProps> = ({
|
|||
<span
|
||||
className={cn(
|
||||
"font-normal text-muted-foreground word-break-break-word",
|
||||
description === "" ? "font-light italic" : ""
|
||||
description === "" ? "font-light italic" : "",
|
||||
)}
|
||||
>
|
||||
{description === "" ? "No description" : description}
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
{setEndpointName && (
|
||||
<Label>
|
||||
<div className="edit-flow-arrangement mt-3">
|
||||
<span className="font-medium">Endpoint Name</span>
|
||||
{!isEndpointNameValid && (
|
||||
<span className="edit-flow-span">
|
||||
Invalid endpoint name. Use only letters, numbers, hyphens, and
|
||||
underscores ({maxLength} characters max).
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
className="nopan nodelete nodrag noundo nocopy mt-2 font-normal"
|
||||
onChange={handleEndpointNameChange}
|
||||
type="text"
|
||||
name="endpoint_name"
|
||||
value={endpointName ?? ""}
|
||||
placeholder="An alternative name to run the endpoint"
|
||||
maxLength={maxLength}
|
||||
id="endpoint_name"
|
||||
onDoubleClickCapture={(event) => {
|
||||
handleFocus(event);
|
||||
}}
|
||||
/>
|
||||
</Label>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import BaseModal from "../../modals/baseModal";
|
||||
import { fetchErrorComponentType } from "../../types/components";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export default function FetchErrorComponent({
|
||||
message,
|
||||
|
|
@ -12,7 +11,14 @@ export default function FetchErrorComponent({
|
|||
}: fetchErrorComponentType) {
|
||||
return (
|
||||
<>
|
||||
<BaseModal size="small-h-full" open={openModal} type="modal">
|
||||
<BaseModal
|
||||
size="small-h-full"
|
||||
open={openModal}
|
||||
type="modal"
|
||||
onSubmit={() => {
|
||||
setRetry();
|
||||
}}
|
||||
>
|
||||
<BaseModal.Content>
|
||||
<div role="status" className="m-auto flex flex-col items-center">
|
||||
<IconComponent
|
||||
|
|
@ -27,24 +33,9 @@ export default function FetchErrorComponent({
|
|||
</div>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<div className="m-auto">
|
||||
<Button
|
||||
disabled={isLoadingHealth}
|
||||
onClick={() => {
|
||||
setRetry();
|
||||
}}
|
||||
>
|
||||
{isLoadingHealth ? (
|
||||
<div>
|
||||
<IconComponent name={"Loader2"} className={"animate-spin"} />
|
||||
</div>
|
||||
) : (
|
||||
"Retry"
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</BaseModal.Footer>
|
||||
<BaseModal.Footer
|
||||
submit={{ label: "Retry", loading: isLoadingHealth }}
|
||||
/>
|
||||
</BaseModal>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import {
|
|||
} from "../../../ui/dropdown-menu";
|
||||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Node } from "reactflow";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
|
|
@ -22,11 +21,7 @@ import IconComponent from "../../../genericIconComponent";
|
|||
import ShadTooltip from "../../../shadTooltipComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
|
||||
export const MenuBar = ({
|
||||
removeFunction,
|
||||
}: {
|
||||
removeFunction: (nodes: Node[]) => void;
|
||||
}): JSX.Element => {
|
||||
export const MenuBar = ({}: {}): JSX.Element => {
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
|
@ -36,26 +31,15 @@ export const MenuBar = ({
|
|||
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
|
||||
const [openSettings, setOpenSettings] = useState(false);
|
||||
const [openLogs, setOpenLogs] = useState(false);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
const navigate = useNavigate();
|
||||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
|
||||
function handleAddFlow(duplicate?: boolean) {
|
||||
function handleAddFlow() {
|
||||
try {
|
||||
if (duplicate) {
|
||||
if (!currentFlow) {
|
||||
throw new Error("No flow to duplicate");
|
||||
}
|
||||
addFlow(true, currentFlow).then((id) => {
|
||||
setSuccessData({ title: "Flow duplicated successfully" });
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
} else {
|
||||
addFlow(true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}
|
||||
addFlow(true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
} catch (err) {
|
||||
setErrorData(err as { title: string; list?: Array<string> });
|
||||
}
|
||||
|
|
@ -72,14 +56,6 @@ export const MenuBar = ({
|
|||
|
||||
return currentFlow ? (
|
||||
<div className="round-button-div">
|
||||
<button
|
||||
onClick={() => {
|
||||
removeFunction(nodes);
|
||||
navigate("/");
|
||||
}}
|
||||
>
|
||||
<IconComponent name="ChevronLeft" className="w-4" />
|
||||
</button>
|
||||
<div className="header-menu-bar">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
|
|
@ -103,15 +79,6 @@ export const MenuBar = ({
|
|||
<IconComponent name="Plus" className="header-menu-options" />
|
||||
New
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
handleAddFlow(true);
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<IconComponent name="Copy" className="header-menu-options" />
|
||||
Duplicate
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@ import { FaDiscord, FaGithub } from "react-icons/fa";
|
|||
import { RiTwitterXFill } from "react-icons/ri";
|
||||
import { Link, useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import AlertDropdown from "../../alerts/alertDropDown";
|
||||
import { USER_PROJECTS_HEADER } from "../../constants/constants";
|
||||
import {
|
||||
LOCATIONS_TO_RETURN,
|
||||
USER_PROJECTS_HEADER,
|
||||
} from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
|
||||
import { Node } from "reactflow";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useLocationStore } from "../../stores/locationStore";
|
||||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import { gradients } from "../../utils/styleUtils";
|
||||
import IconComponent from "../genericIconComponent";
|
||||
|
|
@ -29,6 +32,7 @@ import MenuBar from "./components/menuBar";
|
|||
export default function Header(): JSX.Element {
|
||||
const notificationCenter = useAlertStore((state) => state.notificationCenter);
|
||||
const location = useLocation();
|
||||
|
||||
const { logout, autoLogin, isAdmin, userData } = useContext(AuthContext);
|
||||
const navigate = useNavigate();
|
||||
const removeFlow = useFlowsManagerStore((store) => store.removeFlow);
|
||||
|
|
@ -40,20 +44,56 @@ export default function Header(): JSX.Element {
|
|||
const setDark = useDarkStore((state) => state.setDark);
|
||||
const stars = useDarkStore((state) => state.stars);
|
||||
|
||||
async function checkForChanges(nodes: Node[]): Promise<void> {
|
||||
const routeHistory = useLocationStore((state) => state.routeHistory);
|
||||
|
||||
async function checkForChanges(): Promise<void> {
|
||||
if (nodes.length === 0) {
|
||||
await removeFlow(id!);
|
||||
}
|
||||
}
|
||||
|
||||
const redirectToLastLocation = () => {
|
||||
const lastFlowVisitedIndex = routeHistory
|
||||
.reverse()
|
||||
.findIndex(
|
||||
(path) => path.includes("/flow/") && path !== location.pathname,
|
||||
);
|
||||
|
||||
const lastFlowVisited = routeHistory[lastFlowVisitedIndex];
|
||||
lastFlowVisited && !location.pathname.includes("/flow")
|
||||
? navigate(lastFlowVisited)
|
||||
: navigate("/all");
|
||||
};
|
||||
|
||||
const visitedFlowPathBefore = () => {
|
||||
const lastThreeVisitedPaths = routeHistory.slice(-3);
|
||||
return lastThreeVisitedPaths.some((path) => path.includes("/flow/"));
|
||||
};
|
||||
|
||||
const showArrowReturnIcon =
|
||||
LOCATIONS_TO_RETURN.some((path) => location.pathname.includes(path)) &&
|
||||
visitedFlowPathBefore();
|
||||
|
||||
return (
|
||||
<div className="header-arrangement">
|
||||
<div className="header-start-display lg:w-[30%]">
|
||||
<Link to="/" onClick={() => checkForChanges(nodes)}>
|
||||
<Link to="/all" className="cursor-pointer" onClick={checkForChanges}>
|
||||
<span className="ml-4 text-2xl">⛓️</span>
|
||||
</Link>
|
||||
<MenuBar removeFunction={checkForChanges} />
|
||||
{showArrowReturnIcon && (
|
||||
<button
|
||||
onClick={() => {
|
||||
checkForChanges();
|
||||
redirectToLastLocation();
|
||||
}}
|
||||
>
|
||||
<IconComponent name="ChevronLeft" className="w-4" />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<MenuBar />
|
||||
</div>
|
||||
|
||||
<div className="round-button-div">
|
||||
<Link to="/">
|
||||
<Button
|
||||
|
|
@ -65,9 +105,7 @@ export default function Header(): JSX.Element {
|
|||
: "secondary"
|
||||
}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
checkForChanges(nodes);
|
||||
}}
|
||||
onClick={checkForChanges}
|
||||
>
|
||||
<IconComponent name="Home" className="h-4 w-4" />
|
||||
<div className="hidden flex-1 md:block">{USER_PROJECTS_HEADER}</div>
|
||||
|
|
@ -80,9 +118,7 @@ export default function Header(): JSX.Element {
|
|||
className="gap-2"
|
||||
variant={location.pathname === "/store" ? "primary" : "secondary"}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
checkForChanges(nodes);
|
||||
}}
|
||||
onClick={checkForChanges}
|
||||
data-testid="button-store"
|
||||
>
|
||||
<IconComponent name="Store" className="h-4 w-4" />
|
||||
|
|
@ -145,7 +181,7 @@ export default function Header(): JSX.Element {
|
|||
/>
|
||||
</div>
|
||||
</AlertDropdown>
|
||||
{!autoLogin && (
|
||||
{autoLogin && (
|
||||
<button
|
||||
onClick={() => {
|
||||
navigate("/account/api-keys");
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ const CustomInputPopover = ({
|
|||
/>
|
||||
</PopoverAnchor>
|
||||
<PopoverContentWithoutPortal
|
||||
className="nocopy nopan nodelete nodrag noundo p-0"
|
||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||
side="bottom"
|
||||
align="center"
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ const CustomInputPopoverObject = ({
|
|||
/>
|
||||
</PopoverAnchor>
|
||||
<PopoverContentWithoutPortal
|
||||
className="nocopy nopan nodelete nodrag noundo p-0"
|
||||
className="nocopy nowheel nopan nodelete nodrag noundo p-0"
|
||||
style={{ minWidth: refInput?.current?.clientWidth ?? "200px" }}
|
||||
side="bottom"
|
||||
align="center"
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ export default function InputComponent({
|
|||
optionButton,
|
||||
objectOptions,
|
||||
isObjectOption = false,
|
||||
name,
|
||||
onChangeFolderName,
|
||||
}: InputComponentType): JSX.Element {
|
||||
const [pwdVisible, setPwdVisible] = useState(false);
|
||||
const refInput = useRef<HTMLInputElement>(null);
|
||||
|
|
@ -53,6 +55,7 @@ export default function InputComponent({
|
|||
{isForm ? (
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
name={name}
|
||||
id={"form-" + id}
|
||||
ref={refInput}
|
||||
onBlur={onInputLostFocus}
|
||||
|
|
@ -72,6 +75,9 @@ export default function InputComponent({
|
|||
)}
|
||||
placeholder={password && editNode ? "Key" : placeholder}
|
||||
onChange={(e) => {
|
||||
if (onChangeFolderName) {
|
||||
return onChangeFolderName(e);
|
||||
}
|
||||
onChange && onChange(e.target.value);
|
||||
}}
|
||||
onCopy={(e) => {
|
||||
|
|
|
|||
|
|
@ -66,10 +66,8 @@ export default function InputFileComponent({
|
|||
uploadFile(file, currentFlowId)
|
||||
.then((res) => res.data)
|
||||
.then((data) => {
|
||||
console.log(CONSOLE_SUCCESS_MSG);
|
||||
// Get the file name from the response
|
||||
const { file_path } = data;
|
||||
console.log("File name:", file_path);
|
||||
|
||||
// sets the value that goes to the backend
|
||||
onFileChange(file_path);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export default function InputListComponent({
|
|||
<div
|
||||
className={classNames(
|
||||
value.length > 1 && editNode ? "my-1" : "",
|
||||
"flex flex-col gap-3"
|
||||
"flex flex-col gap-3",
|
||||
)}
|
||||
>
|
||||
{value.map((singleValue, idx) => {
|
||||
|
|
@ -55,10 +55,11 @@ export default function InputListComponent({
|
|||
/>
|
||||
{idx === value.length - 1 ? (
|
||||
<button
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
let newInputList = _.cloneDeep(value);
|
||||
newInputList.push("");
|
||||
onChange(newInputList);
|
||||
e.preventDefault();
|
||||
}}
|
||||
data-testid={
|
||||
`input-list-plus-btn${
|
||||
|
|
@ -79,10 +80,11 @@ export default function InputListComponent({
|
|||
editNode ? "-edit" : ""
|
||||
}_${componentName}-` + idx
|
||||
}
|
||||
onClick={() => {
|
||||
onClick={(e) => {
|
||||
let newInputList = _.cloneDeep(value);
|
||||
newInputList.splice(idx, 1);
|
||||
onChange(newInputList);
|
||||
e.preventDefault();
|
||||
}}
|
||||
disabled={disabled || playgroundDisabled}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { ColDef, ColGroupDef } from "ag-grid-community";
|
|||
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid
|
||||
import "ag-grid-community/styles/ag-theme-balham.css"; // Optional Theme applied to the grid
|
||||
import { FlowPoolObjectType } from "../../types/chat";
|
||||
import TableComponent from "../tableComponent";
|
||||
import { extractColumnsFromRows } from "../../utils/utils";
|
||||
import TableComponent from "../tableComponent";
|
||||
|
||||
function RecordsOutputComponent({
|
||||
flowPool,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ export default function ShadTooltip({
|
|||
return (
|
||||
<Tooltip delayDuration={delayDuration}>
|
||||
<TooltipTrigger asChild={asChild}>{children}</TooltipTrigger>
|
||||
|
||||
<TooltipContent
|
||||
className={cn(styleClasses, "max-w-96")}
|
||||
side={side}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Link } from "react-router-dom";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import { buttonVariants } from "../../../ui/button";
|
||||
import ForwardedIconComponent from "../../../genericIconComponent";
|
||||
|
||||
type SideBarButtonsComponentProps = {
|
||||
items: {
|
||||
|
|
@ -13,10 +14,10 @@ type SideBarButtonsComponentProps = {
|
|||
};
|
||||
const SideBarButtonsComponent = ({
|
||||
items,
|
||||
handleOpenNewFolderModal,
|
||||
pathname,
|
||||
}: SideBarButtonsComponentProps) => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
|
||||
{items.map((item) => (
|
||||
<Link to={item.href!}>
|
||||
<div
|
||||
|
|
@ -24,15 +25,20 @@ const SideBarButtonsComponent = ({
|
|||
data-testid={`sidebar-nav-${item.title}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
"!w-[200px] cursor-pointer justify-start gap-2 border border-transparent hover:border-border hover:bg-transparent",
|
||||
pathname === item.href
|
||||
? "border border-border bg-muted hover:bg-muted"
|
||||
: "border border-transparent hover:border-border hover:bg-transparent",
|
||||
"flex w-full shrink-0 justify-start gap-4",
|
||||
)}
|
||||
onClick={handleOpenNewFolderModal}
|
||||
>
|
||||
{item.title}
|
||||
{item.icon}
|
||||
<span className="block max-w-full truncate opacity-100">
|
||||
{item.title}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default SideBarButtonsComponent;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { FolderType } from "../../../../pages/MainPage/entities";
|
||||
import { addFolder, updateFolder } from "../../../../pages/MainPage/services";
|
||||
import { handleDownloadFolderFn } from "../../../../pages/MainPage/utils/handle-download-folder";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useFolderStore } from "../../../../stores/foldersStore";
|
||||
import { handleKeyDown } from "../../../../utils/reactflowUtils";
|
||||
import { cn } from "../../../../utils/utils";
|
||||
import DropdownButton from "../../../dropdownButtonComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../genericIconComponent";
|
||||
import { Button, buttonVariants } from "../../../ui/button";
|
||||
import { Input } from "../../../ui/input";
|
||||
import useFileDrop from "../../hooks/use-on-file-drop";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { handleDownloadFolderFn } from "../../../../pages/MainPage/utils/handle-download-folder";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
|
||||
type SideBarFoldersButtonsComponentProps = {
|
||||
folders: FolderType[];
|
||||
|
|
@ -18,22 +20,27 @@ type SideBarFoldersButtonsComponentProps = {
|
|||
handleChangeFolder?: (id: string) => void;
|
||||
handleEditFolder?: (item: FolderType) => void;
|
||||
handleDeleteFolder?: (item: FolderType) => void;
|
||||
handleAddFolder?: () => void;
|
||||
};
|
||||
const SideBarFoldersButtonsComponent = ({
|
||||
folders,
|
||||
pathname,
|
||||
handleAddFolder,
|
||||
handleChangeFolder,
|
||||
handleEditFolder,
|
||||
handleDeleteFolder,
|
||||
}: SideBarFoldersButtonsComponentProps) => {
|
||||
const refInput = useRef<HTMLInputElement>(null);
|
||||
const setFolders = useFolderStore((state) => state.setFolders);
|
||||
const folders = useFolderStore((state) => state.folders);
|
||||
const [foldersNames, setFoldersNames] = useState({});
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
const [editFolders, setEditFolderName] = useState(
|
||||
folders.map((obj) => ({ name: obj.name, edit: false })),
|
||||
);
|
||||
const uploadFolder = useFolderStore((state) => state.uploadFolder);
|
||||
const currentFolder = pathname.split("/");
|
||||
const urlWithoutPath = pathname.split("/").length < 4;
|
||||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||
const allFlows = useFlowsManagerStore((state) => state.allFlows);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const folderIdDragging = useFolderStore((state) => state.folderIdDragging);
|
||||
|
||||
const checkPathName = (itemId: string) => {
|
||||
if (urlWithoutPath && itemId === myCollectionId) {
|
||||
|
|
@ -62,106 +69,240 @@ const SideBarFoldersButtonsComponent = ({
|
|||
handleDownloadFolderFn(id);
|
||||
};
|
||||
|
||||
function addNewFolder() {
|
||||
addFolder({ name: "New Folder", parent_id: null, description: "" }).then(
|
||||
(res) => {
|
||||
getFoldersApi(true);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function handleEditFolderName(e, name): void {
|
||||
const {
|
||||
target: { value },
|
||||
} = e;
|
||||
setFoldersNames((old) => ({
|
||||
...old,
|
||||
[name]: value,
|
||||
}));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
folders.map((obj) => ({ name: obj.name, edit: false }));
|
||||
}, [folders]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex shrink-0 items-center justify-between">
|
||||
<DropdownButton
|
||||
firstButtonName="New Folder"
|
||||
onFirstBtnClick={handleAddFolder!}
|
||||
options={[]}
|
||||
plusButton={true}
|
||||
dropdownOptions={false}
|
||||
/>
|
||||
<div className="flex shrink-0 items-center justify-between gap-2">
|
||||
<div className="flex-1 self-start text-lg font-semibold">Folders</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleUploadFlowsToFolder}
|
||||
className="px-7"
|
||||
size="icon"
|
||||
className="px-2"
|
||||
onClick={addNewFolder}
|
||||
data-testid="add-folder-button"
|
||||
>
|
||||
<ForwardedIconComponent
|
||||
name="Upload"
|
||||
className="main-page-nav-button"
|
||||
/>
|
||||
Upload
|
||||
<ForwardedIconComponent name="FolderPlus" className="w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
size="icon"
|
||||
className="px-2"
|
||||
onClick={handleUploadFlowsToFolder}
|
||||
data-testid="upload-folder-button"
|
||||
>
|
||||
<ForwardedIconComponent name="Upload" className="w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 overflow-auto lg:h-[70vh] lg:flex-col">
|
||||
<>
|
||||
{folders.map((item, index) => (
|
||||
<div
|
||||
onDragOver={dragOver}
|
||||
onDragEnter={dragEnter}
|
||||
onDragLeave={dragLeave}
|
||||
onDrop={(e) => onDrop(e, item.id!)}
|
||||
key={item.id}
|
||||
data-testid={`sidebar-nav-${item.name}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
checkPathName(item.id!)
|
||||
? "border border-border bg-muted hover:bg-muted"
|
||||
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
|
||||
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
|
||||
)}
|
||||
onClick={() => handleChangeFolder!(item.id!)}
|
||||
>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<IconComponent
|
||||
name={"folder"}
|
||||
className="mr-2 w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
|
||||
/>
|
||||
<span className="block max-w-full truncate opacity-100">
|
||||
{item.name}
|
||||
</span>
|
||||
<div className="flex-1" />
|
||||
{index > 0 && (
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleDeleteFolder!(item);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"trash"}
|
||||
className=" w-4 stroke-[1.5]"
|
||||
/>
|
||||
</Button>
|
||||
{folders.map((item, index) => {
|
||||
const editFolderName = editFolders?.filter(
|
||||
(folder) => folder.name === item.name,
|
||||
)[0];
|
||||
return (
|
||||
<div
|
||||
onDragOver={(e) => dragOver(e, item.id!)}
|
||||
onDragEnter={(e) => dragEnter(e, item.id!)}
|
||||
onDragLeave={dragLeave}
|
||||
onDrop={(e) => onDrop(e, item.id!)}
|
||||
key={item.id}
|
||||
data-testid={`sidebar-nav-${item.name}`}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "ghost" }),
|
||||
checkPathName(item.id!)
|
||||
? "border border-border bg-muted hover:bg-muted"
|
||||
: "border hover:bg-transparent lg:border-transparent lg:hover:border-border",
|
||||
"group flex w-full shrink-0 cursor-pointer gap-2 opacity-100 lg:min-w-full",
|
||||
folderIdDragging === item.id! ? "bg-border" : "",
|
||||
)}
|
||||
{index > 0 && (
|
||||
onClick={() => handleChangeFolder!(item.id!)}
|
||||
>
|
||||
<div
|
||||
onDoubleClick={(event) => {
|
||||
if (item.name === "My Projects") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!foldersNames[item.name]) {
|
||||
setFoldersNames({ [item.name]: item.name });
|
||||
}
|
||||
|
||||
if (
|
||||
editFolders.find((obj) => obj.name === item.name)?.name
|
||||
) {
|
||||
const newEditFolders = editFolders.map((obj) => {
|
||||
if (obj.name === item.name) {
|
||||
return { name: item.name, edit: true };
|
||||
}
|
||||
return { name: obj.name, edit: false };
|
||||
});
|
||||
setEditFolderName(newEditFolders);
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
setEditFolderName((old) => [
|
||||
...old,
|
||||
{ name: item.name, edit: true },
|
||||
]);
|
||||
setFoldersNames((oldFolder) => ({
|
||||
...oldFolder,
|
||||
[item.name]: item.name,
|
||||
}));
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
className="flex w-full items-center gap-4"
|
||||
>
|
||||
<IconComponent
|
||||
name={"folder"}
|
||||
className="w-4 flex-shrink-0 justify-start stroke-[1.5] opacity-100"
|
||||
/>
|
||||
{editFolderName?.edit ? (
|
||||
<div>
|
||||
<Input
|
||||
className="w-36"
|
||||
onChange={(e) => {
|
||||
handleEditFolderName(e, item.name);
|
||||
}}
|
||||
ref={refInput}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
const newEditFolders = editFolders.map((obj) => {
|
||||
if (obj.name === item.name) {
|
||||
return { name: item.name, edit: false };
|
||||
}
|
||||
return { name: obj.name, edit: false };
|
||||
});
|
||||
setEditFolderName(newEditFolders);
|
||||
setFoldersNames({});
|
||||
setEditFolderName(
|
||||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
})),
|
||||
);
|
||||
}
|
||||
if (e.key === "Enter") {
|
||||
refInput.current?.blur();
|
||||
}
|
||||
handleKeyDown(e, e.key, "");
|
||||
}}
|
||||
autoFocus={true}
|
||||
onBlur={async () => {
|
||||
const newEditFolders = editFolders.map((obj) => {
|
||||
if (obj.name === item.name) {
|
||||
return { name: item.name, edit: false };
|
||||
}
|
||||
return { name: obj.name, edit: false };
|
||||
});
|
||||
setEditFolderName(newEditFolders);
|
||||
if (foldersNames[item.name].trim() !== "") {
|
||||
setFoldersNames((old) => ({
|
||||
...old,
|
||||
[item.name]: foldersNames[item.name],
|
||||
}));
|
||||
const body = {
|
||||
...item,
|
||||
name: foldersNames[item.name],
|
||||
flows: item.flows?.length > 0 ? item.flows : [],
|
||||
components:
|
||||
item.components?.length > 0
|
||||
? item.components
|
||||
: [],
|
||||
};
|
||||
const updatedFolder = await updateFolder(
|
||||
body,
|
||||
item.id!,
|
||||
);
|
||||
const updateFolders = folders.filter(
|
||||
(f) => f.name !== item.name,
|
||||
);
|
||||
setFolders([...updateFolders, updatedFolder]);
|
||||
setFoldersNames({});
|
||||
setEditFolderName(
|
||||
folders.map((obj) => ({
|
||||
name: obj.name,
|
||||
edit: false,
|
||||
})),
|
||||
);
|
||||
} else {
|
||||
setFoldersNames((old) => ({
|
||||
...old,
|
||||
[item.name]: item.name,
|
||||
}));
|
||||
}
|
||||
}}
|
||||
value={foldersNames[item.name]}
|
||||
id={`input-folder-${item.name}`}
|
||||
data-testid={`input-folder`}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<span className="block w-full truncate opacity-100">
|
||||
{item.name}
|
||||
</span>
|
||||
)}
|
||||
{index > 0 && (
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleDeleteFolder!(item);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"trash"}
|
||||
className=" w-4 stroke-[1.5]"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleEditFolder!(item);
|
||||
handleDownloadFolder(item.id!);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
size="none"
|
||||
variant="none"
|
||||
>
|
||||
<IconComponent
|
||||
name={"pencil"}
|
||||
name={"Download"}
|
||||
className=" w-4 stroke-[1.5] text-white "
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="hidden p-0 hover:bg-white group-hover:block hover:dark:bg-[#0c101a00]"
|
||||
onClick={(e) => {
|
||||
handleDownloadFolder(item.id!);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}}
|
||||
variant={"ghost"}
|
||||
>
|
||||
<IconComponent
|
||||
name={"Download"}
|
||||
className=" w-4 stroke-[1.5] text-white "
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -7,13 +7,16 @@ import { uploadFlowsFromFolders } from "../../../pages/MainPage/services";
|
|||
import useAlertStore from "../../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../../stores/flowsManagerStore";
|
||||
import { useFolderStore } from "../../../stores/foldersStore";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
import { addVersionToDuplicates } from "../../../utils/reactflowUtils";
|
||||
|
||||
const useFileDrop = (folderId, folderChangeCallback) => {
|
||||
const setFolderDragging = useFolderStore((state) => state.setFolderDragging);
|
||||
const setFolderIdDragging = useFolderStore(
|
||||
(state) => state.setFolderIdDragging,
|
||||
);
|
||||
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const refreshFlows = useFlowsManagerStore((state) => state.refreshFlows);
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
|
||||
const triggerFolderChange = (folderId) => {
|
||||
|
|
@ -42,12 +45,14 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
| React.DragEvent<HTMLDivElement>
|
||||
| React.DragEvent<HTMLButtonElement>
|
||||
| React.DragEvent<HTMLAnchorElement>,
|
||||
folderId: string,
|
||||
) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (e.dataTransfer.types.some((types) => types === "Files")) {
|
||||
setFolderDragging(true);
|
||||
}
|
||||
setFolderIdDragging(folderId);
|
||||
};
|
||||
|
||||
const dragEnter = (
|
||||
|
|
@ -55,10 +60,12 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
| React.DragEvent<HTMLDivElement>
|
||||
| React.DragEvent<HTMLButtonElement>
|
||||
| React.DragEvent<HTMLAnchorElement>,
|
||||
folderId: string,
|
||||
) => {
|
||||
if (e.dataTransfer.types.some((types) => types === "Files")) {
|
||||
setFolderDragging(true);
|
||||
}
|
||||
setFolderIdDragging(folderId);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
|
|
@ -71,6 +78,7 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
e.preventDefault();
|
||||
if (e.target === e.currentTarget) {
|
||||
setFolderDragging(false);
|
||||
setFolderIdDragging("");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -92,7 +100,6 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
|
||||
e.preventDefault();
|
||||
handleFileDrop(e);
|
||||
setFolderDragging(false);
|
||||
};
|
||||
|
||||
const uploadFromDragCard = (flowId, folderId) => {
|
||||
|
|
@ -101,11 +108,15 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
if (!selectedFlow) {
|
||||
throw new Error("Flow not found");
|
||||
}
|
||||
const updatedFlow = { ...selectedFlow, folder_id: folderId };
|
||||
|
||||
const newName = addVersionToDuplicates(updatedFlow, flows);
|
||||
|
||||
updatedFlow.name = newName;
|
||||
|
||||
setFolderDragging(false);
|
||||
setFolderIdDragging("");
|
||||
|
||||
const updatedFlow: FlowType = {
|
||||
...selectedFlow,
|
||||
folder_id: folderId,
|
||||
};
|
||||
updateFlowInDatabase(updatedFlow).then(() => {
|
||||
getFoldersApi(true);
|
||||
triggerFolderChange(folderId);
|
||||
|
|
@ -115,11 +126,11 @@ const useFileDrop = (folderId, folderChangeCallback) => {
|
|||
const uploadFormData = (data) => {
|
||||
const formData = new FormData();
|
||||
formData.append("file", data);
|
||||
|
||||
setFolderDragging(false);
|
||||
setFolderIdDragging("");
|
||||
uploadFlowsFromFolders(formData).then(() => {
|
||||
getFoldersApi(true);
|
||||
triggerFolderChange(folderId);
|
||||
refreshFlows();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import { cn } from "../../utils/utils";
|
|||
import HorizontalScrollFadeComponent from "../horizontalScrollFadeComponent";
|
||||
import SideBarButtonsComponent from "./components/sideBarButtons";
|
||||
import SideBarFoldersButtonsComponent from "./components/sideBarFolderButtons";
|
||||
import { addFolder } from "../../pages/MainPage/services";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
|
||||
type SidebarNavProps = {
|
||||
items: {
|
||||
|
|
@ -22,7 +25,6 @@ type SidebarNavProps = {
|
|||
export default function SidebarNav({
|
||||
className,
|
||||
items,
|
||||
handleOpenNewFolderModal,
|
||||
handleChangeFolder,
|
||||
handleEditFolder,
|
||||
handleDeleteFolder,
|
||||
|
|
@ -39,21 +41,20 @@ export default function SidebarNav({
|
|||
return (
|
||||
<nav className={cn(className)} {...props}>
|
||||
<HorizontalScrollFadeComponent>
|
||||
<SideBarButtonsComponent
|
||||
items={items}
|
||||
pathname={pathname}
|
||||
handleOpenNewFolderModal={handleOpenNewFolderModal}
|
||||
/>
|
||||
|
||||
{!loadingFolders && folders?.length > 0 && isFolderPath && (
|
||||
<SideBarFoldersButtonsComponent
|
||||
folders={folders}
|
||||
pathname={pathname}
|
||||
handleChangeFolder={handleChangeFolder}
|
||||
handleEditFolder={handleEditFolder}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
handleAddFolder={handleOpenNewFolderModal}
|
||||
/>
|
||||
{items.length > 0 ? (
|
||||
<SideBarButtonsComponent items={items} pathname={pathname} />
|
||||
) : (
|
||||
!loadingFolders &&
|
||||
folders?.length > 0 &&
|
||||
isFolderPath && (
|
||||
<SideBarFoldersButtonsComponent
|
||||
folders={folders}
|
||||
pathname={pathname}
|
||||
handleChangeFolder={handleChangeFolder}
|
||||
handleEditFolder={handleEditFolder}
|
||||
handleDeleteFolder={handleDeleteFolder}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</HorizontalScrollFadeComponent>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import DateReader from "../dateReaderComponent";
|
|||
import NumberReader from "../numberReader";
|
||||
import ObjectRender from "../objectRender";
|
||||
import StringReader from "../stringReaderComponent";
|
||||
import { Label } from "../ui/label";
|
||||
import { Badge } from "../ui/badge";
|
||||
|
||||
export default function TableAutoCellRender({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import { cn } from "../../../../utils/utils";
|
||||
import ShadTooltip from "../../../shadTooltipComponent";
|
||||
import { Toggle } from "../../../ui/toggle";
|
||||
|
||||
export default function ResetColumns({
|
||||
resetGrid,
|
||||
}: {
|
||||
resetGrid: () => void;
|
||||
}): JSX.Element {
|
||||
return (
|
||||
/*<div className="absolute left-2 bottom-1 cursor-pointer">
|
||||
<div
|
||||
className="flex h-10 items-center justify-center px-2 pl-3 rounded-md border border-ring/60 text-sm text-[#bccadc] ring-offset-background placeholder:text-muted-foreground hover:bg-muted focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
onClick={() => setShow(!show)}
|
||||
>
|
||||
<ForwardedIconComponent name="Settings"></ForwardedIconComponent>
|
||||
<ForwardedIconComponent name={show ? "ChevronLeft" : "ChevronRight"} className="transition-all"></ForwardedIconComponent>
|
||||
</div>
|
||||
</div>*/
|
||||
<div className={cn("absolute bottom-4 left-6")}>
|
||||
<span
|
||||
className="cursor-pointer underline"
|
||||
onClick={() => {
|
||||
resetGrid();
|
||||
}}
|
||||
>
|
||||
Reset Columns
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,22 +1,27 @@
|
|||
import "ag-grid-community/styles/ag-grid.css"; // Mandatory CSS required by the grid
|
||||
import "ag-grid-community/styles/ag-theme-quartz.css"; // Optional Theme applied to the grid
|
||||
import { AgGridReact, AgGridReactProps } from "ag-grid-react";
|
||||
import { ElementRef, forwardRef, useCallback } from "react";
|
||||
import { ElementRef, forwardRef, useEffect, useRef } from "react";
|
||||
import {
|
||||
DEFAULT_TABLE_ALERT_MSG,
|
||||
DEFAULT_TABLE_ALERT_TITLE,
|
||||
} from "../../constants/constants";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
import "../../style/ag-theme-shadcn.css"; // Custom CSS applied to the grid
|
||||
import { cn } from "../../utils/utils";
|
||||
import { cn, toTitleCase } from "../../utils/utils";
|
||||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
import { Alert, AlertDescription, AlertTitle } from "../ui/alert";
|
||||
import { Toggle } from "../ui/toggle";
|
||||
import ShadTooltip from "../shadTooltipComponent";
|
||||
import resetGrid from "./utils/reset-grid-columns";
|
||||
import ResetColumns from "./components/ResetColumns";
|
||||
|
||||
interface TableComponentProps extends AgGridReactProps {
|
||||
columnDefs: NonNullable<AgGridReactProps["columnDefs"]>;
|
||||
rowData: NonNullable<AgGridReactProps["rowData"]>;
|
||||
alertTitle?: string;
|
||||
alertDescription?: string;
|
||||
editable?: boolean | string[];
|
||||
}
|
||||
|
||||
const TableComponent = forwardRef<
|
||||
|
|
@ -31,51 +36,66 @@ const TableComponent = forwardRef<
|
|||
},
|
||||
ref,
|
||||
) => {
|
||||
let colDef = props.columnDefs.map((col, index) => {
|
||||
let newCol = {
|
||||
...col,
|
||||
headerName: toTitleCase(col.headerName),
|
||||
};
|
||||
if (index === props.columnDefs.length - 1) {
|
||||
newCol = {
|
||||
...newCol,
|
||||
resizable: false,
|
||||
};
|
||||
}
|
||||
if (props.onSelectionChanged && index === 0) {
|
||||
newCol = {
|
||||
...newCol,
|
||||
checkboxSelection: true,
|
||||
headerCheckboxSelection: true,
|
||||
headerCheckboxSelectionFilteredOnly: true,
|
||||
};
|
||||
}
|
||||
if (
|
||||
(typeof props.editable === "boolean" && props.editable) ||
|
||||
(Array.isArray(props.editable) &&
|
||||
props.editable.includes(newCol.headerName ?? ""))
|
||||
) {
|
||||
newCol = {
|
||||
...newCol,
|
||||
editable: true,
|
||||
};
|
||||
}
|
||||
return newCol;
|
||||
});
|
||||
const gridRef = useRef(null);
|
||||
// @ts-ignore
|
||||
const realRef = ref?.current ? ref : gridRef;
|
||||
const dark = useDarkStore((state) => state.dark);
|
||||
var currentRowHeight: number;
|
||||
var minRowHeight = 25;
|
||||
const initialColumnDefs = useRef(colDef);
|
||||
|
||||
const getRowHeight = useCallback(() => {
|
||||
return currentRowHeight;
|
||||
}, []);
|
||||
|
||||
const onGridReady = useCallback((params: any) => {
|
||||
minRowHeight = params.api.getSizesForCurrentTheme().rowHeight;
|
||||
currentRowHeight = minRowHeight;
|
||||
}, []);
|
||||
|
||||
const updateRowHeight = (params: { api: any }) => {
|
||||
const bodyViewport = document.querySelector(".ag-body-viewport");
|
||||
if (!bodyViewport) {
|
||||
return;
|
||||
}
|
||||
var gridHeight = bodyViewport.clientHeight;
|
||||
var renderedRowCount = params.api.getDisplayedRowCount();
|
||||
|
||||
if (renderedRowCount * minRowHeight >= gridHeight) {
|
||||
if (currentRowHeight !== minRowHeight) {
|
||||
currentRowHeight = minRowHeight;
|
||||
params.api.resetRowHeights();
|
||||
}
|
||||
} else {
|
||||
currentRowHeight = Math.floor(gridHeight / renderedRowCount);
|
||||
params.api.resetRowHeights();
|
||||
}
|
||||
const makeLastColumnNonResizable = (columnDefs) => {
|
||||
columnDefs.forEach((colDef, index) => {
|
||||
colDef.resizable = index !== columnDefs.length - 1;
|
||||
});
|
||||
return columnDefs;
|
||||
};
|
||||
|
||||
const onFirstDataRendered = useCallback(
|
||||
(params: any) => {
|
||||
updateRowHeight(params);
|
||||
},
|
||||
[updateRowHeight],
|
||||
);
|
||||
const onGridReady = (params) => {
|
||||
// @ts-ignore
|
||||
realRef.current = params;
|
||||
const updatedColumnDefs = makeLastColumnNonResizable([...colDef]);
|
||||
params.api.setColumnDefs(updatedColumnDefs);
|
||||
initialColumnDefs.current = params.api.getColumnDefs();
|
||||
if (props.onGridReady) props.onGridReady(params);
|
||||
};
|
||||
|
||||
const onGridSizeChanged = useCallback(
|
||||
(params: any) => {
|
||||
updateRowHeight(params);
|
||||
},
|
||||
[updateRowHeight],
|
||||
);
|
||||
const onColumnMoved = (params) => {
|
||||
const updatedColumnDefs = makeLastColumnNonResizable(
|
||||
params.columnApi.getAllGridColumns().map((col) => col.getColDef()),
|
||||
);
|
||||
params.api.setColumnDefs(updatedColumnDefs);
|
||||
if (props.onColumnMoved) props.onColumnMoved(params);
|
||||
};
|
||||
|
||||
if (props.rowData.length === 0) {
|
||||
return (
|
||||
|
|
@ -91,26 +111,27 @@ const TableComponent = forwardRef<
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
dark ? "ag-theme-quartz-dark" : "ag-theme-quartz",
|
||||
"ag-theme-shadcn flex h-full flex-col",
|
||||
"relative",
|
||||
)} // applying the grid theme
|
||||
>
|
||||
<AgGridReact
|
||||
{...props}
|
||||
className={cn(props.className, "custom-scroll")}
|
||||
getRowHeight={getRowHeight}
|
||||
onGridReady={onGridReady}
|
||||
onFirstDataRendered={onFirstDataRendered}
|
||||
onGridSizeChanged={onGridSizeChanged}
|
||||
defaultColDef={{
|
||||
minWidth: 100,
|
||||
}}
|
||||
ref={ref}
|
||||
columnDefs={colDef}
|
||||
ref={realRef}
|
||||
pagination={true}
|
||||
onGridReady={onGridReady}
|
||||
onColumnMoved={onColumnMoved}
|
||||
/>
|
||||
<ResetColumns resetGrid={() => resetGrid(realRef, initialColumnDefs)} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
export default function resetGrid(ref, initialColumnDefs) {
|
||||
if (ref?.current && ref?.current.api) {
|
||||
ref.current.api.resetColumnState();
|
||||
if (initialColumnDefs.current) {
|
||||
const resetColumns = ref.current.api.applyColumnState({
|
||||
state: initialColumnDefs.current,
|
||||
applyOrder: true,
|
||||
});
|
||||
return resetColumns;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import * as AccordionPrimitive from "@radix-ui/react-accordion";
|
|||
import { ChevronDownIcon } from "@radix-ui/react-icons";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
import ShadTooltip from "../shadTooltipComponent";
|
||||
|
||||
const Accordion = AccordionPrimitive.Root;
|
||||
|
||||
|
|
@ -22,17 +23,33 @@ AccordionItem.displayName = "AccordionItem";
|
|||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
>(({ className, children, disabled, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger asChild ref={ref} {...props}>
|
||||
<AccordionPrimitive.Trigger
|
||||
disabled={disabled}
|
||||
asChild
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-1 cursor-pointer items-center justify-between py-4 text-sm font-medium transition-all [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
<ChevronDownIcon className="h-4 w-4 font-bold text-primary transition-transform duration-200" />
|
||||
<ShadTooltip
|
||||
styleClasses="z-50"
|
||||
content={disabled ? "Empty" : ""}
|
||||
side="top"
|
||||
>
|
||||
<ChevronDownIcon
|
||||
className={cn(
|
||||
"h-4 w-4 font-bold transition-transform duration-200",
|
||||
disabled ? "text-muted-foreground" : "text-primary",
|
||||
)}
|
||||
/>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
|
|
@ -47,7 +64,7 @@ const AccordionContent = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down overflow-hidden text-sm",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const alertVariants = cva(
|
||||
|
|
@ -55,4 +55,4 @@ const AlertDescription = React.forwardRef<
|
|||
));
|
||||
AlertDescription.displayName = "AlertDescription";
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription };
|
||||
export { Alert, AlertDescription, AlertTitle };
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ import { Slot } from "@radix-ui/react-slot";
|
|||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import * as React from "react";
|
||||
import { cn } from "../../utils/utils";
|
||||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
|
||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
|
@ -19,6 +20,7 @@ const buttonVariants = cva(
|
|||
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "underline-offset-4 hover:underline text-primary",
|
||||
none: "",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 py-2 px-4",
|
||||
|
|
@ -26,19 +28,21 @@ const buttonVariants = cva(
|
|||
xs: "py-0.5 px-3 rounded-md",
|
||||
lg: "h-11 px-8 rounded-md",
|
||||
icon: "py-1 px-1 rounded-md",
|
||||
none: "",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
loading?: boolean;
|
||||
}
|
||||
|
||||
function toTitleCase(text: string) {
|
||||
|
|
@ -49,21 +53,49 @@ function toTitleCase(text: string) {
|
|||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, children, ...props }, ref) => {
|
||||
(
|
||||
{
|
||||
className,
|
||||
variant,
|
||||
size,
|
||||
loading,
|
||||
disabled,
|
||||
asChild = false,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
let newChildren = children;
|
||||
if (typeof children === "string") {
|
||||
newChildren = toTitleCase(children);
|
||||
}
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
children={newChildren}
|
||||
{...props}
|
||||
/>
|
||||
<>
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
disabled={loading || disabled}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{loading ? (
|
||||
<span className="relative">
|
||||
<span className="invisible">{newChildren}</span>
|
||||
<span className="absolute inset-0 flex items-center justify-center">
|
||||
<ForwardedIconComponent
|
||||
name={"Loader2"}
|
||||
className={"animate-spin"}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
newChildren
|
||||
)}
|
||||
</Comp>
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
Button.displayName = "Button";
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ const Card = React.forwardRef<
|
|||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-col justify-between rounded-lg border bg-muted text-card-foreground shadow-sm transition-all hover:shadow-lg",
|
||||
className
|
||||
"flex flex-col justify-between rounded-lg border bg-muted text-card-foreground shadow-sm transition-all",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"text-base font-semibold leading-tight tracking-tight",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
|||
45
src/frontend/src/components/ui/toggle.tsx
Normal file
45
src/frontend/src/components/ui/toggle.tsx
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import * as TogglePrimitive from "@radix-ui/react-toggle";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "../../utils/utils";
|
||||
|
||||
const toggleVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-transparent",
|
||||
outline:
|
||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-3",
|
||||
sm: "h-9 px-2.5",
|
||||
lg: "h-11 px-5",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const Toggle = React.forwardRef<
|
||||
React.ElementRef<typeof TogglePrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
||||
VariantProps<typeof toggleVariants>
|
||||
>(({ className, variant, size, ...props }, ref) => (
|
||||
<TogglePrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(toggleVariants({ variant, size, className }))}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
Toggle.displayName = TogglePrimitive.Root.displayName;
|
||||
|
||||
export { Toggle, toggleVariants };
|
||||
|
|
@ -20,7 +20,7 @@ const TooltipContent = React.forwardRef<
|
|||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
@ -28,4 +28,26 @@ const TooltipContent = React.forwardRef<
|
|||
));
|
||||
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
|
||||
const TooltipContentWithoutPortal = React.forwardRef<
|
||||
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<TooltipPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-45 overflow-y-auto rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-50 data-[side=bottom]:slide-in-from-top-1 data-[side=left]:slide-in-from-right-1 data-[side=right]:slide-in-from-left-1 data-[side=top]:slide-in-from-bottom-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TooltipContentWithoutPortal.displayName = TooltipPrimitive.Content.displayName;
|
||||
|
||||
export {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipContentWithoutPortal,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -590,6 +590,7 @@ export const CONTROL_PATCH_USER_STATE = {
|
|||
password: "",
|
||||
cnfPassword: "",
|
||||
gradient: "",
|
||||
apikey: "",
|
||||
};
|
||||
|
||||
export const CONTROL_LOGIN_STATE = {
|
||||
|
|
@ -606,84 +607,6 @@ export const CONTROL_NEW_USER = {
|
|||
|
||||
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 <body> 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 <body> 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],
|
||||
},
|
||||
];
|
||||
}
|
||||
export const FETCH_ERROR_MESSAGE = "Couldn't establish a connection.";
|
||||
export const FETCH_ERROR_DESCRIPION =
|
||||
"Check if everything is working properly and try again.";
|
||||
|
|
@ -800,8 +723,21 @@ export const NATIVE_CATEGORIES = [
|
|||
"agents",
|
||||
];
|
||||
|
||||
export const AUTHORIZED_DUPLICATE_REQUESTS = [
|
||||
"/health",
|
||||
"/flows",
|
||||
"/logout",
|
||||
"/refresh",
|
||||
"/login",
|
||||
"/auto_login",
|
||||
];
|
||||
|
||||
export const SAVE_DEBOUNCE_TIME = 300;
|
||||
|
||||
export const DEFAULT_TABLE_ALERT_MSG = `Oops! It seems there's no data to display right now. Please check back later.`;
|
||||
|
||||
export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
||||
|
||||
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
|
||||
|
||||
export const MAX_BATCH_SIZE = 50;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { BuildStatus } from "../../constants/enums";
|
|||
import { AuthContext } from "../../contexts/authContext";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import { checkDuplicateRequestAndStoreRequest } from "./helpers/check-duplicate-requests";
|
||||
|
||||
// Create a new Axios instance
|
||||
const api: AxiosInstance = axios.create({
|
||||
|
|
@ -80,22 +81,12 @@ function ApiInterceptor() {
|
|||
// Request interceptor to add access token to every request
|
||||
const requestInterceptor = api.interceptors.request.use(
|
||||
(config) => {
|
||||
const lastUrl = localStorage.getItem("lastUrlCalled");
|
||||
const checkRequest = checkDuplicateRequestAndStoreRequest(config);
|
||||
|
||||
if (
|
||||
config?.url === lastUrl &&
|
||||
config?.url !== "/health" &&
|
||||
config?.method === "get"
|
||||
) {
|
||||
return Promise.reject(`
|
||||
Duplicate request detected.
|
||||
URL: ${config.url}
|
||||
Method: ${config.method}
|
||||
`);
|
||||
if (!checkRequest) {
|
||||
return Promise.reject("Duplicate request.");
|
||||
}
|
||||
|
||||
localStorage.setItem("lastUrlCalled", config.url ?? "");
|
||||
|
||||
const accessToken = cookies.get("access_token_lf");
|
||||
if (accessToken && !isAuthorizedURL(config?.url)) {
|
||||
config.headers["Authorization"] = `Bearer ${accessToken}`;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import { AUTHORIZED_DUPLICATE_REQUESTS } from "../../../constants/constants";
|
||||
|
||||
export function checkDuplicateRequestAndStoreRequest(config) {
|
||||
const lastUrl = localStorage.getItem("lastUrlCalled");
|
||||
const lastMethodCalled = localStorage.getItem("lastMethodCalled");
|
||||
const lastRequestTime = localStorage.getItem("lastRequestTime");
|
||||
|
||||
const currentTime = Date.now();
|
||||
|
||||
const isContained = AUTHORIZED_DUPLICATE_REQUESTS.some((request) =>
|
||||
config?.url!.includes(request),
|
||||
);
|
||||
|
||||
if (
|
||||
config?.url === lastUrl &&
|
||||
!isContained &&
|
||||
lastMethodCalled === config.method &&
|
||||
lastMethodCalled === "get" && // Assuming you want to check only for GET requests
|
||||
lastRequestTime &&
|
||||
currentTime - parseInt(lastRequestTime, 10) < 800
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
localStorage.setItem("lastUrlCalled", config.url ?? "");
|
||||
localStorage.setItem("lastMethodCalled", config.method ?? "");
|
||||
localStorage.setItem("lastRequestTime", currentTime.toString());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||
import { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import { Edge, Node, ReactFlowJsonObject } from "reactflow";
|
||||
import { BASE_URL_API } from "../../constants/constants";
|
||||
import { BASE_URL_API, MAX_BATCH_SIZE } from "../../constants/constants";
|
||||
import { api } from "../../controllers/API/api";
|
||||
import {
|
||||
APIObjectType,
|
||||
|
|
@ -28,6 +28,7 @@ import {
|
|||
UploadFileTypeAPI,
|
||||
errorsTypeAPI,
|
||||
} from "./../../types/api/index";
|
||||
import { Message } from "../../types/messages";
|
||||
|
||||
/**
|
||||
* Fetches all objects from the API endpoint.
|
||||
|
|
@ -157,6 +158,7 @@ export async function updateFlowInDatabase(
|
|||
data: updatedFlow.data,
|
||||
description: updatedFlow.description,
|
||||
folder_id: updatedFlow.folder_id === "" ? null : updatedFlow.folder_id,
|
||||
endpoint_name: updatedFlow.endpoint_name,
|
||||
});
|
||||
|
||||
if (response?.status !== 200) {
|
||||
|
|
@ -1003,12 +1005,41 @@ export async function deleteFlowPool(
|
|||
return await api.delete(`${BASE_URL_API}monitor/builds`, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes multiple flow components by their IDs.
|
||||
* @param flowIds - An array of flow IDs to be deleted.
|
||||
* @param token - The authorization token for the API request.
|
||||
* @returns A promise that resolves to an array of AxiosResponse objects representing the delete responses.
|
||||
*/
|
||||
export async function multipleDeleteFlowsComponents(
|
||||
flowIds: string[],
|
||||
): Promise<AxiosResponse<any>> {
|
||||
return await api.post(`${BASE_URL_API}flows/multiple_delete/`, {
|
||||
flow_ids: flowIds,
|
||||
});
|
||||
): Promise<AxiosResponse<any>[]> {
|
||||
const batches: string[][] = [];
|
||||
|
||||
// Split the flowIds into batches
|
||||
for (let i = 0; i < flowIds.length; i += MAX_BATCH_SIZE) {
|
||||
batches.push(flowIds.slice(i, i + MAX_BATCH_SIZE));
|
||||
}
|
||||
|
||||
// Function to delete a batch of flow IDs
|
||||
const deleteBatch = async (batch: string[]): Promise<AxiosResponse<any>> => {
|
||||
try {
|
||||
return await api.delete(`${BASE_URL_API}flows/`, {
|
||||
data: batch,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting flows:", error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// Execute all delete requests
|
||||
const responses: Promise<AxiosResponse<any>>[] = batches.map((batch) =>
|
||||
deleteBatch(batch),
|
||||
);
|
||||
|
||||
// Return the responses after all requests are completed
|
||||
return Promise.all(responses);
|
||||
}
|
||||
|
||||
export async function getTransactionTable(
|
||||
|
|
@ -1027,16 +1058,47 @@ export async function getTransactionTable(
|
|||
}
|
||||
|
||||
export async function getMessagesTable(
|
||||
id: string,
|
||||
mode: "intersection" | "union",
|
||||
id?: string,
|
||||
excludedFields?: string[],
|
||||
params = {},
|
||||
): Promise<{ rows: Array<object>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
): Promise<{ rows: Array<Message>; columns: Array<ColDef | ColGroupDef> }> {
|
||||
const config = {};
|
||||
config["params"] = { flow_id: id };
|
||||
if (id) {
|
||||
config["params"] = { flow_id: id };
|
||||
}
|
||||
if (params) {
|
||||
config["params"] = { ...config["params"], ...params };
|
||||
}
|
||||
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
|
||||
const columns = extractColumnsFromRows(rows.data, mode);
|
||||
const columns = extractColumnsFromRows(rows.data, mode, excludedFields);
|
||||
return { rows: rows.data, columns };
|
||||
}
|
||||
|
||||
export async function getSessions(id?: string): Promise<Array<string>> {
|
||||
const config = {};
|
||||
if (id) {
|
||||
config["params"] = { flow_id: id };
|
||||
}
|
||||
const rows = await api.get(`${BASE_URL_API}monitor/messages`, config);
|
||||
const sessions = new Set<string>();
|
||||
rows.data.forEach((row) => {
|
||||
sessions.add(row.session_id);
|
||||
});
|
||||
return Array.from(sessions);
|
||||
}
|
||||
|
||||
export async function deleteMessagesFn(ids: number[]) {
|
||||
try {
|
||||
return await api.delete(`${BASE_URL_API}monitor/messages`, {
|
||||
data: ids,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting flows:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateMessageApi(data: Message) {
|
||||
return await api.post(`${BASE_URL_API}monitor/messages/${data.index}`, data);
|
||||
}
|
||||
|
|
|
|||
32
src/frontend/src/controllers/API/utils.tsx
Normal file
32
src/frontend/src/controllers/API/utils.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import axios from "axios";
|
||||
import { BASE_URL_API } from "../../constants/constants";
|
||||
|
||||
/**
|
||||
* Fetches the configuration data from the API.
|
||||
* @returns {Promise<any>} A promise that resolves to the configuration data.
|
||||
* @throws {Error} If there was an error fetching the configuration data.
|
||||
*/
|
||||
export async function fetchConfig() {
|
||||
try {
|
||||
const response = await axios.get(`${BASE_URL_API}config`);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch configuration:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up default configurations for Axios.
|
||||
* Fetches the timeout configuration and sets it as the default timeout for Axios requests.
|
||||
*/
|
||||
export async function setupAxiosDefaults() {
|
||||
const config = await fetchConfig();
|
||||
// Create Axios instance with the fetched timeout configuration
|
||||
|
||||
const timeoutInMilliseconds = config.frontend_timeout
|
||||
? config.frontend_timeout * 1000
|
||||
: 30000;
|
||||
axios.defaults.baseURL = "";
|
||||
axios.defaults.timeout = timeoutInMilliseconds;
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const TEXT_FIELD_TYPES: string[] = ["str", "SecretStr"];
|
||||
|
|
@ -46,6 +46,7 @@ import useHandleOnNewValue from "../../../hooks/use-handle-new-value";
|
|||
import useHandleNodeClass from "../../../hooks/use-handle-node-class";
|
||||
import useHandleRefreshButtonPress from "../../../hooks/use-handle-refresh-buttons";
|
||||
import TooltipRenderComponent from "../tooltipRenderComponent";
|
||||
import { TEXT_FIELD_TYPES } from "./constants";
|
||||
|
||||
export default function ParameterComponent({
|
||||
left,
|
||||
|
|
@ -88,7 +89,6 @@ export default function ParameterComponent({
|
|||
debouncedHandleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
);
|
||||
|
||||
|
|
@ -173,8 +173,6 @@ export default function ParameterComponent({
|
|||
renderTooltips();
|
||||
}, [tooltipTitle, flow]);
|
||||
|
||||
console.log(left === true && type === "dict");
|
||||
|
||||
return !showNode ? (
|
||||
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
|
|
@ -240,7 +238,7 @@ export default function ParameterComponent({
|
|||
(left ? "" : " justify-end")
|
||||
}
|
||||
>
|
||||
<Case condition={left && data.node?.frozen}>
|
||||
<Case condition={!left && data.node?.frozen}>
|
||||
<div className="pr-1">
|
||||
<IconComponent className="h-5 w-5 text-ice" name={"Snowflake"} />
|
||||
</div>
|
||||
|
|
@ -311,7 +309,7 @@ export default function ParameterComponent({
|
|||
<Case
|
||||
condition={
|
||||
left === true &&
|
||||
type === "str" &&
|
||||
TEXT_FIELD_TYPES.includes(type ?? "") &&
|
||||
!data.node?.template[name]?.options
|
||||
}
|
||||
>
|
||||
|
|
@ -357,8 +355,7 @@ export default function ParameterComponent({
|
|||
name={name}
|
||||
data={data}
|
||||
button_text={
|
||||
data.node?.template[name]?.refresh_button_text ??
|
||||
"Refresh"
|
||||
data.node?.template[name].refresh_button_text
|
||||
}
|
||||
className="extra-side-bar-buttons mt-1"
|
||||
handleUpdateValues={handleRefreshButtonPress}
|
||||
|
|
@ -406,8 +403,7 @@ export default function ParameterComponent({
|
|||
name={name}
|
||||
data={data}
|
||||
button_text={
|
||||
data.node?.template[name]?.refresh_button_text ??
|
||||
"Refresh"
|
||||
data.node?.template[name].refresh_button_text
|
||||
}
|
||||
className="extra-side-bar-buttons ml-2 mt-1"
|
||||
handleUpdateValues={handleRefreshButtonPress}
|
||||
|
|
@ -549,9 +545,7 @@ export default function ParameterComponent({
|
|||
value={
|
||||
!data.node!.template[name]?.value ||
|
||||
data.node!.template[name]?.value?.toString() === "{}"
|
||||
? {
|
||||
// yourkey: "value",
|
||||
}
|
||||
? {}
|
||||
: data.node!.template[name]?.value
|
||||
}
|
||||
onChange={handleOnNewValue}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import Loading from "../../components/ui/loading";
|
|||
import { Textarea } from "../../components/ui/textarea";
|
||||
import Xmark from "../../components/ui/xmark";
|
||||
import {
|
||||
NATIVE_CATEGORIES,
|
||||
RUN_TIMESTAMP_PREFIX,
|
||||
STATUS_BUILD,
|
||||
STATUS_BUILDING,
|
||||
|
|
@ -28,9 +27,9 @@ import { NodeDataType } from "../../types/flow";
|
|||
import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils";
|
||||
import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils";
|
||||
import { classNames, cn } from "../../utils/utils";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
import getFieldTitle from "../utils/get-field-title";
|
||||
import sortFields from "../utils/sort-fields";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
|
||||
export default function GenericNode({
|
||||
data,
|
||||
|
|
@ -77,18 +76,14 @@ export default function GenericNode({
|
|||
// This one should run only once
|
||||
// first check if data.type in NATIVE_CATEGORIES
|
||||
// if not return
|
||||
if (
|
||||
!NATIVE_CATEGORIES.includes(types[data.type]) ||
|
||||
!data.node?.template?.code?.value
|
||||
)
|
||||
return;
|
||||
const thisNodeTemplate = templates[data.type].template;
|
||||
if (!data.node?.template?.code?.value) return;
|
||||
const thisNodeTemplate = templates[data.type]?.template;
|
||||
// if the template does not have a code key
|
||||
// return
|
||||
if (!thisNodeTemplate.code) return;
|
||||
if (!thisNodeTemplate?.code) return;
|
||||
const currentCode = thisNodeTemplate.code?.value;
|
||||
const thisNodesCode = data.node!.template?.code?.value;
|
||||
const componentsToIgnore = ["Custom Component", "Prompt"];
|
||||
const componentsToIgnore = ["Custom Component"];
|
||||
if (
|
||||
currentCode !== thisNodesCode &&
|
||||
!componentsToIgnore.includes(data.node!.display_name)
|
||||
|
|
@ -340,16 +335,20 @@ export default function GenericNode({
|
|||
|
||||
const baseBorderClass = getBaseBorderClass(selected);
|
||||
const nodeSizeClass = getNodeSizeClass(showNode);
|
||||
return classNames(
|
||||
const names = classNames(
|
||||
baseBorderClass,
|
||||
nodeSizeClass,
|
||||
"generic-node-div",
|
||||
specificClassFromBuildStatus,
|
||||
);
|
||||
return names;
|
||||
};
|
||||
|
||||
const getBaseBorderClass = (selected) =>
|
||||
selected ? "border border-ring" : "border";
|
||||
const getBaseBorderClass = (selected) => {
|
||||
let className = selected ? "border border-ring" : "border";
|
||||
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
||||
return data.node?.frozen ? frozenClass : className;
|
||||
};
|
||||
|
||||
const getNodeSizeClass = (showNode) =>
|
||||
showNode ? "w-96 rounded-lg" : "w-26 h-26 rounded-full";
|
||||
|
|
@ -423,6 +422,7 @@ export default function GenericNode({
|
|||
"generic-node-title-arrangement rounded-full" +
|
||||
(!showNode && " justify-center ")
|
||||
}
|
||||
data-testid="generic-node-title-arrangement"
|
||||
>
|
||||
{iconNodeRender()}
|
||||
{showNode && (
|
||||
|
|
@ -459,7 +459,7 @@ export default function GenericNode({
|
|||
<div className="group flex items-start gap-1.5">
|
||||
<ShadTooltip content={data.node?.display_name}>
|
||||
<div
|
||||
onDoubleClick={(event) => {
|
||||
onClick={(event) => {
|
||||
if (nameEditable) {
|
||||
setInputName(true);
|
||||
}
|
||||
|
|
@ -473,21 +473,6 @@ export default function GenericNode({
|
|||
{data.node?.display_name}
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
{nameEditable && (
|
||||
<div
|
||||
onClick={(event) => {
|
||||
setInputName(true);
|
||||
takeSnapshot();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<IconComponent
|
||||
name="PencilLine"
|
||||
className="hidden h-3 w-3 text-status-blue group-hover:block"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -725,14 +710,14 @@ export default function GenericNode({
|
|||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"generic-node-desc-text truncate-multiline word-break-break-word",
|
||||
"generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
|
||||
(data.node?.description === "" ||
|
||||
!data.node?.description) &&
|
||||
nameEditable
|
||||
? "font-light italic"
|
||||
: "",
|
||||
)}
|
||||
onDoubleClick={(e) => {
|
||||
onClick={(e) => {
|
||||
setInputDescription(true);
|
||||
takeSnapshot();
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ const useFetchDataOnMount = (
|
|||
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail ?? "Unknown error"],
|
||||
list: [responseError?.response?.data?.detail ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ const useHandleOnNewValue = (
|
|||
debouncedHandleUpdateValues,
|
||||
setNode,
|
||||
renderTooltips,
|
||||
isLoading,
|
||||
setIsLoading,
|
||||
) => {
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
|
@ -45,7 +44,9 @@ const useHandleOnNewValue = (
|
|||
let responseError = error as ResponseErrorTypeAPI;
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail.error ?? "Unknown error"],
|
||||
list: [
|
||||
responseError?.response?.data?.detail.error ?? "Unknown error",
|
||||
],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const useHandleRefreshButtonPress = (setIsLoading, setNode, renderTooltips) => {
|
|||
|
||||
setErrorData({
|
||||
title: "Error while updating the Component",
|
||||
list: [responseError.response.data.detail ?? "Unknown error"],
|
||||
list: [responseError?.response?.data?.detail ?? "Unknown error"],
|
||||
});
|
||||
}
|
||||
setIsLoading(false);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { APITemplateType } from "../../types/api";
|
||||
|
||||
export default function getFieldTitle(
|
||||
template: APITemplateType,
|
||||
templateField: string,
|
||||
): string {
|
||||
return template[templateField].display_name
|
||||
? template[templateField].display_name!
|
||||
: template[templateField].name ?? templateField;
|
||||
}
|
||||
template: APITemplateType,
|
||||
templateField: string,
|
||||
): string {
|
||||
return template[templateField].display_name
|
||||
? template[templateField].display_name!
|
||||
: template[templateField].name ?? templateField;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,40 @@
|
|||
import { priorityFields } from "../../constants/constants";
|
||||
|
||||
export default function sortFields(a, b, fieldOrder) {
|
||||
// Early return for empty fields
|
||||
if (!a && !b) return 0;
|
||||
if (!a) return 1;
|
||||
if (!b) return -1;
|
||||
|
||||
// Normalize the case to ensure case-insensitive comparison
|
||||
const normalizedFieldA = a.toLowerCase();
|
||||
const normalizedFieldB = b.toLowerCase();
|
||||
|
||||
const aIsPriority = priorityFields.has(normalizedFieldA);
|
||||
const bIsPriority = priorityFields.has(normalizedFieldB);
|
||||
|
||||
// Sort by priority
|
||||
if (aIsPriority && !bIsPriority) return -1;
|
||||
if (!aIsPriority && bIsPriority) return 1;
|
||||
|
||||
// Check if either field is in the fieldOrder array
|
||||
const indexOfA = fieldOrder.indexOf(normalizedFieldA);
|
||||
const indexOfB = fieldOrder.indexOf(normalizedFieldB);
|
||||
|
||||
// If both fields are in fieldOrder, sort by their order in the array
|
||||
if (indexOfA !== -1 && indexOfB !== -1) {
|
||||
return indexOfA - indexOfB;
|
||||
}
|
||||
|
||||
// If only one of the fields is in fieldOrder, that field comes first
|
||||
if (indexOfA !== -1) {
|
||||
return -1;
|
||||
}
|
||||
if (indexOfB !== -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Default case for fields not in priorityFields and not found in fieldOrder
|
||||
// You might want to sort them alphabetically or in another specific manner
|
||||
return a.localeCompare(b);
|
||||
// Early return for empty fields
|
||||
if (!a && !b) return 0;
|
||||
if (!a) return 1;
|
||||
if (!b) return -1;
|
||||
|
||||
// Normalize the case to ensure case-insensitive comparison
|
||||
const normalizedFieldA = a.toLowerCase();
|
||||
const normalizedFieldB = b.toLowerCase();
|
||||
|
||||
const aIsPriority = priorityFields.has(normalizedFieldA);
|
||||
const bIsPriority = priorityFields.has(normalizedFieldB);
|
||||
|
||||
// Sort by priority
|
||||
if (aIsPriority && !bIsPriority) return -1;
|
||||
if (!aIsPriority && bIsPriority) return 1;
|
||||
|
||||
// Check if either field is in the fieldOrder array
|
||||
const indexOfA = fieldOrder.indexOf(normalizedFieldA);
|
||||
const indexOfB = fieldOrder.indexOf(normalizedFieldB);
|
||||
|
||||
// If both fields are in fieldOrder, sort by their order in the array
|
||||
if (indexOfA !== -1 && indexOfB !== -1) {
|
||||
return indexOfA - indexOfB;
|
||||
}
|
||||
|
||||
// If only one of the fields is in fieldOrder, that field comes first
|
||||
if (indexOfA !== -1) {
|
||||
return -1;
|
||||
}
|
||||
if (indexOfB !== -1) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Default case for fields not in priorityFields and not found in fieldOrder
|
||||
// You might want to sort them alphabetically or in another specific manner
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
|
|
|
|||
14
src/frontend/src/hooks/use-track-last-visited-path.tsx
Normal file
14
src/frontend/src/hooks/use-track-last-visited-path.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { useEffect } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useLocationStore } from "../stores/locationStore";
|
||||
|
||||
function useTrackLastVisitedPath() {
|
||||
const location = useLocation();
|
||||
const setHistory = useLocationStore((state) => state.setRouteHistory);
|
||||
|
||||
useEffect(() => {
|
||||
setHistory(location.pathname);
|
||||
}, [location]);
|
||||
}
|
||||
|
||||
export default useTrackLastVisitedPath;
|
||||
25
src/frontend/src/icons/Streamlit/SvgStreamlit.jsx
Normal file
25
src/frontend/src/icons/Streamlit/SvgStreamlit.jsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
export default function SvgStreamlit(props) {
|
||||
return (
|
||||
<svg
|
||||
width="301"
|
||||
height="165"
|
||||
viewBox="0 0 301 165"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M150.731 101.547L98.1387 73.7471L6.84674 25.4969C6.7634 25.4136 6.59674 25.4136 6.51341 25.4136C3.18007 23.8303 -0.236608 27.1636 1.0134 30.497L47.5302 149.139L47.5385 149.164C47.5885 149.281 47.6302 149.397 47.6802 149.514C49.5885 153.939 53.7552 156.672 58.2886 157.747C58.6719 157.831 58.9461 157.906 59.4064 157.998C59.8645 158.1 60.5052 158.239 61.0552 158.281C61.1469 158.289 61.2302 158.289 61.3219 158.297H61.3886C61.4552 158.306 61.5219 158.306 61.5886 158.314H61.6802C61.7386 158.322 61.8052 158.322 61.8636 158.322H61.9719C62.0386 158.331 62.1052 158.331 62.1719 158.331V158.331C121.084 164.754 180.519 164.754 239.431 158.331V158.331C240.139 158.331 240.831 158.297 241.497 158.231C241.714 158.206 241.922 158.181 242.131 158.156C242.156 158.147 242.189 158.147 242.214 158.139C242.356 158.122 242.497 158.097 242.639 158.072C242.847 158.047 243.056 158.006 243.264 157.964C243.681 157.872 243.87 157.806 244.436 157.611C245.001 157.417 245.94 157.077 246.527 156.794C247.115 156.511 247.522 156.239 248.014 155.931C248.622 155.547 249.201 155.155 249.788 154.715C250.041 154.521 250.214 154.397 250.397 154.222L250.297 154.164L150.731 101.547Z"
|
||||
fill="#FF4B4B"
|
||||
/>
|
||||
<path
|
||||
d="M294.766 25.4981H294.683L203.357 73.7483L254.124 149.357L300.524 30.4981V30.3315C301.691 26.8314 298.108 23.6648 294.766 25.4981"
|
||||
fill="#7D353B"
|
||||
/>
|
||||
<path
|
||||
d="M155.598 2.55572C153.264 -0.852624 148.181 -0.852624 145.931 2.55572L98.1389 73.7477L150.731 101.548L250.398 154.222C251.024 153.609 251.526 153.012 252.056 152.381C252.806 151.456 253.506 150.465 254.123 149.356L203.356 73.7477L155.598 2.55572Z"
|
||||
fill="#BD4043"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
8
src/frontend/src/icons/Streamlit/index.tsx
Normal file
8
src/frontend/src/icons/Streamlit/index.tsx
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import SvgStreamlit from "./SvgStreamlit";
|
||||
|
||||
export const Streamlit = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||
(props, ref) => {
|
||||
return <SvgStreamlit className="icon" ref={ref} {...props} />;
|
||||
},
|
||||
);
|
||||
|
|
@ -84,7 +84,6 @@ export default function IOFileInput({ field, updateValue }: IOFileInputProps) {
|
|||
uploadFile(file, currentFlowId)
|
||||
.then((res) => res.data)
|
||||
.then((data) => {
|
||||
console.log("File uploaded successfully");
|
||||
// Get the file name from the response
|
||||
const { file_path, flowId } = data;
|
||||
setFilePath(file_path);
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ import useFlowStore from "../../../../stores/flowStore";
|
|||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { VertexBuildTypeAPI, sendAllProps } from "../../../../types/api";
|
||||
import { ChatMessageType, ChatOutputType } from "../../../../types/chat";
|
||||
import { chatViewProps, FilePreviewType } from "../../../../types/components";
|
||||
import { FilePreviewType, chatViewProps } from "../../../../types/components";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
import ChatInput from "./chatInput";
|
||||
import ChatMessage from "./chatMessage";
|
||||
import useDragAndDrop from "./chatInput/hooks/use-drag-and-drop";
|
||||
import ChatMessage from "./chatMessage";
|
||||
|
||||
export default function ChatView({
|
||||
sendMessage,
|
||||
|
|
@ -116,10 +116,21 @@ export default function ChatView({
|
|||
if (lockChat) setLockChat(false);
|
||||
}
|
||||
|
||||
function handleSelectChange(event: string): void {
|
||||
switch (event) {
|
||||
case "builds":
|
||||
clearChat();
|
||||
break;
|
||||
case "buildsNSession":
|
||||
console.log("delete build and session");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function updateChat(
|
||||
chat: ChatMessageType,
|
||||
message: string,
|
||||
stream_url?: string,
|
||||
stream_url?: string
|
||||
) {
|
||||
// if (message === "") return;
|
||||
chat.message = message;
|
||||
|
|
@ -148,7 +159,7 @@ export default function ChatView({
|
|||
const { dragOver, dragEnter, dragLeave, onDrop } = useDragAndDrop(
|
||||
setIsDragging,
|
||||
setFiles,
|
||||
currentFlowId,
|
||||
currentFlowId
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -161,18 +172,59 @@ export default function ChatView({
|
|||
>
|
||||
<div className="eraser-size">
|
||||
<div className="eraser-position">
|
||||
<button disabled={lockChat} onClick={() => clearChat()}>
|
||||
<button
|
||||
className="flex gap-1"
|
||||
onClick={() => handleSelectChange("builds")}
|
||||
>
|
||||
<IconComponent
|
||||
name="Eraser"
|
||||
className={classNames(
|
||||
"h-5 w-5",
|
||||
lockChat
|
||||
? "animate-pulse text-primary"
|
||||
: "text-primary hover:text-gray-600",
|
||||
: "text-primary hover:text-gray-600"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
{/* <Select
|
||||
onValueChange={handleSelectChange}
|
||||
value=""
|
||||
disabled={lockChat}
|
||||
>
|
||||
<SelectTrigger className="">
|
||||
<button className="flex gap-1">
|
||||
<IconComponent
|
||||
name="Eraser"
|
||||
className={classNames(
|
||||
"h-5 w-5 transition-all duration-100",
|
||||
lockChat ? "animate-pulse text-primary" : "text-primary",
|
||||
)}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</button>
|
||||
</SelectTrigger>
|
||||
<SelectContent className="right-[9.5em]">
|
||||
<SelectItem value="builds" className="cursor-pointer">
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name={"Trash2"}
|
||||
className={`relative top-0.5 mr-2 h-4 w-4`}
|
||||
/>
|
||||
<span className="">Clear Builds</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="buildsNSession" className="cursor-pointer">
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name={"Trash2"}
|
||||
className={`relative top-0.5 mr-2 h-4 w-4`}
|
||||
/>
|
||||
<span className="">Clear Builds & Session</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select> */}
|
||||
</div>
|
||||
<div ref={messagesRef} className="chat-message-div">
|
||||
{chatHistory?.length > 0 ? (
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import AccordionComponent from "../../components/accordionComponent";
|
|||
import IconComponent from "../../components/genericIconComponent";
|
||||
import ShadTooltip from "../../components/shadTooltipComponent";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
Tabs,
|
||||
TabsContent,
|
||||
|
|
@ -34,25 +33,25 @@ export default function IOModal({
|
|||
}: IOModalPropsType): JSX.Element {
|
||||
const allNodes = useFlowStore((state) => state.nodes);
|
||||
const inputs = useFlowStore((state) => state.inputs).filter(
|
||||
(input) => input.type !== "ChatInput",
|
||||
(input) => input.type !== "ChatInput"
|
||||
);
|
||||
const chatInput = useFlowStore((state) => state.inputs).find(
|
||||
(input) => input.type === "ChatInput",
|
||||
(input) => input.type === "ChatInput"
|
||||
);
|
||||
const outputs = useFlowStore((state) => state.outputs).filter(
|
||||
(output) => output.type !== "ChatOutput",
|
||||
(output) => output.type !== "ChatOutput"
|
||||
);
|
||||
const chatOutput = useFlowStore((state) => state.outputs).find(
|
||||
(output) => output.type === "ChatOutput",
|
||||
(output) => output.type === "ChatOutput"
|
||||
);
|
||||
const nodes = useFlowStore((state) => state.nodes).filter(
|
||||
(node) =>
|
||||
inputs.some((input) => input.id === node.id) ||
|
||||
outputs.some((output) => output.id === node.id),
|
||||
outputs.some((output) => output.id === node.id)
|
||||
);
|
||||
const haveChat = chatInput || chatOutput;
|
||||
const [selectedTab, setSelectedTab] = useState(
|
||||
inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0,
|
||||
inputs.length > 0 ? 1 : outputs.length > 0 ? 2 : 0
|
||||
);
|
||||
|
||||
function startView() {
|
||||
|
|
@ -78,6 +77,7 @@ export default function IOModal({
|
|||
const isBuilding = useFlowStore((state) => state.isBuilding);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
const [sessions, setSessions] = useState<string[]>([]);
|
||||
|
||||
async function updateVertices() {
|
||||
return updateVerticesOrder(currentFlow!.id, null);
|
||||
|
|
@ -89,7 +89,13 @@ export default function IOModal({
|
|||
// }
|
||||
// }, [open, currentFlow]);
|
||||
|
||||
async function sendMessage({repeat=1,files}:{repeat:number,files?:string[]}): Promise<void> {
|
||||
async function sendMessage({
|
||||
repeat = 1,
|
||||
files,
|
||||
}: {
|
||||
repeat: number;
|
||||
files?: string[];
|
||||
}): Promise<void> {
|
||||
if (isBuilding) return;
|
||||
setIsBuilding(true);
|
||||
setLockChat(true);
|
||||
|
|
@ -98,7 +104,8 @@ export default function IOModal({
|
|||
await buildFlow({
|
||||
input_value: chatValue,
|
||||
startNodeId: chatInput?.id,
|
||||
files:files
|
||||
files: files,
|
||||
silent: true,
|
||||
}).catch((err) => {
|
||||
console.error(err);
|
||||
setLockChat(false);
|
||||
|
|
@ -120,6 +127,11 @@ export default function IOModal({
|
|||
|
||||
useEffect(() => {
|
||||
setSelectedViewField(startView());
|
||||
// if (haveChat) {
|
||||
// getSessions().then((sessions) => {
|
||||
// setSessions(sessions);
|
||||
// });
|
||||
// }
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
|
|
@ -128,6 +140,7 @@ export default function IOModal({
|
|||
open={open}
|
||||
setOpen={setOpen}
|
||||
disable={disable}
|
||||
onSubmit={() => sendMessage({ repeat: 1 })}
|
||||
>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
{/* TODO ADAPT TO ALL TYPES OF INPUTS AND OUTPUTS */}
|
||||
|
|
@ -147,7 +160,7 @@ export default function IOModal({
|
|||
{selectedTab !== 0 && (
|
||||
<div
|
||||
className={cn(
|
||||
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start transition-all duration-300",
|
||||
"mr-6 flex h-full w-2/6 flex-shrink-0 flex-col justify-start transition-all duration-300"
|
||||
)}
|
||||
>
|
||||
<Tabs
|
||||
|
|
@ -167,6 +180,9 @@ export default function IOModal({
|
|||
{outputs.length > 0 && (
|
||||
<TabsTrigger value={"2"}>Outputs</TabsTrigger>
|
||||
)}
|
||||
{/* {haveChat && (
|
||||
<TabsTrigger value={"3"}>History</TabsTrigger>
|
||||
)} */}
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
|
|
@ -180,11 +196,11 @@ export default function IOModal({
|
|||
</div>
|
||||
{nodes
|
||||
.filter((node) =>
|
||||
inputs.some((input) => input.id === node.id),
|
||||
inputs.some((input) => input.id === node.id)
|
||||
)
|
||||
.map((node, index) => {
|
||||
const input = inputs.find(
|
||||
(input) => input.id === node.id,
|
||||
(input) => input.id === node.id
|
||||
)!;
|
||||
return (
|
||||
<div
|
||||
|
|
@ -248,11 +264,11 @@ export default function IOModal({
|
|||
</div>
|
||||
{nodes
|
||||
.filter((node) =>
|
||||
outputs.some((output) => output.id === node.id),
|
||||
outputs.some((output) => output.id === node.id)
|
||||
)
|
||||
.map((node, index) => {
|
||||
const output = outputs.find(
|
||||
(output) => output.id === node.id,
|
||||
(output) => output.id === node.id
|
||||
)!;
|
||||
return (
|
||||
<div
|
||||
|
|
@ -260,6 +276,10 @@ export default function IOModal({
|
|||
key={index}
|
||||
>
|
||||
<AccordionComponent
|
||||
disabled={
|
||||
node.data.node!.template["input_value"]
|
||||
?.value === ""
|
||||
}
|
||||
trigger={
|
||||
<div className="file-component-badge-div">
|
||||
<ShadTooltip
|
||||
|
|
@ -315,7 +335,7 @@ export default function IOModal({
|
|||
<div
|
||||
className={cn(
|
||||
"flex h-full w-full flex-col items-start gap-4 pt-4",
|
||||
!selectedViewField ? "hidden" : "",
|
||||
!selectedViewField ? "hidden" : ""
|
||||
)}
|
||||
>
|
||||
<div className="font-xl flex items-center justify-center gap-3 font-semibold">
|
||||
|
|
@ -334,7 +354,7 @@ export default function IOModal({
|
|||
</div>
|
||||
<div className="h-full w-full">
|
||||
{inputs.some(
|
||||
(input) => input.id === selectedViewField.id,
|
||||
(input) => input.id === selectedViewField.id
|
||||
) ? (
|
||||
<IOFieldView
|
||||
type={InputOutput.INPUT}
|
||||
|
|
@ -356,7 +376,7 @@ export default function IOModal({
|
|||
<div
|
||||
className={cn(
|
||||
"flex h-full w-full",
|
||||
selectedViewField ? "hidden" : "",
|
||||
selectedViewField ? "hidden" : ""
|
||||
)}
|
||||
>
|
||||
{haveChat ? (
|
||||
|
|
@ -378,26 +398,22 @@ export default function IOModal({
|
|||
</div>
|
||||
</BaseModal.Content>
|
||||
{!haveChat ? (
|
||||
<BaseModal.Footer>
|
||||
<div className="flex w-full justify-end pt-2">
|
||||
<Button
|
||||
variant={"outline"}
|
||||
className="flex gap-2 px-3"
|
||||
onClick={() => sendMessage({repeat:1})}
|
||||
>
|
||||
<BaseModal.Footer
|
||||
submit={{
|
||||
label: "Run Flow",
|
||||
icon: (
|
||||
<IconComponent
|
||||
name={isBuilding ? "Loader2" : "Zap"}
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
isBuilding
|
||||
? "animate-spin"
|
||||
: "fill-current text-medium-indigo",
|
||||
: "fill-current text-medium-indigo"
|
||||
)}
|
||||
/>
|
||||
Run Flow
|
||||
</Button>
|
||||
</div>
|
||||
</BaseModal.Footer>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -4,23 +4,45 @@
|
|||
* @param {boolean} isAuth - If the API is authenticated
|
||||
* @returns {string} - The curl code
|
||||
*/
|
||||
export default function getCurlCode(
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `curl -X POST \\
|
||||
${window.location.protocol}//${
|
||||
window.location.host
|
||||
}/api/v1/run/${flowId}?stream=false \\
|
||||
export function getCurlRunCode(
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
endpointName?: string,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
// show the endpoint name in the curl command if it exists
|
||||
return `curl -X POST \\
|
||||
"${window.location.protocol}//${window.location.host}/api/v1/run/${
|
||||
endpointName || flowId
|
||||
}?stream=false" \\
|
||||
-H 'Content-Type: application/json'\\${
|
||||
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
|
||||
}
|
||||
-d '{"input_value": "message",
|
||||
"output_type": "chat",
|
||||
"input_type": "chat",
|
||||
"tweaks": ${JSON.stringify(tweaksObject, null, 2)}'
|
||||
"tweaks": ${JSON.stringify(tweaksObject, null, 2)}}'
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cURL command for making a POST request to a webhook endpoint.
|
||||
*
|
||||
* @param {Object} options - The options for generating the cURL command.
|
||||
* @param {string} options.flowId - The ID of the flow.
|
||||
* @param {boolean} options.isAuth - Indicates whether authentication is required.
|
||||
* @param {string} options.endpointName - The name of the webhook endpoint.
|
||||
* @returns {string} The cURL command.
|
||||
*/
|
||||
export function getCurlWebhookCode(flowId, isAuth, endpointName?: string) {
|
||||
return `curl -X POST \\
|
||||
"${window.location.protocol}//${window.location.host}/api/v1/webhook/${
|
||||
endpointName || flowId
|
||||
}" \\
|
||||
-H 'Content-Type: application/json'\\${
|
||||
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
|
||||
}
|
||||
-d '{"any": "data"}'
|
||||
`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,53 +6,102 @@
|
|||
* @returns {string} - The python code
|
||||
*/
|
||||
export default function getPythonApiCode(
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
return `import requests
|
||||
from typing import Optional
|
||||
|
||||
BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run"
|
||||
FLOW_ID = "${flowId}"
|
||||
# You can tweak the flow by adding a tweaks dictionary
|
||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
def run_flow(message: str,
|
||||
flow_id: str,
|
||||
output_type: str = "chat",
|
||||
input_type: str = "chat",
|
||||
tweaks: Optional[dict] = None,
|
||||
api_key: Optional[str] = None) -> dict:
|
||||
"""
|
||||
Run a flow with a given message and optional tweaks.
|
||||
|
||||
:param message: The message to send to the flow
|
||||
:param flow_id: The ID of the flow to run
|
||||
:param tweaks: Optional tweaks to customize the flow
|
||||
:return: The JSON response from the flow
|
||||
"""
|
||||
api_url = f"{BASE_API_URL}/{flow_id}"
|
||||
|
||||
payload = {
|
||||
"input_value": message,
|
||||
"output_type": output_type,
|
||||
"input_type": input_type,
|
||||
}
|
||||
headers = None
|
||||
if tweaks:
|
||||
payload["tweaks"] = tweaks
|
||||
if api_key:
|
||||
headers = {"x-api-key": api_key}
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
return response.json()
|
||||
|
||||
# Setup any tweaks you want to apply to the flow
|
||||
message = "message"
|
||||
${!isAuth ? `api_key = "<your api key>"` : ""}
|
||||
print(run_flow(message=message, flow_id=FLOW_ID, tweaks=TWEAKS${
|
||||
!isAuth ? `, api_key=api_key` : ""
|
||||
}))`;
|
||||
}
|
||||
flowId: string,
|
||||
isAuth: boolean,
|
||||
tweaksBuildedObject,
|
||||
endpointName?: string,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
return `import argparse
|
||||
import json
|
||||
from argparse import RawTextHelpFormatter
|
||||
import requests
|
||||
from typing import Optional
|
||||
import warnings
|
||||
try:
|
||||
from langflow.load import upload_file
|
||||
except ImportError:
|
||||
warnings.warn("Langflow provides a function to help you upload files to the flow. Please install langflow to use it.")
|
||||
upload_file = None
|
||||
|
||||
BASE_API_URL = "${window.location.protocol}//${window.location.host}/api/v1/run"
|
||||
FLOW_ID = "${flowId}"
|
||||
ENDPOINT = "${endpointName || ""}" ${
|
||||
endpointName
|
||||
? `# The endpoint name of the flow`
|
||||
: `# You can set a specific endpoint name in the flow settings`
|
||||
}
|
||||
|
||||
# You can tweak the flow by adding a tweaks dictionary
|
||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
def run_flow(message: str,
|
||||
endpoint: str,
|
||||
output_type: str = "chat",
|
||||
input_type: str = "chat",
|
||||
tweaks: Optional[dict] = None,
|
||||
api_key: Optional[str] = None) -> dict:
|
||||
"""
|
||||
Run a flow with a given message and optional tweaks.
|
||||
|
||||
:param message: The message to send to the flow
|
||||
:param endpoint: The ID or the endpoint name of the flow
|
||||
:param tweaks: Optional tweaks to customize the flow
|
||||
:return: The JSON response from the flow
|
||||
"""
|
||||
api_url = f"{BASE_API_URL}/{endpoint}"
|
||||
|
||||
payload = {
|
||||
"input_value": message,
|
||||
"output_type": output_type,
|
||||
"input_type": input_type,
|
||||
}
|
||||
headers = None
|
||||
if tweaks:
|
||||
payload["tweaks"] = tweaks
|
||||
if api_key:
|
||||
headers = {"x-api-key": api_key}
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
return response.json()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="""Run a flow with a given message and optional tweaks.\nRun it like: python <your file>.py "your message here" --endpoint "your_endpoint" --tweaks '{"key": "value"}'""",
|
||||
formatter_class=RawTextHelpFormatter)
|
||||
parser.add_argument("message", type=str, help="The message to send to the flow")
|
||||
parser.add_argument("--endpoint", type=str, default=ENDPOINT or FLOW_ID, help="The ID or the endpoint name of the flow")
|
||||
parser.add_argument("--tweaks", type=str, help="JSON string representing the tweaks to customize the flow", default=json.dumps(TWEAKS))
|
||||
parser.add_argument("--api_key", type=str, help="API key for authentication", default=None)
|
||||
parser.add_argument("--output_type", type=str, default="chat", help="The output type")
|
||||
parser.add_argument("--input_type", type=str, default="chat", help="The input type")
|
||||
parser.add_argument("--upload_file", type=str, help="Path to the file to upload", default=None)
|
||||
parser.add_argument("--components", type=str, help="Components to upload the file to", default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
tweaks = json.loads(args.tweaks)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError("Invalid tweaks JSON string")
|
||||
|
||||
if args.upload_file:
|
||||
if not upload_file:
|
||||
raise ImportError("Langflow is not installed. Please install it to use the upload_file function.")
|
||||
elif not args.components:
|
||||
raise ValueError("You need to provide the components to upload the file to.")
|
||||
tweaks = upload_file(file_path=args.upload_file, host=BASE_API_URL, flow_id=ENDPOINT, components=args.components, tweaks=tweaks)
|
||||
|
||||
response = run_flow(
|
||||
message=args.message,
|
||||
endpoint=args.endpoint,
|
||||
output_type=args.output_type,
|
||||
input_type=args.input_type,
|
||||
tweaks=tweaks,
|
||||
api_key=args.api_key
|
||||
)
|
||||
|
||||
print(json.dumps(response, indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,17 @@
|
|||
* @param {any[]} tweak - The tweaks
|
||||
* @returns {string} - The python code
|
||||
*/
|
||||
export default function getPythonCode(flowName: string, tweaksBuildedObject): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `from langflow.load import run_flow_from_json
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
result = run_flow_from_json(flow="${flowName}.json",
|
||||
input_value="message",
|
||||
fallback_to_env_vars=True, # False by default
|
||||
tweaks=TWEAKS)`;
|
||||
}
|
||||
export default function getPythonCode(
|
||||
flowName: string,
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
return `from langflow.load import run_flow_from_json
|
||||
TWEAKS = ${JSON.stringify(tweaksObject, null, 2)}
|
||||
|
||||
result = run_flow_from_json(flow="${flowName}.json",
|
||||
input_value="message",
|
||||
fallback_to_env_vars=True, # False by default
|
||||
tweaks=TWEAKS)`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
* @returns {string} - The widget code
|
||||
*/
|
||||
export default function getWidgetCode(
|
||||
flowId: string,
|
||||
flowName: string,
|
||||
isAuth: boolean,
|
||||
): string {
|
||||
return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script>
|
||||
|
||||
flowId: string,
|
||||
flowName: string,
|
||||
isAuth: boolean,
|
||||
): string {
|
||||
return `<script src="https://cdn.jsdelivr.net/gh/langflow-ai/langflow-embedded-chat@1.0_alpha/dist/build/static/js/bundle.min.js"></script>
|
||||
|
||||
<langflow-chat
|
||||
window_title="${flowName}"
|
||||
flow_id="${flowId}"
|
||||
|
|
@ -19,6 +19,6 @@ export default function getWidgetCode(
|
|||
api_key="..."`
|
||||
: ""
|
||||
}
|
||||
|
||||
|
||||
></langflow-chat>`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,78 +1,61 @@
|
|||
export default 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 <body> 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 <body> 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],
|
||||
},
|
||||
];
|
||||
export function createTabsArray(
|
||||
codes,
|
||||
includeWebhookCurl = false,
|
||||
includeTweaks = false,
|
||||
) {
|
||||
const tabs = [
|
||||
{
|
||||
name: "Run 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[2],
|
||||
},
|
||||
{
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[3],
|
||||
},
|
||||
{
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> 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: "html",
|
||||
code: codes[4],
|
||||
},
|
||||
];
|
||||
|
||||
if (includeWebhookCurl) {
|
||||
tabs.splice(1, 0, {
|
||||
name: "Webhook cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: codes[1],
|
||||
});
|
||||
}
|
||||
|
||||
if (includeTweaks) {
|
||||
tabs.push({
|
||||
name: "Tweaks",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes[5],
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ import { buildContent } from "../utils/build-content";
|
|||
import { buildTweaks } from "../utils/build-tweaks";
|
||||
import { checkCanBuildTweakObject } from "../utils/check-can-build-tweak-object";
|
||||
import { getChangesType } from "../utils/get-changes-types";
|
||||
import { getCurlRunCode, getCurlWebhookCode } from "../utils/get-curl-code";
|
||||
import { getNodesWithDefaultValue } from "../utils/get-nodes-with-default-value";
|
||||
import { getValue } from "../utils/get-value";
|
||||
import getPythonApiCode from "../utils/get-python-api-code";
|
||||
import getCurlCode from "../utils/get-curl-code";
|
||||
import getPythonCode from "../utils/get-python-code";
|
||||
import { getValue } from "../utils/get-value";
|
||||
import getWidgetCode from "../utils/get-widget-code";
|
||||
import tabsArray from "../utils/tabs-array";
|
||||
import { createTabsArray } from "../utils/tabs-array";
|
||||
|
||||
const ApiModal = forwardRef(
|
||||
(
|
||||
|
|
@ -46,19 +46,39 @@ const ApiModal = forwardRef(
|
|||
const { autoLogin } = useContext(AuthContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState("0");
|
||||
const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, tweak);
|
||||
const curl_code = getCurlCode(flow?.id, autoLogin, tweak);
|
||||
const pythonApiCode = getPythonApiCode(
|
||||
flow?.id,
|
||||
autoLogin,
|
||||
tweak,
|
||||
flow?.endpoint_name,
|
||||
);
|
||||
const curl_run_code = getCurlRunCode(
|
||||
flow?.id,
|
||||
autoLogin,
|
||||
tweak,
|
||||
flow?.endpoint_name,
|
||||
);
|
||||
const curl_webhook_code = getCurlWebhookCode(
|
||||
flow?.id,
|
||||
autoLogin,
|
||||
flow?.endpoint_name,
|
||||
);
|
||||
const pythonCode = getPythonCode(flow?.name, tweak);
|
||||
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
|
||||
console.log("flow", flow);
|
||||
const includeWebhook = flow.webhook;
|
||||
const tweaksCode = buildTweaks(flow);
|
||||
const codesArray = [
|
||||
curl_code,
|
||||
curl_run_code,
|
||||
curl_webhook_code,
|
||||
pythonApiCode,
|
||||
pythonCode,
|
||||
widgetCode,
|
||||
pythonCode,
|
||||
];
|
||||
const [tabs, setTabs] = useState(tabsArray(codesArray, 0));
|
||||
const [tabs, setTabs] = useState(
|
||||
createTabsArray(codesArray, includeWebhook),
|
||||
);
|
||||
|
||||
const canShowTweaks =
|
||||
flow &&
|
||||
|
|
@ -88,9 +108,9 @@ const ApiModal = forwardRef(
|
|||
|
||||
if (Object.keys(tweaksCode).length > 0) {
|
||||
setActiveTab("0");
|
||||
setTabs(tabsArray(codesArray, 1));
|
||||
setTabs(createTabsArray(codesArray, includeWebhook, true));
|
||||
} else {
|
||||
setTabs(tabsArray(codesArray, 1));
|
||||
setTabs(createTabsArray(codesArray, includeWebhook, true));
|
||||
}
|
||||
}, [flow["data"]!["nodes"], open]);
|
||||
|
||||
|
|
@ -161,7 +181,12 @@ const ApiModal = forwardRef(
|
|||
|
||||
const addCodes = (cloneTweak) => {
|
||||
const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, cloneTweak);
|
||||
const curl_code = getCurlCode(flow?.id, autoLogin, cloneTweak);
|
||||
const curl_code = getCurlRunCode(
|
||||
flow?.id,
|
||||
autoLogin,
|
||||
cloneTweak,
|
||||
flow?.endpoint_name,
|
||||
);
|
||||
const pythonCode = getPythonCode(flow?.name, cloneTweak);
|
||||
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
export const switchCaseModalSize = (size: string) => {
|
||||
let minWidth: string;
|
||||
let height: string;
|
||||
switch (size) {
|
||||
case "x-small":
|
||||
minWidth = "min-w-[20vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
case "smaller":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-[11rem]";
|
||||
break;
|
||||
case "smaller-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
case "small":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-[40vh]";
|
||||
break;
|
||||
case "small-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
case "medium":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-[60vh]";
|
||||
break;
|
||||
case "medium-h-full":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-full";
|
||||
|
||||
break;
|
||||
case "large":
|
||||
minWidth = "min-w-[85vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
case "three-cards":
|
||||
minWidth = "min-w-[1066px]";
|
||||
height = "h-fit";
|
||||
break;
|
||||
case "large-thin":
|
||||
minWidth = "min-w-[65vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
|
||||
case "md-thin":
|
||||
minWidth = "min-w-[85vw]";
|
||||
height = "h-[70vh]";
|
||||
break;
|
||||
|
||||
case "sm-thin":
|
||||
minWidth = "min-w-[65vw]";
|
||||
height = "h-[70vh]";
|
||||
break;
|
||||
|
||||
case "large-h-full":
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
default:
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
}
|
||||
return { minWidth, height };
|
||||
};
|
||||
|
|
@ -15,8 +15,11 @@ import {
|
|||
DialogContent as ModalContent,
|
||||
} from "../../components/ui/dialog-with-no-close";
|
||||
|
||||
import { DialogClose } from "@radix-ui/react-dialog";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { modalHeaderType } from "../../types/components";
|
||||
import { cn } from "../../utils/utils";
|
||||
import { switchCaseModalSize } from "./helpers/switch-case-size";
|
||||
|
||||
type ContentProps = { children: ReactNode };
|
||||
type HeaderProps = { children: ReactNode; description: string };
|
||||
|
|
@ -61,8 +64,38 @@ const Header: React.FC<{ children: ReactNode; description: string | null }> = ({
|
|||
);
|
||||
};
|
||||
|
||||
const Footer: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return <>{children}</>;
|
||||
const Footer: React.FC<{
|
||||
children?: ReactNode;
|
||||
submit?: {
|
||||
label: string;
|
||||
icon?: ReactNode;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
dataTestId?: string;
|
||||
};
|
||||
}> = ({ children, submit }) => {
|
||||
return submit ? (
|
||||
<div className="flex w-full items-center justify-between">
|
||||
{children ?? <div />}
|
||||
<div className="flex items-center gap-3">
|
||||
<DialogClose asChild>
|
||||
<Button variant="outline" type="button">
|
||||
Cancel
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
data-testid={submit.dataTestId}
|
||||
type="submit"
|
||||
loading={submit.loading}
|
||||
>
|
||||
{submit.icon && submit.icon}
|
||||
{submit.label}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>{children && children}</>
|
||||
);
|
||||
};
|
||||
interface BaseModalProps {
|
||||
children: [
|
||||
|
|
@ -91,6 +124,7 @@ interface BaseModalProps {
|
|||
disable?: boolean;
|
||||
onChangeOpenModal?: (open?: boolean) => void;
|
||||
type?: "modal" | "dialog";
|
||||
onSubmit?: () => void;
|
||||
}
|
||||
function BaseModal({
|
||||
open,
|
||||
|
|
@ -99,6 +133,7 @@ function BaseModal({
|
|||
size = "large",
|
||||
onChangeOpenModal,
|
||||
type = "dialog",
|
||||
onSubmit,
|
||||
}: BaseModalProps) {
|
||||
const headerChild = React.Children.toArray(children).find(
|
||||
(child) => (child as React.ReactElement).type === Header,
|
||||
|
|
@ -113,71 +148,7 @@ function BaseModal({
|
|||
(child) => (child as React.ReactElement).type === Footer,
|
||||
);
|
||||
|
||||
let minWidth: string;
|
||||
let height: string;
|
||||
|
||||
switch (size) {
|
||||
case "x-small":
|
||||
minWidth = "min-w-[20vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
case "smaller":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-[11rem]";
|
||||
break;
|
||||
case "smaller-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
case "small":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-[40vh]";
|
||||
break;
|
||||
case "small-h-full":
|
||||
minWidth = "min-w-[40vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
case "medium":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-[60vh]";
|
||||
break;
|
||||
case "medium-h-full":
|
||||
minWidth = "min-w-[60vw]";
|
||||
height = "h-full";
|
||||
|
||||
break;
|
||||
case "large":
|
||||
minWidth = "min-w-[85vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
case "three-cards":
|
||||
minWidth = "min-w-[1066px]";
|
||||
height = "h-fit";
|
||||
break;
|
||||
case "large-thin":
|
||||
minWidth = "min-w-[65vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
|
||||
case "md-thin":
|
||||
minWidth = "min-w-[85vw]";
|
||||
height = "h-[70vh]";
|
||||
break;
|
||||
|
||||
case "sm-thin":
|
||||
minWidth = "min-w-[65vw]";
|
||||
height = "h-[70vh]";
|
||||
break;
|
||||
|
||||
case "large-h-full":
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-full";
|
||||
break;
|
||||
default:
|
||||
minWidth = "min-w-[80vw]";
|
||||
height = "h-[80vh]";
|
||||
break;
|
||||
}
|
||||
let { minWidth, height } = switchCaseModalSize(size);
|
||||
|
||||
useEffect(() => {
|
||||
if (onChangeOpenModal) {
|
||||
|
|
@ -212,13 +183,34 @@ function BaseModal({
|
|||
<div className="truncate-doubleline word-break-break-word">
|
||||
{headerChild}
|
||||
</div>
|
||||
<div
|
||||
className={`flex flex-col ${height} w-full transition-all duration-300`}
|
||||
>
|
||||
{ContentChild}
|
||||
</div>
|
||||
{ContentFooter && (
|
||||
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
||||
{onSubmit ? (
|
||||
<form
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
onSubmit();
|
||||
}}
|
||||
className="flex flex-col gap-6"
|
||||
>
|
||||
<div
|
||||
className={`flex flex-col ${height} w-full transition-all duration-300`}
|
||||
>
|
||||
{ContentChild}
|
||||
</div>
|
||||
{ContentFooter && (
|
||||
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
||||
)}
|
||||
</form>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-col ${height} w-full transition-all duration-300`}
|
||||
>
|
||||
{ContentChild}
|
||||
</div>
|
||||
{ContentFooter && (
|
||||
<div className="flex flex-row-reverse">{ContentFooter}</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ export default function DeleteConfirmationModal({
|
|||
<DialogClose asChild>
|
||||
<Button
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className="mr-3"
|
||||
className="mr-1"
|
||||
variant="outline"
|
||||
>
|
||||
Cancel
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ export default function DictAreaModal({
|
|||
|
||||
useEffect(() => {
|
||||
if (value) ref.current = value;
|
||||
}, [ref]);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<BaseModal size="medium-h-full" open={open} setOpen={setOpen}>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ import ShadTooltip from "../../components/shadTooltipComponent";
|
|||
import TextAreaComponent from "../../components/textAreaComponent";
|
||||
import ToggleShadComponent from "../../components/toggleShadComponent";
|
||||
import { Badge } from "../../components/ui/badge";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
|
|
@ -43,19 +42,23 @@ import BaseModal from "../baseModal";
|
|||
const EditNodeModal = forwardRef(
|
||||
(
|
||||
{
|
||||
data,
|
||||
nodeLength,
|
||||
open,
|
||||
setOpen,
|
||||
data,
|
||||
}: {
|
||||
data: NodeDataType;
|
||||
nodeLength: number;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
data: NodeDataType;
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [myData, setMyData] = useState(data);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
|
||||
const dataFromStore = nodes.find((node) => node.id === node.id)?.data;
|
||||
|
||||
const [myData, setMyData] = useState(dataFromStore ?? data);
|
||||
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
|
|
@ -98,6 +101,16 @@ const EditNodeModal = forwardRef(
|
|||
onChangeOpenModal={(open) => {
|
||||
setMyData(data);
|
||||
}}
|
||||
onSubmit={() => {
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: myData.node,
|
||||
},
|
||||
}));
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<BaseModal.Trigger>
|
||||
<></>
|
||||
|
|
@ -130,6 +143,7 @@ const EditNodeModal = forwardRef(
|
|||
<TableHeader className="edit-node-modal-table-header">
|
||||
<TableRow className="">
|
||||
<TableHead className="h-7 text-center">PARAM</TableHead>
|
||||
<TableHead className="h-7 text-center">DESC</TableHead>
|
||||
<TableHead className="h-7 p-0 text-center">
|
||||
VALUE
|
||||
</TableHead>
|
||||
|
|
@ -184,11 +198,17 @@ const EditNodeModal = forwardRef(
|
|||
>
|
||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
||||
<ShadTooltip
|
||||
styleClasses="z-50"
|
||||
content={
|
||||
myData.node?.template[templateParam].proxy
|
||||
? myData.node?.template[templateParam]
|
||||
.proxy?.id
|
||||
: null
|
||||
: myData.node?.template[templateParam]
|
||||
.display_name
|
||||
? myData.node!.template[templateParam]
|
||||
.display_name
|
||||
: myData.node?.template[templateParam]
|
||||
.name
|
||||
}
|
||||
>
|
||||
<span>
|
||||
|
|
@ -201,6 +221,20 @@ const EditNodeModal = forwardRef(
|
|||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="truncate p-0 text-center text-sm text-foreground sm:px-3">
|
||||
<ShadTooltip
|
||||
styleClasses="z-50"
|
||||
content={
|
||||
data.node?.template[templateParam]?.info ??
|
||||
null
|
||||
}
|
||||
>
|
||||
<span>
|
||||
{data.node?.template[templateParam]?.info ??
|
||||
""}
|
||||
</span>
|
||||
</ShadTooltip>
|
||||
</TableCell>
|
||||
<TableCell className="w-[300px] p-0 text-center text-xs text-foreground ">
|
||||
<Case
|
||||
condition={
|
||||
|
|
@ -297,9 +331,7 @@ const EditNodeModal = forwardRef(
|
|||
myData.node!.template[
|
||||
templateParam
|
||||
]?.value?.toString() === "{}"
|
||||
? {
|
||||
// yourkey: "value",
|
||||
}
|
||||
? {}
|
||||
: myData.node!.template[templateParam]
|
||||
.value
|
||||
}
|
||||
|
|
@ -608,26 +640,7 @@ const EditNodeModal = forwardRef(
|
|||
</div>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
data-test-id="saveChangesBtn"
|
||||
id={"saveChangesBtn"}
|
||||
className="mt-3"
|
||||
onClick={() => {
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: myData.node,
|
||||
},
|
||||
}));
|
||||
setOpen(false);
|
||||
}}
|
||||
type="submit"
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
<BaseModal.Footer submit={{ label: "Save Changes" }} />
|
||||
</BaseModal>
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import { ReactNode, forwardRef, useEffect, useState } from "react";
|
||||
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { API_WARNING_NOTICE_ALERT } from "../../constants/alerts_constants";
|
||||
import {
|
||||
|
|
@ -19,7 +18,7 @@ const ExportModal = forwardRef(
|
|||
(props: { children: ReactNode }, ref): JSX.Element => {
|
||||
const version = useDarkStore((state) => state.version);
|
||||
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [checked, setChecked] = useState(false);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
useEffect(() => {
|
||||
setName(currentFlow!.name);
|
||||
|
|
@ -30,7 +29,43 @@ const ExportModal = forwardRef(
|
|||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<BaseModal size="smaller-h-full" open={open} setOpen={setOpen}>
|
||||
<BaseModal
|
||||
size="smaller-h-full"
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
onSubmit={() => {
|
||||
if (checked) {
|
||||
downloadFlow(
|
||||
{
|
||||
id: currentFlow!.id,
|
||||
data: currentFlow!.data!,
|
||||
description,
|
||||
name,
|
||||
last_tested_version: version,
|
||||
is_component: false,
|
||||
},
|
||||
name!,
|
||||
description,
|
||||
);
|
||||
setNoticeData({
|
||||
title: API_WARNING_NOTICE_ALERT,
|
||||
});
|
||||
} else
|
||||
downloadFlow(
|
||||
removeApiKeys({
|
||||
id: currentFlow!.id,
|
||||
data: currentFlow!.data!,
|
||||
description,
|
||||
name,
|
||||
last_tested_version: version,
|
||||
is_component: false,
|
||||
}),
|
||||
name!,
|
||||
description,
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
<BaseModal.Trigger asChild>{props.children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={EXPORT_DIALOG_SUBTITLE}>
|
||||
<span className="pr-2">Export</span>
|
||||
|
|
@ -64,47 +99,9 @@ const ExportModal = forwardRef(
|
|||
</span>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (checked) {
|
||||
downloadFlow(
|
||||
{
|
||||
id: currentFlow!.id,
|
||||
data: currentFlow!.data!,
|
||||
description,
|
||||
name,
|
||||
last_tested_version: version,
|
||||
is_component: false,
|
||||
},
|
||||
name!,
|
||||
description
|
||||
);
|
||||
setNoticeData({
|
||||
title: API_WARNING_NOTICE_ALERT,
|
||||
});
|
||||
} else
|
||||
downloadFlow(
|
||||
removeApiKeys({
|
||||
id: currentFlow!.id,
|
||||
data: currentFlow!.data!,
|
||||
description,
|
||||
name,
|
||||
last_tested_version: version,
|
||||
is_component: false,
|
||||
}),
|
||||
name!,
|
||||
description
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
type="submit"
|
||||
>
|
||||
Download Flow
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
<BaseModal.Footer submit={{ label: "Download Flow" }} />
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
export default ExportModal;
|
||||
|
|
|
|||
|
|
@ -1,43 +1,29 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { ColDef, ColGroupDef } from "ag-grid-community";
|
||||
import { AxiosError } from "axios";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import TableComponent from "../../components/tableComponent";
|
||||
import { Tabs, TabsList, TabsTrigger } from "../../components/ui/tabs";
|
||||
import { getMessagesTable, getTransactionTable } from "../../controllers/API";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { FlowSettingsPropsType } from "../../types/components";
|
||||
import { FlowType } from "../../types/flow";
|
||||
import { FlowType, NodeDataType } from "../../types/flow";
|
||||
import BaseModal from "../baseModal";
|
||||
import TableComponent from "../../components/tableComponent";
|
||||
import { getMessagesTable, getTransactionTable } from "../../controllers/API";
|
||||
import {
|
||||
ColDef,
|
||||
ColGroupDef,
|
||||
SizeColumnsToFitGridStrategy,
|
||||
} from "ag-grid-community";
|
||||
|
||||
export default function FlowLogsModal({
|
||||
open,
|
||||
setOpen,
|
||||
}: FlowSettingsPropsType): JSX.Element {
|
||||
const saveFlow = useFlowsManagerStore((state) => state.saveFlow);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
useEffect(() => {
|
||||
setName(currentFlow!.name);
|
||||
setDescription(currentFlow!.description);
|
||||
}, [currentFlow!.name, currentFlow!.description, open]);
|
||||
const setNoticeData = useAlertStore((state) => state.setNoticeData);
|
||||
|
||||
const [name, setName] = useState(currentFlow!.name);
|
||||
const [description, setDescription] = useState(currentFlow!.description);
|
||||
const [columns, setColumns] = useState<Array<ColDef | ColGroupDef>>([]);
|
||||
const [rows, setRows] = useState<any>([]);
|
||||
const [activeTab, setActiveTab] = useState("Executions");
|
||||
|
||||
function handleClick(): void {
|
||||
currentFlow!.name = name;
|
||||
currentFlow!.description = description;
|
||||
saveFlow(currentFlow!);
|
||||
setOpen(false);
|
||||
}
|
||||
const noticed = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === "Executions") {
|
||||
|
|
@ -47,24 +33,35 @@ export default function FlowLogsModal({
|
|||
setRows(rows);
|
||||
});
|
||||
} else if (activeTab === "Messages") {
|
||||
getMessagesTable(currentFlowId, "union").then((data) => {
|
||||
const { columns, rows } = data;
|
||||
setColumns(columns.map((col) => ({ ...col, editable: true })));
|
||||
setRows(rows);
|
||||
});
|
||||
getMessagesTable("union", currentFlowId, ["index", "flow_id"]).then(
|
||||
(data) => {
|
||||
const { columns, rows } = data;
|
||||
setColumns(columns.map((col) => ({ ...col, editable: true })));
|
||||
setRows(rows);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (open && activeTab === "Messages" && !noticed.current) {
|
||||
const haStream = nodes
|
||||
.map((nodes) => (nodes.data as NodeDataType).node!.template)
|
||||
.some((template) => template["stream"] && template["stream"].value);
|
||||
console.log(
|
||||
haStream,
|
||||
nodes.map((nodes) => (nodes.data as NodeDataType).node!.template),
|
||||
);
|
||||
if (haStream) {
|
||||
setNoticeData({
|
||||
title: "Streamed messages will not appear in this table.",
|
||||
});
|
||||
noticed.current = true;
|
||||
}
|
||||
}
|
||||
if (!open) {
|
||||
noticed.current = false;
|
||||
}
|
||||
}, [open, activeTab]);
|
||||
|
||||
const [nameLists, setNameList] = useState<string[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const tempNameList: string[] = [];
|
||||
flows.forEach((flow: FlowType) => {
|
||||
if ((flow.is_component ?? false) === false) tempNameList.push(flow.name);
|
||||
});
|
||||
setNameList(tempNameList.filter((name) => name !== currentFlow!.name));
|
||||
}, [flows]);
|
||||
|
||||
return (
|
||||
<BaseModal open={open} setOpen={setOpen} size="large">
|
||||
<BaseModal.Header description="Inspect component executions and monitor sent messages in the playground.">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { SETTINGS_DIALOG_SUBTITLE } from "../../constants/constants";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { FlowSettingsPropsType } from "../../types/components";
|
||||
import { FlowType } from "../../types/flow";
|
||||
|
|
@ -22,12 +22,25 @@ export default function FlowSettingsModal({
|
|||
|
||||
const [name, setName] = useState(currentFlow!.name);
|
||||
const [description, setDescription] = useState(currentFlow!.description);
|
||||
|
||||
const [endpoint_name, setEndpointName] = useState(currentFlow!.endpoint_name);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
function handleClick(): void {
|
||||
setIsSaving(true);
|
||||
currentFlow!.name = name;
|
||||
currentFlow!.description = description;
|
||||
saveFlow(currentFlow!);
|
||||
setOpen(false);
|
||||
currentFlow!.endpoint_name = endpoint_name;
|
||||
saveFlow(currentFlow!)
|
||||
?.then(() => {
|
||||
setOpen(false);
|
||||
setIsSaving(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
useAlertStore.getState().setErrorData({
|
||||
title: "Error while saving changes",
|
||||
list: [err?.response?.data.detail ?? ""],
|
||||
});
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
const [nameLists, setNameList] = useState<string[]>([]);
|
||||
|
|
@ -41,7 +54,12 @@ export default function FlowSettingsModal({
|
|||
}, [flows]);
|
||||
|
||||
return (
|
||||
<BaseModal open={open} setOpen={setOpen} size="smaller">
|
||||
<BaseModal
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
size="smaller-h-full"
|
||||
onSubmit={handleClick}
|
||||
>
|
||||
<BaseModal.Header description={SETTINGS_DIALOG_SUBTITLE}>
|
||||
<span className="pr-2">Settings</span>
|
||||
<IconComponent name="Settings2" className="mr-2 h-4 w-4 " />
|
||||
|
|
@ -51,20 +69,21 @@ export default function FlowSettingsModal({
|
|||
invalidNameList={nameLists}
|
||||
name={name}
|
||||
description={description}
|
||||
endpointName={endpoint_name}
|
||||
setName={setName}
|
||||
setDescription={setDescription}
|
||||
setEndpointName={setEndpointName}
|
||||
/>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<Button
|
||||
disabled={nameLists.includes(name) && name !== currentFlow!.name}
|
||||
onClick={handleClick}
|
||||
type="submit"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
<BaseModal.Footer
|
||||
submit={{
|
||||
label: "Save",
|
||||
disabled: nameLists.includes(name) && name !== currentFlow!.name,
|
||||
dataTestId: "save-flow-settings",
|
||||
loading: isSaving,
|
||||
}}
|
||||
/>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,7 +166,6 @@ export default function GenericModal({
|
|||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
setIsEdit(true);
|
||||
return setErrorData({
|
||||
title: PROMPT_ERROR_ALERT,
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@ export default function SecretKeyModal({
|
|||
{renderKey === false && (
|
||||
<div className="float-right">
|
||||
<Button
|
||||
type="button"
|
||||
className="mr-3"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { Loader2 } from "lucide-react";
|
||||
import { ReactNode, useEffect, useMemo, useState } from "react";
|
||||
import EditFlowSettings from "../../components/editFlowSettingsComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
|
|
@ -129,14 +128,14 @@ export default function ShareModal({
|
|||
title: "Error sharing " + is_component ? "component" : "flow",
|
||||
list: [err["response"]["data"]["detail"]],
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
else
|
||||
updateFlowStore(
|
||||
flow!,
|
||||
getTagsIds(selectedTags, tags),
|
||||
sharePublic,
|
||||
unavaliableNames.find((e) => e.name === name)!.id
|
||||
unavaliableNames.find((e) => e.name === name)!.id,
|
||||
).then(successShare, (err) => {
|
||||
setErrorData({
|
||||
title: "Error sharing " + is_component ? "component" : "flow",
|
||||
|
|
@ -202,6 +201,18 @@ export default function ShareModal({
|
|||
size="smaller-h-full"
|
||||
open={(!disabled && open) ?? internalOpen}
|
||||
setOpen={setOpen ?? internalSetOpen}
|
||||
onSubmit={() => {
|
||||
const isNameAvailable = !unavaliableNames.some(
|
||||
(element) => element.name === name,
|
||||
);
|
||||
|
||||
if (isNameAvailable) {
|
||||
handleShareComponent();
|
||||
(setOpen || internalSetOpen)(false);
|
||||
} else {
|
||||
setOpenConfirmationModal(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<BaseModal.Trigger asChild>
|
||||
{children ? children : <></>}
|
||||
|
|
@ -250,8 +261,13 @@ export default function ShareModal({
|
|||
</span>
|
||||
</BaseModal.Content>
|
||||
|
||||
<BaseModal.Footer>
|
||||
<div className="flex w-full justify-between gap-2">
|
||||
<BaseModal.Footer
|
||||
submit={{
|
||||
label: `Share ${is_component ? " Component" : " Flow"}`,
|
||||
loading: loadingNames,
|
||||
}}
|
||||
>
|
||||
<>
|
||||
{!is_component && (
|
||||
<ExportModal>
|
||||
<Button
|
||||
|
|
@ -281,37 +297,7 @@ export default function ShareModal({
|
|||
Export
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
disabled={loadingNames}
|
||||
type="button"
|
||||
className={is_component ? "w-40" : "w-28"}
|
||||
onClick={() => {
|
||||
const isNameAvailable = !unavaliableNames.some(
|
||||
(element) => element.name === name
|
||||
);
|
||||
|
||||
if (isNameAvailable) {
|
||||
handleShareComponent();
|
||||
(setOpen || internalSetOpen)(false);
|
||||
} else {
|
||||
setOpenConfirmationModal(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loadingNames ? (
|
||||
<>
|
||||
<div className="center">
|
||||
<Loader2 className="m-auto h-4 w-4 animate-spin"></Loader2>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Share{" "}
|
||||
{!loadingNames && (!is_component ? "Flow" : "Component")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</BaseModal.Footer>
|
||||
</BaseModal>
|
||||
<>{modalConfirmation}</>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
export default function getTagsIds(
|
||||
tags: string[],
|
||||
tagListId: { name: string; id: string }[],
|
||||
) {
|
||||
return tags
|
||||
.map((tag) => tagListId.find((tagObj) => tagObj.name === tag))!
|
||||
.map((tag) => tag!.id);
|
||||
tags: string[],
|
||||
tagListId: { name: string; id: string }[],
|
||||
) {
|
||||
return tags
|
||||
.map((tag) => tagListId.find((tagObj) => tagObj.name === tag))!
|
||||
.map((tag) => tag!.id);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,145 +0,0 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { useContext, useState } from "react";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { Input } from "../../components/ui/input";
|
||||
import {
|
||||
API_ERROR_ALERT,
|
||||
API_SUCCESS_ALERT,
|
||||
} from "../../constants/alerts_constants";
|
||||
import {
|
||||
CREATE_API_KEY,
|
||||
INSERT_API_KEY,
|
||||
INVALID_API_KEY,
|
||||
NO_API_KEY,
|
||||
} from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { addApiKeyStore } from "../../controllers/API";
|
||||
import useAlertStore from "../../stores/alertStore";
|
||||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import { StoreApiKeyType } from "../../types/components";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
export default function StoreApiKeyModal({
|
||||
children,
|
||||
disabled = false,
|
||||
}: StoreApiKeyType) {
|
||||
if (disabled) return <>{children}</>;
|
||||
const [open, setOpen] = useState(false);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const { storeApiKey } = useContext(AuthContext);
|
||||
const [apiKeyValue, setApiKeyValue] = useState("");
|
||||
|
||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||
const setHasApiKey = useStoreStore((state) => state.updateHasApiKey);
|
||||
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
|
||||
const setLoadingApiKey = useStoreStore((state) => state.updateLoadingApiKey);
|
||||
|
||||
const handleSaveKey = () => {
|
||||
if (apiKeyValue) {
|
||||
addApiKeyStore(apiKeyValue).then(
|
||||
() => {
|
||||
setSuccessData({
|
||||
title: API_SUCCESS_ALERT,
|
||||
});
|
||||
storeApiKey(apiKeyValue);
|
||||
setOpen(false);
|
||||
setHasApiKey(true);
|
||||
setValidApiKey(true);
|
||||
setLoadingApiKey(false);
|
||||
},
|
||||
(error) => {
|
||||
setErrorData({
|
||||
title: API_ERROR_ALERT,
|
||||
list: [error["response"]["data"]["detail"]],
|
||||
});
|
||||
setHasApiKey(false);
|
||||
setValidApiKey(false);
|
||||
setLoadingApiKey(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseModal size="small-h-full" open={open && !disabled} setOpen={setOpen}>
|
||||
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header
|
||||
description={
|
||||
(hasApiKey && !validApiKey
|
||||
? INVALID_API_KEY
|
||||
: !hasApiKey
|
||||
? NO_API_KEY
|
||||
: "") + INSERT_API_KEY
|
||||
}
|
||||
>
|
||||
<span className="pr-2">API Key</span>
|
||||
<IconComponent
|
||||
name="Key"
|
||||
className="h-6 w-6 pl-1 text-foreground"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
handleSaveKey();
|
||||
}}
|
||||
>
|
||||
<div className="grid gap-5">
|
||||
<Form.Field name="apikey">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<Form.Control asChild>
|
||||
<Input
|
||||
//fake api key
|
||||
value={apiKeyValue}
|
||||
type="password"
|
||||
onChange={({ target: { value } }) => {
|
||||
setApiKeyValue(value);
|
||||
}}
|
||||
placeholder="Insert your API Key"
|
||||
/>
|
||||
</Form.Control>
|
||||
</div>
|
||||
</Form.Field>
|
||||
</div>
|
||||
<div className="flex items-end justify-between">
|
||||
<span className="pr-1 text-xs text-muted-foreground">
|
||||
{CREATE_API_KEY}{" "}
|
||||
<a
|
||||
className="text-high-indigo underline"
|
||||
href="https://langflow.store/"
|
||||
target="_blank"
|
||||
>
|
||||
langflow.store
|
||||
</a>
|
||||
</span>
|
||||
<div className="">
|
||||
<Button
|
||||
className="mr-3"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Form.Submit asChild>
|
||||
<Button
|
||||
data-testid="api-key-save-button-store"
|
||||
className="mt-8"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Form.Submit>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Root>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,33 +3,33 @@ import { getRandomElement } from "../../../../../utils/reactflowUtils";
|
|||
import { toTitleCase } from "../../../../../utils/utils";
|
||||
|
||||
export default function getRandomName(
|
||||
retry: number = 0,
|
||||
noSpace: boolean = false,
|
||||
maxRetries: number = 3,
|
||||
): string {
|
||||
const left: string[] = ADJECTIVES;
|
||||
const right: string[] = NOUNS;
|
||||
|
||||
const lv = getRandomElement(left);
|
||||
const rv = getRandomElement(right);
|
||||
|
||||
// Condition to avoid "boring wozniak"
|
||||
if (lv === "boring" && rv === "wozniak") {
|
||||
if (retry < maxRetries) {
|
||||
return getRandomName(retry + 1, noSpace, maxRetries);
|
||||
} else {
|
||||
console.warn("Max retries reached, returning as is");
|
||||
}
|
||||
retry: number = 0,
|
||||
noSpace: boolean = false,
|
||||
maxRetries: number = 3,
|
||||
): string {
|
||||
const left: string[] = ADJECTIVES;
|
||||
const right: string[] = NOUNS;
|
||||
|
||||
const lv = getRandomElement(left);
|
||||
const rv = getRandomElement(right);
|
||||
|
||||
// Condition to avoid "boring wozniak"
|
||||
if (lv === "boring" && rv === "wozniak") {
|
||||
if (retry < maxRetries) {
|
||||
return getRandomName(retry + 1, noSpace, maxRetries);
|
||||
} else {
|
||||
console.warn("Max retries reached, returning as is");
|
||||
}
|
||||
|
||||
// Append a suffix if retrying and noSpace is true
|
||||
if (retry > 0 && noSpace) {
|
||||
const retrySuffix = Math.floor(Math.random() * 10);
|
||||
return `${lv}_${rv}${retrySuffix}`;
|
||||
}
|
||||
|
||||
// Construct the final name
|
||||
let final_name = noSpace ? `${lv}_${rv}` : `${lv} ${rv}`;
|
||||
// Return title case final name
|
||||
return toTitleCase(final_name);
|
||||
}
|
||||
}
|
||||
|
||||
// Append a suffix if retrying and noSpace is true
|
||||
if (retry > 0 && noSpace) {
|
||||
const retrySuffix = Math.floor(Math.random() * 10);
|
||||
return `${lv}_${rv}${retrySuffix}`;
|
||||
}
|
||||
|
||||
// Construct the final name
|
||||
let final_name = noSpace ? `${lv}_${rv}` : `${lv} ${rv}`;
|
||||
// Return title case final name
|
||||
return toTitleCase(final_name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,10 +19,7 @@ import {
|
|||
nodeIconsLucide,
|
||||
nodeNames,
|
||||
} from "../../../../utils/styleUtils";
|
||||
import {
|
||||
classNames,
|
||||
removeCountFromString,
|
||||
} from "../../../../utils/utils";
|
||||
import { classNames, removeCountFromString } from "../../../../utils/utils";
|
||||
import DisclosureComponent from "../DisclosureComponent";
|
||||
import ParentDisclosureComponent from "../ParentDisclosureComponent";
|
||||
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
||||
|
|
@ -45,7 +42,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
const [search, setSearch] = useState("");
|
||||
function onDragStart(
|
||||
event: React.DragEvent<any>,
|
||||
data: { type: string; node?: APIClassType }
|
||||
data: { type: string; node?: APIClassType },
|
||||
): void {
|
||||
//start drag event
|
||||
var crt = event.currentTarget.cloneNode(true);
|
||||
|
|
@ -71,7 +68,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
let keys = Object.keys(data[d]).filter(
|
||||
(nd) =>
|
||||
nd.toLowerCase().includes(e.toLowerCase()) ||
|
||||
data[d][nd].display_name?.toLowerCase().includes(e.toLowerCase())
|
||||
data[d][nd].display_name?.toLowerCase().includes(e.toLowerCase()),
|
||||
);
|
||||
keys.forEach((element) => {
|
||||
ret[d][element] = data[d][element];
|
||||
|
|
@ -138,7 +135,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
|
||||
if (filtered.some((x) => x !== "")) {
|
||||
let keys = Object.keys(dataClone[d]).filter((nd) =>
|
||||
filtered.includes(nd)
|
||||
filtered.includes(nd),
|
||||
);
|
||||
Object.keys(dataClone[d]).forEach((element) => {
|
||||
if (!keys.includes(element)) {
|
||||
|
|
@ -175,7 +172,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
|
||||
if (filtered.some((x) => x !== "")) {
|
||||
let keys = Object.keys(dataClone[d]).filter((nd) =>
|
||||
filtered.includes(nd)
|
||||
filtered.includes(nd),
|
||||
);
|
||||
Object.keys(dataClone[d]).forEach((element) => {
|
||||
if (!keys.includes(element)) {
|
||||
|
|
@ -204,7 +201,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
"extra-side-bar-buttons gap-[4px] text-sm font-semibold",
|
||||
!hasApiKey || !validApiKey || !hasStore
|
||||
? "button-disable cursor-default text-muted-foreground"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
>
|
||||
<IconComponent
|
||||
|
|
@ -213,14 +210,14 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
"-m-0.5 -ml-1 h-6 w-6",
|
||||
!hasApiKey || !validApiKey || !hasStore
|
||||
? "extra-side-bar-save-disable"
|
||||
: ""
|
||||
: "",
|
||||
)}
|
||||
/>
|
||||
Share
|
||||
</button>
|
||||
</ShareModal>
|
||||
),
|
||||
[hasApiKey, validApiKey, currentFlow, hasStore]
|
||||
[hasApiKey, validApiKey, currentFlow, hasStore],
|
||||
);
|
||||
|
||||
const ExportMemo = useMemo(
|
||||
|
|
@ -231,7 +228,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</button>
|
||||
</ExportModal>
|
||||
),
|
||||
[]
|
||||
[],
|
||||
);
|
||||
|
||||
const getIcon = useMemo(() => {
|
||||
|
|
@ -287,7 +284,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
<div className="side-bar-components-div-arrangement">
|
||||
<div className="parent-disclosure-arrangement">
|
||||
<div className="flex items-center gap-4 align-middle">
|
||||
<span className="parent-disclosure-title">Core Components</span>
|
||||
<span className="parent-disclosure-title">Basic Components</span>
|
||||
</div>
|
||||
</div>
|
||||
{Object.keys(dataFilter)
|
||||
|
|
@ -315,8 +312,8 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
.sort((a, b) =>
|
||||
sensitiveSort(
|
||||
dataFilter[SBSectionName][a].display_name,
|
||||
dataFilter[SBSectionName][b].display_name
|
||||
)
|
||||
dataFilter[SBSectionName][b].display_name,
|
||||
),
|
||||
)
|
||||
.map((SBItemName: string, index) => (
|
||||
<ShadTooltip
|
||||
|
|
@ -360,13 +357,13 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
)
|
||||
),
|
||||
)}{" "}
|
||||
<ParentDisclosureComponent
|
||||
openDisc={false}
|
||||
key={"Extended"}
|
||||
key={"Advanced"}
|
||||
button={{
|
||||
title: "Extended",
|
||||
title: "Advanced",
|
||||
Icon: nodeIconsLucide.unknown,
|
||||
}}
|
||||
testId="extended-disclosure"
|
||||
|
|
@ -397,8 +394,8 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
.sort((a, b) =>
|
||||
sensitiveSort(
|
||||
dataFilter[SBSectionName][a].display_name,
|
||||
dataFilter[SBSectionName][b].display_name
|
||||
)
|
||||
dataFilter[SBSectionName][b].display_name,
|
||||
),
|
||||
)
|
||||
.map((SBItemName: string, index) => (
|
||||
<ShadTooltip
|
||||
|
|
@ -472,7 +469,7 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
</>
|
||||
) : (
|
||||
<div key={index}></div>
|
||||
)
|
||||
),
|
||||
)}
|
||||
</ParentDisclosureComponent>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,25 +1,24 @@
|
|||
export default function sensitiveSort(a: string, b: string): number {
|
||||
// Extract the name and number from each string using regular expressions
|
||||
const regex = /(.+) \((\w+)\)/;
|
||||
const matchA = a.match(regex);
|
||||
const matchB = b.match(regex);
|
||||
|
||||
if (matchA && matchB) {
|
||||
// Compare the names alphabetically
|
||||
const nameA = matchA[1];
|
||||
const nameB = matchB[1];
|
||||
if (nameA !== nameB) {
|
||||
return nameA.localeCompare(nameB);
|
||||
}
|
||||
|
||||
// If the names are the same, compare the numbers numerically
|
||||
const numberA = parseInt(matchA[2]);
|
||||
const numberB = parseInt(matchB[2]);
|
||||
return numberA - numberB;
|
||||
} else {
|
||||
// Handle cases where one or both strings do not match the expected pattern
|
||||
// Simple strings are treated as pure alphabetical comparisons
|
||||
return a.localeCompare(b);
|
||||
// Extract the name and number from each string using regular expressions
|
||||
const regex = /(.+) \((\w+)\)/;
|
||||
const matchA = a.match(regex);
|
||||
const matchB = b.match(regex);
|
||||
|
||||
if (matchA && matchB) {
|
||||
// Compare the names alphabetically
|
||||
const nameA = matchA[1];
|
||||
const nameB = matchB[1];
|
||||
if (nameA !== nameB) {
|
||||
return nameA.localeCompare(nameB);
|
||||
}
|
||||
|
||||
// If the names are the same, compare the numbers numerically
|
||||
const numberA = parseInt(matchA[2]);
|
||||
const numberB = parseInt(matchB[2]);
|
||||
return numberA - numberB;
|
||||
} else {
|
||||
// Handle cases where one or both strings do not match the expected pattern
|
||||
// Simple strings are treated as pure alphabetical comparisons
|
||||
return a.localeCompare(b);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +29,7 @@ import {
|
|||
expandGroupNode,
|
||||
updateFlowPosition,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
import { classNames, cn } from "../../../../utils/utils";
|
||||
import ToolbarSelectItem from "./toolbarSelectItem";
|
||||
|
||||
export default function NodeToolbarComponent({
|
||||
|
|
@ -68,7 +68,7 @@ export default function NodeToolbarComponent({
|
|||
const isMinimal = numberOfHandles <= 1;
|
||||
const isGroup = data.node?.flow ? true : false;
|
||||
|
||||
// const frozen = data.node?.frozen ?? false;
|
||||
const frozen = data.node?.frozen ?? false;
|
||||
const paste = useFlowStore((state) => state.paste);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
|
|
@ -141,6 +141,9 @@ export default function NodeToolbarComponent({
|
|||
break;
|
||||
case "disabled":
|
||||
break;
|
||||
case "unselect":
|
||||
unselectAll();
|
||||
break;
|
||||
case "ungroup":
|
||||
takeSnapshot();
|
||||
expandGroupNode(
|
||||
|
|
@ -180,10 +183,10 @@ export default function NodeToolbarComponent({
|
|||
case "update":
|
||||
takeSnapshot();
|
||||
// to update we must get the code from the templates in useTypesStore
|
||||
const thisNodeTemplate = templates[data.type].template;
|
||||
const thisNodeTemplate = templates[data.type]?.template;
|
||||
// if the template does not have a code key
|
||||
// return
|
||||
if (!thisNodeTemplate.code) return;
|
||||
if (!thisNodeTemplate?.code) return;
|
||||
|
||||
const currentCode = thisNodeTemplate.code.value;
|
||||
if (data.node) {
|
||||
|
|
@ -271,16 +274,20 @@ export default function NodeToolbarComponent({
|
|||
selected &&
|
||||
(hasApiKey || hasStore) &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "u"
|
||||
event.key.toUpperCase() === "U"
|
||||
) {
|
||||
event.preventDefault();
|
||||
handleSelectChange("update");
|
||||
}
|
||||
if (selected && event.key.toUpperCase() === "ESCAPE") {
|
||||
event.preventDefault();
|
||||
handleSelectChange("unselect");
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
isGroup &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "g"
|
||||
event.key.toUpperCase() === "G"
|
||||
) {
|
||||
event.preventDefault();
|
||||
handleSelectChange("ungroup");
|
||||
|
|
@ -290,7 +297,7 @@ export default function NodeToolbarComponent({
|
|||
(hasApiKey || hasStore) &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key === "S"
|
||||
event.key.toUpperCase() === "S"
|
||||
) {
|
||||
event.preventDefault();
|
||||
setShowconfirmShare((state) => !state);
|
||||
|
|
@ -300,7 +307,7 @@ export default function NodeToolbarComponent({
|
|||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key === "Q"
|
||||
event.key.toUpperCase() === "Q"
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (isMinimal) {
|
||||
|
|
@ -317,7 +324,7 @@ export default function NodeToolbarComponent({
|
|||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key === "U"
|
||||
event.key.toUpperCase() === "U"
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (hasCode) return setOpenModal((state) => !state);
|
||||
|
|
@ -327,12 +334,16 @@ export default function NodeToolbarComponent({
|
|||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key === "A"
|
||||
event.key.toUpperCase() === "A"
|
||||
) {
|
||||
event.preventDefault();
|
||||
setShowModalAdvanced((state) => !state);
|
||||
}
|
||||
if (selected && (event.ctrlKey || event.metaKey) && event.key === "s") {
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key.toUpperCase() === "S"
|
||||
) {
|
||||
if (isSaved) {
|
||||
event.preventDefault();
|
||||
return setShowOverrideModal((state) => !state);
|
||||
|
|
@ -347,7 +358,7 @@ export default function NodeToolbarComponent({
|
|||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key === "D"
|
||||
event.key.toUpperCase() === "D"
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (data.node?.documentation) {
|
||||
|
|
@ -357,7 +368,11 @@ export default function NodeToolbarComponent({
|
|||
title: `${data.id} docs is not available at the moment.`,
|
||||
});
|
||||
}
|
||||
if (selected && (event.ctrlKey || event.metaKey) && event.key === "j") {
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key.toUpperCase() === "J"
|
||||
) {
|
||||
event.preventDefault();
|
||||
downloadNode(flowComponent!);
|
||||
}
|
||||
|
|
@ -372,7 +387,7 @@ export default function NodeToolbarComponent({
|
|||
|
||||
return (
|
||||
<>
|
||||
<div className="w-26 h-10">
|
||||
<div className="w-26 nocopy nowheel nopan nodelete nodrag noundo h-10">
|
||||
<span className="isolate inline-flex rounded-md shadow-sm">
|
||||
{hasCode && (
|
||||
<ShadTooltip content="Code" side="top">
|
||||
|
|
@ -422,10 +437,10 @@ export default function NodeToolbarComponent({
|
|||
</button>
|
||||
</ShadTooltip>
|
||||
|
||||
{/* <ShadTooltip content="Freeze" side="top">
|
||||
<ShadTooltip content="Freeze" side="top">
|
||||
<button
|
||||
className={classNames(
|
||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10"
|
||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
|
@ -435,7 +450,7 @@ export default function NodeToolbarComponent({
|
|||
...old.data,
|
||||
node: {
|
||||
...old.data.node,
|
||||
// frozen: old.data?.node?.frozen ? false : true,
|
||||
frozen: old.data?.node?.frozen ? false : true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
|
@ -446,11 +461,11 @@ export default function NodeToolbarComponent({
|
|||
className={cn(
|
||||
"h-4 w-4 transition-all",
|
||||
// TODO UPDATE THIS COLOR TO BE A VARIABLE
|
||||
frozen ? "animate-wiggle text-ice" : ""
|
||||
frozen ? "animate-wiggle text-ice" : "",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</ShadTooltip> */}
|
||||
</ShadTooltip>
|
||||
|
||||
<Select onValueChange={handleSelectChange} value="">
|
||||
<ShadTooltip content="More" side="top">
|
||||
|
|
@ -483,16 +498,6 @@ export default function NodeToolbarComponent({
|
|||
/>
|
||||
</SelectItem>
|
||||
)}
|
||||
{/* <SelectItem value={"duplicate"}>
|
||||
<ToolbarSelectItem
|
||||
keyboardKey="D"
|
||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
||||
shift={false}
|
||||
value={"Duplicate"}
|
||||
icon={"Copy"}
|
||||
dataTestId="duplicate-button-modal"
|
||||
/>
|
||||
</SelectItem> */}
|
||||
<SelectItem value={"copy"}>
|
||||
<ToolbarSelectItem
|
||||
keyboardKey="C"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import FlowToolbar from "../../components/chatComponent";
|
||||
import Header from "../../components/headerComponent";
|
||||
import { useDarkStore } from "../../stores/darkStore";
|
||||
|
|
@ -16,9 +16,19 @@ export default function FlowPage({ view }: { view?: boolean }): JSX.Element {
|
|||
const setOnFlowPage = useFlowStore((state) => state.setOnFlowPage);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
|
||||
// Set flow tab id
|
||||
useEffect(() => {
|
||||
const isAnExistingFlow = flows.some((flow) => flow.id === id);
|
||||
|
||||
if (!isAnExistingFlow) {
|
||||
navigate("/all");
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentFlowId(id!);
|
||||
setOnFlowPage(true);
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export default function ComponentsComponent({
|
|||
const myCollectionId = useFolderStore((state) => state.myCollectionId);
|
||||
const getFoldersApi = useFolderStore((state) => state.getFoldersApi);
|
||||
const setFolderUrl = useFolderStore((state) => state.setFolderUrl);
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
|
||||
useEffect(() => {
|
||||
setFolderUrl(folderId ?? "");
|
||||
|
|
@ -115,7 +116,7 @@ export default function ComponentsComponent({
|
|||
});
|
||||
};
|
||||
|
||||
const handleSelectOptionsChange = () => {
|
||||
const handleSelectOptionsChange = (action: string) => {
|
||||
const hasSelected = selectedFlowsComponentsCards?.length > 0;
|
||||
if (!hasSelected) {
|
||||
setErrorData({
|
||||
|
|
@ -124,7 +125,31 @@ export default function ComponentsComponent({
|
|||
});
|
||||
return;
|
||||
}
|
||||
setOpenDelete(true);
|
||||
if (action === "delete") {
|
||||
setOpenDelete(true);
|
||||
} else if (action === "duplicate") {
|
||||
handleDuplicate();
|
||||
}
|
||||
};
|
||||
|
||||
const handleDuplicate = () => {
|
||||
Promise.all(
|
||||
selectedFlowsComponentsCards.map((selectedFlow) =>
|
||||
addFlow(
|
||||
true,
|
||||
allFlows.find((flow) => flow.id === selectedFlow),
|
||||
),
|
||||
),
|
||||
).then(() => {
|
||||
resetFilter();
|
||||
getFoldersApi(true);
|
||||
if (!folderId || folderId === myCollectionId) {
|
||||
getFolderById(folderId ? folderId : myCollectionId);
|
||||
}
|
||||
setSelectedFlowsComponentsCards([]);
|
||||
|
||||
setSuccessData({ title: "Flows duplicated successfully" });
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteMultiple = () => {
|
||||
|
|
@ -198,9 +223,10 @@ export default function ComponentsComponent({
|
|||
<>
|
||||
{allFlows?.length > 0 && (
|
||||
<HeaderComponent
|
||||
handleDelete={handleSelectOptionsChange}
|
||||
handleDelete={() => handleSelectOptionsChange("delete")}
|
||||
handleSelectAll={handleSelectAll}
|
||||
disableDelete={!(selectedFlowsComponentsCards?.length > 0)}
|
||||
handleDuplicate={() => handleSelectOptionsChange("duplicate")}
|
||||
disableFunctions={!(selectedFlowsComponentsCards?.length > 0)}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { useNavigate } from "react-router-dom";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import NewFlowModal from "../../../../modals/newFlowModal";
|
||||
import { useState } from "react";
|
||||
|
||||
type EmptyComponentProps = {};
|
||||
|
||||
|
|
@ -7,31 +9,28 @@ const EmptyComponent = ({}: EmptyComponentProps) => {
|
|||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-6 flex w-full items-center justify-center text-center">
|
||||
<div className="flex-max-width h-full flex-col">
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<div className="grid w-full gap-4 text-muted-foreground">
|
||||
Flows and components can be created using Langflow.
|
||||
</div>
|
||||
<div className="align-center flex w-full justify-center gap-1 ">
|
||||
<span className="text-muted-foreground">New?</span>
|
||||
<span className="transition-colors hover:text-muted-foreground">
|
||||
<button
|
||||
onClick={() => {
|
||||
addFlow(true).then((id) => {
|
||||
navigate("/flow/" + id);
|
||||
});
|
||||
}}
|
||||
className="underline"
|
||||
>
|
||||
Start Here
|
||||
</button>
|
||||
.
|
||||
</span>
|
||||
<span className="animate-pulse">🚀</span>
|
||||
</div>
|
||||
<div className="align-center flex w-full justify-center gap-1 ">
|
||||
<span className="text-muted-foreground">
|
||||
This folder is empty. New?
|
||||
</span>
|
||||
<span className="transition-colors hover:text-muted-foreground">
|
||||
<NewFlowModal open={openModal} setOpen={setOpenModal} />
|
||||
<button
|
||||
onClick={() => {
|
||||
setOpenModal(true);
|
||||
}}
|
||||
className="underline"
|
||||
>
|
||||
Start Here
|
||||
</button>
|
||||
</span>
|
||||
<span className="animate-pulse">🚀</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ import ShadTooltip from "../../../../components/shadTooltipComponent";
|
|||
type HeaderComponentProps = {
|
||||
handleSelectAll: (select) => void;
|
||||
handleDelete: () => void;
|
||||
disableDelete: boolean;
|
||||
handleDuplicate: () => void;
|
||||
disableFunctions: boolean;
|
||||
};
|
||||
|
||||
const HeaderComponent = ({
|
||||
handleSelectAll,
|
||||
handleDelete,
|
||||
disableDelete,
|
||||
handleDuplicate,
|
||||
disableFunctions,
|
||||
}: HeaderComponentProps) => {
|
||||
const [shouldSelectAll, setShouldSelectAll] = useState(true);
|
||||
|
||||
|
|
@ -55,23 +57,41 @@ const HeaderComponent = ({
|
|||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div className="col-span-2 grid-cols-1 justify-self-end">
|
||||
<div className="col-span-2 flex grid-cols-1 gap-2 justify-self-end">
|
||||
<div>
|
||||
<ShadTooltip
|
||||
content={
|
||||
disableDelete ? (
|
||||
disableFunctions ? (
|
||||
<span>Select items to duplicate</span>
|
||||
) : (
|
||||
<span>Duplicate selected items</span>
|
||||
)
|
||||
}
|
||||
>
|
||||
<button onClick={handleDuplicate} disabled={disableFunctions}>
|
||||
<IconComponent
|
||||
name="Copy"
|
||||
className={cn("h-5 w-5 text-primary transition-all")}
|
||||
/>
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
</div>
|
||||
<div>
|
||||
<ShadTooltip
|
||||
content={
|
||||
disableFunctions ? (
|
||||
<span>Select items to delete</span>
|
||||
) : (
|
||||
<span>Delete selected items</span>
|
||||
)
|
||||
}
|
||||
>
|
||||
<button onClick={handleDelete} disabled={disableDelete}>
|
||||
<button onClick={handleDelete} disabled={disableFunctions}>
|
||||
<IconComponent
|
||||
name="Trash2"
|
||||
className={cn(
|
||||
"h-5 w-5 text-primary transition-all",
|
||||
disableDelete ? "" : "hover:text-destructive",
|
||||
disableFunctions ? "" : "hover:text-destructive",
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -6,13 +6,14 @@ export type FolderType = {
|
|||
id?: string | null;
|
||||
parent_id: string;
|
||||
flows: FlowType[];
|
||||
components: string[];
|
||||
};
|
||||
|
||||
export type AddFolderType = {
|
||||
name: string;
|
||||
description: string;
|
||||
id?: string | null;
|
||||
parent_id: string;
|
||||
parent_id: string | null;
|
||||
flows?: string[];
|
||||
components?: string[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { BASE_URL_API } from "../../../constants/constants";
|
||||
import { api } from "../../../controllers/API/api";
|
||||
import { FlowType } from "../../../types/flow";
|
||||
import { AddFolderType, FolderType, StarterProjectsType } from "../entities";
|
||||
import { AddFolderType, FolderType } from "../entities";
|
||||
|
||||
export async function getFolders(): Promise<FolderType[]> {
|
||||
try {
|
||||
|
|
@ -30,12 +30,12 @@ export async function addFolder(data: AddFolderType): Promise<FolderType> {
|
|||
|
||||
export async function updateFolder(
|
||||
body: FolderType,
|
||||
folderId: string,
|
||||
folderId: string
|
||||
): Promise<FolderType> {
|
||||
try {
|
||||
const response = await api.patch(
|
||||
`${BASE_URL_API}folders/${folderId}`,
|
||||
body,
|
||||
body
|
||||
);
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
|
|
@ -61,15 +61,6 @@ export async function getFolderById(folderId: string): Promise<FolderType> {
|
|||
}
|
||||
}
|
||||
|
||||
export async function getStarterProjects(): Promise<StarterProjectsType> {
|
||||
try {
|
||||
const response = await api.get(`${BASE_URL_API}folders/starter-projects`);
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
||||
flows: FlowType[];
|
||||
folder_name: string;
|
||||
|
|
@ -77,7 +68,7 @@ export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
|||
}> {
|
||||
try {
|
||||
const response = await api.get(
|
||||
`${BASE_URL_API}folders/download/${folderId}`,
|
||||
`${BASE_URL_API}folders/download/${folderId}`
|
||||
);
|
||||
if (response?.status !== 200) {
|
||||
throw new Error(`HTTP error! status: ${response?.status}`);
|
||||
|
|
@ -91,7 +82,7 @@ export async function downloadFlowsFromFolders(folderId: string): Promise<{
|
|||
}
|
||||
|
||||
export async function uploadFlowsFromFolders(
|
||||
flows: FormData,
|
||||
flows: FormData
|
||||
): Promise<FlowType[]> {
|
||||
try {
|
||||
const response = await api.post(`${BASE_URL_API}folders/upload/`, flows);
|
||||
|
|
@ -108,11 +99,11 @@ export async function uploadFlowsFromFolders(
|
|||
|
||||
export async function moveFlowToFolder(
|
||||
flowId: string,
|
||||
folderId: string,
|
||||
folderId: string
|
||||
): Promise<FlowType> {
|
||||
try {
|
||||
const response = await api.patch(
|
||||
`${BASE_URL_API}folders/move_to_folder/${flowId}/${folderId}`,
|
||||
`${BASE_URL_API}folders/move_to_folder/${flowId}/${folderId}`
|
||||
);
|
||||
return response?.data;
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ export default function PlaygroundPage() {
|
|||
|
||||
// Set flow tab id
|
||||
useEffect(() => {
|
||||
console.log("id", id);
|
||||
if (getFlowById(id!)) {
|
||||
setCurrentFlowId(id!);
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export default function SettingsPage(): JSX.Element {
|
|||
icon: (
|
||||
<ForwardedIconComponent
|
||||
name="SlidersHorizontal"
|
||||
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]"
|
||||
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
|
@ -32,13 +32,23 @@ export default function SettingsPage(): JSX.Element {
|
|||
icon: (
|
||||
<ForwardedIconComponent
|
||||
name="Globe"
|
||||
className="mx-[0.08rem] w-[1.1rem] stroke-[1.5]"
|
||||
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Shortcuts",
|
||||
href: "/settings/shortcuts",
|
||||
icon: (
|
||||
<ForwardedIconComponent
|
||||
name="Keyboard"
|
||||
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
|
||||
/>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Messages",
|
||||
href: "/settings/messages",
|
||||
icon: (
|
||||
<ForwardedIconComponent name="Keyboard" className="w-5 stroke-[1.5]" />
|
||||
),
|
||||
|
|
@ -50,10 +60,10 @@ export default function SettingsPage(): JSX.Element {
|
|||
description="Manage the general settings for Langflow."
|
||||
>
|
||||
<div className="flex h-full w-full space-y-8 lg:flex-row lg:space-x-8 lg:space-y-0">
|
||||
<aside className="flex h-full flex-col space-y-6 lg:w-1/5">
|
||||
<aside className="flex h-full shrink-0 flex-col space-y-6 lg:w-[20vw]">
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
</aside>
|
||||
<div className="h-full w-full flex-1">
|
||||
<div className="h-full w-full flex-1 pb-8">
|
||||
<Outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
import ForwardedIconComponent from "../../../../../../components/genericIconComponent";
|
||||
|
||||
const GeneralPageHeaderComponent = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
||||
<div className="flex w-full flex-col">
|
||||
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||
General
|
||||
<ForwardedIconComponent
|
||||
name="SlidersHorizontal"
|
||||
className="ml-2 h-5 w-5 text-primary"
|
||||
/>
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Manage settings related to Langflow and your account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default GeneralPageHeaderComponent;
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import InputComponent from "../../../../../../components/inputComponent";
|
||||
import { Button } from "../../../../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../../../../components/ui/card";
|
||||
|
||||
type PasswordFormComponentProps = {
|
||||
password: string;
|
||||
cnfPassword: string;
|
||||
handleInput: (event: any) => void;
|
||||
handlePatchPassword: (
|
||||
password: string,
|
||||
cnfPassword: string,
|
||||
handleInput: any,
|
||||
) => void;
|
||||
};
|
||||
const PasswordFormComponent = ({
|
||||
password,
|
||||
cnfPassword,
|
||||
handleInput,
|
||||
handlePatchPassword,
|
||||
}: PasswordFormComponentProps) => {
|
||||
return (
|
||||
<>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
handlePatchPassword(password, cnfPassword, handleInput);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Card x-chunk="dashboard-04-chunk-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Password</CardTitle>
|
||||
<CardDescription>
|
||||
Type your new password and confirm it.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex w-full gap-4">
|
||||
<Form.Field name="password" className="w-full">
|
||||
<InputComponent
|
||||
id="pasword"
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
}}
|
||||
value={password}
|
||||
isForm
|
||||
password={true}
|
||||
placeholder="Password"
|
||||
className="w-full"
|
||||
/>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
<Form.Field name="cnfPassword" className="w-full">
|
||||
<InputComponent
|
||||
id="cnfPassword"
|
||||
onChange={(value) => {
|
||||
handleInput({
|
||||
target: { name: "cnfPassword", value },
|
||||
});
|
||||
}}
|
||||
value={cnfPassword}
|
||||
isForm
|
||||
password={true}
|
||||
placeholder="Confirm Password"
|
||||
className="w-full"
|
||||
/>
|
||||
|
||||
<Form.Message className="field-invalid" match="valueMissing">
|
||||
Please confirm your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Form.Submit asChild>
|
||||
<Button type="submit">Save</Button>
|
||||
</Form.Submit>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Form.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default PasswordFormComponent;
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import GradientChooserComponent from "../../../../../../components/gradientChooserComponent";
|
||||
import { Button } from "../../../../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../../../../components/ui/card";
|
||||
import { gradients } from "../../../../../../utils/styleUtils";
|
||||
|
||||
type ProfileGradientFormComponentProps = {
|
||||
gradient: string;
|
||||
handleInput: (event: any) => void;
|
||||
handlePatchGradient: (gradient: string) => void;
|
||||
userData: any;
|
||||
};
|
||||
const ProfileGradientFormComponent = ({
|
||||
gradient,
|
||||
handleInput,
|
||||
handlePatchGradient,
|
||||
userData,
|
||||
}: ProfileGradientFormComponentProps) => {
|
||||
return (
|
||||
<>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
handlePatchGradient(gradient);
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Card x-chunk="dashboard-04-chunk-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Profile Gradient</CardTitle>
|
||||
<CardDescription>
|
||||
Choose the gradient that appears as your profile picture.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="py-2">
|
||||
<GradientChooserComponent
|
||||
value={
|
||||
gradient == ""
|
||||
? userData?.profile_image ??
|
||||
gradients[
|
||||
parseInt(userData?.id ?? "", 30) % gradients.length
|
||||
]
|
||||
: gradient
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "gradient", value } });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Form.Submit asChild>
|
||||
<Button type="submit">Save</Button>
|
||||
</Form.Submit>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Form.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default ProfileGradientFormComponent;
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import InputComponent from "../../../../../../components/inputComponent";
|
||||
import { Button } from "../../../../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../../../../components/ui/card";
|
||||
import {
|
||||
CREATE_API_KEY,
|
||||
INSERT_API_KEY,
|
||||
INVALID_API_KEY,
|
||||
NO_API_KEY,
|
||||
} from "../../../../../../constants/constants";
|
||||
|
||||
type StoreApiKeyFormComponentProps = {
|
||||
apikey: string;
|
||||
handleInput: (event: any) => void;
|
||||
handleSaveKey: (apikey: string, handleInput: any) => void;
|
||||
loadingApiKey: boolean;
|
||||
validApiKey: boolean;
|
||||
hasApiKey: boolean;
|
||||
};
|
||||
const StoreApiKeyFormComponent = ({
|
||||
apikey,
|
||||
handleInput,
|
||||
handleSaveKey,
|
||||
loadingApiKey,
|
||||
validApiKey,
|
||||
hasApiKey,
|
||||
}: StoreApiKeyFormComponentProps) => {
|
||||
return (
|
||||
<>
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
event.preventDefault();
|
||||
handleSaveKey(apikey, handleInput);
|
||||
}}
|
||||
>
|
||||
<Card x-chunk="dashboard-04-chunk-2" id="api">
|
||||
<CardHeader>
|
||||
<CardTitle>Store API Key</CardTitle>
|
||||
<CardDescription>
|
||||
{(hasApiKey && !validApiKey
|
||||
? INVALID_API_KEY
|
||||
: !hasApiKey
|
||||
? NO_API_KEY
|
||||
: "") + INSERT_API_KEY}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex w-full flex-col gap-3">
|
||||
<div className="flex w-full gap-4">
|
||||
<Form.Field name="apikey" className="w-full">
|
||||
<InputComponent
|
||||
id="apikey"
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "apikey", value } });
|
||||
}}
|
||||
value={apikey}
|
||||
isForm
|
||||
password={true}
|
||||
placeholder="Insert your API Key"
|
||||
className="w-full"
|
||||
/>
|
||||
<Form.Message match="valueMissing" className="field-invalid">
|
||||
Please enter your API Key
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
<span className="pr-1 text-xs text-muted-foreground">
|
||||
{CREATE_API_KEY}{" "}
|
||||
<a
|
||||
className="text-high-indigo underline"
|
||||
href="https://langflow.store/"
|
||||
target="_blank"
|
||||
>
|
||||
langflow.store
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Form.Submit asChild>
|
||||
<Button
|
||||
loading={loadingApiKey}
|
||||
type="submit"
|
||||
data-testid="api-key-save-button-store"
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Form.Submit>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Form.Root>
|
||||
</>
|
||||
);
|
||||
};
|
||||
export default StoreApiKeyFormComponent;
|
||||
|
|
@ -1,221 +1,107 @@
|
|||
import * as Form from "@radix-ui/react-form";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import ForwardedIconComponent from "../../../../components/genericIconComponent";
|
||||
import GradientChooserComponent from "../../../../components/gradientChooserComponent";
|
||||
import InputComponent from "../../../../components/inputComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "../../../../components/ui/card";
|
||||
import {
|
||||
EDIT_PASSWORD_ALERT_LIST,
|
||||
EDIT_PASSWORD_ERROR_ALERT,
|
||||
SAVE_ERROR_ALERT,
|
||||
SAVE_SUCCESS_ALERT,
|
||||
} from "../../../../constants/alerts_constants";
|
||||
import { useContext, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { CONTROL_PATCH_USER_STATE } from "../../../../constants/constants";
|
||||
import { AuthContext } from "../../../../contexts/authContext";
|
||||
import { resetPassword, updateUser } from "../../../../controllers/API";
|
||||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useStoreStore } from "../../../../stores/storeStore";
|
||||
import {
|
||||
inputHandlerEventType,
|
||||
patchUserInputStateType,
|
||||
} from "../../../../types/components";
|
||||
import { gradients } from "../../../../utils/styleUtils";
|
||||
import usePatchGradient from "../hooks/use-patch-gradient";
|
||||
import usePatchPassword from "../hooks/use-patch-password";
|
||||
import useSaveKey from "../hooks/use-save-key";
|
||||
import useScrollToElement from "../hooks/use-scroll-to-element";
|
||||
import GeneralPageHeaderComponent from "./components/GeneralPageHeader";
|
||||
import PasswordFormComponent from "./components/PasswordForm";
|
||||
import ProfileGradientFormComponent from "./components/ProfileGradientForm";
|
||||
import StoreApiKeyFormComponent from "./components/StoreApiKeyForm";
|
||||
|
||||
export default function GeneralPage() {
|
||||
const setCurrentFlowId = useFlowsManagerStore(
|
||||
(state) => state.setCurrentFlowId,
|
||||
);
|
||||
|
||||
const { scrollId } = useParams();
|
||||
|
||||
const [inputState, setInputState] = useState<patchUserInputStateType>(
|
||||
CONTROL_PATCH_USER_STATE,
|
||||
);
|
||||
|
||||
const { autoLogin } = useContext(AuthContext);
|
||||
|
||||
// set null id
|
||||
useEffect(() => {
|
||||
setCurrentFlowId("");
|
||||
}, []);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const { userData, setUserData } = useContext(AuthContext);
|
||||
const { password, cnfPassword, gradient } = inputState;
|
||||
const hasStore = useStoreStore((state) => state.hasStore);
|
||||
|
||||
async function handlePatchPassword() {
|
||||
if (password !== cnfPassword) {
|
||||
setErrorData({
|
||||
title: EDIT_PASSWORD_ERROR_ALERT,
|
||||
list: [EDIT_PASSWORD_ALERT_LIST],
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (password !== "") await resetPassword(userData!.id, { password });
|
||||
handleInput({ target: { name: "password", value: "" } });
|
||||
handleInput({ target: { name: "cnfPassword", value: "" } });
|
||||
setSuccessData({ title: SAVE_SUCCESS_ALERT });
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: SAVE_ERROR_ALERT,
|
||||
list: [(error as any).response.data.detail],
|
||||
});
|
||||
}
|
||||
}
|
||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||
const setHasApiKey = useStoreStore((state) => state.updateHasApiKey);
|
||||
const loadingApiKey = useStoreStore((state) => state.loadingApiKey);
|
||||
const setValidApiKey = useStoreStore((state) => state.updateValidApiKey);
|
||||
const setLoadingApiKey = useStoreStore((state) => state.updateLoadingApiKey);
|
||||
const { password, cnfPassword, gradient, apikey } = inputState;
|
||||
|
||||
async function handlePatchGradient() {
|
||||
try {
|
||||
if (gradient !== "")
|
||||
await updateUser(userData!.id, { profile_image: gradient });
|
||||
if (gradient !== "") {
|
||||
let newUserData = cloneDeep(userData);
|
||||
newUserData!.profile_image = gradient;
|
||||
const { handlePatchPassword } = usePatchPassword(
|
||||
userData,
|
||||
setSuccessData,
|
||||
setErrorData,
|
||||
);
|
||||
|
||||
setUserData(newUserData);
|
||||
}
|
||||
setSuccessData({ title: SAVE_SUCCESS_ALERT });
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: SAVE_ERROR_ALERT,
|
||||
list: [(error as any).response.data.detail],
|
||||
});
|
||||
}
|
||||
}
|
||||
const { handlePatchGradient } = usePatchGradient(
|
||||
setSuccessData,
|
||||
setErrorData,
|
||||
userData,
|
||||
setUserData,
|
||||
);
|
||||
|
||||
useScrollToElement(scrollId, setCurrentFlowId);
|
||||
|
||||
const { handleSaveKey } = useSaveKey(
|
||||
setSuccessData,
|
||||
setErrorData,
|
||||
setHasApiKey,
|
||||
setValidApiKey,
|
||||
setLoadingApiKey,
|
||||
);
|
||||
|
||||
function handleInput({
|
||||
target: { name, value },
|
||||
}: inputHandlerEventType): void {
|
||||
setInputState((prev) => ({ ...prev, [name]: value }));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col gap-6">
|
||||
<div className="flex w-full items-center justify-between gap-4 space-y-0.5">
|
||||
<div className="flex w-full flex-col">
|
||||
<h2 className="flex items-center text-lg font-semibold tracking-tight">
|
||||
General
|
||||
<ForwardedIconComponent
|
||||
name="SlidersHorizontal"
|
||||
className="ml-2 h-5 w-5 text-primary"
|
||||
/>
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Manage settings related to Langflow and your account.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<GeneralPageHeaderComponent />
|
||||
|
||||
<div className="grid gap-6">
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
handlePatchGradient();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Card x-chunk="dashboard-04-chunk-1">
|
||||
<CardHeader>
|
||||
<CardTitle>Profile Gradient</CardTitle>
|
||||
<CardDescription>
|
||||
Choose the gradient that appears as your profile picture.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="py-2">
|
||||
<GradientChooserComponent
|
||||
value={
|
||||
gradient == ""
|
||||
? userData?.profile_image ??
|
||||
gradients[
|
||||
parseInt(userData?.id ?? "", 30) % gradients.length
|
||||
]
|
||||
: gradient
|
||||
}
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "gradient", value } });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Form.Submit asChild>
|
||||
<Button type="submit">Save</Button>
|
||||
</Form.Submit>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Form.Root>
|
||||
{!autoLogin && (
|
||||
<Form.Root
|
||||
onSubmit={(event) => {
|
||||
handlePatchPassword();
|
||||
event.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Card x-chunk="dashboard-04-chunk-2">
|
||||
<CardHeader>
|
||||
<CardTitle>Password</CardTitle>
|
||||
<CardDescription>
|
||||
Type your new password and confirm it.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex w-full gap-4">
|
||||
<Form.Field name="password" className="w-full">
|
||||
<InputComponent
|
||||
id="pasword"
|
||||
onChange={(value) => {
|
||||
handleInput({ target: { name: "password", value } });
|
||||
}}
|
||||
value={password}
|
||||
isForm
|
||||
password={true}
|
||||
placeholder="Password"
|
||||
className="w-full"
|
||||
/>
|
||||
<Form.Message
|
||||
match="valueMissing"
|
||||
className="field-invalid"
|
||||
>
|
||||
Please enter your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
<Form.Field name="cnfPassword" className="w-full">
|
||||
<InputComponent
|
||||
id="cnfPassword"
|
||||
onChange={(value) => {
|
||||
handleInput({
|
||||
target: { name: "cnfPassword", value },
|
||||
});
|
||||
}}
|
||||
value={cnfPassword}
|
||||
isForm
|
||||
password={true}
|
||||
placeholder="Confirm Password"
|
||||
className="w-full"
|
||||
/>
|
||||
<ProfileGradientFormComponent
|
||||
gradient={gradient}
|
||||
handleInput={handleInput}
|
||||
handlePatchGradient={handlePatchGradient}
|
||||
userData={userData}
|
||||
/>
|
||||
|
||||
<Form.Message
|
||||
className="field-invalid"
|
||||
match="valueMissing"
|
||||
>
|
||||
Please confirm your password
|
||||
</Form.Message>
|
||||
</Form.Field>
|
||||
</div>
|
||||
</CardContent>
|
||||
<CardFooter className="border-t px-6 py-4">
|
||||
<Form.Submit asChild>
|
||||
<Button type="submit">Save</Button>
|
||||
</Form.Submit>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</Form.Root>
|
||||
{!autoLogin && (
|
||||
<PasswordFormComponent
|
||||
password={password}
|
||||
cnfPassword={cnfPassword}
|
||||
handleInput={handleInput}
|
||||
handlePatchPassword={handlePatchPassword}
|
||||
/>
|
||||
)}
|
||||
{hasStore && (
|
||||
<StoreApiKeyFormComponent
|
||||
apikey={apikey}
|
||||
handleInput={handleInput}
|
||||
handleSaveKey={handleSaveKey}
|
||||
loadingApiKey={loadingApiKey}
|
||||
validApiKey={validApiKey}
|
||||
hasApiKey={hasApiKey}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -78,9 +78,6 @@ export default function GlobalVariablesPage() {
|
|||
// Column Definitions: Defines the columns to be displayed.
|
||||
const [colDefs, setColDefs] = useState<(ColDef<any> | ColGroupDef<any>)[]>([
|
||||
{
|
||||
headerCheckboxSelection: true,
|
||||
checkboxSelection: true,
|
||||
showDisabledCheckboxes: true,
|
||||
headerName: "Variable Name",
|
||||
field: "name",
|
||||
flex: 2,
|
||||
|
|
@ -170,7 +167,7 @@ export default function GlobalVariablesPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex h-full w-full flex-col justify-between pb-8">
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||
<CardContent className="h-full">
|
||||
<TableComponent
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export default function ShortcutsPage() {
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-6 pb-8">
|
||||
<div className="grid gap-6">
|
||||
<Card x-chunk="dashboard-04-chunk-2" className="h-full pt-4">
|
||||
<CardContent className="h-full">
|
||||
<TableComponent
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import cloneDeep from "lodash/cloneDeep";
|
||||
import {
|
||||
SAVE_ERROR_ALERT,
|
||||
SAVE_SUCCESS_ALERT,
|
||||
} from "../../../../constants/alerts_constants";
|
||||
import { updateUser } from "../../../../controllers/API";
|
||||
|
||||
const usePatchGradient = (
|
||||
setSuccessData,
|
||||
setErrorData,
|
||||
currentUserData,
|
||||
setUserData,
|
||||
) => {
|
||||
const handlePatchGradient = async (gradient) => {
|
||||
try {
|
||||
if (gradient !== "") {
|
||||
await updateUser(currentUserData.id, { profile_image: gradient });
|
||||
let newUserData = cloneDeep(currentUserData);
|
||||
newUserData.profile_image = gradient;
|
||||
setUserData(newUserData);
|
||||
}
|
||||
setSuccessData({ title: SAVE_SUCCESS_ALERT });
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: SAVE_ERROR_ALERT,
|
||||
list: [(error as any)?.response?.data?.detail],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
currentUserData,
|
||||
handlePatchGradient,
|
||||
};
|
||||
};
|
||||
|
||||
export default usePatchGradient;
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
import {
|
||||
EDIT_PASSWORD_ALERT_LIST,
|
||||
EDIT_PASSWORD_ERROR_ALERT,
|
||||
SAVE_ERROR_ALERT,
|
||||
SAVE_SUCCESS_ALERT,
|
||||
} from "../../../../constants/alerts_constants";
|
||||
import { resetPassword } from "../../../../controllers/API";
|
||||
|
||||
const usePatchPassword = (userData, setSuccessData, setErrorData) => {
|
||||
const handlePatchPassword = async (password, cnfPassword, handleInput) => {
|
||||
if (password !== cnfPassword) {
|
||||
setErrorData({
|
||||
title: EDIT_PASSWORD_ERROR_ALERT,
|
||||
list: [EDIT_PASSWORD_ALERT_LIST],
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (password !== "") await resetPassword(userData.id, { password });
|
||||
handleInput({ target: { name: "password", value: "" } });
|
||||
handleInput({ target: { name: "cnfPassword", value: "" } });
|
||||
setSuccessData({ title: SAVE_SUCCESS_ALERT });
|
||||
} catch (error) {
|
||||
setErrorData({
|
||||
title: SAVE_ERROR_ALERT,
|
||||
list: [(error as any)?.response?.data?.detail],
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
handlePatchPassword,
|
||||
};
|
||||
};
|
||||
|
||||
export default usePatchPassword;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue