Jon/UI updates 09 15 1 (#3441)

This commit is contained in:
Jonathan Dobson
2025-09-15 18:54:03 -04:00
committed by GitHub
parent 6ee329866b
commit b6c1e16c96
11 changed files with 476 additions and 204 deletions

View File

@@ -304,6 +304,7 @@ export type WorkflowRunApiResponse = {
failure_reason: string | null; failure_reason: string | null;
modified_at: string; modified_at: string;
proxy_location: ProxyLocation | null; proxy_location: ProxyLocation | null;
script_run: boolean | null;
status: Status; status: Status;
title?: string; title?: string;
webhook_callback_url: string; webhook_callback_url: string;

View File

@@ -24,6 +24,7 @@ import { WorkflowPostRunParameters } from "./routes/workflows/workflowRun/Workfl
import { WorkflowRunOutput } from "./routes/workflows/workflowRun/WorkflowRunOutput"; import { WorkflowRunOutput } from "./routes/workflows/workflowRun/WorkflowRunOutput";
import { WorkflowRunOverview } from "./routes/workflows/workflowRun/WorkflowRunOverview"; import { WorkflowRunOverview } from "./routes/workflows/workflowRun/WorkflowRunOverview";
import { WorkflowRunRecording } from "./routes/workflows/workflowRun/WorkflowRunRecording"; import { WorkflowRunRecording } from "./routes/workflows/workflowRun/WorkflowRunRecording";
import { WorkflowRunCode } from "@/routes/workflows/workflowRun/WorkflowRunCode";
import { DebugStoreProvider } from "@/store/DebugStoreContext"; import { DebugStoreProvider } from "@/store/DebugStoreContext";
const router = createBrowserRouter([ const router = createBrowserRouter([
@@ -158,6 +159,12 @@ const router = createBrowserRouter([
path: "recording", path: "recording",
element: <WorkflowRunRecording />, element: <WorkflowRunRecording />,
}, },
{
path: "code",
element: (
<WorkflowRunCode showCacheKeyValueSelector={true} />
),
},
], ],
}, },
], ],

View File

@@ -1,3 +1,6 @@
import { LightningBoltIcon } from "@radix-ui/react-icons";
import { Tip } from "@/components/Tip";
import { Status, Task, WorkflowRunApiResponse } from "@/api/types"; import { Status, Task, WorkflowRunApiResponse } from "@/api/types";
import { StatusBadge } from "@/components/StatusBadge"; import { StatusBadge } from "@/components/StatusBadge";
import { StatusFilterDropdown } from "@/components/StatusFilterDropdown"; import { StatusFilterDropdown } from "@/components/StatusFilterDropdown";
@@ -162,6 +165,19 @@ function RunHistory() {
</TableRow> </TableRow>
); );
} }
const workflowTitle =
run.script_run === true ? (
<div className="flex items-center gap-2">
<Tip content="Ran with code">
<LightningBoltIcon className="text-[gold]" />
</Tip>
<span>{run.workflow_title ?? ""}</span>
</div>
) : (
run.workflow_title ?? ""
);
return ( return (
<TableRow <TableRow
key={run.workflow_run_id} key={run.workflow_run_id}
@@ -183,7 +199,7 @@ function RunHistory() {
className="max-w-0 truncate" className="max-w-0 truncate"
title={run.workflow_title ?? undefined} title={run.workflow_title ?? undefined}
> >
{run.workflow_title ?? ""} {workflowTitle}
</TableCell> </TableCell>
<TableCell> <TableCell>
<StatusBadge status={run.status} /> <StatusBadge status={run.status} />

View File

@@ -2,6 +2,12 @@ import { getClient } from "@/api/AxiosClient";
import { ProxyLocation } from "@/api/types"; import { ProxyLocation } from "@/api/types";
import { ProxySelector } from "@/components/ProxySelector"; import { ProxySelector } from "@/components/ProxySelector";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { import {
Form, Form,
FormControl, FormControl,
@@ -311,7 +317,7 @@ function RunWorkflowForm({
<div className="space-y-8 rounded-lg bg-slate-elevation3 px-6 py-5"> <div className="space-y-8 rounded-lg bg-slate-elevation3 px-6 py-5">
<header> <header>
<h1 className="text-lg">Advanced Settings</h1> <h1 className="text-lg">Settings</h1>
</header> </header>
<FormField <FormField
key="webhookCallbackUrl" key="webhookCallbackUrl"
@@ -433,118 +439,136 @@ function RunWorkflowForm({
); );
}} }}
/> />
<FormField </div>
key="cdpAddress"
control={form.control} <div className="space-y-8 rounded-lg bg-slate-elevation3 px-6 py-5">
name="cdpAddress" <Accordion type="single" collapsible>
render={({ field }) => { <AccordionItem value="advanced" className="border-b-0">
return ( <AccordionTrigger className="py-0">
<FormItem> <header>
<div className="flex gap-16"> <h1 className="text-lg">Advanced Settings</h1>
<FormLabel> </header>
<div className="w-72"> </AccordionTrigger>
<div className="flex items-center gap-2 text-lg"> <AccordionContent className="pl-6 pr-1 pt-1">
Browser Address <div className="space-y-8 pt-5">
</div> <FormField
<h2 className="text-sm text-slate-400"> key="cdpAddress"
The address of the Browser server to use for the control={form.control}
workflow run. name="cdpAddress"
</h2> render={({ field }) => {
</div> return (
</FormLabel> <FormItem>
<div className="w-full space-y-2"> <div className="flex gap-16">
<FormControl> <FormLabel>
<Input <div className="w-72">
{...field} <div className="flex items-center gap-2 text-lg">
placeholder="http://127.0.0.1:9222" Browser Address
value={ </div>
field.value === null ? "" : (field.value as string) <h2 className="text-sm text-slate-400">
} The address of the Browser server to use for
/> the workflow run.
</FormControl> </h2>
<FormMessage /> </div>
</div> </FormLabel>
</div> <div className="w-full space-y-2">
</FormItem> <FormControl>
); <Input
}} {...field}
/> placeholder="http://127.0.0.1:9222"
<FormField value={
key="extraHttpHeaders" field.value === null
control={form.control} ? ""
name="extraHttpHeaders" : (field.value as string)
render={({ field }) => { }
return ( />
<FormItem> </FormControl>
<div className="flex gap-16"> <FormMessage />
<FormLabel> </div>
<div className="w-72"> </div>
<div className="flex items-center gap-2 text-lg"> </FormItem>
Extra HTTP Headers );
</div> }}
<h2 className="text-sm text-slate-400"> />
Specify some self defined HTTP requests headers in <FormField
Dict format key="extraHttpHeaders"
</h2> control={form.control}
</div> name="extraHttpHeaders"
</FormLabel> render={({ field }) => {
<div className="w-full space-y-2"> return (
<FormControl> <FormItem>
<KeyValueInput <div className="flex gap-16">
value={field.value ?? ""} <FormLabel>
onChange={(val) => field.onChange(val)} <div className="w-72">
addButtonText="Add Header" <div className="flex items-center gap-2 text-lg">
/> Extra HTTP Headers
</FormControl> </div>
<FormMessage /> <h2 className="text-sm text-slate-400">
</div> Specify some self defined HTTP requests
</div> headers in Dict format
</FormItem> </h2>
); </div>
}} </FormLabel>
/> <div className="w-full space-y-2">
<FormField <FormControl>
key="maxScreenshotScrolls" <KeyValueInput
control={form.control} value={field.value ?? ""}
name="maxScreenshotScrolls" onChange={(val) => field.onChange(val)}
render={({ field }) => { addButtonText="Add Header"
return ( />
<FormItem> </FormControl>
<div className="flex gap-16"> <FormMessage />
<FormLabel> </div>
<div className="w-72"> </div>
<div className="flex items-center gap-2 text-lg"> </FormItem>
Max Screenshot Scrolls );
</div> }}
<h2 className="text-sm text-slate-400"> />
{`The maximum number of scrolls for the post action screenshot. Default is ${MAX_SCREENSHOT_SCROLLS_DEFAULT}. If it's set to 0, it will take the current viewport screenshot.`} <FormField
</h2> key="maxScreenshotScrolls"
</div> control={form.control}
</FormLabel> name="maxScreenshotScrolls"
<div className="w-full space-y-2"> render={({ field }) => {
<FormControl> return (
<Input <FormItem>
{...field} <div className="flex gap-16">
type="number" <FormLabel>
min={0} <div className="w-72">
value={field.value ?? ""} <div className="flex items-center gap-2 text-lg">
placeholder={`Default: ${MAX_SCREENSHOT_SCROLLS_DEFAULT}`} Max Screenshot Scrolls
onChange={(event) => { </div>
const value = <h2 className="text-sm text-slate-400">
event.target.value === "" {`The maximum number of scrolls for the post action screenshot. Default is ${MAX_SCREENSHOT_SCROLLS_DEFAULT}. If it's set to 0, it will take the current viewport screenshot.`}
? null </h2>
: Number(event.target.value); </div>
field.onChange(value); </FormLabel>
}} <div className="w-full space-y-2">
/> <FormControl>
</FormControl> <Input
<FormMessage /> {...field}
</div> type="number"
</div> min={0}
</FormItem> value={field.value ?? ""}
); placeholder={`Default: ${MAX_SCREENSHOT_SCROLLS_DEFAULT}`}
}} onChange={(event) => {
/> const value =
event.target.value === ""
? null
: Number(event.target.value);
field.onChange(value);
}}
/>
</FormControl>
<FormMessage />
</div>
</div>
</FormItem>
);
}}
/>
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div> </div>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
<CopyApiCommandDropdown <CopyApiCommandDropdown

View File

@@ -1,3 +1,6 @@
import { LightningBoltIcon } from "@radix-ui/react-icons";
import { Tip } from "@/components/Tip";
import { Status } from "@/api/types"; import { Status } from "@/api/types";
import { StatusBadge } from "@/components/StatusBadge"; import { StatusBadge } from "@/components/StatusBadge";
import { StatusFilterDropdown } from "@/components/StatusFilterDropdown"; import { StatusFilterDropdown } from "@/components/StatusFilterDropdown";
@@ -129,34 +132,50 @@ function WorkflowPage() {
<TableCell colSpan={3}>No workflow runs found</TableCell> <TableCell colSpan={3}>No workflow runs found</TableCell>
</TableRow> </TableRow>
) : ( ) : (
workflowRuns?.map((workflowRun) => ( workflowRuns?.map((workflowRun) => {
<TableRow const workflowRunId =
key={workflowRun.workflow_run_id} workflowRun.script_run === true ? (
onClick={(event) => { <div className="flex items-center gap-2">
if (event.ctrlKey || event.metaKey) { <Tip content="Ran with code">
window.open( <LightningBoltIcon className="text-[gold]" />
window.location.origin + </Tip>
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`, <span>{workflowRun.workflow_run_id ?? ""}</span>
"_blank", </div>
"noopener,noreferrer", ) : (
workflowRun.workflow_run_id ?? ""
);
return (
<TableRow
key={workflowRun.workflow_run_id}
onClick={(event) => {
if (event.ctrlKey || event.metaKey) {
window.open(
window.location.origin +
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`,
"_blank",
"noopener,noreferrer",
);
return;
}
navigate(
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`,
); );
return; }}
} className="cursor-pointer"
navigate( >
`/workflows/${workflowPermanentId}/${workflowRun.workflow_run_id}/overview`, <TableCell>{workflowRunId}</TableCell>
); <TableCell>
}} <StatusBadge status={workflowRun.status} />
className="cursor-pointer" </TableCell>
> <TableCell
<TableCell>{workflowRun.workflow_run_id}</TableCell> title={basicTimeFormat(workflowRun.created_at)}
<TableCell> >
<StatusBadge status={workflowRun.status} /> {basicLocalTimeFormat(workflowRun.created_at)}
</TableCell> </TableCell>
<TableCell title={basicTimeFormat(workflowRun.created_at)}> </TableRow>
{basicLocalTimeFormat(workflowRun.created_at)} );
</TableCell> })
</TableRow>
))
)} )}
</TableBody> </TableBody>
</Table> </Table>

View File

@@ -53,6 +53,8 @@ function WorkflowRun() {
workflowPermanentId, workflowPermanentId,
}); });
const hasScript = false;
const { const {
data: workflowRun, data: workflowRun,
isLoading: workflowRunIsLoading, isLoading: workflowRunIsLoading,
@@ -206,6 +208,32 @@ function WorkflowRun() {
webhookFailureReasonData) && webhookFailureReasonData) &&
workflowRun.status === Status.Completed; workflowRun.status === Status.Completed;
const switchBarOptions = [
{
label: "Overview",
to: "overview",
},
{
label: "Output",
to: "output",
},
{
label: "Parameters",
to: "parameters",
},
{
label: "Recording",
to: "recording",
},
];
if (!hasScript) {
switchBarOptions.push({
label: "Code",
to: "code",
});
}
return ( return (
<div className="space-y-8"> <div className="space-y-8">
{!isEmbedded && ( {!isEmbedded && (
@@ -352,28 +380,7 @@ function WorkflowRun() {
</div> </div>
)} )}
{workflowFailureReason} {workflowFailureReason}
{!isEmbedded && ( {!isEmbedded && <SwitchBarNavigation options={switchBarOptions} />}
<SwitchBarNavigation
options={[
{
label: "Overview",
to: "overview",
},
{
label: "Output",
to: "output",
},
{
label: "Parameters",
to: "parameters",
},
{
label: "Recording",
to: "recording",
},
]}
/>
)}
<div className="flex h-[42rem] gap-6"> <div className="flex h-[42rem] gap-6">
<div className="w-2/3"> <div className="w-2/3">
<Outlet /> <Outlet />

View File

@@ -36,7 +36,7 @@ import { ModelSelector } from "@/components/ModelSelector";
import { useBlockScriptStore } from "@/store/BlockScriptStore"; import { useBlockScriptStore } from "@/store/BlockScriptStore";
import { cn } from "@/util/utils"; import { cn } from "@/util/utils";
import { NodeHeader } from "../components/NodeHeader"; import { NodeHeader } from "../components/NodeHeader";
import { NodeFooter } from "../components/NodeFooter"; import { NodeTabs } from "../components/NodeTabs";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types"; import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery"; import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
@@ -278,7 +278,7 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
<NodeFooter blockLabel={label} /> <NodeTabs blockLabel={label} />
</div> </div>
</div> </div>
<BlockCodeEditor blockLabel={label} blockType={type} script={script} /> <BlockCodeEditor blockLabel={label} blockType={type} script={script} />

View File

@@ -17,7 +17,7 @@ import { MAX_STEPS_DEFAULT, type Taskv2Node } from "./types";
import { ModelSelector } from "@/components/ModelSelector"; import { ModelSelector } from "@/components/ModelSelector";
import { cn } from "@/util/utils"; import { cn } from "@/util/utils";
import { NodeHeader } from "../components/NodeHeader"; import { NodeHeader } from "../components/NodeHeader";
import { NodeFooter } from "../components/NodeFooter"; import { NodeTabs } from "../components/NodeTabs";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { statusIsRunningOrQueued } from "@/routes/tasks/types"; import { statusIsRunningOrQueued } from "@/routes/tasks/types";
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery"; import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
@@ -196,7 +196,7 @@ function Taskv2Node({ id, data, type }: NodeProps<Taskv2Node>) {
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
<NodeFooter blockLabel={label} /> <NodeTabs blockLabel={label} />
</div> </div>
</div> </div>
); );

View File

@@ -19,7 +19,7 @@ interface Props {
blockLabel: string; blockLabel: string;
} }
function NodeFooter({ blockLabel }: Props) { function NodeTabs({ blockLabel }: Props) {
const { blockLabel: urlBlockLabel } = useParams(); const { blockLabel: urlBlockLabel } = useParams();
const blockOutput = useBlockOutputStore((state) => state.outputs[blockLabel]); const blockOutput = useBlockOutputStore((state) => state.outputs[blockLabel]);
const [isExpanded, setIsExpanded] = useState(false); const [isExpanded, setIsExpanded] = useState(false);
@@ -61,46 +61,61 @@ function NodeFooter({ blockLabel }: Props) {
</div> </div>
</div> </div>
</div> </div>
<div className="relative flex w-full overflow-visible bg-[pink]"> <div
<TooltipProvider> className={cn(
<Tooltip> "absolute right-[-1rem] top-0 h-[6rem] w-[2rem] overflow-visible",
<TooltipTrigger asChild> { "top-[2.5rem]": thisBlockIsTargetted },
<div )}
className={cn( >
"absolute bottom-[-2.25rem] right-[-0.75rem] flex h-[2.5rem] w-[2.5rem] items-center justify-center gap-2 rounded-[50%] bg-slate-elevation3 p-2", <div className="relative flex h-full w-full items-start justify-center gap-1 overflow-visible">
{ <TooltipProvider>
"opacity-100 outline outline-2 outline-slate-300": <Tooltip>
thisBlockIsTargetted, <TooltipTrigger asChild>
}, <div
)}
>
<Button
variant="link"
size="sm"
className={cn( className={cn(
"p-0 opacity-80 hover:translate-y-[-1px] hover:opacity-100 active:translate-y-[0px]", "flex h-[2.5rem] w-[2.5rem] min-w-[2.5rem] rotate-[-90deg] items-center justify-center gap-2 rounded-[50%] bg-slate-elevation3 p-2",
{ "opacity-100": isExpanded }, {
"opacity-100 outline outline-2 outline-slate-300":
thisBlockIsTargetted,
},
{
"hover:translate-x-[1px] active:translate-x-[0px]":
blockOutput,
},
)} )}
onClick={() => {
setIsExpanded(!isExpanded);
}}
> >
{isExpanded ? ( <Button
<CrossCircledIcon className="scale-[110%]" /> variant="link"
) : ( size="sm"
<OutputIcon className="scale-[80%]" /> disabled={!blockOutput}
)} className={cn("p-0 opacity-80 hover:opacity-100", {
</Button> "opacity-100": isExpanded,
</div> })}
</TooltipTrigger> onClick={() => {
<TooltipContent> setIsExpanded(!isExpanded);
{isExpanded ? "Close Outputs" : "Open Outputs"} }}
</TooltipContent> >
</Tooltip> {isExpanded ? (
</TooltipProvider> <CrossCircledIcon className="scale-[110%]" />
) : (
<OutputIcon className="scale-[80%]" />
)}
</Button>
</div>
</TooltipTrigger>
<TooltipContent>
{!blockOutput
? "No outputs. Run block first."
: isExpanded
? "Close Outputs"
: "Open Outputs"}
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
</div> </div>
</> </>
); );
} }
export { NodeFooter }; export { NodeTabs };

View File

@@ -115,8 +115,12 @@ const getInitialParameters = (workflow: WorkflowApiResponse) => {
*/ */
const constructCacheKeyValue = ( const constructCacheKeyValue = (
codeKey: string, codeKey: string,
workflow: WorkflowApiResponse, workflow?: WorkflowApiResponse,
) => { ) => {
if (!workflow) {
return "";
}
const workflowParameters = getInitialParameters(workflow) const workflowParameters = getInitialParameters(workflow)
.filter((p) => p.parameterType === "workflow") .filter((p) => p.parameterType === "workflow")
.reduce( .reduce(

View File

@@ -0,0 +1,179 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { useQueryClient } from "@tanstack/react-query";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { HelpTooltip } from "@/components/HelpTooltip";
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery";
import { useCacheKeyValuesQuery } from "@/routes/workflows/hooks/useCacheKeyValuesQuery";
import { useWorkflowQuery } from "@/routes/workflows/hooks/useWorkflowQuery";
import { constructCacheKeyValue } from "@/routes/workflows/editor/utils";
import { WorkflowApiResponse } from "@/routes/workflows/types/workflowTypes";
interface Props {
showCacheKeyValueSelector?: boolean;
}
const getOrderedBlockLabels = (workflow?: WorkflowApiResponse) => {
if (!workflow) {
return [];
}
const blockLabels = workflow.workflow_definition.blocks.map(
(block) => block.label,
);
return blockLabels;
};
const getCommentForBlockWithoutCode = (blockLabel: string) => {
return `
# If the "Generate Code" option is turned on for this workflow when it runs, AI will execute block '${blockLabel}', and generate code for it.
`;
};
const getCode = (
orderedBlockLabels: string[],
blockScripts?: {
[blockName: string]: string;
},
): string[] => {
const blockCode: string[] = [];
const startBlockCode = blockScripts?.__start_block__;
if (startBlockCode) {
blockCode.push(startBlockCode);
}
for (const blockLabel of orderedBlockLabels) {
const code = blockScripts?.[blockLabel];
if (!code) {
blockCode.push(getCommentForBlockWithoutCode(blockLabel));
continue;
}
blockCode.push(`${code}
`);
}
return blockCode;
};
function WorkflowRunCode(props?: Props) {
const showCacheKeyValueSelector = props?.showCacheKeyValueSelector ?? false;
const queryClient = useQueryClient();
const { workflowPermanentId } = useParams();
const { data: workflow } = useWorkflowQuery({
workflowPermanentId,
});
const cacheKey = workflow?.cache_key ?? "";
const [cacheKeyValue, setCacheKeyValue] = useState(
cacheKey === "" ? "" : constructCacheKeyValue(cacheKey, workflow),
);
const { data: cacheKeyValues } = useCacheKeyValuesQuery({
cacheKey,
debounceMs: 100,
page: 1,
workflowPermanentId,
});
useEffect(() => {
setCacheKeyValue(
cacheKeyValues?.values[0] ?? constructCacheKeyValue(cacheKey, workflow),
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cacheKeyValues, setCacheKeyValue, workflow]);
useEffect(() => {
queryClient.invalidateQueries({
queryKey: [
"cache-key-values",
workflowPermanentId,
cacheKey,
1,
undefined,
],
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [workflow]);
const { data: blockScripts } = useBlockScriptsQuery({
cacheKey,
cacheKeyValue,
workflowPermanentId,
});
const orderedBlockLabels = getOrderedBlockLabels(workflow);
const code = getCode(orderedBlockLabels, blockScripts).join("");
if (code.length === 0) {
return (
<div className="flex items-center justify-center bg-slate-elevation3 p-8">
No code has been generated yet.
</div>
);
}
if (
!showCacheKeyValueSelector ||
(cacheKeyValues?.values ?? []).length <= 1
) {
return (
<CodeEditor
className="h-full overflow-y-scroll"
language="python"
value={code}
lineWrap={false}
readOnly
fontSize={10}
/>
);
}
return (
<div className="flex h-full w-full flex-col items-end justify-center gap-2">
<div className="flex w-[20rem] gap-4">
<div className="flex items-center justify-around gap-2">
<Label className="w-[7rem]">Code Cache Key</Label>
<HelpTooltip content="Which generated (& cached) code to view." />
</div>
<Select
value={cacheKeyValue}
onValueChange={(v: string) => setCacheKeyValue(v)}
>
<SelectTrigger>
<SelectValue placeholder="Code Key Value" />
</SelectTrigger>
<SelectContent>
{(cacheKeyValues?.values ?? []).map((value) => {
return (
<SelectItem key={value} value={value}>
{value}
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
<CodeEditor
className="h-full w-full overflow-y-scroll"
language="python"
value={code}
lineWrap={false}
readOnly
fontSize={10}
/>
</div>
);
}
export { WorkflowRunCode };