feat: add powershell curl tab to UI (#8889)
* chore: Bump version to 1.5.0 and update dependencies - Updated langflow version to 1.5.0 in pyproject.toml, package.json, and package-lock.json. - Updated langflow-base dependency to version 0.5.0. - Added platform markers for several dependencies in uv.lock to improve compatibility across different systems. * fix: fixes auth check for auto_login (#8796) * ref: improve docling template updates and error message (#8837) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org> * Attempt to provide powershell curl command * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes (attempt 2/3) * Added OS selector to code tabs * Added no select classes to API modal * ✨ (code-tabs.tsx): add data-testid attribute to API tab elements for testing purposes 🔧 (tweaksTest.spec.ts, curlApiGeneration.spec.ts, pythonApiGeneration.spec.ts, generalBugs-shard-3.spec.ts): update test scripts to use data-testid attribute for API tab elements instead of role attribute --------- Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Lucas Oliveira <lucas.edu.oli@hotmail.com> Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
This commit is contained in:
parent
be18f6d03a
commit
7ffec1a696
8 changed files with 174 additions and 94 deletions
|
|
@ -1,11 +1,12 @@
|
|||
import IconComponent from "@/components/common/genericIconComponent";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs-button";
|
||||
import useAuthStore from "@/stores/authStore";
|
||||
import useFlowStore from "@/stores/flowStore";
|
||||
import { useTweaksStore } from "@/stores/tweaksStore";
|
||||
import { tabsArrayType } from "@/types/tabs";
|
||||
import { hasStreaming } from "@/utils/reactflowUtils";
|
||||
import { getOS } from "@/utils/utils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import {
|
||||
|
|
@ -19,6 +20,19 @@ import { getNewCurlCode } from "../utils/get-curl-code";
|
|||
import { getNewJsApiCode } from "../utils/get-js-api-code";
|
||||
import { getNewPythonApiCode } from "../utils/get-python-api-code";
|
||||
|
||||
const operatingSystemTabs = [
|
||||
{
|
||||
name: "macoslinux",
|
||||
title: "macOS/Linux",
|
||||
icon: "FaApple",
|
||||
},
|
||||
{
|
||||
name: "windows",
|
||||
title: "Windows",
|
||||
icon: "FaWindows",
|
||||
},
|
||||
];
|
||||
|
||||
export default function APITabsComponent() {
|
||||
const [isCopied, setIsCopied] = useState<Boolean>(false);
|
||||
const endpointName = useFlowStore(
|
||||
|
|
@ -67,114 +81,142 @@ export default function APITabsComponent() {
|
|||
isAuthenticated: autologin || false,
|
||||
processedPayload: processedPayload,
|
||||
};
|
||||
const tabsList: tabsArrayType = [
|
||||
|
||||
// Platform selection for cURL
|
||||
const [selectedPlatform, setSelectedPlatform] = useState(
|
||||
operatingSystemTabs.find((tab) =>
|
||||
tab.name.includes(getOS() === "windows" ? "windows" : "macoslinux"),
|
||||
)?.name || "macoslinux",
|
||||
);
|
||||
|
||||
const tabsList = [
|
||||
{
|
||||
title: "Python",
|
||||
icon: "BWPython",
|
||||
language: "python",
|
||||
code: getNewPythonApiCode(codeOptions),
|
||||
copyCode: getNewPythonApiCode(codeOptions),
|
||||
},
|
||||
{
|
||||
title: "JavaScript",
|
||||
icon: "javascript",
|
||||
language: "javascript",
|
||||
code: getNewJsApiCode(codeOptions),
|
||||
copyCode: getNewJsApiCode(codeOptions),
|
||||
},
|
||||
{
|
||||
title: "cURL",
|
||||
icon: "TerminalSquare",
|
||||
language: "shell",
|
||||
code: getNewCurlCode(codeOptions),
|
||||
copyCode: getNewCurlCode(codeOptions),
|
||||
language: selectedPlatform === "windows" ? "powershell" : "shell",
|
||||
code: getNewCurlCode({
|
||||
...codeOptions,
|
||||
platform: selectedPlatform === "windows" ? "powershell" : "unix",
|
||||
}),
|
||||
},
|
||||
];
|
||||
const [activeTab, setActiveTab] = useState<number>(0);
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState("Python");
|
||||
|
||||
const copyToClipboard = () => {
|
||||
if (!navigator.clipboard || !navigator.clipboard.writeText) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.clipboard.writeText(tabsList[activeTab].code).then(() => {
|
||||
setIsCopied(true);
|
||||
const currentTab = tabsList.find((tab) => tab.title === selectedTab);
|
||||
if (currentTab) {
|
||||
navigator.clipboard.writeText(currentTab.code).then(() => {
|
||||
setIsCopied(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 2000);
|
||||
});
|
||||
setTimeout(() => {
|
||||
setIsCopied(false);
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsCopied(false);
|
||||
}, [activeTab]);
|
||||
}, [selectedTab, selectedPlatform]);
|
||||
|
||||
const currentTab = tabsList.find((tab) => tab.title === selectedTab);
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
value={activeTab.toString()}
|
||||
className={"api-modal-tabs inset-0 m-0"}
|
||||
onValueChange={(value) => {
|
||||
setActiveTab(parseInt(value));
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
{tabsList.length > 0 && tabsList[0].title !== "" ? (
|
||||
<TabsList className="flex w-fit items-center rounded bg-muted p-1">
|
||||
{tabsList.map((tab, index) => (
|
||||
<TabsTrigger
|
||||
key={index}
|
||||
value={index.toString()}
|
||||
className="flex items-center gap-2.5 rounded-md !border-0 px-4 py-2 !text-sm data-[state=active]:bg-background"
|
||||
<div className="api-modal-tabs inset-0 m-0 h-full overflow-hidden">
|
||||
<div className="flex h-full flex-col gap-4 overflow-hidden">
|
||||
{/* Main language tabs */}
|
||||
<div className="flex flex-row justify-start border-b border-border">
|
||||
{tabsList.map((tab) => (
|
||||
<Button
|
||||
unstyled
|
||||
key={tab.title}
|
||||
className={`flex h-8 select-none flex-row items-center gap-2 text-nowrap border-b-2 border-border border-b-transparent !py-1 font-medium ${
|
||||
selectedTab === tab.title
|
||||
? "border-b-2 border-black dark:border-b-white"
|
||||
: "text-muted-foreground hover:text-foreground"
|
||||
} px-3 py-2 text-[13px]`}
|
||||
onClick={() => setSelectedTab(tab.title)}
|
||||
data-testid={`api_tab_${tab.title.toLowerCase()}`}
|
||||
>
|
||||
<IconComponent name={tab.icon} className="h-4 w-4" />
|
||||
<span>{tab.title}</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Platform selection for cURL */}
|
||||
{selectedTab === "cURL" && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<Tabs value={selectedPlatform} onValueChange={setSelectedPlatform}>
|
||||
<TabsList>
|
||||
{operatingSystemTabs.map((tab, index) => (
|
||||
<TabsTrigger
|
||||
className="flex select-none items-center gap-2"
|
||||
key={index}
|
||||
value={tab.name}
|
||||
>
|
||||
<IconComponent name={tab.icon} aria-hidden="true" />
|
||||
{tab.title}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Code content */}
|
||||
{currentTab && (
|
||||
<div className="api-modal-tabs-content overflow-hidden">
|
||||
<div className="relative flex h-full w-full">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={copyToClipboard}
|
||||
data-testid="btn-copy-code"
|
||||
className="!hover:bg-foreground group absolute right-4 top-2 z-10 select-none"
|
||||
>
|
||||
<IconComponent name={tab.icon} className="h-4 w-4" />
|
||||
{tab.title}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
) : (
|
||||
<div></div>
|
||||
{isCopied ? (
|
||||
<IconComponent
|
||||
name="Check"
|
||||
className="h-5 w-5 text-muted-foreground"
|
||||
/>
|
||||
) : (
|
||||
<IconComponent
|
||||
name="Copy"
|
||||
className="!h-5 !w-5 text-muted-foreground"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
<SyntaxHighlighter
|
||||
showLineNumbers={true}
|
||||
wrapLongLines={true}
|
||||
language={currentTab.language}
|
||||
style={dark ? oneDark : oneLight}
|
||||
className="!mt-0 h-full w-full overflow-scroll !rounded-b-md border border-border text-left !custom-scroll"
|
||||
>
|
||||
{currentTab.code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{tabsList.map((tab, idx) => (
|
||||
<TabsContent
|
||||
value={idx.toString()}
|
||||
className="api-modal-tabs-content mt-4 overflow-hidden"
|
||||
key={idx}
|
||||
>
|
||||
<div className="relative flex h-full w-full">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={copyToClipboard}
|
||||
data-testid="btn-copy-code"
|
||||
className="!hover:bg-foreground group absolute right-4 top-2"
|
||||
>
|
||||
{isCopied ? (
|
||||
<IconComponent
|
||||
name="Check"
|
||||
className="h-5 w-5 text-muted-foreground"
|
||||
/>
|
||||
) : (
|
||||
<IconComponent
|
||||
name="Copy"
|
||||
className="!h-5 !w-5 text-muted-foreground"
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
<SyntaxHighlighter
|
||||
showLineNumbers={true}
|
||||
wrapLongLines={true}
|
||||
language={tab.language}
|
||||
style={dark ? oneDark : oneLight}
|
||||
className="!mt-0 h-full w-full overflow-scroll !rounded-b-md border border-border text-left !custom-scroll"
|
||||
>
|
||||
{tab.code}
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
</TabsContent>
|
||||
))}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export default function ApiModal({
|
|||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-8 px-3"
|
||||
className="h-8 select-none px-3"
|
||||
onClick={() => setOpenTweaks(true)}
|
||||
data-testid="tweaks-button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -43,31 +43,68 @@ export function getNewCurlCode({
|
|||
isAuthenticated,
|
||||
endpointName,
|
||||
processedPayload,
|
||||
platform,
|
||||
}: {
|
||||
flowId: string;
|
||||
isAuthenticated: boolean;
|
||||
endpointName: string;
|
||||
processedPayload: any;
|
||||
platform?: "unix" | "powershell";
|
||||
}): string {
|
||||
const { protocol, host } = customGetHostProtocol();
|
||||
const apiUrl = `${protocol}//${host}/api/v1/run/${endpointName || flowId}`;
|
||||
|
||||
const formattedJsonPayload = JSON.stringify(processedPayload, null, 2)
|
||||
.split("\n")
|
||||
.map((line, index) => (index === 0 ? line : " " + line))
|
||||
.join("\n\t\t");
|
||||
// Auto-detect if no platform specified
|
||||
const detectedPlatform =
|
||||
platform ||
|
||||
(/Windows|Win32|Win64|WOW32|WOW64/i.test(navigator.userAgent)
|
||||
? "powershell"
|
||||
: "unix");
|
||||
|
||||
return `${
|
||||
isAuthenticated
|
||||
? `# Get API key from environment variable
|
||||
const singleLinePayload = JSON.stringify(processedPayload);
|
||||
|
||||
if (detectedPlatform === "powershell") {
|
||||
// PowerShell with here-string (most robust for complex JSON)
|
||||
return `${
|
||||
isAuthenticated
|
||||
? `if (-not $env:LANGFLOW_API_KEY) {
|
||||
Write-Error "LANGFLOW_API_KEY environment variable not found"
|
||||
exit 1
|
||||
}
|
||||
|
||||
`
|
||||
: ""
|
||||
}$jsonData = @'
|
||||
${singleLinePayload}
|
||||
'@
|
||||
|
||||
curl --request POST \`
|
||||
--url "${apiUrl}?stream=false" \`
|
||||
--header "Content-Type: application/json"${
|
||||
isAuthenticated
|
||||
? ` \`
|
||||
--header "x-api-key: $env:LANGFLOW_API_KEY"`
|
||||
: ""
|
||||
} \`
|
||||
--data $jsonData`;
|
||||
} else {
|
||||
// Unix-like systems (Linux, Mac, WSL2)
|
||||
const unixFormattedPayload = JSON.stringify(processedPayload, null, 2)
|
||||
.split("\n")
|
||||
.map((line, index) => (index === 0 ? line : " " + line))
|
||||
.join("\n\t\t");
|
||||
|
||||
return `${
|
||||
isAuthenticated
|
||||
? `# Get API key from environment variable
|
||||
if [ -z "$LANGFLOW_API_KEY" ]; then
|
||||
echo "Error: LANGFLOW_API_KEY environment variable not found. Please set your API key in the environment variables."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
`
|
||||
: ""
|
||||
}curl --request POST \\
|
||||
: ""
|
||||
}curl --request POST \\
|
||||
--url '${apiUrl}?stream=false' \\
|
||||
--header 'Content-Type: application/json' \\${
|
||||
isAuthenticated
|
||||
|
|
@ -75,5 +112,6 @@ fi
|
|||
--header "x-api-key: $LANGFLOW_API_KEY" \\`
|
||||
: ""
|
||||
}
|
||||
--data '${formattedJsonPayload}'`;
|
||||
--data '${unixFormattedPayload}'`;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -905,7 +905,7 @@
|
|||
@apply flex items-center justify-between px-2 py-2;
|
||||
}
|
||||
.api-modal-tabs-content {
|
||||
@apply -mt-1 h-full w-full pb-4;
|
||||
@apply h-full w-full;
|
||||
}
|
||||
.api-modal-accordion-display {
|
||||
@apply mt-2 flex h-full w-full;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ test(
|
|||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("publish-button").click();
|
||||
await page.getByTestId("api-access-item").click();
|
||||
await page.getByRole("tab", { name: "cURL" }).click();
|
||||
await page.getByTestId("api_tab_curl").click();
|
||||
await page.getByTestId("icon-Copy").click();
|
||||
const handle = await page.evaluateHandle(() =>
|
||||
navigator.clipboard.readText(),
|
||||
|
|
@ -35,7 +35,7 @@ test(
|
|||
|
||||
await page.getByText("Close").last().click();
|
||||
|
||||
await page.getByRole("tab", { name: "cURL" }).click();
|
||||
await page.getByTestId("api_tab_curl").click();
|
||||
await page.getByTestId("icon-Copy").click();
|
||||
const handle2 = await page.evaluateHandle(() =>
|
||||
navigator.clipboard.readText(),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ test(
|
|||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("publish-button").click();
|
||||
await page.getByTestId("api-access-item").click();
|
||||
await page.getByRole("tab", { name: "cURL" }).click();
|
||||
await page.getByTestId("api_tab_curl").click();
|
||||
await page.getByTestId("icon-Copy").last().click();
|
||||
const handle = await page.evaluateHandle(() =>
|
||||
navigator.clipboard.readText(),
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ test(
|
|||
await page.getByRole("heading", { name: "Basic Prompting" }).click();
|
||||
await page.getByTestId("publish-button").click();
|
||||
await page.getByTestId("api-access-item").click();
|
||||
await page.getByRole("tab", { name: "Python" }).click();
|
||||
await page.getByTestId("api_tab_python").click();
|
||||
await page.getByTestId("icon-Copy").click();
|
||||
const handle = await page.evaluateHandle(() =>
|
||||
navigator.clipboard.readText(),
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ test(
|
|||
|
||||
await page.getByTestId("button-send").click();
|
||||
|
||||
await page.getByRole("tab", { name: "python" }).isVisible({
|
||||
await page.getByTestId("api_tab_python").isVisible({
|
||||
timeout: 100000,
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue