Feat: Implement Customizable Shortcuts
This pull request improve user experience by enabling customization of keyboard shortcuts.
This commit is contained in:
commit
20e7b82ba4
42 changed files with 1587 additions and 684 deletions
10
src/frontend/package-lock.json
generated
10
src/frontend/package-lock.json
generated
|
|
@ -60,6 +60,7 @@
|
|||
"react-dom": "^18.2.21",
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-hook-form": "^7.51.4",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-laag": "^2.0.5",
|
||||
"react-markdown": "^8.0.7",
|
||||
|
|
@ -11022,6 +11023,15 @@
|
|||
"react": "^16.8.0 || ^17 || ^18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-hotkeys-hook": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz",
|
||||
"integrity": "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.1",
|
||||
"react-dom": ">=16.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.2.1.tgz",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"@headlessui/react": "^1.7.17",
|
||||
"@hookform/resolvers": "^3.3.4",
|
||||
"@million/lint": "^0.0.73",
|
||||
"react-hotkeys-hook": "^4.5.0",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import getFieldTitle from "../utils/get-field-title";
|
|||
import sortFields from "../utils/sort-fields";
|
||||
import ParameterComponent from "./components/parameterComponent";
|
||||
import { postCustomComponent } from "../../controllers/API";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { useShortcutsStore } from "../../stores/shortcuts";
|
||||
|
||||
export default function GenericNode({
|
||||
data,
|
||||
|
|
@ -137,6 +137,8 @@ export default function GenericNode({
|
|||
return names;
|
||||
};
|
||||
|
||||
// const [openWDoubleCLick, setOpenWDoubleCLick] = useState(false);
|
||||
|
||||
const getBaseBorderClass = (selected) => {
|
||||
let className = selected ? "border border-ring" : "border";
|
||||
let frozenClass = selected ? "border-ring-frozen" : "border-frozen";
|
||||
|
|
@ -231,10 +233,14 @@ export default function GenericNode({
|
|||
}
|
||||
};
|
||||
|
||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
|
||||
const memoizedNodeToolbarComponent = useMemo(() => {
|
||||
return (
|
||||
<NodeToolbar>
|
||||
<NodeToolbarComponent
|
||||
// openWDoubleClick={openWDoubleCLick}
|
||||
// setOpenWDoubleClick={setOpenWDoubleCLick}
|
||||
data={data}
|
||||
deleteNode={(id) => {
|
||||
takeSnapshot();
|
||||
|
|
@ -266,11 +272,18 @@ export default function GenericNode({
|
|||
updateNodeCode,
|
||||
isOutdated,
|
||||
selected,
|
||||
shortcuts,
|
||||
// openWDoubleCLick,
|
||||
// setOpenWDoubleCLick,
|
||||
]);
|
||||
return (
|
||||
<>
|
||||
{memoizedNodeToolbarComponent}
|
||||
<div
|
||||
// onDoubleClick={(event) => {
|
||||
// if (!isWrappedWithClass(event, "nodoubleclick"))
|
||||
// setOpenWDoubleCLick(true);
|
||||
// }}
|
||||
className={getNodeBorderClassName(
|
||||
selected,
|
||||
showNode,
|
||||
|
|
@ -344,7 +357,7 @@ export default function GenericNode({
|
|||
event.preventDefault();
|
||||
}}
|
||||
data-testid={"title-" + data.node?.display_name}
|
||||
className="generic-node-tooltip-div cursor-text text-primary"
|
||||
className="nodoubleclick generic-node-tooltip-div cursor-text text-primary"
|
||||
>
|
||||
{data.node?.display_name}
|
||||
</div>
|
||||
|
|
@ -592,7 +605,7 @@ export default function GenericNode({
|
|||
) : (
|
||||
<div
|
||||
className={cn(
|
||||
"generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
|
||||
"nodoubleclick generic-node-desc-text cursor-text truncate-multiline word-break-break-word",
|
||||
(data.node?.description === "" ||
|
||||
!data.node?.description) &&
|
||||
nameEditable
|
||||
|
|
|
|||
|
|
@ -1,40 +1,53 @@
|
|||
import { Transition } from "@headlessui/react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import IOModal from "../../modals/IOModal";
|
||||
import ApiModal from "../../modals/apiModal/views";
|
||||
import ShareModal from "../../modals/shareModal";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../stores/flowsManagerStore";
|
||||
import { useShortcutsStore } from "../../stores/shortcuts";
|
||||
import { useStoreStore } from "../../stores/storeStore";
|
||||
import { classNames } from "../../utils/utils";
|
||||
import { classNames, isThereModal } from "../../utils/utils";
|
||||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
import { Separator } from "../ui/separator";
|
||||
|
||||
export default function FlowToolbar(): JSX.Element {
|
||||
const [open, setOpen] = useState(false);
|
||||
const preventDefault = true;
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [openCodeModal, setOpenCodeModal] = useState<boolean>(false);
|
||||
const [openShareModal, setOpenShareModal] = useState<boolean>(false);
|
||||
function handleAPIWShortcut(e: KeyboardEvent) {
|
||||
if (isThereModal() && !openCodeModal) return;
|
||||
setOpenCodeModal((oldOpen) => !oldOpen);
|
||||
}
|
||||
|
||||
function handleChatWShortcut(e: KeyboardEvent) {
|
||||
if (isThereModal() && !open) return;
|
||||
if (useFlowStore.getState().hasIO) {
|
||||
setOpen((oldState) => !oldState);
|
||||
}
|
||||
}
|
||||
|
||||
function handleShareWShortcut(e: KeyboardEvent) {
|
||||
if (isThereModal() && !openShareModal) return;
|
||||
setOpenShareModal((oldState) => !oldState);
|
||||
}
|
||||
|
||||
const openPlayground = useShortcutsStore((state) => state.open);
|
||||
const api = useShortcutsStore((state) => state.api);
|
||||
const flow = useShortcutsStore((state) => state.flow);
|
||||
|
||||
useHotkeys(openPlayground, handleChatWShortcut, { preventDefault });
|
||||
useHotkeys(api, handleAPIWShortcut, { preventDefault });
|
||||
useHotkeys(flow, handleShareWShortcut, { preventDefault });
|
||||
|
||||
const hasIO = useFlowStore((state) => state.hasIO);
|
||||
const hasStore = useStoreStore((state) => state.hasStore);
|
||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
if (
|
||||
(event.key === "K" || event.key === "k") &&
|
||||
(event.metaKey || event.ctrlKey) &&
|
||||
useFlowStore.getState().hasIO
|
||||
) {
|
||||
event.preventDefault();
|
||||
setOpen((oldState) => !oldState);
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const prevNodesRef = useRef<any[] | undefined>();
|
||||
|
||||
const ModalMemo = useMemo(
|
||||
|
|
@ -43,6 +56,8 @@ export default function FlowToolbar(): JSX.Element {
|
|||
is_component={false}
|
||||
component={currentFlow!}
|
||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
||||
open={openShareModal}
|
||||
setOpen={setOpenShareModal}
|
||||
>
|
||||
<button
|
||||
disabled={!hasApiKey || !validApiKey || !hasStore}
|
||||
|
|
@ -66,7 +81,14 @@ export default function FlowToolbar(): JSX.Element {
|
|||
</button>
|
||||
</ShareModal>
|
||||
),
|
||||
[hasApiKey, validApiKey, currentFlow, hasStore],
|
||||
[
|
||||
hasApiKey,
|
||||
validApiKey,
|
||||
currentFlow,
|
||||
hasStore,
|
||||
openShareModal,
|
||||
setOpenShareModal,
|
||||
],
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -115,7 +137,11 @@ export default function FlowToolbar(): JSX.Element {
|
|||
</div>
|
||||
<div className="flex cursor-pointer items-center gap-2">
|
||||
{currentFlow && currentFlow.data && (
|
||||
<ApiModal flow={currentFlow}>
|
||||
<ApiModal
|
||||
flow={currentFlow}
|
||||
open={openCodeModal}
|
||||
setOpen={setOpenCodeModal}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"relative inline-flex w-full items-center justify-center gap-1 px-5 py-3 text-sm font-semibold text-foreground transition-all duration-150 ease-in-out hover:bg-hover",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import {
|
|||
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { UPLOAD_ERROR_ALERT } from "../../../../constants/alerts_constants";
|
||||
import { SAVED_HOVER } from "../../../../constants/constants";
|
||||
import { IS_MAC, SAVED_HOVER } from "../../../../constants/constants";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
import FlowLogsModal from "../../../../modals/flowLogsModal";
|
||||
import FlowSettingsModal from "../../../../modals/flowSettingsModal";
|
||||
|
|
@ -20,8 +20,10 @@ import { cn } from "../../../../utils/utils";
|
|||
import IconComponent from "../../../genericIconComponent";
|
||||
import ShadTooltip from "../../../shadTooltipComponent";
|
||||
import { Button } from "../../../ui/button";
|
||||
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||
|
||||
export const MenuBar = ({}: {}): JSX.Element => {
|
||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
const addFlow = useFlowsManagerStore((state) => state.addFlow);
|
||||
const currentFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
|
@ -113,7 +115,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
title: UPLOAD_ERROR_ALERT,
|
||||
list: [error],
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
}}
|
||||
>
|
||||
|
|
@ -137,17 +139,19 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
>
|
||||
<IconComponent name="Undo" className="header-menu-options " />
|
||||
Undo
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
{IS_MAC ? (
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
/>
|
||||
) : (
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
{
|
||||
shortcuts.find((s) => s.name.toLowerCase() === "undo")
|
||||
?.shortcut
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.4em]">Z</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
|
|
@ -157,17 +161,19 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
>
|
||||
<IconComponent name="Redo" className="header-menu-options " />
|
||||
Redo
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
{IS_MAC ? (
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
/>
|
||||
) : (
|
||||
<span className="absolute right-[1.15rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
{
|
||||
shortcuts.find((s) => s.name.toLowerCase() === "redo")
|
||||
?.shortcut
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.4em]">Y</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
|
@ -195,7 +201,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
|||
name={isBuilding || saveLoading ? "Loader2" : "CheckCircle2"}
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle",
|
||||
isBuilding || saveLoading ? "animate-spin" : "animate-wiggle"
|
||||
)}
|
||||
/>
|
||||
{printByBuildStatus()}
|
||||
|
|
|
|||
46
src/frontend/src/components/renderIconComponent/index.tsx
Normal file
46
src/frontend/src/components/renderIconComponent/index.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import ForwardedIconComponent from "../genericIconComponent";
|
||||
|
||||
export default function RenderIcons({
|
||||
isMac,
|
||||
hasShift,
|
||||
filteredShortcut,
|
||||
shortcutWPlus,
|
||||
}: {
|
||||
isMac: boolean;
|
||||
hasShift: boolean;
|
||||
filteredShortcut: string[];
|
||||
shortcutWPlus: string[];
|
||||
}): JSX.Element {
|
||||
return hasShift ? (
|
||||
<>
|
||||
{isMac ? (
|
||||
<ForwardedIconComponent name="Command" className="h-4 w-4" />
|
||||
) : (
|
||||
filteredShortcut[0]
|
||||
)}
|
||||
<ForwardedIconComponent name="ArrowBigUp" className="ml-1 h-5 w-5" />
|
||||
{filteredShortcut.map((key, idx) => {
|
||||
if (idx > 0) {
|
||||
return <span className="ml-1"> {key.toUpperCase()} </span>;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{shortcutWPlus[0].toLowerCase() === "space" ? (
|
||||
"Space"
|
||||
) : shortcutWPlus[0].length <= 1 ? (
|
||||
shortcutWPlus[0]
|
||||
) : isMac ? (
|
||||
<ForwardedIconComponent name="Command" className="h-4 w-4" />
|
||||
) : (
|
||||
shortcutWPlus[0]
|
||||
)}
|
||||
{shortcutWPlus.map((key, idx) => {
|
||||
if (idx > 0) {
|
||||
return <span className="ml-0.5"> {key.toUpperCase()} </span>;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -33,10 +33,10 @@ const SelectContent = React.forwardRef<
|
|||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
"relative z-50 min-w-[14rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
|
|
@ -45,7 +45,7 @@ const SelectContent = React.forwardRef<
|
|||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
|
@ -75,7 +75,7 @@ const SelectItem = React.forwardRef<
|
|||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-3 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -734,6 +734,91 @@ export const AUTHORIZED_DUPLICATE_REQUESTS = [
|
|||
|
||||
export const SAVE_DEBOUNCE_TIME = 300;
|
||||
|
||||
export const IS_MAC = navigator.userAgent.toUpperCase().includes("MAC");
|
||||
|
||||
export const defaultShortcuts = [
|
||||
{
|
||||
name: "Advanced Settings",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + A`,
|
||||
},
|
||||
{
|
||||
name: "Minimize",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Q`,
|
||||
},
|
||||
{
|
||||
name: "Code",
|
||||
shortcut: `Space`,
|
||||
},
|
||||
{
|
||||
name: "Copy",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + C`,
|
||||
},
|
||||
{
|
||||
name: "Duplicate",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + D`,
|
||||
},
|
||||
{
|
||||
name: "Share",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + S`,
|
||||
},
|
||||
{
|
||||
name: "Docs",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Shift + D`,
|
||||
},
|
||||
{
|
||||
name: "Save",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + S`,
|
||||
},
|
||||
{
|
||||
name: "Delete",
|
||||
shortcut: "Backspace",
|
||||
},
|
||||
{
|
||||
name: "Open playground",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + K`,
|
||||
},
|
||||
{
|
||||
name: "Undo",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Z`,
|
||||
},
|
||||
{
|
||||
name: "Redo",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + Y`,
|
||||
},
|
||||
{
|
||||
name: "Group",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + G`,
|
||||
},
|
||||
{
|
||||
name: "Cut",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + X`,
|
||||
},
|
||||
{
|
||||
name: "Paste",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + V`,
|
||||
},
|
||||
{
|
||||
name: "API",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + R`,
|
||||
},
|
||||
{
|
||||
name: "Download",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + J`,
|
||||
},
|
||||
{
|
||||
name: "Update",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + U`,
|
||||
},
|
||||
{
|
||||
name: "Freeze",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + F`,
|
||||
},
|
||||
{
|
||||
name: "Flow Share",
|
||||
shortcut: `${IS_MAC ? "Cmd" : "Ctrl"} + B`,
|
||||
},
|
||||
];
|
||||
|
||||
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";
|
||||
|
|
@ -741,3 +826,6 @@ export const DEFAULT_TABLE_ALERT_TITLE = "No Data Available";
|
|||
export const LOCATIONS_TO_RETURN = ["/flow/", "/settings/"];
|
||||
|
||||
export const MAX_BATCH_SIZE = 50;
|
||||
|
||||
export const MODAL_CLASSES =
|
||||
"nopan nodelete nodrag noundo nocopy fixed inset-0 bottom-0 left-0 right-0 top-0 z-50 overflow-auto bg-blur-shared backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0";
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@ import SvgGroqLogo from "./GroqLogo";
|
|||
export const GroqIcon = forwardRef<SVGSVGElement, React.PropsWithChildren<{}>>(
|
||||
(props, ref) => {
|
||||
return <SvgGroqLogo ref={ref} {...props} />;
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
252
src/frontend/src/modals/apiModal/index.tsx
Normal file
252
src/frontend/src/modals/apiModal/index.tsx
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import "ace-builds/src-noconflict/ext-language_tools";
|
||||
import "ace-builds/src-noconflict/mode-python";
|
||||
import "ace-builds/src-noconflict/theme-github";
|
||||
import "ace-builds/src-noconflict/theme-twilight";
|
||||
import {
|
||||
ReactNode,
|
||||
forwardRef,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
// import "ace-builds/webpack-resolver";
|
||||
import CodeTabsComponent from "../../components/codeTabsComponent";
|
||||
import IconComponent from "../../components/genericIconComponent";
|
||||
import {
|
||||
EXPORT_CODE_DIALOG,
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
} from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import useFlowStore from "../../stores/flowStore";
|
||||
import { TemplateVariableType } from "../../types/api";
|
||||
import { tweakType, uniqueTweakType } from "../../types/components";
|
||||
import { FlowType, NodeType } from "../../types/flow/index";
|
||||
import { buildTweaks, convertArrayToObj } from "../../utils/reactflowUtils";
|
||||
import {
|
||||
getCurlCode,
|
||||
getPythonApiCode,
|
||||
getPythonCode,
|
||||
getWidgetCode,
|
||||
tabsArray,
|
||||
} from "../../utils/utils";
|
||||
import BaseModal from "../baseModal";
|
||||
|
||||
const ApiModal = forwardRef(
|
||||
(
|
||||
{
|
||||
flow,
|
||||
children,
|
||||
open: myOpen,
|
||||
setOpen: mySetOpen,
|
||||
}: {
|
||||
flow: FlowType;
|
||||
children: ReactNode;
|
||||
open: any;
|
||||
setOpen: any;
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { autoLogin } = useContext(AuthContext);
|
||||
const [open, setOpen] =
|
||||
mySetOpen !== undefined && myOpen !== undefined
|
||||
? [myOpen, mySetOpen]
|
||||
: useState(false);
|
||||
const [activeTab, setActiveTab] = useState("0");
|
||||
const tweak = useRef<tweakType>([]);
|
||||
const tweaksList = useRef<string[]>([]);
|
||||
const [getTweak, setTweak] = useState<tweakType>([]);
|
||||
const flowState = useFlowStore((state) => state.flowState);
|
||||
const pythonApiCode = getPythonApiCode(flow, autoLogin, tweak.current);
|
||||
const curl_code = getCurlCode(flow, autoLogin, tweak.current);
|
||||
const pythonCode = getPythonCode(flow, tweak.current);
|
||||
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
|
||||
const tweaksCode = buildTweaks(flow);
|
||||
const codesArray = [
|
||||
curl_code,
|
||||
pythonApiCode,
|
||||
pythonCode,
|
||||
widgetCode,
|
||||
pythonCode,
|
||||
];
|
||||
const [tabs, setTabs] = useState(tabsArray(codesArray, 0));
|
||||
|
||||
function startState() {
|
||||
tweak.current = [];
|
||||
setTweak([]);
|
||||
tweaksList.current = [];
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (flow["data"]!["nodes"].length == 0) {
|
||||
startState();
|
||||
} else {
|
||||
tweak.current = [];
|
||||
const t = buildTweaks(flow);
|
||||
tweak.current.push(t);
|
||||
}
|
||||
|
||||
filterNodes();
|
||||
|
||||
if (Object.keys(tweaksCode).length > 0) {
|
||||
setActiveTab("0");
|
||||
setTabs(tabsArray(codesArray, 1));
|
||||
} else {
|
||||
setTabs(tabsArray(codesArray, 1));
|
||||
}
|
||||
}, [flow["data"]!["nodes"], open]);
|
||||
|
||||
function filterNodes() {
|
||||
let arrNodesWithValues: string[] = [];
|
||||
|
||||
flow["data"]!["nodes"].forEach((node) => {
|
||||
if (!node["data"]["node"]["template"]) {
|
||||
return;
|
||||
}
|
||||
Object.keys(node["data"]["node"]["template"])
|
||||
.filter(
|
||||
(templateField) =>
|
||||
templateField.charAt(0) !== "_" &&
|
||||
node.data.node.template[templateField].show &&
|
||||
LANGFLOW_SUPPORTED_TYPES.has(
|
||||
node.data.node.template[templateField].type
|
||||
)
|
||||
)
|
||||
.map((n, i) => {
|
||||
arrNodesWithValues.push(node["id"]);
|
||||
});
|
||||
});
|
||||
|
||||
tweaksList.current = arrNodesWithValues.filter((value, index, self) => {
|
||||
return self.indexOf(value) === index;
|
||||
});
|
||||
}
|
||||
function buildTweakObject(
|
||||
tw: string,
|
||||
changes: string | string[] | boolean | number | Object[] | Object,
|
||||
template: TemplateVariableType
|
||||
) {
|
||||
if (typeof changes === "string" && template.type === "float") {
|
||||
changes = parseFloat(changes);
|
||||
}
|
||||
if (typeof changes === "string" && template.type === "int") {
|
||||
changes = parseInt(changes);
|
||||
}
|
||||
if (template.list === true && Array.isArray(changes)) {
|
||||
changes = changes?.filter((x) => x !== "");
|
||||
}
|
||||
|
||||
if (template.type === "dict" && Array.isArray(changes)) {
|
||||
changes = convertArrayToObj(changes);
|
||||
}
|
||||
|
||||
if (template.type === "NestedDict") {
|
||||
changes = JSON.stringify(changes);
|
||||
}
|
||||
|
||||
const existingTweak = tweak.current.find((element) =>
|
||||
element.hasOwnProperty(tw)
|
||||
);
|
||||
|
||||
if (existingTweak) {
|
||||
existingTweak[tw][template["name"]!] = changes as string;
|
||||
|
||||
if (existingTweak[tw][template["name"]!] == template.value) {
|
||||
tweak.current.forEach((element) => {
|
||||
if (element[tw] && Object.keys(element[tw])?.length === 0) {
|
||||
tweak.current = tweak.current.filter((obj) => {
|
||||
const prop = obj[Object.keys(obj)[0]].prop;
|
||||
return prop !== undefined && prop !== null && prop !== "";
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const newTweak = {
|
||||
[tw]: {
|
||||
[template["name"]!]: changes,
|
||||
},
|
||||
} as uniqueTweakType;
|
||||
tweak.current.push(newTweak);
|
||||
}
|
||||
|
||||
const pythonApiCode = getPythonApiCode(flow, autoLogin, tweak.current);
|
||||
const curl_code = getCurlCode(flow, autoLogin, tweak.current);
|
||||
const pythonCode = getPythonCode(flow, tweak.current);
|
||||
const widgetCode = getWidgetCode(flow, autoLogin, flowState);
|
||||
|
||||
tabs![0].code = curl_code;
|
||||
tabs![1].code = pythonApiCode;
|
||||
tabs![2].code = pythonCode;
|
||||
tabs![3].code = widgetCode;
|
||||
|
||||
setTweak(tweak.current);
|
||||
}
|
||||
|
||||
function buildContent(value: string) {
|
||||
const htmlContent = (
|
||||
<div className="w-[200px]">
|
||||
<span>{value != null && value != "" ? value : "None"}</span>
|
||||
</div>
|
||||
);
|
||||
return htmlContent;
|
||||
}
|
||||
|
||||
function getValue(
|
||||
value: string,
|
||||
node: NodeType,
|
||||
template: TemplateVariableType
|
||||
) {
|
||||
let returnValue = value ?? "";
|
||||
|
||||
if (getTweak.length > 0) {
|
||||
for (const obj of getTweak) {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
const value = obj[key];
|
||||
if (key == node["id"]) {
|
||||
Object.keys(value).forEach((key) => {
|
||||
if (key == template["name"]) {
|
||||
returnValue = value[key];
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return value ?? "";
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseModal open={open} setOpen={setOpen}>
|
||||
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header description={EXPORT_CODE_DIALOG}>
|
||||
<span className="pr-2">API</span>
|
||||
<IconComponent
|
||||
name="Code2"
|
||||
className="h-6 w-6 pl-1 text-gray-800 dark:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content>
|
||||
<CodeTabsComponent
|
||||
flow={flow}
|
||||
tabs={tabs!}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
tweaks={{
|
||||
tweak,
|
||||
tweaksList,
|
||||
buildContent,
|
||||
buildTweakObject,
|
||||
getValue,
|
||||
}}
|
||||
/>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default ApiModal;
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
export default function getPythonCode(
|
||||
flowName: string,
|
||||
tweaksBuildedObject
|
||||
tweaksBuildedObject,
|
||||
): string {
|
||||
const tweaksObject = tweaksBuildedObject[0];
|
||||
|
||||
|
|
|
|||
|
|
@ -31,9 +31,13 @@ const ApiModal = forwardRef(
|
|||
{
|
||||
flow,
|
||||
children,
|
||||
open: myOpen,
|
||||
setOpen: mySetOpen,
|
||||
}: {
|
||||
flow: FlowType;
|
||||
children: ReactNode;
|
||||
open?: boolean;
|
||||
setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void;
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
|
|
@ -44,7 +48,10 @@ const ApiModal = forwardRef(
|
|||
|
||||
const [activeTweaks, setActiveTweaks] = useState(false);
|
||||
const { autoLogin } = useContext(AuthContext);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] =
|
||||
mySetOpen !== undefined && myOpen !== undefined
|
||||
? [myOpen, mySetOpen]
|
||||
: useState(false);
|
||||
const [activeTab, setActiveTab] = useState("0");
|
||||
const pythonApiCode = getPythonApiCode(
|
||||
flow?.id,
|
||||
|
|
|
|||
|
|
@ -15,11 +15,13 @@ const EditNodeModal = forwardRef(
|
|||
nodeLength,
|
||||
open,
|
||||
setOpen,
|
||||
// setOpenWDoubleClick,
|
||||
data,
|
||||
}: {
|
||||
nodeLength: number;
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
// setOpenWDoubleClick: (open: boolean) => void;
|
||||
data: NodeDataType;
|
||||
},
|
||||
ref,
|
||||
|
|
@ -55,6 +57,12 @@ const EditNodeModal = forwardRef(
|
|||
}
|
||||
}, [gridApi, open]);
|
||||
|
||||
// useEffect(() => {
|
||||
// return () => {
|
||||
// setOpenWDoubleClick(false);
|
||||
// };
|
||||
// }, []);
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
key={data.id}
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ export default function GenericModal({
|
|||
<div
|
||||
className={classNames(
|
||||
!isEdit ? "rounded-lg border" : "",
|
||||
"flex h-full w-full",
|
||||
"flex h-full max-h-[85%] w-full",
|
||||
)}
|
||||
>
|
||||
{type === TypeModal.PROMPT && isEdit && !readonly ? (
|
||||
|
|
@ -244,7 +244,7 @@ export default function GenericModal({
|
|||
) : type !== TypeModal.PROMPT ? (
|
||||
<Textarea
|
||||
ref={textRef}
|
||||
className="form-input h-full w-full rounded-lg focus-visible:ring-1"
|
||||
className="form-input h-full w-full overflow-auto rounded-lg focus-visible:ring-1"
|
||||
value={inputValue}
|
||||
onChange={(event) => {
|
||||
setInputValue(event.target.value);
|
||||
|
|
|
|||
|
|
@ -47,7 +47,10 @@ export default function ShareModal({
|
|||
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
const [internalOpen, internalSetOpen] = useState(children ? false : true);
|
||||
const [internalOpen, internalSetOpen] =
|
||||
setOpen !== undefined && open !== undefined
|
||||
? [open, setOpen]
|
||||
: useState(children ? false : true);
|
||||
const [openConfirmationModal, setOpenConfirmationModal] = useState(false);
|
||||
const nameComponent = is_component ? "component" : "workflow";
|
||||
|
||||
|
|
@ -66,13 +69,13 @@ export default function ShareModal({
|
|||
const description = component?.description ?? "";
|
||||
|
||||
useEffect(() => {
|
||||
if (open || internalOpen) {
|
||||
if (internalOpen) {
|
||||
if (hasApiKey && hasStore) {
|
||||
handleGetTags();
|
||||
handleGetNames();
|
||||
}
|
||||
}
|
||||
}, [open, internalOpen, hasApiKey, hasStore]);
|
||||
}, [internalOpen, hasApiKey, hasStore]);
|
||||
|
||||
function handleGetTags() {
|
||||
setLoadingTags(true);
|
||||
|
|
@ -146,8 +149,7 @@ export default function ShareModal({
|
|||
|
||||
const handleUpdateComponent = () => {
|
||||
handleShareComponent(true);
|
||||
if (setOpen) setOpen(false);
|
||||
else internalSetOpen(false);
|
||||
internalSetOpen(false);
|
||||
};
|
||||
|
||||
const handleExportComponent = () => {
|
||||
|
|
@ -199,8 +201,8 @@ export default function ShareModal({
|
|||
<>
|
||||
<BaseModal
|
||||
size="smaller-h-full"
|
||||
open={(!disabled && open) ?? internalOpen}
|
||||
setOpen={setOpen ?? internalSetOpen}
|
||||
open={!disabled && internalOpen}
|
||||
setOpen={internalSetOpen}
|
||||
onSubmit={() => {
|
||||
const isNameAvailable = !unavaliableNames.some(
|
||||
(element) => element.name === name,
|
||||
|
|
@ -208,7 +210,7 @@ export default function ShareModal({
|
|||
|
||||
if (isNameAvailable) {
|
||||
handleShareComponent();
|
||||
(setOpen || internalSetOpen)(false);
|
||||
internalSetOpen(false);
|
||||
} else {
|
||||
setOpenConfirmationModal(true);
|
||||
}
|
||||
|
|
@ -289,7 +291,7 @@ export default function ShareModal({
|
|||
variant="outline"
|
||||
className="gap-2"
|
||||
onClick={() => {
|
||||
(setOpen || internalSetOpen)(false);
|
||||
internalSetOpen(false);
|
||||
handleExportComponent();
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,13 @@
|
|||
import _, { cloneDeep } from "lodash";
|
||||
import { MouseEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
KeyboardEvent,
|
||||
MouseEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import ReactFlow, {
|
||||
Background,
|
||||
Connection,
|
||||
|
|
@ -21,6 +29,7 @@ import {
|
|||
import useAlertStore from "../../../../stores/alertStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
import { APIClassType } from "../../../../types/api";
|
||||
import { FlowType, NodeType } from "../../../../types/flow";
|
||||
|
|
@ -50,6 +59,7 @@ export default function Page({
|
|||
flow: FlowType;
|
||||
view?: boolean;
|
||||
}): JSX.Element {
|
||||
const preventDefault = true;
|
||||
const uploadFlow = useFlowsManagerStore((state) => state.uploadFlow);
|
||||
const autoSaveCurrentFlow = useFlowsManagerStore(
|
||||
(state) => state.autoSaveCurrentFlow,
|
||||
|
|
@ -141,119 +151,13 @@ export default function Page({
|
|||
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
useEffect(() => {
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
const selectedNode = lastSelection?.nodes ?? [];
|
||||
const selectedEdges = lastSelection?.edges ?? [];
|
||||
if (
|
||||
selectionMenuVisible &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "g"
|
||||
) {
|
||||
event.preventDefault();
|
||||
handleGroupNode();
|
||||
}
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "p" &&
|
||||
selectedNode.length > 0
|
||||
) {
|
||||
event.preventDefault();
|
||||
setNode(selectedNode[0].id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: {
|
||||
...old.data.node,
|
||||
frozen: old.data?.node?.frozen ? false : true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "d" &&
|
||||
selectedNode.length > 0
|
||||
) {
|
||||
event.preventDefault();
|
||||
paste(
|
||||
{ nodes: selectedNode, edges: selectedEdges },
|
||||
{
|
||||
x: position.current.x,
|
||||
y: position.current.y,
|
||||
},
|
||||
);
|
||||
}
|
||||
if (!isWrappedWithClass(event, "noundo")) {
|
||||
if (
|
||||
(event.key === "y" || (event.key === "z" && event.shiftKey)) &&
|
||||
(event.ctrlKey || event.metaKey)
|
||||
) {
|
||||
event.preventDefault(); // prevent the default action
|
||||
redo();
|
||||
} else if (event.key === "z" && (event.ctrlKey || event.metaKey)) {
|
||||
event.preventDefault();
|
||||
undo();
|
||||
}
|
||||
}
|
||||
if (
|
||||
!isWrappedWithClass(event, "nocopy") &&
|
||||
window.getSelection()?.toString().length === 0
|
||||
) {
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "c" &&
|
||||
lastSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
||||
} else if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "x" &&
|
||||
lastSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
setLastCopiedSelection(_.cloneDeep(lastSelection), true);
|
||||
} else if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "v" &&
|
||||
lastCopiedSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
takeSnapshot();
|
||||
paste(lastCopiedSelection, {
|
||||
x: position.current.x,
|
||||
y: position.current.y,
|
||||
});
|
||||
} else if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key === "g" &&
|
||||
lastSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
if (!isWrappedWithClass(event, "nodelete")) {
|
||||
if (
|
||||
(event.key === "Delete" || event.key === "Backspace") &&
|
||||
lastSelection
|
||||
) {
|
||||
event.preventDefault();
|
||||
takeSnapshot();
|
||||
deleteNode(lastSelection.nodes.map((node) => node.id));
|
||||
deleteEdge(lastSelection.edges.map((edge) => edge.id));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseMove = (event) => {
|
||||
position.current = { x: event.clientX, y: event.clientY };
|
||||
};
|
||||
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
document.addEventListener("mousemove", handleMouseMove);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
document.removeEventListener("mousemove", handleMouseMove);
|
||||
};
|
||||
}, [lastCopiedSelection, lastSelection, takeSnapshot, selectionMenuVisible]);
|
||||
|
|
@ -274,6 +178,115 @@ export default function Page({
|
|||
};
|
||||
}, []);
|
||||
|
||||
function handleUndo(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (!isWrappedWithClass(e, "noundo")) {
|
||||
undo();
|
||||
}
|
||||
}
|
||||
|
||||
function handleRedo(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (!isWrappedWithClass(e, "noundo")) {
|
||||
redo();
|
||||
}
|
||||
}
|
||||
|
||||
function handleGroup(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (selectionMenuVisible) {
|
||||
handleGroupNode();
|
||||
}
|
||||
}
|
||||
|
||||
function handleDuplicate(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const selectedNode = nodes.filter((obj) => obj.selected);
|
||||
if (selectedNode.length > 0) {
|
||||
paste(
|
||||
{ nodes: selectedNode, edges: [] },
|
||||
{
|
||||
x: position.current.x,
|
||||
y: position.current.y,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCopy(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (
|
||||
!isWrappedWithClass(e, "nocopy") &&
|
||||
window.getSelection()?.toString().length === 0 &&
|
||||
lastSelection
|
||||
) {
|
||||
setLastCopiedSelection(_.cloneDeep(lastSelection));
|
||||
}
|
||||
}
|
||||
|
||||
function handleCut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (
|
||||
!isWrappedWithClass(e, "nocopy") &&
|
||||
window.getSelection()?.toString().length === 0 &&
|
||||
lastSelection
|
||||
) {
|
||||
setLastCopiedSelection(_.cloneDeep(lastSelection), true);
|
||||
}
|
||||
}
|
||||
|
||||
function handlePaste(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (
|
||||
!isWrappedWithClass(e, "nocopy") &&
|
||||
window.getSelection()?.toString().length === 0 &&
|
||||
lastCopiedSelection
|
||||
) {
|
||||
takeSnapshot();
|
||||
paste(lastCopiedSelection, {
|
||||
x: position.current.x,
|
||||
y: position.current.y,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleDelete(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (!isWrappedWithClass(e, "nodelete") && lastSelection) {
|
||||
takeSnapshot();
|
||||
deleteNode(lastSelection.nodes.map((node) => node.id));
|
||||
deleteEdge(lastSelection.edges.map((edge) => edge.id));
|
||||
}
|
||||
}
|
||||
|
||||
const undoAction = useShortcutsStore((state) => state.undo);
|
||||
const redoAction = useShortcutsStore((state) => state.redo);
|
||||
const copyAction = useShortcutsStore((state) => state.copy);
|
||||
const duplicate = useShortcutsStore((state) => state.duplicate);
|
||||
const deleteAction = useShortcutsStore((state) => state.delete);
|
||||
const groupAction = useShortcutsStore((state) => state.group);
|
||||
const cutAction = useShortcutsStore((state) => state.cut);
|
||||
const pasteAction = useShortcutsStore((state) => state.paste);
|
||||
//@ts-ignore
|
||||
useHotkeys(undoAction, handleUndo, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys(redoAction, handleRedo, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys(groupAction, handleGroup, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys(duplicate, handleDuplicate, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys(copyAction, handleCopy, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys(cutAction, handleCut, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys(pasteAction, handlePaste, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys(deleteAction, handleDelete, { preventDefault });
|
||||
//@ts-ignore
|
||||
useHotkeys("delete", handleDelete, { preventDefault });
|
||||
|
||||
useEffect(() => {
|
||||
setSHowCanvas(
|
||||
Object.keys(templates).length > 0 && Object.keys(types).length > 0,
|
||||
|
|
@ -473,6 +486,7 @@ export default function Page({
|
|||
zoomOnScroll={!view}
|
||||
zoomOnPinch={!view}
|
||||
panOnDrag={!view}
|
||||
panActivationKeyCode={""}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
onPaneClick={onPaneClick}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
import _, { cloneDeep } from "lodash";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useHotkeys } from "react-hotkeys-hook";
|
||||
import { useUpdateNodeInternals } from "reactflow";
|
||||
import CodeAreaComponent from "../../../../components/codeAreaComponent";
|
||||
import IconComponent from "../../../../components/genericIconComponent";
|
||||
import IconComponent, {
|
||||
ForwardedIconComponent,
|
||||
} from "../../../../components/genericIconComponent";
|
||||
import ShadTooltip from "../../../../components/shadTooltipComponent";
|
||||
import {
|
||||
Select,
|
||||
|
|
@ -10,7 +13,6 @@ import {
|
|||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "../../../../components/ui/select-custom";
|
||||
import { postCustomComponent } from "../../../../controllers/API";
|
||||
import ConfirmationModal from "../../../../modals/confirmationModal";
|
||||
import EditNodeModal from "../../../../modals/editNodeModal";
|
||||
import ShareModal from "../../../../modals/shareModal";
|
||||
|
|
@ -18,6 +20,7 @@ import useAlertStore from "../../../../stores/alertStore";
|
|||
import { useDarkStore } from "../../../../stores/darkStore";
|
||||
import useFlowStore from "../../../../stores/flowStore";
|
||||
import useFlowsManagerStore from "../../../../stores/flowsManagerStore";
|
||||
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||
import { useStoreStore } from "../../../../stores/storeStore";
|
||||
import { useTypesStore } from "../../../../stores/typesStore";
|
||||
import { APIClassType } from "../../../../types/api";
|
||||
|
|
@ -29,8 +32,9 @@ import {
|
|||
expandGroupNode,
|
||||
updateFlowPosition,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { classNames, cn } from "../../../../utils/utils";
|
||||
import { classNames, cn, isThereModal } from "../../../../utils/utils";
|
||||
import ToolbarSelectItem from "./toolbarSelectItem";
|
||||
import RenderIcons from "../../../../components/renderIconComponent";
|
||||
|
||||
export default function NodeToolbarComponent({
|
||||
data,
|
||||
|
|
@ -43,6 +47,15 @@ export default function NodeToolbarComponent({
|
|||
setShowState,
|
||||
onCloseAdvancedModal,
|
||||
}: nodeToolbarPropsType): JSX.Element {
|
||||
const version = useDarkStore((state) => state.version);
|
||||
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
||||
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
||||
const [showOverrideModal, setShowOverrideModal] = useState(false);
|
||||
const [flowComponent, setFlowComponent] = useState<FlowType>(
|
||||
createFlowComponent(cloneDeep(data), version),
|
||||
);
|
||||
const preventDefault = true;
|
||||
const isMac = navigator.platform.toUpperCase().includes("MAC");
|
||||
const nodeLength = Object.keys(data.node!.template).filter(
|
||||
(templateField) =>
|
||||
templateField.charAt(0) !== "_" &&
|
||||
|
|
@ -58,10 +71,127 @@ export default function NodeToolbarComponent({
|
|||
data.node.template[templateField].type === "dict" ||
|
||||
data.node.template[templateField].type === "NestedDict"),
|
||||
).length;
|
||||
const templates = useTypesStore((state) => state.templates);
|
||||
|
||||
const hasStore = useStoreStore((state) => state.hasStore);
|
||||
const hasApiKey = useStoreStore((state) => state.hasApiKey);
|
||||
const validApiKey = useStoreStore((state) => state.validApiKey);
|
||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
const unselectAll = useFlowStore((state) => state.unselectAll);
|
||||
function handleMinimizeWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (isMinimal) {
|
||||
setShowState((show) => !show);
|
||||
setShowNode(data.showNode ?? true ? false : true);
|
||||
return;
|
||||
}
|
||||
setNoticeData({
|
||||
title:
|
||||
"Minimization are only available for nodes with one handle or fewer.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
function handleUpdateWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (hasApiKey || hasStore) {
|
||||
handleSelectChange("update");
|
||||
}
|
||||
}
|
||||
|
||||
function handleGroupWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (isGroup) {
|
||||
handleSelectChange("ungroup");
|
||||
}
|
||||
}
|
||||
|
||||
function handleShareWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (isThereModal() && !showOverrideModal) return;
|
||||
if (hasApiKey || hasStore) {
|
||||
setShowconfirmShare((state) => !state);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCodeWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (isThereModal() && !openModal) return;
|
||||
if (hasCode) return setOpenModal((state) => !state);
|
||||
setNoticeData({ title: `You can not access ${data.id} code` });
|
||||
}
|
||||
|
||||
function handleAdvancedWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (isThereModal() && !showModalAdvanced) return;
|
||||
if (!isGroup) {
|
||||
setShowModalAdvanced((state) => !state);
|
||||
}
|
||||
}
|
||||
|
||||
function handleSaveWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (isThereModal() && !showOverrideModal) return;
|
||||
if (isSaved) {
|
||||
setShowOverrideModal((state) => !state);
|
||||
return;
|
||||
}
|
||||
if (hasCode && !isSaved) {
|
||||
saveComponent(cloneDeep(data), false);
|
||||
setSuccessData({ title: `${data.id} saved successfully` });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDocsWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
if (data.node?.documentation) {
|
||||
return openInNewTab(data.node?.documentation);
|
||||
}
|
||||
setNoticeData({
|
||||
title: `${data.id} docs is not available at the moment.`,
|
||||
});
|
||||
}
|
||||
|
||||
function handleDownloadWShortcut(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
downloadNode(flowComponent!);
|
||||
}
|
||||
|
||||
function handleFreeze(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: {
|
||||
...old.data.node,
|
||||
frozen: old.data?.node?.frozen ? false : true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
const advanced = useShortcutsStore((state) => state.advanced);
|
||||
const minimize = useShortcutsStore((state) => state.minimize);
|
||||
const share = useShortcutsStore((state) => state.share);
|
||||
const save = useShortcutsStore((state) => state.save);
|
||||
const docs = useShortcutsStore((state) => state.docs);
|
||||
const code = useShortcutsStore((state) => state.code);
|
||||
const group = useShortcutsStore((state) => state.group);
|
||||
const update = useShortcutsStore((state) => state.update);
|
||||
const download = useShortcutsStore((state) => state.download);
|
||||
const freeze = useShortcutsStore((state) => state.freeze);
|
||||
|
||||
useHotkeys(minimize, handleMinimizeWShortcut, { preventDefault });
|
||||
useHotkeys(update, handleUpdateWShortcut, { preventDefault });
|
||||
useHotkeys(group, handleGroupWShortcut, { preventDefault });
|
||||
useHotkeys(share, handleShareWShortcut, { preventDefault });
|
||||
useHotkeys(code, handleCodeWShortcut, { preventDefault });
|
||||
useHotkeys(advanced, handleAdvancedWShortcut, { preventDefault });
|
||||
useHotkeys(save, handleSaveWShortcut, { preventDefault });
|
||||
useHotkeys(docs, handleDocsWShortcut, { preventDefault });
|
||||
useHotkeys(download, handleDownloadWShortcut, { preventDefault });
|
||||
useHotkeys(freeze, handleFreeze, { preventDefault });
|
||||
|
||||
const isMinimal = numberOfHandles <= 1;
|
||||
const isGroup = data.node?.flow ? true : false;
|
||||
|
|
@ -73,18 +203,14 @@ export default function NodeToolbarComponent({
|
|||
const setNodes = useFlowStore((state) => state.setNodes);
|
||||
|
||||
const setEdges = useFlowStore((state) => state.setEdges);
|
||||
const unselectAll = useFlowStore((state) => state.unselectAll);
|
||||
const saveComponent = useFlowsManagerStore((state) => state.saveComponent);
|
||||
const getNodePosition = useFlowStore((state) => state.getNodePosition);
|
||||
const flows = useFlowsManagerStore((state) => state.flows);
|
||||
const version = useDarkStore((state) => state.version);
|
||||
const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot);
|
||||
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
||||
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
||||
const [showOverrideModal, setShowOverrideModal] = useState(false);
|
||||
const [flowComponent, setFlowComponent] = useState<FlowType>(
|
||||
createFlowComponent(cloneDeep(data), version),
|
||||
);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (openWDoubleClick) setShowModalAdvanced(true);
|
||||
// }, [openWDoubleClick, setOpenWDoubleClick]);
|
||||
|
||||
const openInNewTab = (url) => {
|
||||
window.open(url, "_blank", "noreferrer");
|
||||
|
|
@ -118,6 +244,27 @@ export default function NodeToolbarComponent({
|
|||
|
||||
const handleSelectChange = (event) => {
|
||||
switch (event) {
|
||||
case "save":
|
||||
if (isSaved) {
|
||||
return setShowOverrideModal(true);
|
||||
}
|
||||
saveComponent(cloneDeep(data), false);
|
||||
break;
|
||||
case "freeze":
|
||||
setNode(data.id, (old) => ({
|
||||
...old,
|
||||
data: {
|
||||
...old.data,
|
||||
node: {
|
||||
...old.data.node,
|
||||
frozen: old.data?.node?.frozen ? false : true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
break;
|
||||
case "code":
|
||||
setOpenModal(!openModal);
|
||||
break;
|
||||
case "advanced":
|
||||
setShowModalAdvanced(true);
|
||||
break;
|
||||
|
|
@ -185,6 +332,42 @@ export default function NodeToolbarComponent({
|
|||
Object.values(flow).includes(data.node?.display_name!),
|
||||
);
|
||||
|
||||
function displayShortcut({
|
||||
name,
|
||||
shortcut,
|
||||
}: {
|
||||
name: string;
|
||||
shortcut: string;
|
||||
}): JSX.Element {
|
||||
let hasShift: boolean = false;
|
||||
const fixedShortcut = shortcut?.split("+");
|
||||
fixedShortcut.forEach((key) => {
|
||||
if (key.toLowerCase().includes("shift")) {
|
||||
hasShift = true;
|
||||
}
|
||||
});
|
||||
const filteredShortcut = fixedShortcut.filter(
|
||||
(key) => !key.toLowerCase().includes("shift"),
|
||||
);
|
||||
let shortcutWPlus: string[] = [];
|
||||
if (!hasShift) shortcutWPlus = filteredShortcut.join("+").split(" ");
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
<span> {name} </span>
|
||||
<span
|
||||
className={`ml-3 flex items-center rounded-sm bg-muted px-1 py-[0.2] text-muted-foreground`}
|
||||
>
|
||||
<RenderIcons
|
||||
isMac={isMac}
|
||||
hasShift={hasShift}
|
||||
filteredShortcut={filteredShortcut}
|
||||
shortcutWPlus={shortcutWPlus}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const setNode = useFlowStore((state) => state.setNode);
|
||||
|
||||
const handleOnNewValue = (
|
||||
|
|
@ -235,131 +418,21 @@ export default function NodeToolbarComponent({
|
|||
const [openModal, setOpenModal] = useState(false);
|
||||
const hasCode = Object.keys(data.node!.template).includes("code");
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(event: KeyboardEvent) {
|
||||
if (
|
||||
selected &&
|
||||
(hasApiKey || hasStore) &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
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.toUpperCase() === "G"
|
||||
) {
|
||||
event.preventDefault();
|
||||
handleSelectChange("ungroup");
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(hasApiKey || hasStore) &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key.toUpperCase() === "S"
|
||||
) {
|
||||
event.preventDefault();
|
||||
setShowconfirmShare((state) => !state);
|
||||
}
|
||||
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key.toUpperCase() === "Q"
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (isMinimal) {
|
||||
setShowState((show) => !show);
|
||||
setShowNode(data.showNode ?? true ? false : true);
|
||||
return;
|
||||
}
|
||||
setNoticeData({
|
||||
title:
|
||||
"Minimization are only available for nodes with one handle or fewer.",
|
||||
});
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key.toUpperCase() === "U"
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (hasCode) return setOpenModal((state) => !state);
|
||||
setNoticeData({ title: `You can not access ${data.id} code` });
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key.toUpperCase() === "A"
|
||||
) {
|
||||
event.preventDefault();
|
||||
setShowModalAdvanced((state) => !state);
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key.toUpperCase() === "S"
|
||||
) {
|
||||
if (isSaved) {
|
||||
event.preventDefault();
|
||||
return setShowOverrideModal((state) => !state);
|
||||
}
|
||||
if (hasCode) {
|
||||
event.preventDefault();
|
||||
saveComponent(cloneDeep(data), false);
|
||||
setSuccessData({ title: `${data.id} saved successfully` });
|
||||
}
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.shiftKey &&
|
||||
event.key.toUpperCase() === "D"
|
||||
) {
|
||||
event.preventDefault();
|
||||
if (data.node?.documentation) {
|
||||
return openInNewTab(data.node?.documentation);
|
||||
}
|
||||
setNoticeData({
|
||||
title: `${data.id} docs is not available at the moment.`,
|
||||
});
|
||||
}
|
||||
if (
|
||||
selected &&
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.key.toUpperCase() === "J"
|
||||
) {
|
||||
event.preventDefault();
|
||||
downloadNode(flowComponent!);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [isSaved, showNode, data.showNode, isMinimal]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
<ShadTooltip
|
||||
content={displayShortcut(
|
||||
shortcuts.find(
|
||||
({ name }) => name.split(" ")[0].toLowerCase() === "code",
|
||||
)!,
|
||||
)}
|
||||
side="top"
|
||||
>
|
||||
<button
|
||||
className="relative inline-flex items-center rounded-l-md 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"
|
||||
className="relative inline-flex items-center rounded-l-md 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={() => {
|
||||
setOpenModal(!openModal);
|
||||
}}
|
||||
|
|
@ -369,8 +442,28 @@ export default function NodeToolbarComponent({
|
|||
</button>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
{nodeLength > 0 && (
|
||||
<ShadTooltip
|
||||
content={displayShortcut(
|
||||
shortcuts.find(
|
||||
({ name }) => name.split(" ")[0].toLowerCase() === "advanced",
|
||||
)!,
|
||||
)}
|
||||
side="top"
|
||||
>
|
||||
<button
|
||||
className={`${isGroup ? "rounded-l-md" : ""} 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={() => {
|
||||
setShowModalAdvanced(true);
|
||||
}}
|
||||
data-testid="code-button-modal"
|
||||
>
|
||||
<IconComponent name="Settings2" className="h-4 w-4" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
)}
|
||||
|
||||
<ShadTooltip content={"Save"} side="top">
|
||||
{/*<ShadTooltip content={"Save"} side="top">
|
||||
<button
|
||||
data-testid="save-button-modal"
|
||||
className={classNames(
|
||||
|
|
@ -387,24 +480,15 @@ export default function NodeToolbarComponent({
|
|||
>
|
||||
<IconComponent name="SaveAll" className="h-4 w-4" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
|
||||
<ShadTooltip content={"Duplicate"} side="top">
|
||||
<button
|
||||
data-testid="duplicate-button-modal"
|
||||
className={classNames(
|
||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
handleSelectChange("duplicate");
|
||||
}}
|
||||
>
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
|
||||
<ShadTooltip content="Freeze" side="top">
|
||||
</ShadTooltip>*/}
|
||||
<ShadTooltip
|
||||
content={displayShortcut(
|
||||
shortcuts.find(
|
||||
({ name }) => name.split(" ")[0].toLowerCase() === "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",
|
||||
|
|
@ -434,8 +518,23 @@ export default function NodeToolbarComponent({
|
|||
</button>
|
||||
</ShadTooltip>
|
||||
|
||||
{/*<ShadTooltip content={"Duplicate"} side="top">
|
||||
<button
|
||||
data-testid="duplicate-button-modal"
|
||||
className={classNames(
|
||||
"relative -ml-px inline-flex items-center bg-background px-2 py-2 text-foreground shadow-md ring-1 ring-inset ring-ring transition-all duration-500 ease-in-out hover:bg-muted focus:z-10",
|
||||
)}
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
handleSelectChange("duplicate");
|
||||
}}
|
||||
>
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
</button>
|
||||
</ShadTooltip>*/}
|
||||
|
||||
<Select onValueChange={handleSelectChange} value="">
|
||||
<ShadTooltip content="More" side="top">
|
||||
<ShadTooltip content="All" side="top">
|
||||
<SelectTrigger>
|
||||
<div>
|
||||
<div
|
||||
|
|
@ -453,23 +552,56 @@ export default function NodeToolbarComponent({
|
|||
</SelectTrigger>
|
||||
</ShadTooltip>
|
||||
<SelectContent>
|
||||
{hasCode && (
|
||||
<SelectItem value={"code"}>
|
||||
<ToolbarSelectItem
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Code")?.shortcut!
|
||||
}
|
||||
value={"Code"}
|
||||
icon={"Code"}
|
||||
dataTestId="code-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
)}
|
||||
{nodeLength > 0 && (
|
||||
<SelectItem value={nodeLength === 0 ? "disabled" : "advanced"}>
|
||||
<ToolbarSelectItem
|
||||
keyboardKey="A"
|
||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
||||
shift={true}
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Advanced Settings")
|
||||
?.shortcut!
|
||||
}
|
||||
value={"Advanced"}
|
||||
icon={"Settings2"}
|
||||
dataTestId="edit-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
)}
|
||||
<SelectItem value={"save"}>
|
||||
<ToolbarSelectItem
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Save")?.shortcut!
|
||||
}
|
||||
value={"Save"}
|
||||
icon={"SaveAll"}
|
||||
dataTestId="save-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
<SelectItem value={"duplicate"}>
|
||||
<ToolbarSelectItem
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Duplicate")?.shortcut!
|
||||
}
|
||||
value={"Duplicate"}
|
||||
icon={"Copy"}
|
||||
dataTestId="copy-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
<SelectItem value={"copy"}>
|
||||
<ToolbarSelectItem
|
||||
keyboardKey="C"
|
||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
||||
shift={false}
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Copy")?.shortcut!
|
||||
}
|
||||
value={"Copy"}
|
||||
icon={"Clipboard"}
|
||||
dataTestId="copy-button-modal"
|
||||
|
|
@ -481,14 +613,11 @@ export default function NodeToolbarComponent({
|
|||
disabled={!hasApiKey || !validApiKey}
|
||||
>
|
||||
<ToolbarSelectItem
|
||||
keyboardKey="S"
|
||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
||||
shift={true}
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Share")?.shortcut!
|
||||
}
|
||||
value={"Share"}
|
||||
icon={"Share3"}
|
||||
styleObj={{
|
||||
iconClasses: "relative top-0.5 -m-1 mr-[0.25rem] h-6 w-6",
|
||||
}}
|
||||
dataTestId="share-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
|
|
@ -496,13 +625,13 @@ export default function NodeToolbarComponent({
|
|||
{(!hasStore || !hasApiKey || !validApiKey) && (
|
||||
<SelectItem value={"Download"}>
|
||||
<ToolbarSelectItem
|
||||
value="Download"
|
||||
shift={false}
|
||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
||||
icon="Download"
|
||||
styleObj={{ iconClasses: "relative top-0.5 mr-2 h-4 w-4" }}
|
||||
keyboardKey={"J"}
|
||||
dataTestId={"Dowload-button-nodeToolbar"}
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Download")
|
||||
?.shortcut!
|
||||
}
|
||||
value={"Download"}
|
||||
icon={"Download"}
|
||||
dataTestId="Download-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
)}
|
||||
|
|
@ -511,9 +640,9 @@ export default function NodeToolbarComponent({
|
|||
disabled={data.node?.documentation === ""}
|
||||
>
|
||||
<ToolbarSelectItem
|
||||
keyboardKey="D"
|
||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
||||
shift={true}
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Docs")?.shortcut!
|
||||
}
|
||||
value={"Docs"}
|
||||
icon={"FileText"}
|
||||
dataTestId="docs-button-modal"
|
||||
|
|
@ -522,38 +651,49 @@ export default function NodeToolbarComponent({
|
|||
{isMinimal && (
|
||||
<SelectItem value={"show"}>
|
||||
<ToolbarSelectItem
|
||||
icon={showNode ? "Minimize2" : "Maximize2"}
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Minimize")
|
||||
?.shortcut!
|
||||
}
|
||||
value={showNode ? "Minimize" : "Expand"}
|
||||
isMac={navigator.userAgent.toUpperCase().includes("MAC")}
|
||||
shift={true}
|
||||
keyboardKey={"Q"}
|
||||
dataTestId={"minimize-button-nodeToolbar"}
|
||||
icon={showNode ? "Minimize2" : "Maximize2"}
|
||||
dataTestId="minimize-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
)}
|
||||
{isGroup && (
|
||||
<SelectItem value="ungroup">
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Ungroup"
|
||||
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||
/>{" "}
|
||||
<span className="">Ungroup</span>{" "}
|
||||
{navigator.userAgent.toUpperCase().includes("MAC") ? (
|
||||
<IconComponent
|
||||
name="Command"
|
||||
className="absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2"
|
||||
></IconComponent>
|
||||
) : (
|
||||
<span className="absolute right-[1.30rem] top-[0.40em] stroke-2">
|
||||
Ctrl +{" "}
|
||||
</span>
|
||||
)}
|
||||
<span className="absolute right-2 top-[0.43em]">G</span>
|
||||
</div>
|
||||
<ToolbarSelectItem
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Group")?.shortcut!
|
||||
}
|
||||
value={"Ungroup"}
|
||||
icon={"Ungroup"}
|
||||
dataTestId="group-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
)}
|
||||
|
||||
<SelectItem value="freeze">
|
||||
<ToolbarSelectItem
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Freeze")?.shortcut!
|
||||
}
|
||||
value={"Freeze"}
|
||||
icon={"Snowflake"}
|
||||
dataTestId="group-button-modal"
|
||||
style={`${frozen ? " text-ice" : ""} transition-all`}
|
||||
/>
|
||||
</SelectItem>
|
||||
<SelectItem value="Download">
|
||||
<ToolbarSelectItem
|
||||
shortcut={
|
||||
shortcuts.find((obj) => obj.name === "Download")?.shortcut!
|
||||
}
|
||||
value={"Download"}
|
||||
icon={"Download"}
|
||||
dataTestId="download-button-modal"
|
||||
/>
|
||||
</SelectItem>
|
||||
<SelectItem value={"delete"} className="focus:bg-red-400/[.20]">
|
||||
<div className="font-red flex text-status-red">
|
||||
<IconComponent
|
||||
|
|
@ -561,10 +701,10 @@ export default function NodeToolbarComponent({
|
|||
className="relative top-0.5 mr-2 h-4 w-4 "
|
||||
/>{" "}
|
||||
<span className="">Delete</span>{" "}
|
||||
<span>
|
||||
<span className="justify absolute right-2 top-2 flex items-center rounded-sm bg-muted px-1 py-[0.2]">
|
||||
<IconComponent
|
||||
name="Delete"
|
||||
className="absolute right-2 top-2 h-4 w-4 stroke-2 text-red-400"
|
||||
className="h-4 w-4 stroke-2 text-red-400"
|
||||
></IconComponent>
|
||||
</span>
|
||||
</div>
|
||||
|
|
@ -597,12 +737,15 @@ export default function NodeToolbarComponent({
|
|||
</span>
|
||||
</ConfirmationModal.Content>
|
||||
</ConfirmationModal>
|
||||
<EditNodeModal
|
||||
data={data}
|
||||
nodeLength={nodeLength}
|
||||
open={showModalAdvanced}
|
||||
setOpen={setShowModalAdvanced}
|
||||
/>
|
||||
{showModalAdvanced && (
|
||||
<EditNodeModal
|
||||
// setOpenWDoubleClick={setOpenWDoubleClick}
|
||||
data={data}
|
||||
nodeLength={nodeLength}
|
||||
open={showModalAdvanced}
|
||||
setOpen={setShowModalAdvanced}
|
||||
/>
|
||||
)}
|
||||
{showconfirmShare && (
|
||||
<ShareModal
|
||||
open={showconfirmShare}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,49 @@
|
|||
import ForwardedIconComponent from "../../../../../components/genericIconComponent";
|
||||
import RenderIcons from "../../../../../components/renderIconComponent";
|
||||
import { toolbarSelectItemProps } from "../../../../../types/components";
|
||||
|
||||
export default function ToolbarSelectItem({
|
||||
shift,
|
||||
isMac,
|
||||
keyboardKey,
|
||||
value,
|
||||
icon,
|
||||
styleObj,
|
||||
style,
|
||||
dataTestId,
|
||||
ping,
|
||||
shortcut,
|
||||
}: toolbarSelectItemProps) {
|
||||
const isMac = navigator.platform.toUpperCase().includes("MAC");
|
||||
let hasShift = false;
|
||||
const fixedShortcut = shortcut?.split("+");
|
||||
fixedShortcut.forEach((key) => {
|
||||
if (key.toLowerCase().includes("shift")) {
|
||||
hasShift = true;
|
||||
}
|
||||
});
|
||||
const filteredShortcut = fixedShortcut.filter(
|
||||
(key) => !key.toLowerCase().includes("shift"),
|
||||
);
|
||||
let shortcutWPlus: string[] = [];
|
||||
if (!hasShift) shortcutWPlus = filteredShortcut.join("+").split(" ");
|
||||
|
||||
return (
|
||||
<div className="flex" data-testid={dataTestId}>
|
||||
<div className={`flex ${style}`} data-testid={dataTestId}>
|
||||
<ForwardedIconComponent
|
||||
name={icon}
|
||||
className={`relative top-0.5 mr-2 h-4 w-4 ${styleObj?.iconClasses} ${
|
||||
className={` mr-2 ${icon === "Share3" ? "absolute left-2 top-[0.25em] h-6 w-6" : "mt-[0.15em] h-4 w-4"} ${
|
||||
ping && "animate-pulse text-green-500"
|
||||
}`}
|
||||
/>
|
||||
<span className={styleObj?.valueClasses}>{value}</span>
|
||||
|
||||
{isMac ? (
|
||||
<ForwardedIconComponent
|
||||
name="Command"
|
||||
className={`absolute
|
||||
${shift ? " right-[2rem] " : "right-[1.15rem]"}
|
||||
top-[0.65em] h-3.5 w-3.5 stroke-2 ${styleObj?.commandClasses}`}
|
||||
></ForwardedIconComponent>
|
||||
) : (
|
||||
<span
|
||||
className={`absolute ${
|
||||
shift ? " right-[2.15rem] " : "right-[1.15rem]"
|
||||
} top-[0.43em] stroke-2 `}
|
||||
>
|
||||
{shift ? "Ctrl" : "Ctrl +"}
|
||||
</span>
|
||||
)}
|
||||
{shift && (
|
||||
<ForwardedIconComponent
|
||||
name="ArrowBigUp"
|
||||
className={`absolute right-[1.15rem] top-[0.65em] h-3.5 w-3.5 stroke-2 ${styleObj?.shiftClasses}`}
|
||||
<span className={`${icon === "Share3" ? "ml-[1.8em]" : " "}`}>
|
||||
{value}
|
||||
</span>
|
||||
<span
|
||||
className={`absolute right-2 top-[0.43em] flex items-center rounded-sm bg-muted px-1 py-[0.2] text-muted-foreground `}
|
||||
>
|
||||
<RenderIcons
|
||||
isMac={isMac}
|
||||
hasShift={hasShift}
|
||||
filteredShortcut={filteredShortcut}
|
||||
shortcutWPlus={shortcutWPlus}
|
||||
/>
|
||||
)}
|
||||
<span className={`absolute right-2 top-[0.43em] ${styleObj?.keyClasses}`}>
|
||||
{keyboardKey}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -68,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}`);
|
||||
|
|
@ -82,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);
|
||||
|
|
@ -99,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) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,183 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import useAlertStore from "../../../../../stores/alertStore";
|
||||
|
||||
import ForwardedIconComponent from "../../../../../components/genericIconComponent";
|
||||
import { Button } from "../../../../../components/ui/button";
|
||||
import BaseModal from "../../../../../modals/baseModal";
|
||||
import { useShortcutsStore } from "../../../../../stores/shortcuts";
|
||||
import { toTitleCase } from "../../../../../utils/utils";
|
||||
|
||||
export default function EditShortcutButton({
|
||||
children,
|
||||
shortcut,
|
||||
defaultShortcuts,
|
||||
defaultCombination,
|
||||
open,
|
||||
setOpen,
|
||||
disable,
|
||||
setSelected,
|
||||
}: {
|
||||
children: JSX.Element;
|
||||
shortcut: string[];
|
||||
defaultShortcuts: Array<{ name: string; shortcut: string }>;
|
||||
defaultCombination: string;
|
||||
open: boolean;
|
||||
setOpen: (bool: boolean) => void;
|
||||
disable?: boolean;
|
||||
setSelected: (selected: string[]) => void;
|
||||
}): JSX.Element {
|
||||
let shortcutInitialValue =
|
||||
defaultShortcuts.length > 0
|
||||
? defaultShortcuts.find(
|
||||
(s) =>
|
||||
s.name.split(" ")[0].toLowerCase().toLowerCase() ===
|
||||
shortcut[0]?.split(" ")[0].toLowerCase(),
|
||||
)?.shortcut
|
||||
: "";
|
||||
const [key, setKey] = useState<string | null>(null);
|
||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||
const setShortcuts = useShortcutsStore((state) => state.setShortcuts);
|
||||
const setErrorData = useAlertStore((state) => state.setErrorData);
|
||||
|
||||
function canEditCombination(newCombination: string): boolean {
|
||||
let canSave = true;
|
||||
defaultShortcuts.forEach(({ shortcut }) => {
|
||||
if (shortcut.toLowerCase() === newCombination.toLowerCase()) {
|
||||
canSave = false;
|
||||
}
|
||||
});
|
||||
return canSave;
|
||||
}
|
||||
|
||||
const setUniqueShortcut = useShortcutsStore(
|
||||
(state) => state.updateUniqueShortcut,
|
||||
);
|
||||
|
||||
function editCombination(): void {
|
||||
if (key) {
|
||||
if (canEditCombination(key)) {
|
||||
const newCombination = defaultShortcuts.map((s) => {
|
||||
if (s.name === shortcut[0]) {
|
||||
return { name: s.name, shortcut: key };
|
||||
}
|
||||
return { name: s.name, shortcut: s.shortcut };
|
||||
});
|
||||
const fixCombination = key.split(" ");
|
||||
if (
|
||||
fixCombination[0].toLowerCase().includes("ctrl") ||
|
||||
fixCombination[0].toLowerCase().includes("cmd")
|
||||
) {
|
||||
fixCombination[0] = "mod";
|
||||
}
|
||||
const shortcutName = shortcut[0].split(" ")[0].toLowerCase();
|
||||
setUniqueShortcut(shortcutName, fixCombination.join("").toLowerCase());
|
||||
setShortcuts(newCombination);
|
||||
localStorage.setItem(
|
||||
"langflow-shortcuts",
|
||||
JSON.stringify(newCombination),
|
||||
);
|
||||
setKey(null);
|
||||
setOpen(false);
|
||||
setSuccessData({
|
||||
title: `${shortcut[0]} shortcut successfully changed`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
setErrorData({
|
||||
title: "Error saving key combination",
|
||||
list: ["This combination already exists!"],
|
||||
});
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setKey(null);
|
||||
setSelected([]);
|
||||
}
|
||||
}, [open, setOpen, key]);
|
||||
|
||||
function getFixedCombination({
|
||||
oldKey,
|
||||
key,
|
||||
}: {
|
||||
oldKey: string;
|
||||
key: string;
|
||||
}): string {
|
||||
if (oldKey === null) {
|
||||
return `${key.length > 0 ? toTitleCase(key) : toTitleCase(key)}`;
|
||||
}
|
||||
return `${oldKey.length > 0 ? toTitleCase(oldKey) : oldKey.toUpperCase()} + ${key.length > 0 ? toTitleCase(key) : key.toUpperCase()}`;
|
||||
}
|
||||
|
||||
function checkForKeys(keys: string, keyToCompare: string): boolean {
|
||||
const keysArr = keys.split(" ");
|
||||
let hasNewKey = false;
|
||||
return keysArr.some(
|
||||
(k) => k.toLowerCase().trim() === keyToCompare.toLowerCase().trim(),
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function onKeyDown(e: KeyboardEvent) {
|
||||
e.preventDefault();
|
||||
let fixedKey = e.key;
|
||||
if (e.key?.toLowerCase() === "control") {
|
||||
fixedKey = "Ctrl";
|
||||
}
|
||||
if (e.key?.toLowerCase() === "meta") {
|
||||
fixedKey = "Cmd";
|
||||
}
|
||||
if (e.key?.toLowerCase() === " ") {
|
||||
fixedKey = "Space";
|
||||
}
|
||||
if (key) {
|
||||
if (checkForKeys(key, fixedKey)) return;
|
||||
}
|
||||
setKey((oldKey) =>
|
||||
getFixedCombination({ oldKey: oldKey!, key: fixedKey }),
|
||||
);
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", onKeyDown);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, [key, setKey]);
|
||||
|
||||
return (
|
||||
<BaseModal open={open} setOpen={setOpen} size="x-small" disable={disable}>
|
||||
<BaseModal.Header description={"Recording your keyboard"}>
|
||||
<span className="pr-2"> Key Combination </span>
|
||||
<ForwardedIconComponent
|
||||
name="Keyboard"
|
||||
className="h-6 w-6 pl-1 text-primary "
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Trigger>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Content>
|
||||
<div className="align-center flex h-full w-full justify-center gap-4 rounded-md border border-border py-2">
|
||||
<div className="flex items-center justify-center text-center text-lg font-bold">
|
||||
{key === null
|
||||
? shortcutInitialValue?.toUpperCase()
|
||||
: key.toUpperCase()}
|
||||
</div>
|
||||
</div>
|
||||
</BaseModal.Content>
|
||||
<BaseModal.Footer>
|
||||
<Button variant={"default"} onClick={editCombination}>
|
||||
Apply
|
||||
</Button>
|
||||
<Button
|
||||
className="mr-5"
|
||||
variant={"destructive"}
|
||||
onClick={() => setKey(null)}
|
||||
>
|
||||
Reset
|
||||
</Button>
|
||||
</BaseModal.Footer>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,20 +1,16 @@
|
|||
import { ColDef, ColGroupDef, SelectionChangedEvent } from "ag-grid-community";
|
||||
import { useEffect, useState } from "react";
|
||||
import ForwardedIconComponent from "../../../../components/genericIconComponent";
|
||||
import TableComponent from "../../../../components/tableComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import { defaultShortcuts } from "../../../../constants/constants";
|
||||
import { useShortcutsStore } from "../../../../stores/shortcuts";
|
||||
import EditShortcutButton from "./EditShortcutButton";
|
||||
|
||||
export default function ShortcutsPage() {
|
||||
const isMac = navigator.userAgent.toUpperCase().includes("MAC");
|
||||
const advancedShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + A`;
|
||||
const minizmizeShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + Q`;
|
||||
const codeShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + C`;
|
||||
const copyShortcut = `${isMac ? "Cmd" : "Ctrl"} + C`;
|
||||
const duplicateShortcut = `${isMac ? "Cmd" : "Ctrl"} + D`;
|
||||
const shareShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + S`;
|
||||
const docsShortcut = `${isMac ? "Cmd" : "Ctrl"} + Shift + D`;
|
||||
const saveShortcut = `${isMac ? "Cmd" : "Ctrl"} + S`;
|
||||
const deleteShortcut = `Backspace`;
|
||||
const interactionShortcut = `${isMac ? "Cmd" : "Ctrl"} + K`;
|
||||
const undoShortcut = `${isMac ? "Cmd" : "Ctrl"} + Z`;
|
||||
const redoShortcut = `${isMac ? "Cmd" : "Ctrl"} + Y`;
|
||||
const [selectedRows, setSelectedRows] = useState<string[]>([]);
|
||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||
const setShortcuts = useShortcutsStore((state) => state.setShortcuts);
|
||||
|
||||
// Column Definitions: Defines the columns to be displayed.
|
||||
const colDefs = [
|
||||
|
|
@ -23,8 +19,10 @@ export default function ShortcutsPage() {
|
|||
field: "name",
|
||||
flex: 1,
|
||||
editable: false,
|
||||
resizable: false,
|
||||
}, //This column will be twice as wide as the others
|
||||
{
|
||||
headerName: "Keyboard Shortcut",
|
||||
field: "shortcut",
|
||||
flex: 2,
|
||||
editable: false,
|
||||
|
|
@ -32,71 +30,31 @@ export default function ShortcutsPage() {
|
|||
},
|
||||
];
|
||||
|
||||
const nodesRowData = [
|
||||
{
|
||||
name: "Advanced Settings Component",
|
||||
shortcut: advancedShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Minimize Component",
|
||||
shortcut: minizmizeShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Code Component",
|
||||
shortcut: codeShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Copy Component",
|
||||
shortcut: copyShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Duplicate Component",
|
||||
shortcut: duplicateShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Share Component",
|
||||
shortcut: shareShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Docs Component",
|
||||
shortcut: docsShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Save Component",
|
||||
shortcut: saveShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Delete Component",
|
||||
shortcut: deleteShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Open Playground",
|
||||
shortcut: interactionShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Undo",
|
||||
shortcut: undoShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
{
|
||||
name: "Redo",
|
||||
shortcut: redoShortcut,
|
||||
resizable: false,
|
||||
},
|
||||
];
|
||||
const [nodesRowData, setNodesRowData] = useState<
|
||||
Array<{ name: string; shortcut: string }>
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
setNodesRowData(shortcuts);
|
||||
}, [shortcuts]);
|
||||
|
||||
const combinationToEdit = shortcuts.filter((s) => s.name === selectedRows[0]);
|
||||
const [open, setOpen] = useState(false);
|
||||
const updateUniqueShortcut = useShortcutsStore(
|
||||
(state) => state.updateUniqueShortcut,
|
||||
);
|
||||
|
||||
function handleRestore() {
|
||||
setShortcuts(defaultShortcuts);
|
||||
defaultShortcuts.forEach(({ name, shortcut }) => {
|
||||
const fixedName = name.split(" ")[0].toLowerCase();
|
||||
updateUniqueShortcut(fixedName, shortcut);
|
||||
});
|
||||
localStorage.removeItem("langflow-shortcuts");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col gap-6">
|
||||
<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">
|
||||
|
|
@ -110,14 +68,48 @@ export default function ShortcutsPage() {
|
|||
Manage Shortcuts for quick access to frequently used actions.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div className="align-end mb-4 flex w-full justify-end">
|
||||
<div className="justify center flex items-center">
|
||||
{open && (
|
||||
<EditShortcutButton
|
||||
disable={selectedRows.length === 0}
|
||||
defaultCombination={combinationToEdit[0]?.shortcut}
|
||||
shortcut={selectedRows}
|
||||
defaultShortcuts={shortcuts}
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
setSelected={setSelectedRows}
|
||||
>
|
||||
<div style={{ display: "none" }} />
|
||||
</EditShortcutButton>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
className="ml-3"
|
||||
onClick={handleRestore}
|
||||
>
|
||||
<ForwardedIconComponent name="RotateCcw" className="mr-2 w-4" />
|
||||
Restore
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col justify-between">
|
||||
<TableComponent
|
||||
key={"shortcuts"}
|
||||
pagination={false}
|
||||
columnDefs={colDefs}
|
||||
rowData={nodesRowData}
|
||||
/>
|
||||
<div className="grid gap-6 pb-8">
|
||||
<div>
|
||||
<TableComponent
|
||||
suppressRowClickSelection={true}
|
||||
domLayout="autoHeight"
|
||||
pagination={false}
|
||||
columnDefs={colDefs}
|
||||
rowData={nodesRowData}
|
||||
onCellDoubleClicked={(e) => {
|
||||
setSelectedRows([e.data.name]);
|
||||
setOpen(true);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,15 +1,50 @@
|
|||
import { create } from "zustand";
|
||||
import { defaultShortcuts } from "../constants/constants";
|
||||
import { shortcutsStoreType } from "../types/store";
|
||||
|
||||
export const useShortcutsStore = create<shortcutsStoreType>((set, get) => ({
|
||||
openCodeModalWShortcut: false,
|
||||
handleModalWShortcut: (modal) => {
|
||||
switch (modal) {
|
||||
case "code":
|
||||
shortcuts: defaultShortcuts,
|
||||
setShortcuts: (newShortcuts) => {
|
||||
set({ shortcuts: newShortcuts });
|
||||
},
|
||||
flow: "mod+b",
|
||||
undo: "mod+z",
|
||||
redo: "mod+y",
|
||||
open: "mod+k",
|
||||
advanced: "mod+shift+a",
|
||||
minimize: "mod+shift+q",
|
||||
code: "space",
|
||||
copy: "mod+c",
|
||||
duplicate: "mod+d",
|
||||
share: "mod+shift+s",
|
||||
docs: "mod+shift+d",
|
||||
save: "mod+s",
|
||||
delete: "backspace",
|
||||
group: "mod+g",
|
||||
cut: "mod+x",
|
||||
paste: "mod+v",
|
||||
api: "mod+r",
|
||||
update: "mod+u",
|
||||
download: "mod+j",
|
||||
freeze: "mod+f",
|
||||
updateUniqueShortcut: (name, combination) => {
|
||||
set({
|
||||
[name]: combination,
|
||||
});
|
||||
},
|
||||
getShortcutsFromStorage: () => {
|
||||
if (localStorage.getItem("langflow-shortcuts")) {
|
||||
const savedShortcuts = localStorage.getItem("langflow-shortcuts");
|
||||
const savedArr = JSON.parse(savedShortcuts!);
|
||||
savedArr.forEach(({ name, shortcut }) => {
|
||||
let shortcutName = name.split(" ")[0].toLowerCase();
|
||||
set({
|
||||
openCodeModalWShortcut: !get().openCodeModalWShortcut,
|
||||
[shortcutName]: shortcut,
|
||||
});
|
||||
break;
|
||||
});
|
||||
get().setShortcuts(JSON.parse(savedShortcuts!));
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
useShortcutsStore.getState().getShortcutsFromStorage();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@
|
|||
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
font-feature-settings: "rlig" 1, "calt" 1;
|
||||
font-feature-settings:
|
||||
"rlig" 1,
|
||||
"calt" 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,10 @@ pre {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.ag-row {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.react-flow__pane {
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
--secondary-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||
--accent: 210 30% 96.1%; /* hsl(210 30% 96%) */
|
||||
--accent-foreground: 222.2 47.4% 11.2%; /* hsl(222 47% 11%) */
|
||||
--destructive: 0 100% 50%; /* hsl(0 100% 50%) */
|
||||
--destructive: 0 60% 40%; /* hsl(0 60% 40%) */
|
||||
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||
--radius: 0.5rem;
|
||||
--ring: 215 20.2% 65.1%; /* hsl(215 20% 65%) */
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
--accent: 216 24% 20%; /* hsl(216 34% 17%) */
|
||||
--accent-foreground: 210 30% 98%; /* hsl(210 40% 98%) */
|
||||
|
||||
--destructive: 0 63% 31%; /* hsl(0 63% 31%) */
|
||||
--destructive: 0 60% 25%; /* hsl(0 60% 25%) */
|
||||
--destructive-foreground: 210 40% 98%; /* hsl(210 40% 98%) */
|
||||
|
||||
--ring: 216 24% 30%; /* hsl(216 24% 30%) */
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ export type ChatInputType = {
|
|||
isDragging: boolean;
|
||||
files: FilePreviewType[];
|
||||
setFiles: (
|
||||
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[]),
|
||||
files: FilePreviewType[] | ((prev: FilePreviewType[]) => FilePreviewType[])
|
||||
) => void;
|
||||
chatValue: string;
|
||||
inputRef: {
|
||||
|
|
@ -535,6 +535,8 @@ export type fileCardPropsType = {
|
|||
};
|
||||
|
||||
export type nodeToolbarPropsType = {
|
||||
// openWDoubleClick: boolean;
|
||||
// setOpenWDoubleClick: (open: boolean) => void;
|
||||
data: NodeDataType;
|
||||
deleteNode: (idx: string) => void;
|
||||
setShowNode: (boolean: any) => void;
|
||||
|
|
@ -591,7 +593,7 @@ export type chatMessagePropsType = {
|
|||
updateChat: (
|
||||
chat: ChatMessageType,
|
||||
message: string,
|
||||
stream_url?: string,
|
||||
stream_url?: string
|
||||
) => void;
|
||||
};
|
||||
|
||||
|
|
@ -683,12 +685,12 @@ export type codeTabsPropsType = {
|
|||
value: string,
|
||||
node: NodeType,
|
||||
template: TemplateVariableType,
|
||||
tweak: tweakType,
|
||||
tweak: tweakType
|
||||
) => string;
|
||||
buildTweakObject?: (
|
||||
tw: string,
|
||||
changes: string | string[] | boolean | number | Object[] | Object,
|
||||
template: TemplateVariableType,
|
||||
template: TemplateVariableType
|
||||
) => Promise<string | void>;
|
||||
};
|
||||
activeTweaks?: boolean;
|
||||
|
|
@ -770,21 +772,12 @@ export type IOFileInputProps = {
|
|||
};
|
||||
|
||||
export type toolbarSelectItemProps = {
|
||||
isMac: boolean;
|
||||
shift: boolean;
|
||||
keyboardKey: string;
|
||||
value: string;
|
||||
icon: string;
|
||||
styleObj?: {
|
||||
iconClasses?: string;
|
||||
commandClasses?: string;
|
||||
shiftClasses?: string;
|
||||
ctrlClasses?: string;
|
||||
keyClasses?: string;
|
||||
valueClasses?: string;
|
||||
};
|
||||
style?: string;
|
||||
dataTestId: string;
|
||||
ping?: boolean;
|
||||
shortcut: string;
|
||||
};
|
||||
|
||||
export type clearChatPropsType = {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,33 @@ export type StoreComponentResponse = {
|
|||
};
|
||||
|
||||
export type shortcutsStoreType = {
|
||||
openCodeModalWShortcut: boolean;
|
||||
handleModalWShortcut: (str: string) => void;
|
||||
updateUniqueShortcut: (name: string, combination: string) => void;
|
||||
flow: string;
|
||||
group: string;
|
||||
cut: string;
|
||||
paste: string;
|
||||
api: string;
|
||||
open: string;
|
||||
undo: string;
|
||||
redo: string;
|
||||
advanced: string;
|
||||
minimize: string;
|
||||
code: string;
|
||||
copy: string;
|
||||
duplicate: string;
|
||||
share: string;
|
||||
docs: string;
|
||||
save: string;
|
||||
delete: string;
|
||||
update: string;
|
||||
download: string;
|
||||
freeze: string;
|
||||
shortcuts: Array<{
|
||||
name: string;
|
||||
shortcut: string;
|
||||
}>;
|
||||
setShortcuts: (
|
||||
newShortcuts: Array<{ name: string; shortcut: string }>,
|
||||
) => void;
|
||||
getShortcutsFromStorage: () => void;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import ShortUniqueId from "short-unique-id";
|
|||
import getFieldTitle from "../CustomNodes/utils/get-field-title";
|
||||
import {
|
||||
INPUT_TYPES,
|
||||
IS_MAC,
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
OUTPUT_TYPES,
|
||||
SUCCESS_BUILD,
|
||||
|
|
@ -99,18 +100,18 @@ export function unselectAllNodes({ updateNodes, data }: unselectAllNodesType) {
|
|||
export function isValidConnection(
|
||||
{ source, target, sourceHandle, targetHandle }: Connection,
|
||||
nodes: Node[],
|
||||
edges: Edge[],
|
||||
edges: Edge[]
|
||||
) {
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(targetHandle!);
|
||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(sourceHandle!);
|
||||
if (
|
||||
targetHandleObject.inputTypes?.some(
|
||||
(n) => n === sourceHandleObject.dataType,
|
||||
(n) => n === sourceHandleObject.dataType
|
||||
) ||
|
||||
sourceHandleObject.baseClasses.some(
|
||||
(t) =>
|
||||
targetHandleObject.inputTypes?.some((n) => n === t) ||
|
||||
t === targetHandleObject.type,
|
||||
t === targetHandleObject.type
|
||||
)
|
||||
) {
|
||||
let targetNode = nodes.find((node) => node.id === target!)?.data?.node;
|
||||
|
|
@ -143,7 +144,7 @@ export function removeApiKeys(flow: FlowType): FlowType {
|
|||
|
||||
export function updateTemplate(
|
||||
reference: APITemplateType,
|
||||
objectToUpdate: APITemplateType,
|
||||
objectToUpdate: APITemplateType
|
||||
): APITemplateType {
|
||||
let clonedObject: APITemplateType = cloneDeep(reference);
|
||||
|
||||
|
|
@ -203,7 +204,7 @@ export const processDataFromFlow = (flow: FlowType, refreshIds = true) => {
|
|||
|
||||
export function updateIds(
|
||||
{ edges, nodes }: { edges: Edge[]; nodes: Node[] },
|
||||
selection?: { edges: Edge[]; nodes: Node[] },
|
||||
selection?: { edges: Edge[]; nodes: Node[] }
|
||||
) {
|
||||
let idsMap = {};
|
||||
const selectionIds = selection?.nodes.map((n) => n.id);
|
||||
|
|
@ -231,7 +232,7 @@ export function updateIds(
|
|||
edge.source = idsMap[edge.source];
|
||||
edge.target = idsMap[edge.target];
|
||||
const sourceHandleObject: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!,
|
||||
edge.sourceHandle!
|
||||
);
|
||||
edge.sourceHandle = scapedJSONStringfy({
|
||||
...sourceHandleObject,
|
||||
|
|
@ -241,7 +242,7 @@ export function updateIds(
|
|||
edge.data.sourceHandle.id = edge.source;
|
||||
}
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||
edge.targetHandle!,
|
||||
edge.targetHandle!
|
||||
);
|
||||
edge.targetHandle = scapedJSONStringfy({
|
||||
...targetHandleObject,
|
||||
|
|
@ -287,11 +288,11 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
|||
(scapeJSONParse(edge.targetHandle!) as targetHandleType).fieldName ===
|
||||
t &&
|
||||
(scapeJSONParse(edge.targetHandle!) as targetHandleType).id ===
|
||||
node.id,
|
||||
node.id
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
`${displayName || type} is missing ${getFieldTitle(template, t)}.`,
|
||||
`${displayName || type} is missing ${getFieldTitle(template, t)}.`
|
||||
);
|
||||
} else if (
|
||||
template[t].type === "dict" &&
|
||||
|
|
@ -305,15 +306,15 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
|||
errors.push(
|
||||
`${displayName || type} (${getFieldTitle(
|
||||
template,
|
||||
t,
|
||||
)}) contains duplicate keys with the same values.`,
|
||||
t
|
||||
)}) contains duplicate keys with the same values.`
|
||||
);
|
||||
if (hasEmptyKey(template[t].value))
|
||||
errors.push(
|
||||
`${displayName || type} (${getFieldTitle(
|
||||
template,
|
||||
t,
|
||||
)}) field must not be empty.`,
|
||||
t
|
||||
)}) field must not be empty.`
|
||||
);
|
||||
}
|
||||
return errors;
|
||||
|
|
@ -322,7 +323,7 @@ export function validateNode(node: NodeType, edges: Edge[]): Array<string> {
|
|||
|
||||
export function validateNodes(
|
||||
nodes: Node[],
|
||||
edges: Edge[],
|
||||
edges: Edge[]
|
||||
): // this returns an array of tuples with the node id and the errors
|
||||
Array<{ id: string; errors: Array<string> }> {
|
||||
if (nodes.length === 0) {
|
||||
|
|
@ -343,7 +344,7 @@ export function updateEdges(edges: Edge[]) {
|
|||
if (edges)
|
||||
edges.forEach((edge) => {
|
||||
const targetHandleObject: targetHandleType = scapeJSONParse(
|
||||
edge.targetHandle!,
|
||||
edge.targetHandle!
|
||||
);
|
||||
edge.className = "";
|
||||
});
|
||||
|
|
@ -410,7 +411,7 @@ export function handleKeyDown(
|
|||
| React.KeyboardEvent<HTMLInputElement>
|
||||
| React.KeyboardEvent<HTMLTextAreaElement>,
|
||||
inputValue: string | string[] | null,
|
||||
block: string,
|
||||
block: string
|
||||
) {
|
||||
//condition to fix bug control+backspace on Windows/Linux
|
||||
if (
|
||||
|
|
@ -420,9 +421,7 @@ export function handleKeyDown(
|
|||
(inputValue === block ||
|
||||
inputValue?.charAt(inputValue?.length - 1) === " " ||
|
||||
specialCharsRegex.test(inputValue?.charAt(inputValue?.length - 1)))) ||
|
||||
(navigator.userAgent.toUpperCase().includes("MAC") &&
|
||||
e.ctrlKey === true &&
|
||||
e.key === "Backspace")
|
||||
(IS_MAC && e.ctrlKey === true && e.key === "Backspace")
|
||||
) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
@ -435,7 +434,7 @@ export function handleKeyDown(
|
|||
}
|
||||
|
||||
export function handleOnlyIntegerInput(
|
||||
event: React.KeyboardEvent<HTMLInputElement>,
|
||||
event: React.KeyboardEvent<HTMLInputElement>
|
||||
) {
|
||||
if (
|
||||
event.key === "." ||
|
||||
|
|
@ -451,7 +450,7 @@ export function handleOnlyIntegerInput(
|
|||
|
||||
export function getConnectedNodes(
|
||||
edge: Edge,
|
||||
nodes: Array<NodeType>,
|
||||
nodes: Array<NodeType>
|
||||
): Array<NodeType> {
|
||||
const sourceId = edge.source;
|
||||
const targetId = edge.target;
|
||||
|
|
@ -552,7 +551,7 @@ export function checkOldEdgesHandles(edges: Edge[]): boolean {
|
|||
!edge.sourceHandle ||
|
||||
!edge.targetHandle ||
|
||||
!edge.sourceHandle.includes("{") ||
|
||||
!edge.targetHandle.includes("{"),
|
||||
!edge.targetHandle.includes("{")
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -575,7 +574,7 @@ export function customStringify(obj: any): string {
|
|||
|
||||
const keys = Object.keys(obj).sort();
|
||||
const keyValuePairs = keys.map(
|
||||
(key) => `"${key}":${customStringify(obj[key])}`,
|
||||
(key) => `"${key}":${customStringify(obj[key])}`
|
||||
);
|
||||
return `{${keyValuePairs.join(",")}}`;
|
||||
}
|
||||
|
|
@ -604,7 +603,7 @@ export function getHandleId(
|
|||
source: string,
|
||||
sourceHandle: string,
|
||||
target: string,
|
||||
targetHandle: string,
|
||||
targetHandle: string
|
||||
) {
|
||||
return (
|
||||
"reactflow__edge-" + source + sourceHandle + "-" + target + targetHandle
|
||||
|
|
@ -615,7 +614,7 @@ export function generateFlow(
|
|||
selection: OnSelectionChangeParams,
|
||||
nodes: Node[],
|
||||
edges: Edge[],
|
||||
name: string,
|
||||
name: string
|
||||
): generateFlowType {
|
||||
const newFlowData = { nodes, edges, viewport: { zoom: 1, x: 0, y: 0 } };
|
||||
const uid = new ShortUniqueId({ length: 5 });
|
||||
|
|
@ -624,7 +623,7 @@ export function generateFlow(
|
|||
newFlowData.edges = selection.edges.filter(
|
||||
(edge) =>
|
||||
selection.nodes.some((node) => node.id === edge.target) &&
|
||||
selection.nodes.some((node) => node.id === edge.source),
|
||||
selection.nodes.some((node) => node.id === edge.source)
|
||||
);
|
||||
newFlowData.nodes = selection.nodes;
|
||||
|
||||
|
|
@ -645,7 +644,7 @@ export function generateFlow(
|
|||
(edge) =>
|
||||
(selection.nodes.some((node) => node.id === edge.target) ||
|
||||
selection.nodes.some((node) => node.id === edge.source)) &&
|
||||
newFlowData.edges.every((e) => e.id !== edge.id),
|
||||
newFlowData.edges.every((e) => e.id !== edge.id)
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
@ -656,13 +655,13 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
|
|||
const { nodes, edges } = groupNode.data.node!.flow!.data!;
|
||||
const lastNode = findLastNode(groupNode.data.node!.flow!.data!);
|
||||
newEdges = newEdges.filter(
|
||||
(e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id),
|
||||
(e) => !(nodes.some((n) => n.id === e.source) && e.source !== lastNode?.id)
|
||||
);
|
||||
newEdges.forEach((edge) => {
|
||||
if (lastNode && edge.source === lastNode.id) {
|
||||
edge.source = groupNode.id;
|
||||
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
||||
edge.sourceHandle!,
|
||||
edge.sourceHandle!
|
||||
);
|
||||
newSourceHandle.id = groupNode.id;
|
||||
edge.sourceHandle = scapedJSONStringfy(newSourceHandle);
|
||||
|
|
@ -689,7 +688,7 @@ export function reconnectEdges(groupNode: NodeType, excludedEdges: Edge[]) {
|
|||
export function filterFlow(
|
||||
selection: OnSelectionChangeParams,
|
||||
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
|
||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||
) {
|
||||
setNodes((nodes) => nodes.filter((node) => !selection.nodes.includes(node)));
|
||||
setEdges((edges) => edges.filter((edge) => !selection.edges.includes(edge)));
|
||||
|
|
@ -727,7 +726,7 @@ export function updateFlowPosition(NewPosition: XYPosition, flow: FlowType) {
|
|||
export function concatFlows(
|
||||
flow: FlowType,
|
||||
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
|
||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||
) {
|
||||
const { nodes, edges } = flow.data!;
|
||||
setNodes((old) => [...old, ...nodes]);
|
||||
|
|
@ -736,7 +735,7 @@ export function concatFlows(
|
|||
|
||||
export function validateSelection(
|
||||
selection: OnSelectionChangeParams,
|
||||
edges: Edge[],
|
||||
edges: Edge[]
|
||||
): Array<string> {
|
||||
const clonedSelection = cloneDeep(selection);
|
||||
const clonedEdges = cloneDeep(edges);
|
||||
|
|
@ -750,7 +749,7 @@ export function validateSelection(
|
|||
let nodesSet = new Set(clonedSelection.nodes.map((n) => n.id));
|
||||
// then filter the edges that are connected to the nodes in the set
|
||||
let connectedEdges = clonedSelection.edges.filter(
|
||||
(e) => nodesSet.has(e.source) && nodesSet.has(e.target),
|
||||
(e) => nodesSet.has(e.source) && nodesSet.has(e.target)
|
||||
);
|
||||
// add the edges to the selection
|
||||
clonedSelection.edges = connectedEdges;
|
||||
|
|
@ -764,17 +763,17 @@ export function validateSelection(
|
|||
clonedSelection.nodes.some(
|
||||
(node) =>
|
||||
isInputNode(node.data as NodeDataType) ||
|
||||
isOutputNode(node.data as NodeDataType),
|
||||
isOutputNode(node.data as NodeDataType)
|
||||
)
|
||||
) {
|
||||
errorsArray.push(
|
||||
"Please select only nodes that are not input or output nodes",
|
||||
"Please select only nodes that are not input or output nodes"
|
||||
);
|
||||
}
|
||||
//check if there are two or more nodes with free outputs
|
||||
if (
|
||||
clonedSelection.nodes.filter(
|
||||
(n) => !clonedSelection.edges.some((e) => e.source === n.id),
|
||||
(n) => !clonedSelection.edges.some((e) => e.source === n.id)
|
||||
).length > 1
|
||||
) {
|
||||
errorsArray.push("Please select only one node with free outputs");
|
||||
|
|
@ -785,7 +784,7 @@ export function validateSelection(
|
|||
clonedSelection.nodes.some(
|
||||
(node) =>
|
||||
!clonedSelection.edges.some((edge) => edge.target === node.id) &&
|
||||
!clonedSelection.edges.some((edge) => edge.source === node.id),
|
||||
!clonedSelection.edges.some((edge) => edge.source === node.id)
|
||||
)
|
||||
) {
|
||||
errorsArray.push("Please select only nodes that are connected");
|
||||
|
|
@ -842,8 +841,8 @@ export function mergeNodeTemplates({
|
|||
nodeTemplate[key].display_name
|
||||
? nodeTemplate[key].display_name
|
||||
: nodeTemplate[key].name
|
||||
? toTitleCase(nodeTemplate[key].name)
|
||||
: toTitleCase(key);
|
||||
? toTitleCase(nodeTemplate[key].name)
|
||||
: toTitleCase(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -854,7 +853,7 @@ function isHandleConnected(
|
|||
edges: Edge[],
|
||||
key: string,
|
||||
field: TemplateVariableType,
|
||||
nodeId: string,
|
||||
nodeId: string
|
||||
) {
|
||||
/*
|
||||
this function receives a flow and a handleId and check if there is a connection with this handle
|
||||
|
|
@ -870,7 +869,7 @@ function isHandleConnected(
|
|||
id: nodeId,
|
||||
proxy: { id: field.proxy!.id, field: field.proxy!.field },
|
||||
inputTypes: field.input_types,
|
||||
} as targetHandleType),
|
||||
} as targetHandleType)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
|
|
@ -885,7 +884,7 @@ function isHandleConnected(
|
|||
fieldName: key,
|
||||
id: nodeId,
|
||||
inputTypes: field.input_types,
|
||||
} as targetHandleType),
|
||||
} as targetHandleType)
|
||||
)
|
||||
) {
|
||||
return true;
|
||||
|
|
@ -908,7 +907,7 @@ export function generateNodeTemplate(Flow: FlowType) {
|
|||
|
||||
export function generateNodeFromFlow(
|
||||
flow: FlowType,
|
||||
getNodeId: (type: string) => string,
|
||||
getNodeId: (type: string) => string
|
||||
): NodeType {
|
||||
const { nodes } = flow.data!;
|
||||
const outputNode = cloneDeep(findLastNode(flow.data!));
|
||||
|
|
@ -939,7 +938,7 @@ export function generateNodeFromFlow(
|
|||
export function connectedInputNodesOnHandle(
|
||||
nodeId: string,
|
||||
handleId: string,
|
||||
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] },
|
||||
{ nodes, edges }: { nodes: NodeType[]; edges: Edge[] }
|
||||
) {
|
||||
const connectedNodes: Array<{ name: string; id: string; isGroup: boolean }> =
|
||||
[];
|
||||
|
|
@ -976,7 +975,7 @@ export function connectedInputNodesOnHandle(
|
|||
|
||||
export function updateProxyIdsOnTemplate(
|
||||
template: APITemplateType,
|
||||
idsMap: { [key: string]: string },
|
||||
idsMap: { [key: string]: string }
|
||||
) {
|
||||
Object.keys(template).forEach((key) => {
|
||||
if (template[key].proxy && idsMap[template[key].proxy!.id]) {
|
||||
|
|
@ -987,7 +986,7 @@ export function updateProxyIdsOnTemplate(
|
|||
|
||||
export function updateEdgesIds(
|
||||
edges: Edge[],
|
||||
idsMap: { [key: string]: string },
|
||||
idsMap: { [key: string]: string }
|
||||
) {
|
||||
edges.forEach((edge) => {
|
||||
let targetHandle: targetHandleType = edge.data.targetHandle;
|
||||
|
|
@ -1014,7 +1013,7 @@ export function expandGroupNode(
|
|||
nodes: Node[],
|
||||
edges: Edge[],
|
||||
setNodes: (update: Node[] | ((oldState: Node[]) => Node[])) => void,
|
||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void,
|
||||
setEdges: (update: Edge[] | ((oldState: Edge[]) => Edge[])) => void
|
||||
) {
|
||||
const idsMap = updateIds(flow!.data!);
|
||||
updateProxyIdsOnTemplate(template, idsMap);
|
||||
|
|
@ -1057,7 +1056,7 @@ export function expandGroupNode(
|
|||
const lastNode = cloneDeep(findLastNode(flow!.data!));
|
||||
newEdge.source = lastNode!.id;
|
||||
let newSourceHandle: sourceHandleType = scapeJSONParse(
|
||||
newEdge.sourceHandle!,
|
||||
newEdge.sourceHandle!
|
||||
);
|
||||
newSourceHandle.id = lastNode!.id;
|
||||
newEdge.data.sourceHandle = newSourceHandle;
|
||||
|
|
@ -1114,7 +1113,7 @@ export function expandGroupNode(
|
|||
|
||||
export function getGroupStatus(
|
||||
flow: FlowType,
|
||||
ssData: { [key: string]: { valid: boolean; params: string } },
|
||||
ssData: { [key: string]: { valid: boolean; params: string } }
|
||||
) {
|
||||
let status = { valid: true, params: SUCCESS_BUILD };
|
||||
const { nodes } = flow.data!;
|
||||
|
|
@ -1133,7 +1132,7 @@ export function getGroupStatus(
|
|||
|
||||
export function createFlowComponent(
|
||||
nodeData: NodeDataType,
|
||||
version: string,
|
||||
version: string
|
||||
): FlowType {
|
||||
const flowNode: FlowType = {
|
||||
data: {
|
||||
|
|
@ -1169,7 +1168,7 @@ export function downloadNode(NodeFLow: FlowType) {
|
|||
|
||||
export function updateComponentNameAndType(
|
||||
data: any,
|
||||
component: NodeDataType,
|
||||
component: NodeDataType
|
||||
) {}
|
||||
|
||||
export function removeFileNameFromComponents(flow: FlowType) {
|
||||
|
|
@ -1243,7 +1242,7 @@ export function extractFieldsFromComponenents(data: APIObjectType) {
|
|||
export function downloadFlow(
|
||||
flow: FlowType,
|
||||
flowName: string,
|
||||
flowDescription?: string,
|
||||
flowDescription?: string
|
||||
) {
|
||||
let clonedFlow = cloneDeep(flow);
|
||||
removeFileNameFromComponents(clonedFlow);
|
||||
|
|
@ -1253,7 +1252,7 @@ export function downloadFlow(
|
|||
...clonedFlow,
|
||||
name: flowName,
|
||||
description: flowDescription,
|
||||
}),
|
||||
})
|
||||
)}`;
|
||||
|
||||
// create a link element and set its properties
|
||||
|
|
@ -1268,7 +1267,7 @@ export function downloadFlow(
|
|||
export function downloadFlows() {
|
||||
downloadFlowsFromDatabase().then((flows) => {
|
||||
const jsonString = `data:text/json;chatset=utf-8,${encodeURIComponent(
|
||||
JSON.stringify(flows),
|
||||
JSON.stringify(flows)
|
||||
)}`;
|
||||
|
||||
// create a link element and set its properties
|
||||
|
|
@ -1292,7 +1291,7 @@ export function getRandomDescription(): string {
|
|||
export const createNewFlow = (
|
||||
flowData: ReactFlowJsonObject,
|
||||
flow: FlowType,
|
||||
folderId: string,
|
||||
folderId: string
|
||||
) => {
|
||||
return {
|
||||
description: flow?.description ?? getRandomDescription(),
|
||||
|
|
|
|||
|
|
@ -149,6 +149,8 @@ import {
|
|||
X,
|
||||
XCircle,
|
||||
Zap,
|
||||
PlaySquare,
|
||||
Wrench,
|
||||
} from "lucide-react";
|
||||
import { FaApple, FaDiscord, FaGithub } from "react-icons/fa";
|
||||
import { AWSIcon } from "../icons/AWS";
|
||||
|
|
@ -533,11 +535,12 @@ export const nodeIconsLucide: iconsType = {
|
|||
Command,
|
||||
ArrowBigUp,
|
||||
Dot,
|
||||
RotateCcw,
|
||||
Wrench,
|
||||
FolderPlusIcon,
|
||||
FolderIcon,
|
||||
Discord: FaDiscord,
|
||||
PaperclipIcon,
|
||||
RotateCcw,
|
||||
Settings,
|
||||
Streamlit,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
import { NodeType } from "../types/flow";
|
||||
import { FlowState } from "../types/tabs";
|
||||
import TableAutoCellRender from "../components/tableComponent/components/tableAutoCellRender";
|
||||
import { MODAL_CLASSES } from "../constants/constants";
|
||||
|
||||
export function classNames(...classes: Array<string>): string {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
|
|
@ -406,3 +407,8 @@ export function extractColumnsFromRows(
|
|||
|
||||
return Object.values(columnsKeys);
|
||||
}
|
||||
|
||||
export function isThereModal(): boolean {
|
||||
const modal = document.body.getElementsByClassName(MODAL_CLASSES);
|
||||
return modal.length > 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue