Feat: Add option to replace saved component or create a new one using confirmation modal
This commit is contained in:
parent
037f7f5b1b
commit
7e4dd16881
15 changed files with 659 additions and 556 deletions
|
|
@ -22,7 +22,10 @@ import PromptAreaComponent from "../../../../components/promptComponent";
|
|||
import TextAreaComponent from "../../../../components/textAreaComponent";
|
||||
import ToggleShadComponent from "../../../../components/toggleShadComponent";
|
||||
import { Button } from "../../../../components/ui/button";
|
||||
import { LANGFLOW_SUPPORTED_TYPES, TOOLTIP_EMPTY } from "../../../../constants/constants";
|
||||
import {
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
TOOLTIP_EMPTY,
|
||||
} from "../../../../constants/constants";
|
||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import { ParameterComponentType } from "../../../../types/components";
|
||||
|
|
@ -222,9 +225,7 @@ export default function ParameterComponent({
|
|||
}, [tooltipTitle, flow]);
|
||||
|
||||
return !showNode ? (
|
||||
left &&
|
||||
(LANGFLOW_SUPPORTED_TYPES.has(type??"")) &&
|
||||
!optionalHandle ? (
|
||||
left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
|
||||
|
|
@ -297,9 +298,7 @@ export default function ParameterComponent({
|
|||
)}
|
||||
</div>
|
||||
</div>
|
||||
{left &&
|
||||
(LANGFLOW_SUPPORTED_TYPES.has(type??"")) &&
|
||||
!optionalHandle ? (
|
||||
{left && LANGFLOW_SUPPORTED_TYPES.has(type ?? "") && !optionalHandle ? (
|
||||
<></>
|
||||
) : (
|
||||
<Button className="h-7 truncate bg-muted p-0 text-sm font-normal text-black hover:bg-muted">
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -664,5 +664,14 @@ export const LAST_USED_SPAN_1 = "The last time this key was used.";
|
|||
export const LAST_USED_SPAN_2 =
|
||||
"Accurate to within the hour from the most recent usage.";
|
||||
|
||||
export const LANGFLOW_SUPPORTED_TYPES= new Set(["str","bool","float","code",
|
||||
"prompt","file","int","dict","NestedDict"])
|
||||
export const LANGFLOW_SUPPORTED_TYPES = new Set([
|
||||
"str",
|
||||
"bool",
|
||||
"float",
|
||||
"code",
|
||||
"prompt",
|
||||
"file",
|
||||
"int",
|
||||
"dict",
|
||||
"NestedDict",
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -56,7 +56,11 @@ const FlowsContextInitialValue: FlowsContextType = {
|
|||
isLoading: true,
|
||||
flows: [],
|
||||
removeFlow: (id: string) => {},
|
||||
addFlow: async (newProject: boolean, flowData?: FlowType) => "",
|
||||
addFlow: async (
|
||||
newProject: boolean,
|
||||
flowData?: FlowType,
|
||||
override?: boolean
|
||||
) => "",
|
||||
updateFlow: (newFlow: FlowType) => {},
|
||||
incrementNodeId: () => uid(),
|
||||
downloadFlow: (flow: FlowType) => {},
|
||||
|
|
@ -78,7 +82,7 @@ const FlowsContextInitialValue: FlowsContextType = {
|
|||
selection: { nodes: any; edges: any },
|
||||
position: { x: number; y: number; paneX?: number; paneY?: number }
|
||||
) => {},
|
||||
saveComponent: async (component: NodeDataType) => "",
|
||||
saveComponent: async (component: NodeDataType, override: boolean) => "",
|
||||
deleteComponent: (key: string) => {},
|
||||
version: "",
|
||||
};
|
||||
|
|
@ -496,7 +500,8 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
const addFlow = async (
|
||||
newProject: Boolean,
|
||||
flow?: FlowType
|
||||
flow?: FlowType,
|
||||
override?: boolean
|
||||
): Promise<String | undefined> => {
|
||||
if (newProject) {
|
||||
let flowData = flow
|
||||
|
|
@ -505,6 +510,15 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
|
||||
// Create a new flow with a default name if no flow is provided.
|
||||
|
||||
if (override) {
|
||||
deleteComponent(flow!.name);
|
||||
const newFlow = createNewFlow(flowData, flow!);
|
||||
const { id } = await saveFlowToDatabase(newFlow);
|
||||
newFlow.id = id;
|
||||
addFlowToLocalState(newFlow);
|
||||
return;
|
||||
}
|
||||
|
||||
const newFlow = createNewFlow(flowData, flow!);
|
||||
|
||||
const newName = addVersionToDuplicates(newFlow, flows);
|
||||
|
|
@ -642,9 +656,9 @@ export function FlowsProvider({ children }: { children: ReactNode }) {
|
|||
}
|
||||
}
|
||||
|
||||
function saveComponent(component: NodeDataType) {
|
||||
function saveComponent(component: NodeDataType, override: boolean) {
|
||||
component.node!.official = false;
|
||||
return addFlow(true, createFlowComponent(component, version));
|
||||
return addFlow(true, createFlowComponent(component, version), override);
|
||||
}
|
||||
|
||||
function deleteComponent(key: string) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import {
|
|||
// 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 {
|
||||
EXPORT_CODE_DIALOG,
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
} from "../../constants/constants";
|
||||
import { AuthContext } from "../../contexts/authContext";
|
||||
import { FlowsContext } from "../../contexts/flowsContext";
|
||||
import { TemplateVariableType } from "../../types/api";
|
||||
|
|
@ -99,7 +102,9 @@ const ApiModal = forwardRef(
|
|||
(templateField) =>
|
||||
templateField.charAt(0) !== "_" &&
|
||||
node.data.node.template[templateField].show &&
|
||||
(LANGFLOW_SUPPORTED_TYPES.has(node.data.node.template[templateField].type))
|
||||
LANGFLOW_SUPPORTED_TYPES.has(
|
||||
node.data.node.template[templateField].type
|
||||
)
|
||||
)
|
||||
.map((n, i) => {
|
||||
arrNodesWithValues.push(node["id"]);
|
||||
|
|
|
|||
|
|
@ -36,10 +36,15 @@ function ConfirmationModal({
|
|||
size,
|
||||
open,
|
||||
onClose,
|
||||
onCancel,
|
||||
}: ConfirmationModalType) {
|
||||
const Icon: any = nodeIconsLucide[icon];
|
||||
const [modalOpen, setModalOpen] = useState(open ?? false);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) setModalOpen(open);
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (onClose) onClose!(modalOpen);
|
||||
}, [modalOpen]);
|
||||
|
|
@ -86,6 +91,7 @@ function ConfirmationModal({
|
|||
className="mt-5"
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
if (onCancel) onCancel();
|
||||
setModalOpen(false);
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,10 @@ import {
|
|||
TableHeader,
|
||||
TableRow,
|
||||
} from "../../components/ui/table";
|
||||
import { LANGFLOW_SUPPORTED_TYPES, limitScrollFieldsModal } from "../../constants/constants";
|
||||
import {
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
limitScrollFieldsModal,
|
||||
} from "../../constants/constants";
|
||||
import { FlowsContext } from "../../contexts/flowsContext";
|
||||
import { typesContext } from "../../contexts/typesContext";
|
||||
import { NodeDataType } from "../../types/flow";
|
||||
|
|
@ -145,7 +148,9 @@ const EditNodeModal = forwardRef(
|
|||
(templateParam) =>
|
||||
templateParam.charAt(0) !== "_" &&
|
||||
myData.current.node?.template[templateParam].show &&
|
||||
(LANGFLOW_SUPPORTED_TYPES.has(myData.current.node.template[templateParam].type))
|
||||
LANGFLOW_SUPPORTED_TYPES.has(
|
||||
myData.current.node.template[templateParam].type
|
||||
)
|
||||
)
|
||||
.map((templateParam, index) => (
|
||||
<TableRow key={index} className="h-10">
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Button } from "../../components/ui/button";
|
|||
import { Checkbox } from "../../components/ui/checkbox";
|
||||
import { alertContext } from "../../contexts/alertContext";
|
||||
import { FlowsContext } from "../../contexts/flowsContext";
|
||||
import { StoreContext } from "../../contexts/storeContext";
|
||||
import {
|
||||
getStoreComponents,
|
||||
getStoreTags,
|
||||
|
|
@ -15,7 +16,6 @@ import { FlowType } from "../../types/flow";
|
|||
import { removeApiKeys } from "../../utils/reactflowUtils";
|
||||
import { getTagsIds } from "../../utils/storeUtils";
|
||||
import BaseModal from "../baseModal";
|
||||
import { StoreContext } from "../../contexts/storeContext";
|
||||
|
||||
export default function ShareModal({
|
||||
component,
|
||||
|
|
@ -23,7 +23,7 @@ export default function ShareModal({
|
|||
children,
|
||||
open,
|
||||
setOpen,
|
||||
disabled
|
||||
disabled,
|
||||
}: {
|
||||
children?: ReactNode;
|
||||
is_component: boolean;
|
||||
|
|
@ -33,7 +33,7 @@ export default function ShareModal({
|
|||
disabled?: boolean;
|
||||
}): JSX.Element {
|
||||
const { version, addFlow } = useContext(FlowsContext);
|
||||
const {hasApiKey} = useContext(StoreContext)
|
||||
const { hasApiKey } = useContext(StoreContext);
|
||||
const { setSuccessData, setErrorData } = useContext(alertContext);
|
||||
const [checked, setChecked] = useState(true);
|
||||
const [name, setName] = useState(component?.name ?? "");
|
||||
|
|
@ -50,12 +50,12 @@ export default function ShareModal({
|
|||
|
||||
useEffect(() => {
|
||||
if (open || internalOpen) {
|
||||
if(hasApiKey){
|
||||
if (hasApiKey) {
|
||||
handleGetTags();
|
||||
handleGetNames();
|
||||
}
|
||||
}
|
||||
}, [open, internalOpen,hasApiKey]);
|
||||
}, [open, internalOpen, hasApiKey]);
|
||||
|
||||
function handleGetTags() {
|
||||
setLoadingTags(true);
|
||||
|
|
|
|||
|
|
@ -324,7 +324,7 @@ export default function Page({
|
|||
// Calculate the position where the node should be created
|
||||
const position = reactFlowInstance!.screenToFlowPosition({
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
y: event.clientY,
|
||||
});
|
||||
|
||||
// Generate a unique node ID
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { Input } from "../../../../components/ui/input";
|
|||
import { Separator } from "../../../../components/ui/separator";
|
||||
import { alertContext } from "../../../../contexts/alertContext";
|
||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
import { typesContext } from "../../../../contexts/typesContext";
|
||||
import ApiModal from "../../../../modals/ApiModal";
|
||||
import ExportModal from "../../../../modals/exportModal";
|
||||
|
|
@ -23,14 +24,13 @@ import {
|
|||
} from "../../../../utils/utils";
|
||||
import DisclosureComponent from "../DisclosureComponent";
|
||||
import SidebarDraggableComponent from "./sideBarDraggableComponent";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
|
||||
export default function ExtraSidebar(): JSX.Element {
|
||||
const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } =
|
||||
useContext(typesContext);
|
||||
const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } =
|
||||
useContext(FlowsContext);
|
||||
const {hasApiKey} = useContext(StoreContext)
|
||||
const { hasApiKey } = useContext(StoreContext);
|
||||
const { setErrorData } = useContext(alertContext);
|
||||
const [dataFilter, setFilterData] = useState(data);
|
||||
const [search, setSearch] = useState("");
|
||||
|
|
@ -50,8 +50,6 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
event.dataTransfer.setData("nodedata", JSON.stringify(data));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Handle showing components after use search input
|
||||
function handleSearchInput(e: string) {
|
||||
if (e === "") {
|
||||
|
|
@ -199,7 +197,10 @@ export default function ExtraSidebar(): JSX.Element {
|
|||
() => (
|
||||
<ExportModal>
|
||||
<ShadTooltip content="Export" side="top">
|
||||
<button disabled={!hasApiKey} className={classNames("extra-side-bar-buttons")}>
|
||||
<button
|
||||
disabled={!hasApiKey}
|
||||
className={classNames("extra-side-bar-buttons")}
|
||||
>
|
||||
<IconComponent name="FileDown" className="side-bar-button-size" />
|
||||
</button>
|
||||
</ShadTooltip>
|
||||
|
|
|
|||
|
|
@ -93,12 +93,11 @@ export default function SidebarDraggableComponent({
|
|||
>
|
||||
<span className="side-bar-components-text">{display_name}</span>
|
||||
<div>
|
||||
<IconComponent
|
||||
name="Menu"
|
||||
className="side-bar-components-icon "
|
||||
/>
|
||||
<SelectTrigger>
|
||||
</SelectTrigger>
|
||||
<IconComponent
|
||||
name="Menu"
|
||||
className="side-bar-components-icon "
|
||||
/>
|
||||
<SelectTrigger></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={"download"}>
|
||||
<div className="flex">
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import {
|
|||
SelectTrigger,
|
||||
} from "../../../../components/ui/select-custom";
|
||||
import { FlowsContext } from "../../../../contexts/flowsContext";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
import ConfirmationModal from "../../../../modals/ConfirmationModal";
|
||||
import EditNodeModal from "../../../../modals/EditNodeModal";
|
||||
import ShareModal from "../../../../modals/shareModal";
|
||||
import { nodeToolbarPropsType } from "../../../../types/components";
|
||||
|
|
@ -21,7 +23,6 @@ import {
|
|||
updateFlowPosition,
|
||||
} from "../../../../utils/reactflowUtils";
|
||||
import { classNames } from "../../../../utils/utils";
|
||||
import { StoreContext } from "../../../../contexts/storeContext";
|
||||
|
||||
export default function NodeToolbarComponent({
|
||||
data,
|
||||
|
|
@ -51,7 +52,7 @@ export default function NodeToolbarComponent({
|
|||
);
|
||||
const updateNodeInternals = useUpdateNodeInternals();
|
||||
const { getNodeId } = useContext(FlowsContext);
|
||||
const {hasApiKey} = useContext(StoreContext)
|
||||
const { hasApiKey } = useContext(StoreContext);
|
||||
|
||||
function canMinimize() {
|
||||
let countHandles: number = 0;
|
||||
|
|
@ -64,11 +65,12 @@ export default function NodeToolbarComponent({
|
|||
const isMinimal = canMinimize();
|
||||
const isGroup = data.node?.flow ? true : false;
|
||||
|
||||
const { paste, saveComponent, version } = useContext(FlowsContext);
|
||||
const { paste, saveComponent, version, flows } = useContext(FlowsContext);
|
||||
const reactFlowInstance = useReactFlow();
|
||||
const [showModalAdvanced, setShowModalAdvanced] = useState(false);
|
||||
const [showconfirmShare, setShowconfirmShare] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState("");
|
||||
const [showOverrideModal, setShowOverrideModal] = useState(false);
|
||||
|
||||
const [flowComponent, setFlowComponent] = useState<FlowType>();
|
||||
|
||||
|
|
@ -76,6 +78,11 @@ export default function NodeToolbarComponent({
|
|||
setFlowComponent(createFlowComponent(cloneDeep(data), version));
|
||||
}, [data]);
|
||||
|
||||
function onOverrideModalConfirmation(override: boolean): void {
|
||||
if (override) saveComponent(cloneDeep(data), true);
|
||||
saveComponent(cloneDeep(data), false);
|
||||
}
|
||||
|
||||
const handleSelectChange = (event) => {
|
||||
switch (event) {
|
||||
case "advanced":
|
||||
|
|
@ -89,10 +96,10 @@ export default function NodeToolbarComponent({
|
|||
downloadNode(createFlowComponent(cloneDeep(data), version));
|
||||
break;
|
||||
case "Share":
|
||||
if(hasApiKey) setShowconfirmShare(true);
|
||||
if (hasApiKey) setShowconfirmShare(true);
|
||||
break;
|
||||
case "SaveAll":
|
||||
saveComponent(cloneDeep(data));
|
||||
saveComponent(cloneDeep(data), false);
|
||||
break;
|
||||
case "disabled":
|
||||
break;
|
||||
|
|
@ -100,9 +107,16 @@ export default function NodeToolbarComponent({
|
|||
updateFlowPosition(position, data.node?.flow!);
|
||||
expandGroupNode(data, reactFlowInstance, getNodeId);
|
||||
break;
|
||||
case "override":
|
||||
setShowOverrideModal(true);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const isSaved = flows.some((flow) =>
|
||||
Object.values(flow).includes(data.node?.display_name!)
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-26 h-10">
|
||||
|
|
@ -204,24 +218,38 @@ export default function NodeToolbarComponent({
|
|||
</SelectItem>
|
||||
)}
|
||||
|
||||
<SelectItem value={"SaveAll"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="SaveAll"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Save{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
{hasApiKey && <SelectItem value={"Share"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Share2"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Share{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>}
|
||||
{isSaved ? (
|
||||
<SelectItem value={"override"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="SaveAll"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Save{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
) : (
|
||||
<SelectItem value={"SaveAll"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="SaveAll"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Save{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
)}
|
||||
{hasApiKey && (
|
||||
<SelectItem value={"Share"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
name="Share2"
|
||||
className="relative top-0.5 mr-2 h-4 w-4"
|
||||
/>{" "}
|
||||
Share{" "}
|
||||
</div>{" "}
|
||||
</SelectItem>
|
||||
)}
|
||||
<SelectItem value={"Download"}>
|
||||
<div className="flex">
|
||||
<IconComponent
|
||||
|
|
@ -255,6 +283,32 @@ export default function NodeToolbarComponent({
|
|||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<ConfirmationModal
|
||||
asChild
|
||||
open={showOverrideModal}
|
||||
title={`Replace ${data.node?.display_name}`}
|
||||
titleHeader={`Please, confirm your save actions`}
|
||||
modalContentTitle="Attention!"
|
||||
cancelText="New"
|
||||
confirmationText="Replace"
|
||||
icon={"SaveAll"}
|
||||
index={6}
|
||||
onConfirm={(index, user) => {
|
||||
saveComponent(cloneDeep(data), true);
|
||||
}}
|
||||
onClose={setShowOverrideModal}
|
||||
onCancel={() => saveComponent(cloneDeep(data), false)}
|
||||
>
|
||||
<ConfirmationModal.Content>
|
||||
<span>
|
||||
It seems {data.node?.display_name} already exists. Replacing it
|
||||
will switch the current component. Proceed with replacement?
|
||||
</span>
|
||||
</ConfirmationModal.Content>
|
||||
<ConfirmationModal.Trigger>
|
||||
</ConfirmationModal.Trigger>
|
||||
</ConfirmationModal>
|
||||
<EditNodeModal
|
||||
data={data}
|
||||
setData={setData}
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ export type PaginatorComponentType = {
|
|||
};
|
||||
|
||||
export type ConfirmationModalType = {
|
||||
onCancel?: () => void;
|
||||
title: string;
|
||||
titleHeader: string;
|
||||
asChild?: boolean;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ export type FlowsContextType = {
|
|||
removeFlow: (id: string) => void;
|
||||
addFlow: (
|
||||
newProject: boolean,
|
||||
flow?: FlowType
|
||||
flow?: FlowType,
|
||||
override?: boolean
|
||||
) => Promise<String | undefined>;
|
||||
updateFlow: (newFlow: FlowType) => void;
|
||||
incrementNodeId: () => string;
|
||||
|
|
@ -44,7 +45,10 @@ export type FlowsContextType = {
|
|||
setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void;
|
||||
setTweak: (tweak: tweakType) => tweakType | void;
|
||||
getTweak: tweakType;
|
||||
saveComponent: (component: NodeDataType) => Promise<String | undefined>;
|
||||
saveComponent: (
|
||||
component: NodeDataType,
|
||||
override: boolean
|
||||
) => Promise<String | undefined>;
|
||||
deleteComponent: (key: string) => void;
|
||||
version: string;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,10 @@ import {
|
|||
XYPosition,
|
||||
} from "reactflow";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import { LANGFLOW_SUPPORTED_TYPES, specialCharsRegex } from "../constants/constants";
|
||||
import {
|
||||
LANGFLOW_SUPPORTED_TYPES,
|
||||
specialCharsRegex,
|
||||
} from "../constants/constants";
|
||||
import { APITemplateType, TemplateVariableType } from "../types/api";
|
||||
import {
|
||||
FlowType,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue