refactor: custom api code generator and fix chat locking when saving (#3618)

* Removed saveLoading from the chat

* Fixed initialSetup that couldnt be null

* Added types for get codes type

* Added new types for getCodes and initialSetup on Tweaks store

* Created a use custom api code to return all code

* Passed the getCodes from the custom hook into initialSetup

* Updated tabs array to receive object

* Update every code to get correct props

* Update tweaks store to handle refactored getCodes and createTabsArray

* Added custom api generator component on top of the codetabscomponent
This commit is contained in:
Lucas Oliveira 2024-08-29 16:31:03 -03:00 committed by GitHub
commit 32d51b69e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 178 additions and 128 deletions

View file

@ -0,0 +1,3 @@
export function CustomAPIGenerator() {
return <></>;
}

View file

@ -0,0 +1,19 @@
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,
};
}

View file

@ -8,7 +8,6 @@ type ButtonSendWrapperProps = {
send: () => void;
lockChat: boolean;
noInput: boolean;
saveLoading: boolean;
chatValue: string;
files: FilePreviewType[];
};
@ -17,7 +16,6 @@ const ButtonSendWrapper = ({
send,
lockChat,
noInput,
saveLoading,
chatValue,
files,
}: ButtonSendWrapperProps) => {
@ -31,15 +29,11 @@ const ButtonSendWrapper = ({
? "text-primary"
: "bg-chat-send text-background",
)}
disabled={lockChat || saveLoading}
disabled={lockChat}
onClick={(): void => send()}
unstyled
>
<Case
condition={
lockChat || saveLoading || files.some((file) => file.loading)
}
>
<Case condition={lockChat || files.some((file) => file.loading)}>
<IconComponent
name="Lock"
className="form-modal-lock-icon"
@ -57,8 +51,7 @@ const ButtonSendWrapper = ({
<Case
condition={
!(lockChat || saveLoading || files.some((file) => file.loading)) &&
!noInput
!(lockChat || files.some((file) => file.loading)) && !noInput
}
>
<IconComponent

View file

@ -7,7 +7,6 @@ const TextAreaWrapper = ({
send,
lockChat,
noInput,
saveLoading,
chatValue,
setChatValue,
CHAT_INPUT_PLACEHOLDER,
@ -30,12 +29,11 @@ const TextAreaWrapper = ({
}
};
const lockClass =
lockChat || saveLoading
? "form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
: "form-modal-lock-false bg-background";
const lockClass = lockChat
? "form-modal-lock-true bg-input"
: noInput
? "form-modal-no-input bg-input"
: "form-modal-lock-false bg-background";
const fileClass =
files.length > 0
@ -45,10 +43,10 @@ const TextAreaWrapper = ({
const additionalClassNames = "form-modal-lockchat pl-14";
useEffect(() => {
if (!lockChat && !noInput && !saveLoading) {
if (!lockChat && !noInput) {
inputRef.current?.focus();
}
}, [lockChat, noInput, saveLoading]);
}, [lockChat, noInput]);
return (
<Textarea
@ -65,7 +63,7 @@ const TextAreaWrapper = ({
}}
rows={1}
ref={inputRef}
disabled={lockChat || noInput || saveLoading}
disabled={lockChat || noInput}
style={{
resize: "none",
bottom: `${inputRef?.current?.scrollHeight}px`,
@ -76,7 +74,7 @@ const TextAreaWrapper = ({
: "hidden"
}`,
}}
value={lockChat ? "Thinking..." : saveLoading ? "Saving..." : chatValue}
value={lockChat ? "Thinking..." : chatValue}
onChange={(event): void => {
setChatValue(event.target.value);
}}

View file

@ -32,8 +32,6 @@ export default function ChatInput({
setFiles,
isDragging,
}: ChatInputType): JSX.Element {
const [repeat, setRepeat] = useState(1);
const saveLoading = useFlowsManagerStore((state) => state.saveLoading);
const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId);
const [inputFocus, setInputFocus] = useState<boolean>(false);
const fileInputRef = useRef<HTMLInputElement>(null);
@ -128,7 +126,7 @@ export default function ChatInput({
const send = () => {
sendMessage({
repeat,
repeat: 1,
files: files.map((file) => file.path ?? "").filter((file) => file !== ""),
});
setFiles([]);
@ -138,7 +136,6 @@ export default function ChatInput({
return (
event.key === "Enter" &&
!lockChat &&
!saveLoading &&
!event.shiftKey &&
!event.nativeEvent.isComposing
);
@ -158,7 +155,6 @@ export default function ChatInput({
send={send}
lockChat={lockChat}
noInput={noInput}
saveLoading={saveLoading}
chatValue={chatValue}
setChatValue={setChatValue}
CHAT_INPUT_PLACEHOLDER={CHAT_INPUT_PLACEHOLDER}
@ -173,7 +169,6 @@ export default function ChatInput({
send={send}
lockChat={lockChat}
noInput={noInput}
saveLoading={saveLoading}
chatValue={chatValue}
files={files}
/>
@ -181,11 +176,11 @@ export default function ChatInput({
<div
className={`absolute bottom-2 left-4 ${
lockChat || saveLoading ? "cursor-not-allowed" : ""
lockChat ? "cursor-not-allowed" : ""
}`}
>
<UploadFileButton
lockChat={lockChat || saveLoading}
lockChat={lockChat}
fileInputRef={fileInputRef}
handleFileChange={handleFileChange}
handleButtonClick={handleButtonClick}

View file

@ -1,3 +1,5 @@
import { CustomAPIGenerator } from "@/customization/components/custom-api-generator";
import { useCustomAPICode } from "@/customization/hooks/use-custom-api-code";
import useAuthStore from "@/stores/authStore";
import "ace-builds/src-noconflict/ext-language_tools";
import "ace-builds/src-noconflict/mode-python";
@ -33,8 +35,10 @@ export default function ApiModal({
const tabs = useTweaksStore((state) => state.tabs);
const initialSetup = useTweaksStore((state) => state.initialSetup);
const getCodes = useCustomAPICode();
useEffect(() => {
if (open) initialSetup(autoLogin, flow);
if (open) initialSetup(autoLogin ?? false, flow, getCodes);
setActiveTab("0");
}, [open]);
@ -50,6 +54,7 @@ export default function ApiModal({
/>
</BaseModal.Header>
<BaseModal.Content overflowHidden>
<CustomAPIGenerator />
<CodeTabsComponent
open={open}
tabs={tabs!}

View file

@ -1,4 +1,5 @@
import useFlowStore from "@/stores/flowStore";
import { GetCodeType } from "@/types/tweaks";
/**
* Function to get the curl code for the API
@ -6,12 +7,12 @@ import useFlowStore from "@/stores/flowStore";
* @param {boolean} isAuth - If the API is authenticated
* @returns {string} - The curl code
*/
export function getCurlRunCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject?: {},
endpointName?: string | null,
): string {
export function getCurlRunCode({
flowId,
isAuth,
tweaksBuildedObject,
endpointName,
}: GetCodeType): string {
let tweaksString = "{}";
const inputs = useFlowStore.getState().inputs;
const outputs = useFlowStore.getState().outputs;
@ -43,11 +44,11 @@ export function getCurlRunCode(
* @param {string} options.endpointName - The name of the webhook endpoint.
* @returns {string} The cURL command.
*/
export function getCurlWebhookCode(
export function getCurlWebhookCode({
flowId,
isAuth,
endpointName?: string | null,
) {
endpointName,
}: GetCodeType) {
return `curl -X POST \\
"${window.location.protocol}//${window.location.host}/api/v1/webhook/${
endpointName || flowId

View file

@ -1,3 +1,5 @@
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.
@ -6,12 +8,12 @@
* @param {string} [endpointName] - Optional endpoint name.
* @returns {string} - The JavaScript code as a string.
*/
export default function getJsApiCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject: {},
endpointName?: string | null,
): string {
export default function getJsApiCode({
flowId,
isAuth,
tweaksBuildedObject,
endpointName,
}: GetCodeType): string {
let tweaksString = "{}";
if (tweaksBuildedObject)
tweaksString = JSON.stringify(tweaksBuildedObject, null, 2);

View file

@ -1,3 +1,5 @@
import { GetCodeType } from "@/types/tweaks";
/**
* Function to get the python code for the API
* @param {string} flowId - The id of the flow
@ -6,12 +8,11 @@
* @param {string} [endpointName] - The optional endpoint name
* @returns {string} - The python code
*/
export default function getPythonApiCode(
flowId: string,
isAuth: boolean,
tweaksBuildedObject?: {},
endpointName?: string | null,
): string {
export default function getPythonApiCode({
flowId,
tweaksBuildedObject,
endpointName,
}: GetCodeType): string {
let tweaksString = "{}";
if (tweaksBuildedObject)
tweaksString = JSON.stringify(tweaksBuildedObject, null, 2)

View file

@ -1,13 +1,15 @@
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: string,
tweaksBuildedObject: {},
): string {
export default function getPythonCode({
flowName,
tweaksBuildedObject,
}: GetCodeType): string {
let tweaksString = "{}";
if (tweaksBuildedObject)
tweaksString = JSON.stringify(tweaksBuildedObject, null, 2)

View file

@ -1,13 +1,15 @@
import { GetCodeType } from "@/types/tweaks";
/**
* Function to get the widget code for the API
* @param {string} flow - The current flow.
* @returns {string} - The widget code
*/
export default function getWidgetCode(
flowId: string,
flowName: string,
isAuth: boolean,
): string {
export default function getWidgetCode({
flowId,
flowName,
isAuth,
}: GetCodeType): string {
return `<script src="https://cdn.jsdelivr.net/gh/logspace-ai/langflow-embedded-chat@v1.0.6/dist/build/static/js/bundle.min.js""></script>
<langflow-chat

View file

@ -1,72 +1,78 @@
import { tabsArrayType } from "@/types/components";
export function createTabsArray(
codes,
includeWebhookCurl = false,
codes: { [key: string]: string },
includeTweaks = false,
) {
const tabs: tabsArrayType[] = [
{
const tabs: tabsArrayType[] = [];
if (codes.runCurlCode) {
tabs.push({
name: "Run cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[0],
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[2],
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[3],
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[4],
code: codes.pythonCode,
hasTweaks: includeTweaks,
},
{
});
}
if (codes.widgetCode) {
tabs.push({
name: "Chat Widget HTML",
description:
"Insert this code anywhere in your &lt;body&gt; 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[5],
},
];
if (includeWebhookCurl) {
tabs.splice(1, 0, {
name: "Webhook cURL",
mode: "bash",
image: "https://curl.se/logo/curl-symbol-transparent.png",
language: "sh",
code: codes[1],
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[6],
code: codes.tweaksCode,
});
}

View file

@ -1,15 +1,8 @@
import { getChangesType } from "@/modals/apiModal/utils/get-changes-types";
import {
getCurlRunCode,
getCurlWebhookCode,
} from "@/modals/apiModal/utils/get-curl-code";
import getJsApiCode from "@/modals/apiModal/utils/get-js-api-code";
import { getNodesWithDefaultValue } from "@/modals/apiModal/utils/get-nodes-with-default-value";
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";
import { createTabsArray } from "@/modals/apiModal/utils/tabs-array";
import { FlowType, NodeDataType } from "@/types/flow";
import { GetCodesType } from "@/types/tweaks";
import { customStringify } from "@/utils/reactflowUtils";
import { create } from "zustand";
import { TweaksStoreType } from "../types/zustand/tweaks";
@ -51,12 +44,18 @@ export const useTweaksStore = create<TweaksStoreType>((set, get) => ({
},
autoLogin: false,
flow: null,
initialSetup: (autoLogin: boolean, flow: FlowType) => {
getCodes: {},
initialSetup: (
autoLogin: boolean,
flow: FlowType,
getCodes: GetCodesType,
) => {
useFlowStore.getState().unselectAll();
set({
nodes: getNodesWithDefaultValue(flow?.data?.nodes ?? []),
autoLogin,
flow,
getCodes,
});
get().refreshTabs();
},
@ -90,38 +89,40 @@ export const useTweaksStore = create<TweaksStoreType>((set, get) => ({
tweak[node.id] = currentTweak;
}
});
const codesObj = {};
const getCodes = get().getCodes;
const pythonApiCode = getPythonApiCode(flow?.id, autoLogin, tweak);
const runCurlCode = getCurlRunCode(
flow?.id,
autoLogin,
tweak,
flow?.endpoint_name,
);
const jsApiCode = getJsApiCode(
flow?.id,
autoLogin,
tweak,
flow?.endpoint_name,
);
const webhookCurlCode = getCurlWebhookCode(
flow?.id,
autoLogin,
flow?.endpoint_name,
);
const pythonCode = getPythonCode(flow?.name, tweak);
const widgetCode = getWidgetCode(flow?.id, flow?.name, autoLogin);
const props = {
flowId: flow?.id,
flowName: flow?.name,
isAuth: autoLogin,
tweaksBuildedObject: tweak,
endpointName: flow?.endpoint_name,
};
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);
}
}
const codesArray = [
runCurlCode,
webhookCurlCode,
pythonApiCode,
jsApiCode,
pythonCode,
widgetCode,
];
set({
tabs: createTabsArray(codesArray, !!flow.webhook, nodes.length > 0),
tabs: createTabsArray(codesObj, nodes.length > 0),
});
},
}));

View file

@ -0,0 +1,16 @@
export type GetCodesType = {
getCurlRunCode?: (GetCodeType) => string;
getCurlWebhookCode?: (GetCodeType) => string;
getJsApiCode?: (GetCodeType) => string;
getPythonApiCode?: (GetCodeType) => string;
getPythonCode?: (GetCodeType) => string;
getWidgetCode?: (GetCodeType) => string;
};
export type GetCodeType = {
flowId: string;
flowName: string;
isAuth: boolean;
tweaksBuildedObject: {};
endpointName?: string | null;
};

View file

@ -1,4 +1,5 @@
import { FlowType, NodeType } from "@/types/flow";
import { GetCodesType } from "@/types/tweaks";
import { tabsArrayType } from "../../components";
export type TweaksStoreType = {
@ -13,9 +14,14 @@ export type TweaksStoreType = {
id: string,
update: NodeType | ((oldState: NodeType) => NodeType),
) => void;
getCodes: GetCodesType;
getNode: (id: string) => NodeType | undefined;
tabs: tabsArrayType[];
initialSetup: (autoLogin: boolean, flow: FlowType) => void;
initialSetup: (
autoLogin: boolean,
flow: FlowType,
getCodes: GetCodesType,
) => void;
refreshTabs: () => void;
autoLogin: boolean;
flow: FlowType | null;