code caching: enabled/disabled UX for workflow run UI (#3497)
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import { getClient } from "@/api/AxiosClient";
|
||||
import { ProxyLocation, Status } from "@/api/types";
|
||||
import { StatusBadge } from "@/components/StatusBadge";
|
||||
import { SwitchBarNavigation } from "@/components/SwitchBarNavigation";
|
||||
import {
|
||||
SwitchBarNavigation,
|
||||
type SwitchBarNavigationOption,
|
||||
} from "@/components/SwitchBarNavigation";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -19,6 +22,7 @@ import { useApiCredential } from "@/hooks/useApiCredential";
|
||||
import { useCredentialGetter } from "@/hooks/useCredentialGetter";
|
||||
import { apiBaseUrl } from "@/util/env";
|
||||
import {
|
||||
CodeIcon,
|
||||
FileIcon,
|
||||
Pencil2Icon,
|
||||
PlayIcon,
|
||||
@@ -61,6 +65,8 @@ function WorkflowRun() {
|
||||
isFetched,
|
||||
} = useWorkflowRunQuery();
|
||||
|
||||
const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null;
|
||||
|
||||
const { data: workflowRunTimeline } = useWorkflowRunTimelineQuery();
|
||||
|
||||
const cancelWorkflowMutation = useMutation({
|
||||
@@ -208,7 +214,7 @@ function WorkflowRun() {
|
||||
webhookFailureReasonData) &&
|
||||
workflowRun.status === Status.Completed;
|
||||
|
||||
const switchBarOptions = [
|
||||
const switchBarOptions: SwitchBarNavigationOption[] = [
|
||||
{
|
||||
label: "Overview",
|
||||
to: "overview",
|
||||
@@ -227,10 +233,18 @@ function WorkflowRun() {
|
||||
},
|
||||
];
|
||||
|
||||
const isGeneratingCode = !isFinalized && workflow?.generate_script === true;
|
||||
|
||||
if (!hasScript) {
|
||||
switchBarOptions.push({
|
||||
label: "Code",
|
||||
to: "code",
|
||||
icon:
|
||||
isFinalized || !isGeneratingCode ? (
|
||||
<CodeIcon className="inline-block size-5" />
|
||||
) : (
|
||||
<ReloadIcon className="inline-block size-5 animate-spin" />
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
import { Flippable } from "@/components/Flippable";
|
||||
import { useRerender } from "@/hooks/useRerender";
|
||||
import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||
import { useUiStore } from "@/store/UiStore";
|
||||
import { BlockCodeEditor } from "@/routes/workflows/components/BlockCodeEditor";
|
||||
import { cn } from "@/util/utils";
|
||||
|
||||
@@ -78,11 +79,31 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
||||
runSequentially: data.withWorkflowSettings ? data.runSequentially : false,
|
||||
});
|
||||
|
||||
const { highlightGenerateCodeToggle, setHighlightGenerateCodeToggle } =
|
||||
useUiStore();
|
||||
const [facing, setFacing] = useState<"front" | "back">("front");
|
||||
const blockScriptStore = useBlockScriptStore();
|
||||
const script = blockScriptStore.scripts.__start_block__;
|
||||
const rerender = useRerender({ prefix: "accordion" });
|
||||
const toggleScriptForNodeCallback = useToggleScriptForNodeCallback();
|
||||
const [expandWorkflowSettings, setExpandWorkflowSettings] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const tm = setTimeout(() => {
|
||||
if (highlightGenerateCodeToggle) {
|
||||
setExpandWorkflowSettings(true);
|
||||
rerender.bump();
|
||||
|
||||
setTimeout(() => {
|
||||
setHighlightGenerateCodeToggle(false);
|
||||
}, 3000);
|
||||
}
|
||||
}, 200);
|
||||
|
||||
return () => clearTimeout(tm);
|
||||
// onMount only
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setFacing(data.showCode ? "back" : "front");
|
||||
@@ -137,6 +158,10 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
||||
}
|
||||
}
|
||||
|
||||
const defaultWorkflowSettings = expandWorkflowSettings
|
||||
? "settings"
|
||||
: undefined;
|
||||
|
||||
if (data.withWorkflowSettings) {
|
||||
return (
|
||||
<Flippable facing={facing} preserveFrontsideHeight={true}>
|
||||
@@ -159,7 +184,12 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
||||
<Accordion
|
||||
type="single"
|
||||
collapsible
|
||||
onValueChange={() => rerender.bump()}
|
||||
value={defaultWorkflowSettings}
|
||||
defaultValue={defaultWorkflowSettings}
|
||||
onValueChange={(value) => {
|
||||
setExpandWorkflowSettings(value === "settings");
|
||||
rerender.bump();
|
||||
}}
|
||||
>
|
||||
<AccordionItem value="settings" className="mt-4 border-b-0">
|
||||
<AccordionTrigger className="py-2">
|
||||
@@ -207,10 +237,16 @@ function StartNode({ id, data }: NodeProps<StartNode>) {
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label>Run Cached Code</Label>
|
||||
<div
|
||||
className={cn("flex items-center gap-2", {
|
||||
"animate-pulse rounded-md bg-yellow-600/20":
|
||||
highlightGenerateCodeToggle,
|
||||
})}
|
||||
>
|
||||
<Label>Generate Code</Label>
|
||||
<HelpTooltip content="If code has been cached, run the workflow using code for faster execution." />
|
||||
<Switch
|
||||
disabled={inputs.useScriptCache === true} // TODO(jdo/always-generate): remove
|
||||
className="ml-auto"
|
||||
checked={inputs.useScriptCache}
|
||||
onCheckedChange={(value) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ type Props = {
|
||||
cacheKeyValue?: string;
|
||||
workflowPermanentId?: string;
|
||||
pollIntervalMs?: number;
|
||||
status?: string;
|
||||
status?: "pending" | "published";
|
||||
workflowRunId?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ExclamationTriangleIcon, ReloadIcon } from "@radix-ui/react-icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
@@ -19,6 +20,7 @@ import { useWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowQuery";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
import { constructCacheKeyValue } from "@/routes/workflows/editor/utils";
|
||||
import { getCode, getOrderedBlockLabels } from "@/routes/workflows/utils";
|
||||
import { useUiStore } from "@/store/UiStore";
|
||||
|
||||
interface Props {
|
||||
showCacheKeyValueSelector?: boolean;
|
||||
@@ -44,6 +46,7 @@ function WorkflowRunCode(props?: Props) {
|
||||
page: 1,
|
||||
workflowPermanentId,
|
||||
});
|
||||
|
||||
const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null;
|
||||
const parameters = workflowRun?.parameters;
|
||||
|
||||
@@ -52,11 +55,17 @@ function WorkflowRunCode(props?: Props) {
|
||||
cacheKeyValue,
|
||||
workflowPermanentId,
|
||||
pollIntervalMs: !isFinalized ? 3000 : undefined,
|
||||
status: "pending",
|
||||
status: isFinalized ? "published" : "pending",
|
||||
workflowRunId: workflowRun?.workflow_run_id,
|
||||
});
|
||||
|
||||
const orderedBlockLabels = getOrderedBlockLabels(workflow);
|
||||
const code = getCode(orderedBlockLabels, blockScripts).join("").trim();
|
||||
const isGeneratingCode = !isFinalized && workflow?.generate_script === true;
|
||||
const couldBeGeneratingCode =
|
||||
!isFinalized && workflow?.generate_script !== true;
|
||||
|
||||
const { setHighlightGenerateCodeToggle } = useUiStore();
|
||||
|
||||
useEffect(() => {
|
||||
setCacheKeyValue(
|
||||
@@ -93,7 +102,7 @@ function WorkflowRunCode(props?: Props) {
|
||||
});
|
||||
}, [queryClient, workflowRun, workflowPermanentId, cacheKey, cacheKeyValue]);
|
||||
|
||||
if (code.length === 0) {
|
||||
if (code.length === 0 && isFinalized) {
|
||||
return (
|
||||
<div className="flex items-center justify-center bg-slate-elevation3 p-8">
|
||||
No code has been generated yet.
|
||||
@@ -101,19 +110,6 @@ function WorkflowRunCode(props?: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
if (!showCacheKeyValueSelector || !cacheKey || cacheKey === "") {
|
||||
return (
|
||||
<CodeEditor
|
||||
className="h-full overflow-y-scroll"
|
||||
language="python"
|
||||
value={code}
|
||||
lineWrap={false}
|
||||
readOnly
|
||||
fontSize={10}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const cacheKeyValueSet = new Set([...(cacheKeyValues?.values ?? [])]);
|
||||
|
||||
const cacheKeyValueForWorkflowRun = constructCacheKeyValue({
|
||||
@@ -127,52 +123,87 @@ function WorkflowRunCode(props?: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-end justify-center gap-2">
|
||||
<div className="flex w-full justify-end gap-4">
|
||||
<div className="flex items-center justify-around gap-2">
|
||||
<Label className="w-[7rem]">Code Key Value</Label>
|
||||
<HelpTooltip
|
||||
content={
|
||||
!isFinalized
|
||||
? "The code key value the generated code is being stored under."
|
||||
: "Which generated (& cached) code to view."
|
||||
}
|
||||
/>
|
||||
<div className="flex h-full w-full flex-col items-end justify-start gap-2">
|
||||
{isGeneratingCode && (
|
||||
<div className="mb-6 flex w-full gap-2 rounded-md border-[1px] border-[slate-300] p-2">
|
||||
<div className="p6 flex w-full items-center justify-center rounded-l-md bg-slate-elevation5 px-4 py-2 text-sm">
|
||||
Generating code...
|
||||
</div>
|
||||
<div className="p6 flex items-center justify-center rounded-r-md bg-slate-elevation5 px-4 py-2 text-sm">
|
||||
<ReloadIcon className="size-8 animate-spin" />
|
||||
</div>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!isFinalized}
|
||||
value={cacheKeyValue}
|
||||
onValueChange={(v: string) => setCacheKeyValue(v)}
|
||||
>
|
||||
<SelectTrigger className="max-w-[15rem] [&>span]:text-ellipsis">
|
||||
<SelectValue placeholder="Code Key Value" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from(cacheKeyValueSet)
|
||||
.sort()
|
||||
.map((value) => {
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{value === cacheKeyValueForWorkflowRun &&
|
||||
isFinalized === true ? (
|
||||
<span className="underline">{value}</span>
|
||||
) : (
|
||||
value
|
||||
)}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<CodeEditor
|
||||
className="h-full w-full overflow-y-scroll"
|
||||
language="python"
|
||||
value={code}
|
||||
lineWrap={false}
|
||||
readOnly
|
||||
fontSize={10}
|
||||
/>
|
||||
)}
|
||||
{couldBeGeneratingCode && (
|
||||
<div className="mb-6 flex w-full gap-2 rounded-md border-[1px] border-[slate-300] p-2">
|
||||
<div className="flex w-full items-center justify-center gap-2 rounded-l-md text-sm">
|
||||
<div className="flex-1 bg-slate-elevation5 p-4">
|
||||
Code generation disabled for this run. Please enable{" "}
|
||||
<a
|
||||
className="underline hover:text-sky-500"
|
||||
href={`${location.origin}/workflows/${workflowPermanentId}/debug`}
|
||||
target="_blank"
|
||||
onClick={() => setHighlightGenerateCodeToggle(true)}
|
||||
>
|
||||
Generate Code
|
||||
</a>{" "}
|
||||
in your Workflow Settings to have Skyvern generate code.
|
||||
</div>
|
||||
</div>
|
||||
<div className="p6 flex items-center justify-center rounded-r-md bg-slate-elevation5 px-4 py-2 text-sm">
|
||||
<ExclamationTriangleIcon className="size-8 text-[gold]" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{showCacheKeyValueSelector && cacheKey && cacheKey !== "" && (
|
||||
<div className="flex w-full justify-end gap-4">
|
||||
<div className="flex items-center justify-around gap-2">
|
||||
<Label className="w-[7rem]">Code Key Value</Label>
|
||||
<HelpTooltip
|
||||
content={
|
||||
!isFinalized
|
||||
? "The code key value the generated code is being stored under."
|
||||
: "Which generated (& cached) code to view."
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Select
|
||||
disabled={!isFinalized}
|
||||
value={cacheKeyValue}
|
||||
onValueChange={(v: string) => setCacheKeyValue(v)}
|
||||
>
|
||||
<SelectTrigger className="max-w-[15rem] [&>span]:text-ellipsis">
|
||||
<SelectValue placeholder="Code Key Value" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{Array.from(cacheKeyValueSet)
|
||||
.sort()
|
||||
.map((value) => {
|
||||
return (
|
||||
<SelectItem key={value} value={value}>
|
||||
{value === cacheKeyValueForWorkflowRun &&
|
||||
isFinalized === true ? (
|
||||
<span className="underline">{value}</span>
|
||||
) : (
|
||||
value
|
||||
)}
|
||||
</SelectItem>
|
||||
);
|
||||
})}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
{(isGeneratingCode || (code && code.length > 0)) && (
|
||||
<CodeEditor
|
||||
className="h-full w-full overflow-y-scroll"
|
||||
language="python"
|
||||
value={code}
|
||||
lineWrap={false}
|
||||
readOnly
|
||||
fontSize={10}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user