feat: changed tweaks into input schema and enabled persistence (#8235)
* Fixed layground and share font weight * Fixed namings and deleted unused api files * Deleted unused code * Changed tweaks to input schema * Changed TableAdvancedToggleCellRender to change Tweaks instead of Nodes if in tweaks page * Removed Hide Visibility to show tweaks' Enable Input * Set advanced of all nodes as true in tweaks * Changed column defs to change width and pass isTweaks to advanced * Changed tweaks store to identify changes in the Advanced field instead of the Values field * Changed to not change quantity of hooks * Changed table node cell render to not change the value of the tweaks * Changed to disable inputs * Added disabled behavior to table node cell render * Added cursor default to cell classes * Updated tweaks test * Added persistence in session * Removed initial setup from api modal to enable persistence * Added persistence to storage * Changed test to test persistence of tweaks * Re-added initial setup to api modal * Fixed size of tweaks modal
This commit is contained in:
parent
c13d3eb21b
commit
d88abdd658
25 changed files with 319 additions and 957 deletions
|
|
@ -4,7 +4,7 @@ import Markdown from "react-markdown";
|
|||
import rehypeMathjax from "rehype-mathjax";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import ForwardedIconComponent from "../../common/genericIconComponent";
|
||||
import SimplifiedCodeTabComponent from "../codeTabsComponent/ChatCodeTabComponent";
|
||||
import SimplifiedCodeTabComponent from "../codeTabsComponent";
|
||||
import DurationDisplay from "./DurationDisplay";
|
||||
|
||||
export default function ContentDisplay({
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
import { useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { tomorrow } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import IconComponent from "../../common/genericIconComponent";
|
||||
import { Button } from "../../ui/button";
|
||||
|
||||
type SimplifiedCodeTabProps = {
|
||||
code: string;
|
||||
language: string;
|
||||
};
|
||||
|
||||
export default function SimplifiedCodeTabComponent({
|
||||
code,
|
||||
language,
|
||||
}: SimplifiedCodeTabProps) {
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
setIsCopied(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="mt-2 flex w-full flex-col overflow-hidden rounded-md text-left dark"
|
||||
data-testid="chat-code-tab"
|
||||
>
|
||||
<div className="flex w-full items-center justify-between rounded-t-md border border-b-0 border-border bg-muted px-4 py-2">
|
||||
<span className="dar text-sm font-semibold text-white">{language}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground hover:bg-card"
|
||||
data-testid="copy-code-button"
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
{isCopied ? (
|
||||
<IconComponent name="Check" className="h-4 w-4" />
|
||||
) : (
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
language={language.toLowerCase()}
|
||||
style={tomorrow}
|
||||
className="!mt-0 h-full w-full overflow-scroll !rounded-b-md !rounded-t-none border border-border text-left !custom-scroll"
|
||||
>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -37,7 +37,6 @@ export function TweakComponent({
|
|||
<EditNodeComponent
|
||||
open={open}
|
||||
autoHeight
|
||||
hideVisibility
|
||||
nodeClass={nodeClass}
|
||||
isTweaks
|
||||
nodeId={node.data.id}
|
||||
|
|
|
|||
|
|
@ -1,36 +1,26 @@
|
|||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import { useTweaksStore } from "@/stores/tweaksStore";
|
||||
import { useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import { useDarkStore } from "../../../stores/darkStore";
|
||||
import { codeTabsPropsType } from "../../../types/components";
|
||||
import { tomorrow } from "react-syntax-highlighter/dist/cjs/styles/prism";
|
||||
import IconComponent from "../../common/genericIconComponent";
|
||||
import { Button } from "../../ui/button";
|
||||
import { Label } from "../../ui/label";
|
||||
import { Switch } from "../../ui/switch";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../ui/tabs";
|
||||
import { TweaksComponent } from "./components/tweaksComponent";
|
||||
|
||||
export default function CodeTabsComponent({
|
||||
open,
|
||||
tabs,
|
||||
activeTab,
|
||||
setActiveTab,
|
||||
isMessage,
|
||||
setActiveTweaks,
|
||||
activeTweaks,
|
||||
}: codeTabsPropsType) {
|
||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||
const dark = useDarkStore((state) => state.dark);
|
||||
const nodes = useTweaksStore((state) => state.nodes);
|
||||
type SimplifiedCodeTabProps = {
|
||||
code: string;
|
||||
language: string;
|
||||
};
|
||||
|
||||
export default function SimplifiedCodeTabComponent({
|
||||
code,
|
||||
language,
|
||||
}: SimplifiedCodeTabProps) {
|
||||
const [isCopied, setIsCopied] = useState<boolean>(false);
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(tabs[activeTab].code).then(() => {
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
setIsCopied(true);
|
||||
|
||||
setTimeout(() => {
|
||||
|
|
@ -40,121 +30,33 @@ export default function CodeTabsComponent({
|
|||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
className={
|
||||
"api-modal-tabs inset-0 m-0 " +
|
||||
(isMessage ? "dark" : "") +
|
||||
(dark && isMessage ? "bg-background" : "")
|
||||
}
|
||||
onValueChange={(value) => {
|
||||
setActiveTab(value);
|
||||
}}
|
||||
<div
|
||||
className="mt-2 flex w-full flex-col overflow-hidden rounded-md text-left dark"
|
||||
data-testid="chat-code-tab"
|
||||
>
|
||||
<div className="api-modal-tablist-div">
|
||||
{tabs.length > 0 && tabs[0].name !== "" ? (
|
||||
<TabsList className="mt-[-1px] border-b">
|
||||
{tabs.map((tab, index) => (
|
||||
<TabsTrigger
|
||||
className={
|
||||
isMessage ? "data-[state=active]:bg-primary-foreground" : ""
|
||||
}
|
||||
key={index}
|
||||
value={index.toString()}
|
||||
>
|
||||
{tab.name}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
) : (
|
||||
<div></div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{tabs.map((tab, idx) => (
|
||||
<TabsContent
|
||||
value={idx.toString()}
|
||||
className="api-modal-tabs-content overflow-hidden dark"
|
||||
key={idx} // Remember to add a unique key prop
|
||||
<div className="flex w-full items-center justify-between rounded-t-md border border-b-0 border-border bg-muted px-4 py-2">
|
||||
<span className="dar text-sm font-semibold text-white">{language}</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="text-muted-foreground hover:bg-card"
|
||||
data-testid="copy-code-button"
|
||||
onClick={copyToClipboard}
|
||||
>
|
||||
{tab.name.toLowerCase() !== "tweaks" ? (
|
||||
<div className="mt-2 flex h-full w-full flex-col">
|
||||
{tab.description && (
|
||||
<div
|
||||
className="mb-2 w-full text-left text-sm"
|
||||
dangerouslySetInnerHTML={{ __html: tab.description }}
|
||||
></div>
|
||||
)}
|
||||
<div className="flex w-full items-center justify-end gap-4 rounded-t-md border border-border bg-muted px-4 py-2">
|
||||
{nodes.length > 0 &&
|
||||
tabs.find((tab) => tab.name.toLowerCase() === "tweaks") &&
|
||||
tabs[activeTab].hasTweaks && (
|
||||
<div className="flex items-center gap-2">
|
||||
<ShadTooltip content="Makes temporary adjustments managed in 'Tweaks'">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label
|
||||
className={"text-xs font-medium text-white"}
|
||||
htmlFor="tweaks-switch"
|
||||
>
|
||||
Enable Tweaks
|
||||
</Label>
|
||||
<IconComponent
|
||||
name="info"
|
||||
className="h-3.5 w-3.5 text-placeholder-foreground"
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
<Switch
|
||||
style={{
|
||||
transform: `scaleX(${0.85}) scaleY(${0.85})`,
|
||||
}}
|
||||
id="tweaks-switch"
|
||||
onCheckedChange={setActiveTweaks}
|
||||
checked={activeTweaks}
|
||||
autoFocus={false}
|
||||
/>
|
||||
<span className="text-lg text-accent">|</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tabs[activeTab].name.toLowerCase !== "tweaks" && (
|
||||
<>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={copyToClipboard}
|
||||
data-testid="btn-copy-code"
|
||||
>
|
||||
{isCopied ? (
|
||||
<IconComponent
|
||||
name="Check"
|
||||
className="h-4 w-4 text-muted-foreground"
|
||||
/>
|
||||
) : (
|
||||
<IconComponent
|
||||
name="Copy"
|
||||
className="h-4 w-4 text-muted-foreground"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
language={tab.language}
|
||||
style={oneDark}
|
||||
className="!my-0 h-full overflow-auto rounded-sm !rounded-t-none border border-t-0 border-border text-left custom-scroll"
|
||||
>
|
||||
{tab.code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
) : tab.name.toLowerCase() === "tweaks" ? (
|
||||
<>
|
||||
<TweaksComponent open={open ?? false} />
|
||||
</>
|
||||
) : null}
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
{isCopied ? (
|
||||
<IconComponent name="Check" className="h-4 w-4" />
|
||||
) : (
|
||||
<IconComponent name="Copy" className="h-4 w-4" />
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<SyntaxHighlighter
|
||||
language={language.toLowerCase()}
|
||||
style={tomorrow}
|
||||
className="!mt-0 h-full w-full overflow-scroll !rounded-b-md !rounded-t-none border border-border text-left !custom-scroll"
|
||||
>
|
||||
{code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import { usePatchUpdateFlow } from "@/controllers/API/queries/flows/use-patch-up
|
|||
import { CustomLink } from "@/customization/components/custom-link";
|
||||
import { ENABLE_PUBLISH, ENABLE_WIDGET } from "@/customization/feature-flags";
|
||||
import { customMcpOpen } from "@/customization/utils/custom-mcp-open";
|
||||
import ApiModal from "@/modals/apiModal/new-api-modal";
|
||||
import ApiModal from "@/modals/apiModal";
|
||||
import EmbedModal from "@/modals/EmbedModal/embed-modal";
|
||||
import useAlertStore from "@/stores/alertStore";
|
||||
import useAuthStore from "@/stores/authStore";
|
||||
|
|
@ -87,7 +87,7 @@ export default function PublishDropdown() {
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="md"
|
||||
className="!px-2.5 font-medium"
|
||||
className="!px-2.5 font-normal"
|
||||
data-testid="publish-button"
|
||||
>
|
||||
Share
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value";
|
||||
import ShadTooltip from "@/components/common/shadTooltipComponent";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { useTweaksStore } from "@/stores/tweaksStore";
|
||||
import { APIClassType } from "@/types/api";
|
||||
import { isTargetHandleConnected } from "@/utils/reactflowUtils";
|
||||
import { CustomCellRendererProps } from "ag-grid-react";
|
||||
import ToggleShadComponent from "../../../toggleShadComponent";
|
||||
|
||||
export default function TableAdvancedToggleCellRender({
|
||||
value: { nodeId, parameterId },
|
||||
value: { nodeId, parameterId, isTweaks },
|
||||
}: CustomCellRendererProps) {
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
const node = useFlowStore((state) => state.getNode(nodeId));
|
||||
const node = isTweaks
|
||||
? useTweaksStore((state) => state.getNode(nodeId))
|
||||
: useFlowStore((state) => state.getNode(nodeId));
|
||||
const parameter = node?.data?.node?.template?.[parameterId];
|
||||
|
||||
const setNode = useTweaksStore((state) => state.setNode);
|
||||
|
||||
const disabled = isTargetHandleConnected(
|
||||
edges,
|
||||
parameterId,
|
||||
|
|
@ -24,6 +29,7 @@ export default function TableAdvancedToggleCellRender({
|
|||
node: node?.data.node as APIClassType,
|
||||
nodeId,
|
||||
name: parameterId,
|
||||
setNode: isTweaks ? setNode : undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
|
|
@ -31,22 +37,24 @@ export default function TableAdvancedToggleCellRender({
|
|||
<ShadTooltip
|
||||
content={
|
||||
disabled
|
||||
? "Cannot change visibility of connected handles"
|
||||
: "Change visibility of the field"
|
||||
? isTweaks
|
||||
? "Cannot enable input of connected handles"
|
||||
: "Cannot change visibility of connected handles"
|
||||
: isTweaks
|
||||
? "Toggle input of the field in the API"
|
||||
: "Change visibility of the field"
|
||||
}
|
||||
styleClasses="z-50"
|
||||
>
|
||||
<div>
|
||||
<div className="flex h-full items-center">
|
||||
<ToggleShadComponent
|
||||
disabled={disabled}
|
||||
value={!parameter.advanced}
|
||||
handleOnNewValue={handleOnNewValue}
|
||||
editNode={true}
|
||||
showToogle
|
||||
id={"show" + parameterId}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex h-full w-full items-center justify-center">
|
||||
<ToggleShadComponent
|
||||
disabled={disabled}
|
||||
value={!parameter.advanced}
|
||||
handleOnNewValue={handleOnNewValue}
|
||||
editNode={true}
|
||||
showToogle
|
||||
id={"show" + parameterId}
|
||||
/>
|
||||
</div>
|
||||
</ShadTooltip>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import { ParameterRenderComponent } from "@/components/core/parameterRenderCompo
|
|||
import { NodeInfoType } from "@/components/core/parameterRenderComponent/types";
|
||||
import useAuthStore from "@/stores/authStore";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { useTweaksStore } from "@/stores/tweaksStore";
|
||||
import { APIClassType } from "@/types/api";
|
||||
import { isTargetHandleConnected } from "@/utils/reactflowUtils";
|
||||
import { cn } from "@/utils/utils";
|
||||
import { CustomCellRendererProps } from "ag-grid-react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
|
|
@ -14,15 +14,11 @@ export default function TableNodeCellRender({
|
|||
value: { nodeId, parameterId, isTweaks },
|
||||
}: CustomCellRendererProps) {
|
||||
const edges = useFlowStore((state) => state.edges);
|
||||
const node = isTweaks
|
||||
? useTweaksStore((state) => state.getNode(nodeId))
|
||||
: useFlowStore((state) => state.getNode(nodeId));
|
||||
const node = useFlowStore((state) => state.getNode(nodeId));
|
||||
const parameter = node?.data?.node?.template?.[parameterId];
|
||||
const currentFlow = useFlowStore((state) => state.currentFlow);
|
||||
const isAuth = useAuthStore((state) => state.isAuthenticated);
|
||||
|
||||
const setNode = useTweaksStore((state) => state.setNode);
|
||||
|
||||
const disabled = isTargetHandleConnected(
|
||||
edges,
|
||||
parameterId,
|
||||
|
|
@ -34,12 +30,12 @@ export default function TableNodeCellRender({
|
|||
node: node?.data.node as APIClassType,
|
||||
nodeId,
|
||||
name: parameterId,
|
||||
setNode: isTweaks ? setNode : undefined,
|
||||
setNode: isTweaks ? () => {} : undefined,
|
||||
});
|
||||
|
||||
const { handleNodeClass } = useHandleNodeClass(
|
||||
nodeId,
|
||||
isTweaks ? setNode : undefined,
|
||||
isTweaks ? () => {} : undefined,
|
||||
);
|
||||
|
||||
const nodeInformationMetadata: NodeInfoType = useMemo(() => {
|
||||
|
|
@ -54,7 +50,12 @@ export default function TableNodeCellRender({
|
|||
|
||||
return (
|
||||
parameter && (
|
||||
<div className="group mx-auto flex h-full max-h-48 w-[300px] items-center justify-center overflow-auto px-1 py-2.5 custom-scroll">
|
||||
<div
|
||||
className={cn(
|
||||
"group mx-auto flex h-full max-h-48 w-[300px] items-center justify-center overflow-auto px-1 py-2.5 custom-scroll",
|
||||
isTweaks && "pointer-events-none opacity-70",
|
||||
)}
|
||||
>
|
||||
<ParameterRenderComponent
|
||||
nodeId={nodeId}
|
||||
handleOnNewValue={handleOnNewValue}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
import {
|
||||
getCurlRunCode,
|
||||
getCurlWebhookCode,
|
||||
} from "@/modals/apiModal/utils/get-curl-code";
|
||||
import getJsApiCode from "@/modals/apiModal/utils/get-js-api-code";
|
||||
import getPythonApiCode from "@/modals/apiModal/utils/get-python-api-code";
|
||||
import getPythonCode from "@/modals/apiModal/utils/get-python-code";
|
||||
import getWidgetCode from "@/modals/apiModal/utils/get-widget-code";
|
||||
|
||||
export function useCustomAPICode() {
|
||||
return {
|
||||
getCurlRunCode,
|
||||
getCurlWebhookCode,
|
||||
getJsApiCode,
|
||||
getPythonApiCode,
|
||||
getPythonCode,
|
||||
getWidgetCode,
|
||||
};
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import { cn } from "@/utils/utils";
|
|||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import Markdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import CodeTabsComponent from "../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent";
|
||||
import CodeTabsComponent from "../../../../../../components/core/codeTabsComponent";
|
||||
import LogoIcon from "./chat-logo-icon";
|
||||
|
||||
export const ErrorView = ({
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import Markdown from "react-markdown";
|
|||
import rehypeMathjax from "rehype-mathjax";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import CodeTabsComponent from "../../../../../../components/core/codeTabsComponent/ChatCodeTabComponent";
|
||||
import CodeTabsComponent from "../../../../../../components/core/codeTabsComponent";
|
||||
|
||||
type MarkdownFieldProps = {
|
||||
chat: any;
|
||||
|
|
|
|||
|
|
@ -1,73 +1,226 @@
|
|||
import { TweaksComponent } from "@/components/core/codeTabsComponent/components/tweaksComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { CustomAPIGenerator } from "@/customization/components/custom-api-generator";
|
||||
import { useCustomAPICode } from "@/customization/hooks/use-custom-api-code";
|
||||
import useSaveFlow from "@/hooks/flows/use-save-flow";
|
||||
import useAuthStore from "@/stores/authStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { isEndpointNameValid } from "@/utils/utils";
|
||||
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, useEffect, useState } from "react";
|
||||
import { cloneDeep } from "lodash";
|
||||
import { ChangeEvent, ReactNode, useEffect, useState } from "react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import IconComponent from "../../components/common/genericIconComponent";
|
||||
import CodeTabsComponent from "../../components/core/codeTabsComponent";
|
||||
import { EXPORT_CODE_DIALOG } from "../../constants/constants";
|
||||
import { useTweaksStore } from "../../stores/tweaksStore";
|
||||
import { FlowType } from "../../types/flow/index";
|
||||
import BaseModal from "../baseModal";
|
||||
import APITabsComponent from "./codeTabs/code-tabs";
|
||||
|
||||
const MAX_LENGTH = 20;
|
||||
const MIN_LENGTH = 1;
|
||||
|
||||
export default function ApiModal({
|
||||
flow,
|
||||
children,
|
||||
open: myOpen,
|
||||
setOpen: mySetOpen,
|
||||
}: {
|
||||
flow: FlowType;
|
||||
children: ReactNode;
|
||||
open?: boolean;
|
||||
setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void;
|
||||
}) {
|
||||
const autoLogin = useAuthStore((state) => state.autoLogin);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const [openTweaks, setOpenTweaks] = useState(false);
|
||||
const tweaks = useTweaksStore((state) => state.tweaks);
|
||||
const [open, setOpen] =
|
||||
mySetOpen !== undefined && myOpen !== undefined
|
||||
? [myOpen, mySetOpen]
|
||||
: useState(false);
|
||||
const [activeTab, setActiveTab] = useState("0");
|
||||
const activeTweaks = useTweaksStore((state) => state.activeTweaks);
|
||||
const setActiveTweaks = useTweaksStore((state) => state.setActiveTweaks);
|
||||
const tabs = useTweaksStore((state) => state.tabs);
|
||||
const initialSetup = useTweaksStore((state) => state.initialSetup);
|
||||
|
||||
const getCodes = useCustomAPICode();
|
||||
const flowEndpointName = useFlowStore(
|
||||
useShallow((state) => state.currentFlow?.endpoint_name),
|
||||
);
|
||||
|
||||
const currentFlowId = useFlowStore(
|
||||
useShallow((state) => state.currentFlow?.id),
|
||||
);
|
||||
|
||||
const [endpointName, setEndpointName] = useState(flowEndpointName ?? "");
|
||||
const [validEndpointName, setValidEndpointName] = useState(true);
|
||||
|
||||
const handleEndpointNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
// Validate the endpoint name
|
||||
// use this regex r'^[a-zA-Z0-9_-]+$'
|
||||
const isValid = isEndpointNameValid(event.target.value, MAX_LENGTH);
|
||||
setValidEndpointName(isValid);
|
||||
|
||||
// Only update if valid and meets minimum length (if set)
|
||||
if (isValid && value.length >= MIN_LENGTH) {
|
||||
setEndpointName!(value);
|
||||
} else if (value.length === 0) {
|
||||
// Always allow empty endpoint name (it's optional)
|
||||
setEndpointName!("");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) initialSetup(autoLogin ?? false, flow, getCodes);
|
||||
setActiveTab("0");
|
||||
if (open && currentFlowId) initialSetup(nodes, currentFlowId);
|
||||
}, [open]);
|
||||
|
||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||
const saveFlow = useSaveFlow();
|
||||
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
||||
|
||||
function handleSave(): void {
|
||||
const newFlow = cloneDeep(useFlowStore.getState().currentFlow);
|
||||
if (!newFlow) return;
|
||||
newFlow.endpoint_name =
|
||||
endpointName && endpointName.length > 0 ? endpointName : null;
|
||||
|
||||
if (autoSaving) {
|
||||
saveFlow(newFlow);
|
||||
} else {
|
||||
setCurrentFlow(newFlow);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!openTweaks && endpointName !== flowEndpointName) handleSave();
|
||||
else if (openTweaks) {
|
||||
setEndpointName(flowEndpointName ?? "");
|
||||
}
|
||||
}, [openTweaks]);
|
||||
|
||||
return (
|
||||
<BaseModal open={open} setOpen={setOpen} size="x-large">
|
||||
<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 overflowHidden>
|
||||
{open && (
|
||||
<>
|
||||
<CustomAPIGenerator isOpen={open} />
|
||||
<CodeTabsComponent
|
||||
open={open}
|
||||
tabs={tabs!}
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTab}
|
||||
activeTweaks={activeTweaks}
|
||||
setActiveTweaks={setActiveTweaks}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
<>
|
||||
<BaseModal
|
||||
closeButtonClassName="!top-3"
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
size="medium"
|
||||
className="pt-4"
|
||||
>
|
||||
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header
|
||||
description={
|
||||
autoLogin ? undefined : (
|
||||
<>
|
||||
<span className="pr-2">
|
||||
API access requires an API key. You can{" "}
|
||||
<a
|
||||
href="/settings/api-keys"
|
||||
className="text-accent-pink-foreground"
|
||||
>
|
||||
{" "}
|
||||
create an API key
|
||||
</a>{" "}
|
||||
in settings.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconComponent
|
||||
name="Code2"
|
||||
className="h-6 w-6 text-gray-800 dark:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="pl-2">API access</span>
|
||||
{nodes.length > 0 && (
|
||||
<div className="border-r-1 absolute right-12 flex items-center text-mmd font-medium leading-[16px]">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 px-3"
|
||||
onClick={() => setOpenTweaks(true)}
|
||||
data-testid="tweaks-button"
|
||||
>
|
||||
<IconComponent
|
||||
name="SlidersHorizontal"
|
||||
className="h-3.5 w-3.5"
|
||||
/>
|
||||
<span>Input Schema ({Object.keys(tweaks)?.length}) </span>
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="ml-2 h-8" />
|
||||
</div>
|
||||
)}
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content overflowHidden>
|
||||
{open && (
|
||||
<>
|
||||
<CustomAPIGenerator isOpen={open} />
|
||||
<APITabsComponent />
|
||||
</>
|
||||
)}
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
|
||||
<BaseModal
|
||||
open={openTweaks}
|
||||
setOpen={setOpenTweaks}
|
||||
size="medium-small-tall"
|
||||
>
|
||||
<BaseModal.Header
|
||||
description={
|
||||
autoLogin ? undefined : (
|
||||
<>
|
||||
<span className="pr-2">
|
||||
API access requires an API key. You can{" "}
|
||||
<a
|
||||
href="/settings/api-keys"
|
||||
className="text-accent-pink-foreground"
|
||||
>
|
||||
{" "}
|
||||
create an API key
|
||||
</a>{" "}
|
||||
in settings.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconComponent name="SlidersHorizontal" className="text-f h-6 w-6" />
|
||||
<span className="pl-2">Input Schema</span>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content overflowHidden className="flex flex-col gap-4">
|
||||
{true && (
|
||||
<Label>
|
||||
<div className="edit-flow-arrangement mt-2">
|
||||
<span className="shrink-0 text-mmd font-medium">
|
||||
Endpoint Name
|
||||
</span>
|
||||
{!validEndpointName && (
|
||||
<span className="edit-flow-span">
|
||||
Use only letters, numbers, hyphens, and underscores (
|
||||
{MAX_LENGTH} characters max).
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
className="nopan nodelete nodrag noflow mt-2 font-normal"
|
||||
onChange={handleEndpointNameChange}
|
||||
type="text"
|
||||
name="endpoint_name"
|
||||
value={endpointName ?? ""}
|
||||
placeholder="An alternative name to run the endpoint"
|
||||
maxLength={MAX_LENGTH}
|
||||
minLength={MIN_LENGTH}
|
||||
id="endpoint_name"
|
||||
/>
|
||||
</Label>
|
||||
)}
|
||||
<div className="h-full w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll">
|
||||
<TweaksComponent open={openTweaks} />
|
||||
</div>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,225 +0,0 @@
|
|||
import { TweaksComponent } from "@/components/core/codeTabsComponent/components/tweaksComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { CustomAPIGenerator } from "@/customization/components/custom-api-generator";
|
||||
import useSaveFlow from "@/hooks/flows/use-save-flow";
|
||||
import useAuthStore from "@/stores/authStore";
|
||||
import useFlowsManagerStore from "@/stores/flowsManagerStore";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { isEndpointNameValid } from "@/utils/utils";
|
||||
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 { cloneDeep } from "lodash";
|
||||
import { ChangeEvent, ReactNode, useEffect, useState } from "react";
|
||||
import { useShallow } from "zustand/react/shallow";
|
||||
import IconComponent from "../../components/common/genericIconComponent";
|
||||
import { useTweaksStore } from "../../stores/tweaksStore";
|
||||
import BaseModal from "../baseModal";
|
||||
import APITabsComponent from "./codeTabs/code-tabs";
|
||||
|
||||
const MAX_LENGTH = 20;
|
||||
const MIN_LENGTH = 1;
|
||||
|
||||
export default function ApiModal({
|
||||
children,
|
||||
open: myOpen,
|
||||
setOpen: mySetOpen,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
open?: boolean;
|
||||
setOpen?: (a: boolean | ((o?: boolean) => boolean)) => void;
|
||||
}) {
|
||||
const autoLogin = useAuthStore((state) => state.autoLogin);
|
||||
const nodes = useFlowStore((state) => state.nodes);
|
||||
const [openTweaks, setOpenTweaks] = useState(false);
|
||||
const tweaks = useTweaksStore((state) => state.tweaks);
|
||||
const [open, setOpen] =
|
||||
mySetOpen !== undefined && myOpen !== undefined
|
||||
? [myOpen, mySetOpen]
|
||||
: useState(false);
|
||||
const newInitialSetup = useTweaksStore((state) => state.newInitialSetup);
|
||||
|
||||
const flowEndpointName = useFlowStore(
|
||||
useShallow((state) => state.currentFlow?.endpoint_name),
|
||||
);
|
||||
|
||||
const [endpointName, setEndpointName] = useState(flowEndpointName ?? "");
|
||||
const [validEndpointName, setValidEndpointName] = useState(true);
|
||||
|
||||
const handleEndpointNameChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
// Validate the endpoint name
|
||||
// use this regex r'^[a-zA-Z0-9_-]+$'
|
||||
const isValid = isEndpointNameValid(event.target.value, MAX_LENGTH);
|
||||
setValidEndpointName(isValid);
|
||||
|
||||
// Only update if valid and meets minimum length (if set)
|
||||
if (isValid && value.length >= MIN_LENGTH) {
|
||||
setEndpointName!(value);
|
||||
} else if (value.length === 0) {
|
||||
// Always allow empty endpoint name (it's optional)
|
||||
setEndpointName!("");
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (open) newInitialSetup(nodes);
|
||||
}, [open]);
|
||||
|
||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||
const saveFlow = useSaveFlow();
|
||||
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
||||
|
||||
function handleSave(): void {
|
||||
const newFlow = cloneDeep(useFlowStore.getState().currentFlow);
|
||||
if (!newFlow) return;
|
||||
newFlow.endpoint_name =
|
||||
endpointName && endpointName.length > 0 ? endpointName : null;
|
||||
|
||||
if (autoSaving) {
|
||||
saveFlow(newFlow);
|
||||
} else {
|
||||
setCurrentFlow(newFlow);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!openTweaks && endpointName !== flowEndpointName) handleSave();
|
||||
else if (openTweaks) {
|
||||
setEndpointName(flowEndpointName ?? "");
|
||||
}
|
||||
}, [openTweaks]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseModal
|
||||
closeButtonClassName="!top-3"
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
size="medium"
|
||||
className="pt-4"
|
||||
>
|
||||
<BaseModal.Trigger asChild>{children}</BaseModal.Trigger>
|
||||
<BaseModal.Header
|
||||
description={
|
||||
autoLogin ? undefined : (
|
||||
<>
|
||||
<span className="pr-2">
|
||||
API access requires an API key. You can{" "}
|
||||
<a
|
||||
href="/settings/api-keys"
|
||||
className="text-accent-pink-foreground"
|
||||
>
|
||||
{" "}
|
||||
create an API key
|
||||
</a>{" "}
|
||||
in settings.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconComponent
|
||||
name="Code2"
|
||||
className="h-6 w-6 text-gray-800 dark:text-white"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="pl-2">API access</span>
|
||||
{nodes.length > 0 && (
|
||||
<div className="border-r-1 absolute right-12 flex items-center text-mmd font-medium leading-[16px]">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 px-3"
|
||||
onClick={() => setOpenTweaks(true)}
|
||||
data-testid="tweaks-button"
|
||||
>
|
||||
<IconComponent
|
||||
name="SlidersHorizontal"
|
||||
className="h-3.5 w-3.5"
|
||||
/>
|
||||
<span>Tweaks ({Object.keys(tweaks)?.length}) </span>
|
||||
</Button>
|
||||
<Separator orientation="vertical" className="ml-2 h-8" />
|
||||
</div>
|
||||
)}
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content overflowHidden>
|
||||
{open && (
|
||||
<>
|
||||
<CustomAPIGenerator isOpen={open} />
|
||||
<APITabsComponent />
|
||||
</>
|
||||
)}
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
|
||||
<BaseModal
|
||||
open={openTweaks}
|
||||
setOpen={setOpenTweaks}
|
||||
size="medium-small-tall"
|
||||
>
|
||||
<BaseModal.Header
|
||||
description={
|
||||
autoLogin ? undefined : (
|
||||
<>
|
||||
<span className="pr-2">
|
||||
API access requires an API key. You can{" "}
|
||||
<a
|
||||
href="/settings/api-keys"
|
||||
className="text-accent-pink-foreground"
|
||||
>
|
||||
{" "}
|
||||
create an API key
|
||||
</a>{" "}
|
||||
in settings.
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<IconComponent
|
||||
name="SlidersHorizontal"
|
||||
className="h-6 w-6 text-gray-800 dark:text-white"
|
||||
/>
|
||||
<span className="pl-2">Tweaks</span>
|
||||
</BaseModal.Header>
|
||||
<BaseModal.Content overflowHidden className="flex flex-col gap-4">
|
||||
{true && (
|
||||
<Label>
|
||||
<div className="edit-flow-arrangement mt-2">
|
||||
<span className="shrink-0 text-mmd font-medium">
|
||||
Endpoint Name
|
||||
</span>
|
||||
{!validEndpointName && (
|
||||
<span className="edit-flow-span">
|
||||
Use only letters, numbers, hyphens, and underscores (
|
||||
{MAX_LENGTH} characters max).
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<Input
|
||||
className="nopan nodelete nodrag noflow mt-2 font-normal"
|
||||
onChange={handleEndpointNameChange}
|
||||
type="text"
|
||||
name="endpoint_name"
|
||||
value={endpointName ?? ""}
|
||||
placeholder="An alternative name to run the endpoint"
|
||||
maxLength={MAX_LENGTH}
|
||||
minLength={MIN_LENGTH}
|
||||
id="endpoint_name"
|
||||
/>
|
||||
</Label>
|
||||
)}
|
||||
<div className="h-full w-full overflow-y-auto overflow-x-hidden rounded-lg bg-muted custom-scroll">
|
||||
<TweaksComponent open={openTweaks} />
|
||||
</div>
|
||||
</BaseModal.Content>
|
||||
</BaseModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,44 +1,7 @@
|
|||
import { ENABLE_DATASTAX_LANGFLOW } from "@/customization/feature-flags";
|
||||
import { customGetHostProtocol } from "@/customization/utils/custom-get-host-protocol";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { GetCodeType } from "@/types/tweaks";
|
||||
|
||||
/**
|
||||
* Function to get the curl code for the API
|
||||
* @param {string} flowId - The id of the flow
|
||||
* @param {boolean} isAuth - If the API is authenticated
|
||||
* @returns {string} - The curl code
|
||||
*/
|
||||
export function getCurlRunCode({
|
||||
flowId,
|
||||
isAuth,
|
||||
tweaksBuildedObject,
|
||||
endpointName,
|
||||
activeTweaks,
|
||||
}: GetCodeType): string {
|
||||
let tweaksString = "{}";
|
||||
const inputs = useFlowStore.getState().inputs;
|
||||
const outputs = useFlowStore.getState().outputs;
|
||||
const hasChatInput = inputs.some((input) => input.type === "ChatInput");
|
||||
const hasChatOutput = outputs.some((output) => output.type === "ChatOutput");
|
||||
if (tweaksBuildedObject)
|
||||
tweaksString = JSON.stringify(tweaksBuildedObject, null, 2);
|
||||
// show the endpoint name in the curl command if it exists
|
||||
|
||||
const { protocol, host } = customGetHostProtocol();
|
||||
|
||||
return `curl -X POST \\
|
||||
"${protocol}//${host}/api/v1/run/${endpointName || flowId}?stream=false" \\
|
||||
-H 'Content-Type: application/json'\\${
|
||||
!isAuth ? `\n -H 'x-api-key: <your api key>'\\` : ""
|
||||
}
|
||||
-d '{${!activeTweaks ? `"input_value": "message",` : ""}
|
||||
"output_type": ${hasChatOutput ? '"chat"' : '"text"'},
|
||||
"input_type": ${hasChatInput ? '"chat"' : '"text"'},
|
||||
"tweaks": ${tweaksString}}'
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a cURL command for making a POST request to a webhook endpoint.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,52 +1,5 @@
|
|||
import { customGetHostProtocol } from "@/customization/utils/custom-get-host-protocol";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { GetCodeType } from "@/types/tweaks";
|
||||
|
||||
/**
|
||||
* Function to generate JavaScript code for interfacing with an API using the LangflowClient class.
|
||||
* @param {string} flowId - The id of the flow.
|
||||
* @param {boolean} isAuth - Whether the API requires authentication.
|
||||
* @param {any[]} tweaksBuildedObject - Customizations applied to the flow.
|
||||
* @param {string} [endpointName] - Optional endpoint name.
|
||||
* @returns {string} - The JavaScript code as a string.
|
||||
*/
|
||||
export default function getJsApiCode({
|
||||
flowId,
|
||||
isAuth,
|
||||
tweaksBuildedObject,
|
||||
endpointName,
|
||||
activeTweaks,
|
||||
}: GetCodeType): string {
|
||||
let tweaksString = "{}";
|
||||
if (tweaksBuildedObject)
|
||||
tweaksString = JSON.stringify(tweaksBuildedObject, null, 8);
|
||||
const inputs = useFlowStore.getState().inputs;
|
||||
const outputs = useFlowStore.getState().outputs;
|
||||
const hasChatInput = inputs.some((input) => input.type === "ChatInput");
|
||||
const hasChatOutput = outputs.some((output) => output.type === "ChatOutput");
|
||||
|
||||
const { protocol, host } = customGetHostProtocol();
|
||||
|
||||
return `${activeTweaks ? "" : 'let inputValue = ""; // Insert input value here\n\n'}fetch(
|
||||
"${protocol}//${host}/api/v1/run/${endpointName || flowId}?stream=false",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Authorization": "Bearer <TOKEN>",
|
||||
"Content-Type": "application/json",${isAuth ? '\n\t\t\t"x-api-key": <your api key>' : ""}
|
||||
},
|
||||
body: JSON.stringify({${activeTweaks ? "" : "\n\t\t\tinput_value: inputValue, "}
|
||||
output_type: ${hasChatOutput ? '"chat"' : '"text"'},
|
||||
input_type: ${hasChatInput ? '"chat"' : '"text"'},
|
||||
tweaks: ${tweaksString}
|
||||
}),
|
||||
},
|
||||
)
|
||||
.then(res => res.json())
|
||||
.then(data => console.log(data))
|
||||
.catch(error => console.error('Error:', error));
|
||||
`;
|
||||
}
|
||||
/**
|
||||
* Generates JavaScript code for making API calls to a Langflow endpoint.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2,7 +2,14 @@ import { AllNodeType } from "@/types/flow";
|
|||
import { cloneDeep } from "lodash";
|
||||
import { LANGFLOW_SUPPORTED_TYPES } from "../../../constants/constants";
|
||||
|
||||
export const getNodesWithDefaultValue = (nodes: AllNodeType[]) => {
|
||||
export const getNodesWithDefaultValue = (
|
||||
nodes: AllNodeType[],
|
||||
oldTweaks: {
|
||||
[key: string]: {
|
||||
[key: string]: any;
|
||||
};
|
||||
},
|
||||
) => {
|
||||
const filteredNodes: AllNodeType[] = [];
|
||||
|
||||
nodes.forEach((node) => {
|
||||
|
|
@ -20,6 +27,8 @@ export const getNodesWithDefaultValue = (nodes: AllNodeType[]) => {
|
|||
if (newNode?.data?.node?.template) {
|
||||
newNode.data.node.template = templateKeys.reduce((acc, key) => {
|
||||
acc[key] = cloneDeep(node?.data?.node?.template[key]);
|
||||
acc[key].advanced =
|
||||
node.id in oldTweaks && key in oldTweaks[node.id] ? false : true;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,120 +1,4 @@
|
|||
import { customGetHostProtocol } from "@/customization/utils/custom-get-host-protocol";
|
||||
import { GetCodeType } from "@/types/tweaks";
|
||||
|
||||
/**
|
||||
* Function to get the python code for the API
|
||||
* @param {string} flowId - The id of the flow
|
||||
* @param {boolean} isAuth - If the API is authenticated
|
||||
* @param {any[]} tweaksBuildedObject - The tweaks
|
||||
* @param {string} [endpointName] - The optional endpoint name
|
||||
* @returns {string} - The python code
|
||||
*/
|
||||
export default function getPythonApiCode({
|
||||
flowId,
|
||||
tweaksBuildedObject,
|
||||
endpointName,
|
||||
activeTweaks,
|
||||
}: GetCodeType): string {
|
||||
let tweaksString = "{}";
|
||||
if (tweaksBuildedObject)
|
||||
tweaksString = JSON.stringify(tweaksBuildedObject, null, 2)
|
||||
.replace(/true/g, "True")
|
||||
.replace(/false/g, "False")
|
||||
.replace(/null|undefined/g, "None");
|
||||
|
||||
return `import argparse
|
||||
import json
|
||||
from argparse import RawTextHelpFormatter
|
||||
import requests
|
||||
from typing import Optional
|
||||
import warnings
|
||||
try:
|
||||
from langflow.load import upload_file
|
||||
except ImportError:
|
||||
warnings.warn("Langflow provides a function to help you upload files to the flow. Please install langflow to use it.")
|
||||
upload_file = None
|
||||
|
||||
BASE_API_URL = "${window.location.protocol}//${window.location.host}"
|
||||
FLOW_ID = "${flowId}"
|
||||
ENDPOINT = "${endpointName || ""}" ${
|
||||
endpointName
|
||||
? `# The endpoint name of the flow`
|
||||
: `# You can set a specific endpoint name in the flow settings`
|
||||
}
|
||||
|
||||
# You can tweak the flow by adding a tweaks dictionary
|
||||
# e.g {"OpenAI-XXXXX": {"model_name": "gpt-4"}}
|
||||
TWEAKS = ${tweaksString}
|
||||
|
||||
def run_flow(message: str,
|
||||
endpoint: str,
|
||||
output_type: str = "chat",
|
||||
input_type: str = "chat",
|
||||
tweaks: Optional[dict] = None,
|
||||
api_key: Optional[str] = None) -> dict:
|
||||
"""
|
||||
Run a flow with a given message and optional tweaks.
|
||||
|
||||
:param message: The message to send to the flow
|
||||
:param endpoint: The ID or the endpoint name of the flow
|
||||
:param tweaks: Optional tweaks to customize the flow
|
||||
:return: The JSON response from the flow
|
||||
"""
|
||||
api_url = f"{BASE_API_URL}/api/v1/run/{endpoint}"
|
||||
|
||||
payload = {
|
||||
${!activeTweaks ? `"input_value": message,` : ""}
|
||||
"output_type": output_type,
|
||||
"input_type": input_type,
|
||||
}
|
||||
headers = None
|
||||
if tweaks:
|
||||
payload["tweaks"] = tweaks
|
||||
if api_key:
|
||||
headers = {"x-api-key": api_key}
|
||||
response = requests.post(api_url, json=payload, headers=headers)
|
||||
return response.json()
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="""Run a flow with a given message and optional tweaks.\nRun it like: python <your file>.py "your message here" --endpoint "your_endpoint" --tweaks '{"key": "value"}'""",
|
||||
formatter_class=RawTextHelpFormatter)
|
||||
parser.add_argument("message", type=str, help="The message to send to the flow")
|
||||
parser.add_argument("--endpoint", type=str, default=ENDPOINT or FLOW_ID, help="The ID or the endpoint name of the flow")
|
||||
parser.add_argument("--tweaks", type=str, help="JSON string representing the tweaks to customize the flow", default=json.dumps(TWEAKS))
|
||||
parser.add_argument("--api_key", type=str, help="API key for authentication", default=None)
|
||||
parser.add_argument("--output_type", type=str, default="chat", help="The output type")
|
||||
parser.add_argument("--input_type", type=str, default="chat", help="The input type")
|
||||
parser.add_argument("--upload_file", type=str, help="Path to the file to upload", default=None)
|
||||
parser.add_argument("--components", type=str, help="Components to upload the file to", default=None)
|
||||
|
||||
args = parser.parse_args()
|
||||
try:
|
||||
tweaks = json.loads(args.tweaks)
|
||||
except json.JSONDecodeError:
|
||||
raise ValueError("Invalid tweaks JSON string")
|
||||
|
||||
if args.upload_file:
|
||||
if not upload_file:
|
||||
raise ImportError("Langflow is not installed. Please install it to use the upload_file function.")
|
||||
elif not args.components:
|
||||
raise ValueError("You need to provide the components to upload the file to.")
|
||||
tweaks = upload_file(file_path=args.upload_file, host=BASE_API_URL, flow_id=args.endpoint, components=[args.components], tweaks=tweaks)
|
||||
|
||||
response = run_flow(
|
||||
message=args.message,
|
||||
endpoint=args.endpoint,
|
||||
output_type=args.output_type,
|
||||
input_type=args.input_type,
|
||||
tweaks=tweaks,
|
||||
api_key=args.api_key
|
||||
)
|
||||
|
||||
print(json.dumps(response, indent=2))
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
`;
|
||||
}
|
||||
|
||||
export function getNewPythonApiCode({
|
||||
flowId,
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { GetCodeType } from "@/types/tweaks";
|
||||
|
||||
/**
|
||||
* Function to get the python code for the API
|
||||
* @param {string} flow - The current flow
|
||||
* @param {any[]} tweaksBuildedObject - The tweaks
|
||||
* @returns {string} - The python code
|
||||
*/
|
||||
export default function getPythonCode({
|
||||
flowName,
|
||||
tweaksBuildedObject,
|
||||
activeTweaks,
|
||||
}: GetCodeType): string {
|
||||
let tweaksString = "{}";
|
||||
if (tweaksBuildedObject)
|
||||
tweaksString = JSON.stringify(tweaksBuildedObject, null, 2)
|
||||
.replace(/true/g, "True")
|
||||
.replace(/false/g, "False")
|
||||
.replace(/null|undefined/g, "None");
|
||||
|
||||
return `from langflow.load import run_flow_from_json
|
||||
TWEAKS = ${tweaksString}
|
||||
|
||||
result = run_flow_from_json(flow="${flowName}.json",
|
||||
${!activeTweaks ? `input_value="message",\n ` : ""}session_id="", # provide a session id if you want to use session state
|
||||
fallback_to_env_vars=True, # False by default
|
||||
tweaks=TWEAKS)`;
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
import { tabsArrayType } from "@/types/components";
|
||||
|
||||
export function createTabsArray(
|
||||
codes: { [key: string]: string },
|
||||
includeTweaks = false,
|
||||
) {
|
||||
const tabs: tabsArrayType[] = [];
|
||||
if (codes.runCurlCode) {
|
||||
tabs.push({
|
||||
name: "cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: codes.runCurlCode,
|
||||
hasTweaks: includeTweaks,
|
||||
});
|
||||
}
|
||||
if (codes.webhookCurlCode) {
|
||||
tabs.push({
|
||||
name: "Webhook cURL",
|
||||
mode: "bash",
|
||||
image: "https://curl.se/logo/curl-symbol-transparent.png",
|
||||
language: "sh",
|
||||
code: codes.webhookCurlCode,
|
||||
});
|
||||
}
|
||||
if (codes.pythonApiCode) {
|
||||
tabs.push({
|
||||
name: "Python API",
|
||||
mode: "python",
|
||||
image:
|
||||
"https://images.squarespace-cdn.com/content/v1/5df3d8c5d2be5962e4f87890/1628015119369-OY4TV3XJJ53ECO0W2OLQ/Python+API+Training+Logo.png?format=1000w",
|
||||
language: "py",
|
||||
code: codes.pythonApiCode,
|
||||
hasTweaks: includeTweaks,
|
||||
});
|
||||
}
|
||||
if (codes.jsApiCode) {
|
||||
tabs.push({
|
||||
name: "JS API",
|
||||
mode: "javascript",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/136/136530.png",
|
||||
language: "js",
|
||||
code: codes.jsApiCode,
|
||||
hasTweaks: includeTweaks,
|
||||
});
|
||||
}
|
||||
if (codes.pythonCode) {
|
||||
tabs.push({
|
||||
name: "Python Code",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes.pythonCode,
|
||||
hasTweaks: includeTweaks,
|
||||
});
|
||||
}
|
||||
if (codes.widgetCode) {
|
||||
tabs.push({
|
||||
name: "Chat Widget HTML",
|
||||
description:
|
||||
"Insert this code anywhere in your <body> tag. To use with react and other libs, check our <a class='link-color' href='https://langflow.org/guidelines/widget'>documentation</a>.",
|
||||
mode: "html",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "html",
|
||||
code: codes.widgetCode,
|
||||
});
|
||||
}
|
||||
if (includeTweaks) {
|
||||
tabs.push({
|
||||
name: "Tweaks",
|
||||
mode: "python",
|
||||
image: "https://cdn-icons-png.flaticon.com/512/5968/5968350.png",
|
||||
language: "py",
|
||||
code: codes.tweaksCode,
|
||||
});
|
||||
}
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ export const switchCaseModalSize = (size: string) => {
|
|||
height = "h-fit";
|
||||
break;
|
||||
case "medium-small-tall":
|
||||
minWidth = "min-w-[50vw]";
|
||||
minWidth = "w-[900px] max-w-[98vw]";
|
||||
height = "h-[70vh]";
|
||||
break;
|
||||
case "small-h-full":
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const useColumnDefs = (
|
|||
autoHeight: true,
|
||||
flex: 1,
|
||||
resizable: false,
|
||||
cellClass: "no-border",
|
||||
cellClass: "no-border cursor-default",
|
||||
},
|
||||
{
|
||||
headerName: "Description",
|
||||
|
|
@ -36,7 +36,7 @@ const useColumnDefs = (
|
|||
autoHeight: true,
|
||||
flex: 2,
|
||||
resizable: false,
|
||||
cellClass: "no-border",
|
||||
cellClass: "no-border cursor-default",
|
||||
},
|
||||
{
|
||||
headerName: "Value",
|
||||
|
|
@ -61,24 +61,26 @@ const useColumnDefs = (
|
|||
autoHeight: true,
|
||||
flex: 1,
|
||||
resizable: false,
|
||||
cellClass: "no-border",
|
||||
cellClass: "no-border cursor-default",
|
||||
},
|
||||
];
|
||||
if (!hideVisibility) {
|
||||
colDefs.push({
|
||||
headerName: "Show",
|
||||
headerName: isTweaks ? "Enable Input" : "Show",
|
||||
field: "advanced",
|
||||
cellRenderer: TableAdvancedToggleCellRender,
|
||||
valueGetter: (params: ValueGetterParams) => {
|
||||
return {
|
||||
nodeId,
|
||||
parameterId: params.data.key,
|
||||
isTweaks,
|
||||
};
|
||||
},
|
||||
editable: false,
|
||||
maxWidth: 80,
|
||||
maxWidth: !isTweaks ? 80 : 120,
|
||||
minWidth: !isTweaks ? 80 : 120,
|
||||
resizable: false,
|
||||
cellClass: "no-border",
|
||||
cellClass: "no-border cursor-default",
|
||||
});
|
||||
}
|
||||
return colDefs;
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ import useAlertStore from "./alertStore";
|
|||
import { useDarkStore } from "./darkStore";
|
||||
import useFlowsManagerStore from "./flowsManagerStore";
|
||||
import { useGlobalVariablesStore } from "./globalVariablesStore/globalVariables";
|
||||
import { useTweaksStore } from "./tweaksStore";
|
||||
import { useTypesStore } from "./typesStore";
|
||||
|
||||
// this is our useStore hook that we can use in our components to get parts of the store and call actions
|
||||
|
|
@ -226,6 +227,9 @@ const useFlowStore = create<FlowStoreType>((set, get) => ({
|
|||
) as string[],
|
||||
});
|
||||
unselectAllNodesEdges(nodes, edges);
|
||||
if (flow?.id) {
|
||||
useTweaksStore.getState().initialSetup(nodes, flow?.id);
|
||||
}
|
||||
set({
|
||||
nodes,
|
||||
edges: newEdges,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,13 @@
|
|||
import { getChangesType } from "@/modals/apiModal/utils/get-changes-types";
|
||||
import { getNodesWithDefaultValue } from "@/modals/apiModal/utils/get-nodes-with-default-value";
|
||||
import { createTabsArray } from "@/modals/apiModal/utils/tabs-array";
|
||||
import { AllNodeType, FlowType, NodeDataType } from "@/types/flow";
|
||||
import { GetCodesType } from "@/types/tweaks";
|
||||
import { customStringify } from "@/utils/reactflowUtils";
|
||||
import { AllNodeType, NodeDataType } from "@/types/flow";
|
||||
import { getLocalStorage, setLocalStorage } from "@/utils/local-storage-util";
|
||||
import { create } from "zustand";
|
||||
import { TweaksStoreType } from "../types/zustand/tweaks";
|
||||
import useFlowStore from "./flowStore";
|
||||
|
||||
export const useTweaksStore = create<TweaksStoreType>((set, get) => ({
|
||||
activeTweaks: false,
|
||||
tweaks: {},
|
||||
setActiveTweaks: (activeTweaks: boolean) => {
|
||||
set({ activeTweaks }), get().refreshTabs();
|
||||
},
|
||||
nodes: [],
|
||||
setNodes: (change) => {
|
||||
let newChange = typeof change === "function" ? change(get().nodes) : change;
|
||||
|
|
@ -21,7 +15,6 @@ export const useTweaksStore = create<TweaksStoreType>((set, get) => ({
|
|||
set({
|
||||
nodes: newChange,
|
||||
});
|
||||
get().refreshTabs();
|
||||
get().updateTweaks();
|
||||
},
|
||||
setNode: (id, change) => {
|
||||
|
|
@ -44,112 +37,28 @@ export const useTweaksStore = create<TweaksStoreType>((set, get) => ({
|
|||
getNode: (id: string) => {
|
||||
return get().nodes.find((node) => node.id === id);
|
||||
},
|
||||
autoLogin: false,
|
||||
flow: null,
|
||||
getCodes: {},
|
||||
initialSetup: (
|
||||
autoLogin: boolean,
|
||||
flow: FlowType,
|
||||
getCodes: GetCodesType,
|
||||
) => {
|
||||
currentFlowId: "",
|
||||
initialSetup: (nodes: AllNodeType[], flowId: string) => {
|
||||
useFlowStore.getState().unselectAll();
|
||||
set({
|
||||
nodes: getNodesWithDefaultValue(flow?.data?.nodes ?? []),
|
||||
autoLogin,
|
||||
flow,
|
||||
getCodes,
|
||||
currentFlowId: flowId,
|
||||
});
|
||||
get().refreshTabs();
|
||||
},
|
||||
newInitialSetup: (nodes: AllNodeType[]) => {
|
||||
useFlowStore.getState().unselectAll();
|
||||
const tweaks = JSON.parse(getLocalStorage(`lf_tweaks_${flowId}`) || "{}");
|
||||
set({
|
||||
nodes: getNodesWithDefaultValue(nodes),
|
||||
nodes: getNodesWithDefaultValue(nodes, tweaks),
|
||||
});
|
||||
get().updateTweaks();
|
||||
},
|
||||
tabs: [],
|
||||
refreshTabs: () => {
|
||||
const autoLogin = get().autoLogin;
|
||||
const flow = get().flow;
|
||||
const tweak = {};
|
||||
const nodes = get().nodes;
|
||||
const originalNodes = flow?.data?.nodes;
|
||||
if (!flow) return;
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const originalNodeTemplate = originalNodes?.find((n) => n.id === node.id)
|
||||
?.data?.node?.template;
|
||||
const nodeTemplate = node.data?.node?.template;
|
||||
if (originalNodeTemplate && nodeTemplate && node.type === "genericNode") {
|
||||
const currentTweak = {};
|
||||
Object.keys(nodeTemplate).forEach((name) => {
|
||||
if (
|
||||
customStringify(nodeTemplate[name]) !==
|
||||
customStringify(originalNodeTemplate[name]) ||
|
||||
get().activeTweaks
|
||||
) {
|
||||
currentTweak[name] = getChangesType(
|
||||
nodeTemplate[name].value,
|
||||
nodeTemplate[name],
|
||||
);
|
||||
}
|
||||
});
|
||||
tweak[node.id] = currentTweak;
|
||||
}
|
||||
});
|
||||
const codesObj = {};
|
||||
const getCodes = get().getCodes;
|
||||
|
||||
const props = {
|
||||
flowId: flow?.id,
|
||||
flowName: flow?.name,
|
||||
isAuth: autoLogin,
|
||||
tweaksBuildedObject: tweak,
|
||||
endpointName: flow?.endpoint_name,
|
||||
activeTweaks: get().activeTweaks,
|
||||
};
|
||||
|
||||
if (getCodes) {
|
||||
if (getCodes.getCurlRunCode) {
|
||||
codesObj["runCurlCode"] = getCodes.getCurlRunCode(props);
|
||||
}
|
||||
if (getCodes.getCurlWebhookCode && !!flow.webhook) {
|
||||
codesObj["webhookCurlCode"] = getCodes.getCurlWebhookCode(props);
|
||||
}
|
||||
if (getCodes.getJsApiCode) {
|
||||
codesObj["jsApiCode"] = getCodes.getJsApiCode(props);
|
||||
}
|
||||
if (getCodes.getPythonApiCode) {
|
||||
codesObj["pythonApiCode"] = getCodes.getPythonApiCode(props);
|
||||
}
|
||||
if (getCodes.getPythonCode) {
|
||||
codesObj["pythonCode"] = getCodes.getPythonCode(props);
|
||||
}
|
||||
if (getCodes.getWidgetCode) {
|
||||
codesObj["widgetCode"] = getCodes.getWidgetCode(props);
|
||||
}
|
||||
}
|
||||
|
||||
set({
|
||||
tabs: createTabsArray(codesObj, nodes.length > 0),
|
||||
});
|
||||
},
|
||||
updateTweaks: () => {
|
||||
const nodes = get().nodes;
|
||||
const originalNodes = useFlowStore.getState().nodes;
|
||||
const tweak = {};
|
||||
const flowId = get().currentFlowId;
|
||||
nodes.forEach((node) => {
|
||||
const originalNodeTemplate = originalNodes?.find((n) => n.id === node.id)
|
||||
?.data?.node?.template;
|
||||
const nodeTemplate = node.data?.node?.template;
|
||||
if (originalNodeTemplate && nodeTemplate && node.type === "genericNode") {
|
||||
if (nodeTemplate && node.type === "genericNode") {
|
||||
const currentTweak = {};
|
||||
Object.keys(nodeTemplate).forEach((name) => {
|
||||
if (
|
||||
customStringify(nodeTemplate[name]) !==
|
||||
customStringify(originalNodeTemplate[name])
|
||||
) {
|
||||
if (!nodeTemplate[name].advanced) {
|
||||
currentTweak[name] = getChangesType(
|
||||
nodeTemplate[name].value,
|
||||
nodeTemplate[name],
|
||||
|
|
@ -161,6 +70,7 @@ export const useTweaksStore = create<TweaksStoreType>((set, get) => ({
|
|||
}
|
||||
}
|
||||
});
|
||||
setLocalStorage(`lf_tweaks_${flowId}`, JSON.stringify(tweak));
|
||||
set({
|
||||
tweaks: tweak,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1240,7 +1240,7 @@
|
|||
}
|
||||
|
||||
.playground-btn-flow-toolbar {
|
||||
@apply relative inline-flex h-8 w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-semibold transition-all duration-500 ease-in-out;
|
||||
@apply relative inline-flex h-8 w-full items-center justify-center gap-1.5 rounded px-3 py-1.5 text-sm font-normal transition-all duration-500 ease-in-out;
|
||||
}
|
||||
|
||||
.input-slider-text {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import { AllNodeType, FlowType, TweaksType } from "@/types/flow";
|
||||
import { GetCodesType } from "@/types/tweaks";
|
||||
import { tabsArrayType } from "../../components";
|
||||
import { AllNodeType } from "@/types/flow";
|
||||
|
||||
export type TweaksStoreType = {
|
||||
activeTweaks: boolean;
|
||||
setActiveTweaks: (activeTweaks: boolean) => void;
|
||||
nodes: AllNodeType[];
|
||||
currentFlowId: string;
|
||||
setNodes: (
|
||||
update: AllNodeType[] | ((oldState: AllNodeType[]) => AllNodeType[]),
|
||||
skipSave?: boolean,
|
||||
|
|
@ -14,18 +11,8 @@ export type TweaksStoreType = {
|
|||
id: string,
|
||||
update: AllNodeType | ((oldState: AllNodeType) => AllNodeType),
|
||||
) => void;
|
||||
getCodes: GetCodesType;
|
||||
getNode: (id: string) => AllNodeType | undefined;
|
||||
tabs: tabsArrayType[];
|
||||
initialSetup: (
|
||||
autoLogin: boolean,
|
||||
flow: FlowType,
|
||||
getCodes: GetCodesType,
|
||||
) => void;
|
||||
newInitialSetup: (nodes: AllNodeType[]) => void;
|
||||
refreshTabs: () => void;
|
||||
autoLogin: boolean;
|
||||
flow: FlowType | null;
|
||||
initialSetup: (nodes: AllNodeType[], flowId: string) => void;
|
||||
updateTweaks: () => void;
|
||||
tweaks: {
|
||||
[key: string]: {
|
||||
|
|
|
|||
|
|
@ -27,17 +27,11 @@ test(
|
|||
.first()
|
||||
.click();
|
||||
|
||||
await page.waitForSelector(
|
||||
'[data-testid="popover-anchor-input-openai_api_base-edit"]',
|
||||
{
|
||||
timeout: 1000,
|
||||
},
|
||||
);
|
||||
await page.waitForSelector('[data-testid="showstream"]', {
|
||||
timeout: 1000,
|
||||
});
|
||||
|
||||
await page
|
||||
.getByTestId("popover-anchor-input-openai_api_base-edit")
|
||||
.first()
|
||||
.fill("teste");
|
||||
await page.getByTestId("showstream").first().click();
|
||||
|
||||
await page.getByText("Close").last().click();
|
||||
|
||||
|
|
@ -50,6 +44,13 @@ test(
|
|||
const newValue = clipboardContent2;
|
||||
expect(oldValue).not.toBe(newValue);
|
||||
expect(clipboardContent2.length).toBeGreaterThan(clipboardContent.length);
|
||||
await awaitBootstrapTest(page, { skipModal: true });
|
||||
await page.getByText("Basic Prompting").first().click();
|
||||
await page.getByTestId("publish-button").click();
|
||||
await page.getByTestId("api-access-item").click();
|
||||
expect(
|
||||
await page.getByText("Input Schema (1)", { exact: true }).isVisible(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
|
@ -131,5 +132,5 @@ test("check if tweaks are updating when someothing on the flow changes", async (
|
|||
await page.getByText("collection_name_test_123123123!@#$&*(&%$@").isVisible();
|
||||
await page.getByText("persist_directory_123123123!@#$&*(&%$@").isVisible();
|
||||
|
||||
expect(await page.getByText("Tweaks (2)", { exact: true }).isVisible());
|
||||
expect(await page.getByText("Input Schema (2)", { exact: true }).isVisible());
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue