Jon/sky 5906 debugger show actions inputs outputs for a block run (#3202)
This commit is contained in:
@@ -317,6 +317,7 @@ export type WorkflowRunStatusApiResponse = {
|
||||
webhook_callback_url: string | null;
|
||||
extra_http_headers: Record<string, string> | null;
|
||||
created_at: string;
|
||||
finished_at: string;
|
||||
modified_at: string;
|
||||
parameters: Record<string, unknown>;
|
||||
screenshot_urls: Array<string> | null;
|
||||
|
||||
@@ -7,10 +7,26 @@ interface HMS {
|
||||
}
|
||||
|
||||
interface Props {
|
||||
override?: number;
|
||||
startAt?: HMS;
|
||||
}
|
||||
|
||||
function Timer({ startAt }: Props) {
|
||||
const formatMs = (elapsed: number) => {
|
||||
let seconds = Math.floor(elapsed / 1000);
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
let hours = Math.floor(minutes / 60);
|
||||
seconds = seconds % 60;
|
||||
minutes = minutes % 60;
|
||||
hours = hours % 24;
|
||||
|
||||
return {
|
||||
hour: hours,
|
||||
minute: minutes,
|
||||
second: seconds,
|
||||
};
|
||||
};
|
||||
|
||||
function Timer({ override, startAt }: Props) {
|
||||
const [time, setTime] = useState<HMS>({
|
||||
hour: 0,
|
||||
minute: 0,
|
||||
@@ -18,20 +34,22 @@ function Timer({ startAt }: Props) {
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (override) {
|
||||
const formatted = formatMs(override);
|
||||
setTime(() => formatted);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const start = performance.now();
|
||||
|
||||
const loop = () => {
|
||||
const elapsed = performance.now() - start;
|
||||
let seconds = Math.floor(elapsed / 1000);
|
||||
let minutes = Math.floor(seconds / 60);
|
||||
let hours = Math.floor(minutes / 60);
|
||||
seconds = seconds % 60;
|
||||
minutes = minutes % 60;
|
||||
hours = hours % 24;
|
||||
const formatted = formatMs(elapsed);
|
||||
setTime(() => ({
|
||||
hour: hours + (startAt?.hour ?? 0),
|
||||
minute: minutes + (startAt?.minute ?? 0),
|
||||
second: seconds + (startAt?.second ?? 0),
|
||||
hour: formatted.hour + (startAt?.hour ?? 0),
|
||||
minute: formatted.minute + (startAt?.minute ?? 0),
|
||||
second: formatted.second + (startAt?.second ?? 0),
|
||||
}));
|
||||
|
||||
rAF = requestAnimationFrame(loop);
|
||||
@@ -40,7 +58,7 @@ function Timer({ startAt }: Props) {
|
||||
let rAF = requestAnimationFrame(loop);
|
||||
|
||||
return () => cancelAnimationFrame(rAF);
|
||||
}, [startAt]);
|
||||
}, [override, startAt]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -18,7 +18,7 @@ import { WorkflowRun } from "./routes/workflows/WorkflowRun";
|
||||
import { WorkflowRunParameters } from "./routes/workflows/WorkflowRunParameters";
|
||||
import { Workflows } from "./routes/workflows/Workflows";
|
||||
import { WorkflowsPageLayout } from "./routes/workflows/WorkflowsPageLayout";
|
||||
import { WorkflowDebugger } from "./routes/workflows/editor/WorkflowDebugger";
|
||||
import { Debugger } from "./routes/workflows/debugger/Debugger";
|
||||
import { WorkflowEditor } from "./routes/workflows/editor/WorkflowEditor";
|
||||
import { WorkflowPostRunParameters } from "./routes/workflows/workflowRun/WorkflowPostRunParameters";
|
||||
import { WorkflowRunOutput } from "./routes/workflows/workflowRun/WorkflowRunOutput";
|
||||
@@ -111,11 +111,11 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: "debug",
|
||||
element: <WorkflowDebugger />,
|
||||
element: <Debugger />,
|
||||
},
|
||||
{
|
||||
path: ":workflowRunId/:blockLabel/debug",
|
||||
element: <WorkflowDebugger />,
|
||||
element: <Debugger />,
|
||||
},
|
||||
{
|
||||
path: "edit",
|
||||
|
||||
@@ -4,12 +4,12 @@ import { ReactFlowProvider } from "@xyflow/react";
|
||||
|
||||
import { useWorkflowQuery } from "../hooks/useWorkflowQuery";
|
||||
import { WorkflowSettings } from "../types/workflowTypes";
|
||||
import { getElements } from "./workflowEditorUtils";
|
||||
import { getInitialParameters } from "./utils";
|
||||
import { Workspace } from "./Workspace";
|
||||
import { getElements } from "@/routes/workflows/editor/workflowEditorUtils";
|
||||
import { getInitialParameters } from "@/routes/workflows/editor/utils";
|
||||
import { Workspace } from "@/routes/workflows/editor/Workspace";
|
||||
import { useWorkflowParametersStore } from "@/store/WorkflowParametersStore";
|
||||
|
||||
function WorkflowDebugger() {
|
||||
function Debugger() {
|
||||
const { workflowPermanentId } = useParams();
|
||||
const { data: workflow } = useWorkflowQuery({
|
||||
workflowPermanentId,
|
||||
@@ -64,4 +64,4 @@ function WorkflowDebugger() {
|
||||
);
|
||||
}
|
||||
|
||||
export { WorkflowDebugger };
|
||||
export { Debugger };
|
||||
@@ -0,0 +1,201 @@
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { CodeEditor } from "../components/CodeEditor";
|
||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
||||
import { useActiveWorkflowRunItem } from "@/routes/workflows/workflowRun/useActiveWorkflowRunItem";
|
||||
import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuery";
|
||||
import { isAction, isWorkflowRunBlock } from "../types/workflowRunTypes";
|
||||
import { findBlockSurroundingAction } from "@/routes/workflows/workflowRun/workflowTimelineUtils";
|
||||
import { DebuggerTaskBlockParameters } from "./DebuggerTaskBlockParameters";
|
||||
import { isTaskVariantBlock, WorkflowBlockTypes } from "../types/workflowTypes";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { ProxySelector } from "@/components/ProxySelector";
|
||||
import { DebuggerSendEmailBlockParameters } from "./DebuggerSendEmailBlockInfo";
|
||||
import { ProxyLocation } from "@/api/types";
|
||||
import { KeyValueInput } from "@/components/KeyValueInput";
|
||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||
|
||||
function DebuggerPostRunParameters() {
|
||||
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
|
||||
useWorkflowRunTimelineQuery();
|
||||
const [activeItem] = useActiveWorkflowRunItem();
|
||||
const { data: workflowRun, isLoading: workflowRunIsLoading } =
|
||||
useWorkflowRunQuery();
|
||||
const parameters = workflowRun?.parameters ?? {};
|
||||
|
||||
if (workflowRunIsLoading || workflowRunTimelineIsLoading) {
|
||||
return <div>Loading workflow parameters...</div>;
|
||||
}
|
||||
|
||||
if (!workflowRun || !workflowRunTimeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getActiveBlock() {
|
||||
if (!workflowRunTimeline) {
|
||||
return;
|
||||
}
|
||||
if (isWorkflowRunBlock(activeItem)) {
|
||||
return activeItem;
|
||||
}
|
||||
if (isAction(activeItem)) {
|
||||
return findBlockSurroundingAction(
|
||||
workflowRunTimeline,
|
||||
activeItem.action_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const activeBlock = getActiveBlock();
|
||||
const isTaskV2 = workflowRun.task_v2 !== null;
|
||||
|
||||
const webhookCallbackUrl = isTaskV2
|
||||
? workflowRun.task_v2?.webhook_callback_url
|
||||
: workflowRun.webhook_callback_url;
|
||||
|
||||
const proxyLocation = isTaskV2
|
||||
? workflowRun.task_v2?.proxy_location
|
||||
: workflowRun.proxy_location;
|
||||
|
||||
const extraHttpHeaders = isTaskV2
|
||||
? workflowRun.task_v2?.extra_http_headers
|
||||
: workflowRun.extra_http_headers;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
{activeBlock && isTaskVariantBlock(activeBlock) ? (
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Task Block Parameters</h1>
|
||||
<DebuggerTaskBlockParameters block={activeBlock} />
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{activeBlock &&
|
||||
activeBlock.block_type === WorkflowBlockTypes.SendEmail ? (
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Email Block Parameters</h1>
|
||||
<DebuggerSendEmailBlockParameters
|
||||
body={activeBlock?.body ?? ""}
|
||||
recipients={activeBlock?.recipients ?? []}
|
||||
subject={activeBlock?.subject ?? ""}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{activeBlock && activeBlock.block_type === WorkflowBlockTypes.ForLoop ? (
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">For Loop Block Parameters</h1>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Loop Values</h1>
|
||||
<HelpTooltip content="The values that are being looped over." />
|
||||
</div>
|
||||
<CodeEditor
|
||||
className="w-full"
|
||||
language="json"
|
||||
value={JSON.stringify(activeBlock?.loop_values, null, 2)}
|
||||
readOnly
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Workflow Parameters</h1>
|
||||
{Object.entries(parameters).map(([key, value]) => {
|
||||
return (
|
||||
<div key={key} className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">{key}</h1>
|
||||
<HelpTooltip content="The value of the parameter." />
|
||||
</div>
|
||||
{typeof value === "string" ||
|
||||
typeof value === "number" ||
|
||||
typeof value === "boolean" ? (
|
||||
<AutoResizingTextarea value={String(value)} readOnly />
|
||||
) : (
|
||||
<CodeEditor
|
||||
value={JSON.stringify(value, null, 2)}
|
||||
readOnly
|
||||
language="json"
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{Object.entries(parameters).length === 0 ? (
|
||||
<div className="text-sm">
|
||||
No input parameters found for this workflow
|
||||
</div>
|
||||
) : null}
|
||||
<h1 className="text-sm font-bold">Other Workflow Parameters</h1>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Webhook Callback URL</h1>
|
||||
<HelpTooltip content="The webhook callback URL for the workflow." />
|
||||
</div>
|
||||
<Input value={webhookCallbackUrl ?? ""} readOnly />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Proxy Location</h1>
|
||||
<HelpTooltip content="The proxy location for the workflow." />
|
||||
</div>
|
||||
<ProxySelector
|
||||
value={proxyLocation ?? ProxyLocation.Residential}
|
||||
onChange={() => {
|
||||
// TODO
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Extra HTTP Headers</h1>
|
||||
<HelpTooltip content="The extra HTTP headers for the workflow." />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<KeyValueInput
|
||||
value={
|
||||
extraHttpHeaders ? JSON.stringify(extraHttpHeaders) : null
|
||||
}
|
||||
readOnly={true}
|
||||
onChange={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{workflowRun.task_v2 ? (
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Task 2.0 Parameters</h1>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Task 2.0 Prompt</h1>
|
||||
<HelpTooltip content="The original prompt for the task." />
|
||||
</div>
|
||||
<AutoResizingTextarea
|
||||
value={workflowRun.task_v2?.prompt ?? ""}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
<AutoResizingTextarea
|
||||
value={workflowRun.task_v2?.prompt ?? ""}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { DebuggerPostRunParameters };
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
import { WorkflowDebuggerRunTimeline } from "./WorkflowDebuggerRunTimeline";
|
||||
import { DebuggerRunTimeline } from "./DebuggerRunTimeline";
|
||||
|
||||
function WorkflowDebuggerRun() {
|
||||
function DebuggerRun() {
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
|
||||
const workflowFailureReason = workflowRun?.failure_reason ? (
|
||||
<div
|
||||
className="m-4 w-full rounded-md border border-red-600 p-4"
|
||||
className="align-self-start max-h-[8rem] w-full overflow-y-auto rounded-md border border-red-600 p-4"
|
||||
style={{
|
||||
backgroundColor: "rgba(220, 38, 38, 0.10)",
|
||||
width: "calc(100% - 2rem)",
|
||||
@@ -19,19 +19,17 @@ function WorkflowDebuggerRun() {
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col items-center justify-start overflow-hidden overflow-y-auto">
|
||||
<div className="flex h-full w-full flex-col items-center justify-start gap-4 bg-[#0c1121]">
|
||||
{workflowFailureReason}
|
||||
<div className="h-full w-full">
|
||||
<WorkflowDebuggerRunTimeline
|
||||
activeItem="stream"
|
||||
onActionItemSelected={() => {}}
|
||||
onBlockItemSelected={() => {}}
|
||||
onObserverThoughtCardSelected={() => {}}
|
||||
/>
|
||||
</div>
|
||||
{workflowFailureReason}
|
||||
<div className="h-full w-full">
|
||||
<DebuggerRunTimeline
|
||||
activeItem="stream"
|
||||
onActionItemSelected={() => {}}
|
||||
onBlockItemSelected={() => {}}
|
||||
onObserverThoughtCardSelected={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { WorkflowDebuggerRun };
|
||||
export { DebuggerRun };
|
||||
@@ -0,0 +1,175 @@
|
||||
import { FileIcon } from "@radix-ui/react-icons";
|
||||
import { CodeEditor } from "../components/CodeEditor";
|
||||
import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery";
|
||||
import { useActiveWorkflowRunItem } from "@/routes/workflows/workflowRun/useActiveWorkflowRunItem";
|
||||
import {
|
||||
hasExtractedInformation,
|
||||
isAction,
|
||||
isWorkflowRunBlock,
|
||||
} from "../types/workflowRunTypes";
|
||||
import { findBlockSurroundingAction } from "@/routes/workflows/workflowRun/workflowTimelineUtils";
|
||||
import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuery";
|
||||
import { Status } from "@/api/types";
|
||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
||||
import { isTaskVariantBlock } from "../types/workflowTypes";
|
||||
|
||||
function DebuggerRunOutput() {
|
||||
const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } =
|
||||
useWorkflowRunTimelineQuery();
|
||||
const [activeItem] = useActiveWorkflowRunItem();
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
|
||||
if (workflowRunTimelineIsLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!workflowRunTimeline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getActiveBlock() {
|
||||
if (!workflowRunTimeline) {
|
||||
return;
|
||||
}
|
||||
if (isWorkflowRunBlock(activeItem)) {
|
||||
return activeItem;
|
||||
}
|
||||
if (isAction(activeItem)) {
|
||||
return findBlockSurroundingAction(
|
||||
workflowRunTimeline,
|
||||
activeItem.action_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const activeBlock = getActiveBlock();
|
||||
|
||||
const showExtractedInformation =
|
||||
activeBlock &&
|
||||
isTaskVariantBlock(activeBlock) &&
|
||||
activeBlock.status === Status.Completed;
|
||||
|
||||
const outputs = workflowRun?.outputs;
|
||||
const fileUrls = workflowRun?.downloaded_file_urls ?? [];
|
||||
const observerOutput = workflowRun?.task_v2?.output;
|
||||
const webhookFailureReasonData =
|
||||
workflowRun?.task_v2?.webhook_failure_reason ??
|
||||
workflowRun?.webhook_failure_reason;
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
{webhookFailureReasonData ? (
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Webhook Failure Reason</h1>
|
||||
<div className="space-y-2 text-yellow-600">
|
||||
{webhookFailureReasonData}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{activeBlock ? (
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Block Outputs</h1>
|
||||
{activeBlock.output === null ? (
|
||||
<div className="text-sm">This block has no outputs</div>
|
||||
) : isTaskVariantBlock(activeBlock) ? (
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-sm">
|
||||
{showExtractedInformation
|
||||
? "Extracted Information"
|
||||
: "Failure Reason"}
|
||||
</h2>
|
||||
{showExtractedInformation ? (
|
||||
<CodeEditor
|
||||
language="json"
|
||||
value={JSON.stringify(
|
||||
(hasExtractedInformation(activeBlock.output) &&
|
||||
activeBlock.output.extracted_information) ??
|
||||
null,
|
||||
null,
|
||||
2,
|
||||
)}
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
readOnly
|
||||
/>
|
||||
) : (
|
||||
<AutoResizingTextarea
|
||||
value={
|
||||
activeBlock.status === "canceled"
|
||||
? "This block was cancelled"
|
||||
: activeBlock.failure_reason ?? ""
|
||||
}
|
||||
readOnly
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
<h2 className="text-sm">Output</h2>
|
||||
<CodeEditor
|
||||
language="json"
|
||||
value={JSON.stringify(activeBlock.output, null, 2)}
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{observerOutput ? (
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Task 2.0 Output</h1>
|
||||
<CodeEditor
|
||||
language="json"
|
||||
value={JSON.stringify(observerOutput, null, 2)}
|
||||
readOnly
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Workflow Run Outputs</h1>
|
||||
<CodeEditor
|
||||
language="json"
|
||||
value={outputs ? JSON.stringify(outputs, null, 2) : ""}
|
||||
readOnly
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded bg-slate-elevation2 p-6">
|
||||
<div className="space-y-4">
|
||||
<h1 className="text-sm font-bold">Workflow Run Downloaded Files</h1>
|
||||
<div className="space-y-2">
|
||||
{fileUrls.length > 0 ? (
|
||||
fileUrls.map((url, index) => {
|
||||
return (
|
||||
<div key={url} title={url} className="flex gap-2">
|
||||
<FileIcon className="size-6" />
|
||||
<a href={url} className="underline underline-offset-4">
|
||||
<span>{`File ${index + 1}`}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div className="text-sm">No files downloaded</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { DebuggerRunOutput };
|
||||
@@ -25,7 +25,7 @@ type Props = {
|
||||
onBlockItemSelected: (item: WorkflowRunBlock) => void;
|
||||
};
|
||||
|
||||
function WorkflowDebuggerRunTimeline({
|
||||
function DebuggerRunTimeline({
|
||||
activeItem,
|
||||
onObserverThoughtCardSelected,
|
||||
onActionItemSelected,
|
||||
@@ -126,4 +126,4 @@ function WorkflowDebuggerRunTimeline({
|
||||
);
|
||||
}
|
||||
|
||||
export { WorkflowDebuggerRunTimeline };
|
||||
export { DebuggerRunTimeline };
|
||||
@@ -0,0 +1,43 @@
|
||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||
|
||||
type Props = {
|
||||
recipients: Array<string>;
|
||||
body: string;
|
||||
subject: string;
|
||||
};
|
||||
|
||||
function DebuggerSendEmailBlockParameters({
|
||||
recipients,
|
||||
body,
|
||||
subject,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">To</h1>
|
||||
<HelpTooltip content="The recipients of the email." />
|
||||
</div>
|
||||
<Input value={recipients.join(", ")} readOnly />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Subject</h1>
|
||||
<HelpTooltip content="The subject of the email." />
|
||||
</div>
|
||||
<Input value={subject} readOnly />
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Body</h1>
|
||||
<HelpTooltip content="The body of the email." />
|
||||
</div>
|
||||
<AutoResizingTextarea value={body} readOnly />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export { DebuggerSendEmailBlockParameters };
|
||||
@@ -0,0 +1,147 @@
|
||||
import { AutoResizingTextarea } from "@/components/AutoResizingTextarea/AutoResizingTextarea";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { CodeEditor } from "@/routes/workflows/components/CodeEditor";
|
||||
import { WorkflowRunBlock } from "../types/workflowRunTypes";
|
||||
import { isTaskVariantBlock, WorkflowBlockTypes } from "../types/workflowTypes";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { HelpTooltip } from "@/components/HelpTooltip";
|
||||
|
||||
type Props = {
|
||||
block: WorkflowRunBlock;
|
||||
};
|
||||
|
||||
function DebuggerTaskBlockParameters({ block }: Props) {
|
||||
const isTaskVariant = isTaskVariantBlock(block);
|
||||
if (!isTaskVariant) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const showNavigationParameters =
|
||||
block.block_type === WorkflowBlockTypes.Task ||
|
||||
block.block_type === WorkflowBlockTypes.Action ||
|
||||
block.block_type === WorkflowBlockTypes.Login ||
|
||||
block.block_type === WorkflowBlockTypes.Navigation;
|
||||
|
||||
const showDataExtractionParameters =
|
||||
block.block_type === WorkflowBlockTypes.Task ||
|
||||
block.block_type === WorkflowBlockTypes.Extraction;
|
||||
|
||||
const showValidationParameters =
|
||||
block.block_type === WorkflowBlockTypes.Validation;
|
||||
|
||||
const showIncludeActionHistoryInVerification =
|
||||
block.block_type === WorkflowBlockTypes.Task ||
|
||||
block.block_type === WorkflowBlockTypes.Navigation;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">URL</h1>
|
||||
<HelpTooltip content="The starting URL for the block." />
|
||||
</div>
|
||||
<Input value={block.url ?? ""} readOnly />
|
||||
</div>
|
||||
|
||||
{showNavigationParameters ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Navigation Goal</h1>
|
||||
<HelpTooltip content="What should Skyvern do on this page?" />
|
||||
</div>
|
||||
<AutoResizingTextarea value={block.navigation_goal ?? ""} readOnly />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showNavigationParameters ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-nowrap text-sm">Navigation Payload</h1>
|
||||
<HelpTooltip content="Specify important parameters, routes, or states." />
|
||||
</div>
|
||||
<CodeEditor
|
||||
className="w-full"
|
||||
language="json"
|
||||
value={JSON.stringify(block.navigation_payload, null, 2)}
|
||||
readOnly
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showDataExtractionParameters ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Data Extraction Goal</h1>
|
||||
<HelpTooltip content="What outputs are you looking to get?" />
|
||||
</div>
|
||||
<AutoResizingTextarea
|
||||
value={block.data_extraction_goal ?? ""}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showDataExtractionParameters ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Data Schema</h1>
|
||||
<HelpTooltip content="Specify the output format in JSON" />
|
||||
</div>
|
||||
<CodeEditor
|
||||
className="w-full"
|
||||
language="json"
|
||||
value={JSON.stringify(block.data_schema, null, 2)}
|
||||
readOnly
|
||||
minHeight="96px"
|
||||
maxHeight="200px"
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showValidationParameters ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Completion Criteria</h1>
|
||||
<HelpTooltip content="Complete if..." />
|
||||
</div>
|
||||
<AutoResizingTextarea
|
||||
value={block.complete_criterion ?? ""}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showValidationParameters ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-sm">Termination Criteria</h1>
|
||||
<HelpTooltip content="Terminate if..." />
|
||||
</div>
|
||||
<AutoResizingTextarea
|
||||
value={block.terminate_criterion ?? ""}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{showIncludeActionHistoryInVerification ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex w-full items-center justify-start gap-2">
|
||||
<h1 className="text-nowrap text-sm">Include Action History</h1>
|
||||
<HelpTooltip content="Whether to include action history in the completion verification" />
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<Switch
|
||||
checked={block.include_action_history_in_verification ?? false}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { DebuggerTaskBlockParameters };
|
||||
@@ -226,7 +226,7 @@ type Props = {
|
||||
initialTitle: string;
|
||||
// initialParameters: ParametersState;
|
||||
workflow: WorkflowApiResponse;
|
||||
onDebuggableBlockCountChange: (count: number) => void;
|
||||
onDebuggableBlockCountChange?: (count: number) => void;
|
||||
onMouseDownCapture?: () => void;
|
||||
zIndex?: number;
|
||||
};
|
||||
@@ -305,7 +305,7 @@ function FlowRenderer({
|
||||
}
|
||||
}
|
||||
|
||||
onDebuggableBlockCountChange(debuggable.length);
|
||||
onDebuggableBlockCountChange?.(debuggable.length);
|
||||
}, [nodes, edges, onDebuggableBlockCountChange]);
|
||||
|
||||
const constructSaveData = useCallback((): WorkflowSaveData => {
|
||||
|
||||
@@ -15,17 +15,18 @@ import {
|
||||
ReloadIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useGlobalWorkflowsQuery } from "../hooks/useGlobalWorkflowsQuery";
|
||||
import { EditableNodeTitle } from "./nodes/components/EditableNodeTitle";
|
||||
import { useCreateWorkflowMutation } from "../hooks/useCreateWorkflowMutation";
|
||||
import { convert } from "./workflowEditorUtils";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { useWorkflowTitleStore } from "@/store/WorkflowTitleStore";
|
||||
import { useWorkflowHasChangesStore } from "@/store/WorkflowHasChangesStore";
|
||||
import { cn } from "@/util/utils";
|
||||
|
||||
type Props = {
|
||||
debuggableBlockCount: number;
|
||||
parametersPanelOpen: boolean;
|
||||
onParametersClick: () => void;
|
||||
onSave: () => void;
|
||||
@@ -34,7 +35,6 @@ type Props = {
|
||||
};
|
||||
|
||||
function WorkflowHeader({
|
||||
debuggableBlockCount,
|
||||
parametersPanelOpen,
|
||||
onParametersClick,
|
||||
onSave,
|
||||
@@ -43,13 +43,14 @@ function WorkflowHeader({
|
||||
}: Props) {
|
||||
const { title, setTitle } = useWorkflowTitleStore();
|
||||
const workflowChangesStore = useWorkflowHasChangesStore();
|
||||
const { blockLabel: urlBlockLabel, workflowPermanentId } = useParams();
|
||||
const { workflowPermanentId } = useParams();
|
||||
const { data: globalWorkflows } = useGlobalWorkflowsQuery();
|
||||
const navigate = useNavigate();
|
||||
const createWorkflowMutation = useCreateWorkflowMutation();
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const debugStore = useDebugStore();
|
||||
const anyBlockIsPlaying =
|
||||
urlBlockLabel !== undefined && urlBlockLabel.length > 0;
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
|
||||
if (!globalWorkflows) {
|
||||
return null; // this should be loaded already by some other components
|
||||
@@ -105,7 +106,7 @@ function WorkflowHeader({
|
||||
<Button
|
||||
size="lg"
|
||||
variant={debugStore.isDebugMode ? "default" : "tertiary"}
|
||||
disabled={debuggableBlockCount === 0 || anyBlockIsPlaying}
|
||||
disabled={workflowRunIsRunningOrQueued}
|
||||
onClick={() => {
|
||||
if (debugStore.isDebugMode) {
|
||||
navigate(`/workflows/${workflowPermanentId}/edit`);
|
||||
|
||||
@@ -27,12 +27,15 @@ import {
|
||||
DialogTitle,
|
||||
DialogClose,
|
||||
} from "@/components/ui/dialog";
|
||||
import { SwitchBar } from "@/components/SwitchBar";
|
||||
import { toast } from "@/components/ui/use-toast";
|
||||
import { BrowserStream } from "@/components/BrowserStream";
|
||||
import { FloatingWindow } from "@/components/FloatingWindow";
|
||||
import { statusIsFinalized } from "@/routes/tasks/types.ts";
|
||||
import { WorkflowDebuggerRun } from "@/routes/workflows/editor/WorkflowDebuggerRun";
|
||||
import { DebuggerRun } from "@/routes/workflows/debugger/DebuggerRun";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
import { DebuggerRunOutput } from "@/routes/workflows/debugger/DebuggerRunOutput";
|
||||
import { DebuggerPostRunParameters } from "@/routes/workflows/debugger/DebuggerPostRunParameters";
|
||||
import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { useWorkflowPanelStore } from "@/store/WorkflowPanelStore";
|
||||
import {
|
||||
@@ -80,11 +83,11 @@ function Workspace({
|
||||
showBrowser = false,
|
||||
workflow,
|
||||
}: Props) {
|
||||
const { blockLabel, workflowPermanentId } = useParams();
|
||||
const { blockLabel, workflowPermanentId, workflowRunId } = useParams();
|
||||
const [content, setContent] = useState("actions");
|
||||
const { workflowPanelState, setWorkflowPanelState, closeWorkflowPanel } =
|
||||
useWorkflowPanelStore();
|
||||
const debugStore = useDebugStore();
|
||||
const [debuggableBlockCount, setDebuggableBlockCount] = useState(0);
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes);
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges);
|
||||
const saveWorkflow = useWorkflowSave();
|
||||
@@ -384,7 +387,6 @@ function Workspace({
|
||||
}}
|
||||
>
|
||||
<WorkflowHeader
|
||||
debuggableBlockCount={debuggableBlockCount}
|
||||
saving={workflowChangesStore.saveIsPending}
|
||||
parametersPanelOpen={
|
||||
workflowPanelState.active &&
|
||||
@@ -468,9 +470,38 @@ function Workspace({
|
||||
promote("history");
|
||||
}}
|
||||
>
|
||||
<div className="pointer-events-none absolute right-0 top-0 flex h-full w-[400px] flex-col items-end justify-end">
|
||||
<div className="pointer-events-auto relative h-full w-full overflow-hidden rounded-xl border-2 border-slate-500">
|
||||
<WorkflowDebuggerRun />
|
||||
<div className="pointer-events-none absolute right-0 top-0 flex h-full w-[400px] flex-col items-end justify-end bg-slate-900">
|
||||
<div className="pointer-events-auto relative flex h-full w-full flex-col items-start overflow-hidden rounded-xl border-2 border-slate-500">
|
||||
{workflowRunId && (
|
||||
<SwitchBar
|
||||
className="m-2 border-none"
|
||||
onChange={(value) => setContent(value)}
|
||||
value={content}
|
||||
options={[
|
||||
{
|
||||
label: "Actions",
|
||||
value: "actions",
|
||||
},
|
||||
{
|
||||
label: "Inputs",
|
||||
value: "inputs",
|
||||
},
|
||||
{
|
||||
label: "Outputs",
|
||||
value: "outputs",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
<div className="h-full w-full overflow-hidden overflow-y-auto">
|
||||
{(!workflowRunId || content === "actions") && <DebuggerRun />}
|
||||
{workflowRunId && content === "inputs" && (
|
||||
<DebuggerPostRunParameters />
|
||||
)}
|
||||
{workflowRunId && content === "outputs" && (
|
||||
<DebuggerRunOutput />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -487,7 +518,6 @@ function Workspace({
|
||||
initialTitle={initialTitle}
|
||||
// initialParameters={initialParameters}
|
||||
workflow={workflow}
|
||||
onDebuggableBlockCountChange={(c) => setDebuggableBlockCount(c)}
|
||||
onMouseDownCapture={() => promote("infiniteCanvas")}
|
||||
zIndex={rankedItems.infiniteCanvas}
|
||||
/>
|
||||
|
||||
@@ -38,6 +38,8 @@ import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
const urlTooltip =
|
||||
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
|
||||
@@ -67,8 +69,13 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
|
||||
});
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const debugStore = useDebugStore();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
|
||||
const nodes = useNodes<AppNode>();
|
||||
@@ -108,8 +115,9 @@ function ActionNode({ id, data, type }: NodeProps<ActionNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -8,6 +8,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function CodeBlockNode({ id, data }: NodeProps<CodeBlockNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -15,8 +17,13 @@ function CodeBlockNode({ id, data }: NodeProps<CodeBlockNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
code: data.code,
|
||||
parameterKeys: data.parameterKeys,
|
||||
@@ -40,8 +47,9 @@ function CodeBlockNode({ id, data }: NodeProps<CodeBlockNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -8,14 +8,21 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function DownloadNode({ id, data }: NodeProps<DownloadNode>) {
|
||||
const { debuggable, editable, label } = data;
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -35,8 +42,9 @@ function DownloadNode({ id, data }: NodeProps<DownloadNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -38,6 +38,8 @@ import { useBlockScriptStore } from "@/store/BlockScriptStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -48,8 +50,13 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
url: data.url,
|
||||
dataExtractionGoal: data.dataExtractionGoal,
|
||||
@@ -97,8 +104,9 @@ function ExtractionNode({ id, data, type }: NodeProps<ExtractionNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -38,6 +38,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
const urlTooltip =
|
||||
"The URL Skyvern is navigating to. Leave this field blank to pick up from where the last block left off.";
|
||||
@@ -54,8 +56,13 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||
const script = blockScriptStore.scripts[label];
|
||||
const debugStore = useDebugStore();
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const [inputs, setInputs] = useState({
|
||||
url: data.url,
|
||||
@@ -105,8 +112,9 @@ function FileDownloadNode({ id, data }: NodeProps<FileDownloadNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,8 @@ import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/WorkflowDataSchemaInputGroup";
|
||||
import { dataSchemaExampleForFileExtraction } from "../types";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function FileParserNode({ id, data }: NodeProps<FileParserNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -19,8 +21,13 @@ function FileParserNode({ id, data }: NodeProps<FileParserNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
fileUrl: data.fileUrl,
|
||||
jsonSchema: data.jsonSchema,
|
||||
@@ -54,8 +61,9 @@ function FileParserNode({ id, data }: NodeProps<FileParserNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -17,6 +17,8 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function FileUploadNode({ id, data }: NodeProps<FileUploadNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -24,8 +26,13 @@ function FileUploadNode({ id, data }: NodeProps<FileUploadNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
|
||||
const [inputs, setInputs] = useState({
|
||||
storageType: data.storageType,
|
||||
@@ -65,8 +72,9 @@ function FileUploadNode({ id, data }: NodeProps<FileUploadNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -39,6 +39,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -49,8 +51,13 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
url: data.url,
|
||||
navigationGoal: data.navigationGoal,
|
||||
@@ -102,8 +109,9 @@ function LoginNode({ id, data, type }: NodeProps<LoginNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -20,6 +20,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function LoopNode({ id, data }: NodeProps<LoopNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -32,8 +34,13 @@ function LoopNode({ id, data }: NodeProps<LoopNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
loopVariableReference: data.loopVariableReference,
|
||||
});
|
||||
@@ -94,8 +101,9 @@ function LoopNode({ id, data }: NodeProps<LoopNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -39,6 +39,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function NavigationNode({ id, data, type }: NodeProps<NavigationNode>) {
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
@@ -48,8 +50,13 @@ function NavigationNode({ id, data, type }: NodeProps<NavigationNode>) {
|
||||
const blockScriptStore = useBlockScriptStore();
|
||||
const { editable, debuggable, label } = data;
|
||||
const script = blockScriptStore.scripts[label];
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const [inputs, setInputs] = useState({
|
||||
allowDownloads: data.allowDownloads,
|
||||
@@ -107,8 +114,9 @@ function NavigationNode({ id, data, type }: NodeProps<NavigationNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -19,8 +21,13 @@ function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
fileUrl: data.fileUrl,
|
||||
jsonSchema: data.jsonSchema,
|
||||
@@ -54,8 +61,9 @@ function PDFParserNode({ id, data }: NodeProps<PDFParserNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -12,6 +12,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -19,8 +21,13 @@ function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
recipients: data.recipients,
|
||||
subject: data.subject,
|
||||
@@ -56,8 +63,9 @@ function SendEmailNode({ id, data }: NodeProps<SendEmailNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -40,6 +40,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -50,8 +52,13 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const nodes = useNodes<AppNode>();
|
||||
const edges = useEdges();
|
||||
const outputParameterKeys = getAvailableOutputParameterKeys(nodes, edges, id);
|
||||
@@ -108,8 +115,9 @@ function TaskNode({ id, data, type }: NodeProps<TaskNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -19,14 +19,21 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function Taskv2Node({ id, data, type }: NodeProps<Taskv2Node>) {
|
||||
const { debuggable, editable, label } = data;
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const { updateNodeData } = useReactFlow();
|
||||
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
|
||||
|
||||
@@ -65,8 +72,9 @@ function Taskv2Node({ id, data, type }: NodeProps<Taskv2Node>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -14,6 +14,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -21,8 +23,13 @@ function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
prompt: data.prompt,
|
||||
jsonSchema: data.jsonSchema,
|
||||
@@ -57,8 +64,9 @@ function TextPromptNode({ id, data }: NodeProps<TextPromptNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -13,6 +13,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function URLNode({ id, data, type }: NodeProps<URLNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -23,8 +25,13 @@ function URLNode({ id, data, type }: NodeProps<URLNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const isFirstWorkflowBlock = useIsFirstBlockInWorkflow({ id });
|
||||
|
||||
const [inputs, setInputs] = useState({
|
||||
@@ -62,8 +69,9 @@ function URLNode({ id, data, type }: NodeProps<URLNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -8,14 +8,20 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
function UploadNode({ id, data }: NodeProps<UploadNode>) {
|
||||
const { debuggable, editable, label } = data;
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
|
||||
return (
|
||||
<div>
|
||||
@@ -35,8 +41,9 @@ function UploadNode({ id, data }: NodeProps<UploadNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -36,6 +36,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -46,8 +48,13 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
completeCriterion: data.completeCriterion,
|
||||
terminateCriterion: data.terminateCriterion,
|
||||
@@ -91,8 +98,9 @@ function ValidationNode({ id, data, type }: NodeProps<ValidationNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -10,6 +10,8 @@ import { useDebugStore } from "@/store/useDebugStore";
|
||||
import { cn } from "@/util/utils";
|
||||
import { NodeHeader } from "../components/NodeHeader";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { statusIsRunningOrQueued } from "@/routes/tasks/types";
|
||||
import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery";
|
||||
|
||||
function WaitNode({ id, data, type }: NodeProps<WaitNode>) {
|
||||
const { updateNodeData } = useReactFlow();
|
||||
@@ -17,8 +19,13 @@ function WaitNode({ id, data, type }: NodeProps<WaitNode>) {
|
||||
const debugStore = useDebugStore();
|
||||
const elideFromDebugging = debugStore.isDebugMode && !debuggable;
|
||||
const { blockLabel: urlBlockLabel } = useParams();
|
||||
const thisBlockIsPlaying =
|
||||
const { data: workflowRun } = useWorkflowRunQuery();
|
||||
const workflowRunIsRunningOrQueued =
|
||||
workflowRun && statusIsRunningOrQueued(workflowRun);
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === label;
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued && thisBlockIsTargetted;
|
||||
const [inputs, setInputs] = useState({
|
||||
waitInSeconds: data.waitInSeconds,
|
||||
});
|
||||
@@ -51,8 +58,9 @@ function WaitNode({ id, data, type }: NodeProps<WaitNode>) {
|
||||
className={cn(
|
||||
"transform-origin-center w-[30rem] space-y-4 rounded-lg bg-slate-elevation3 px-6 py-4 transition-all",
|
||||
{
|
||||
"pointer-events-none bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsPlaying,
|
||||
"pointer-events-none": thisBlockIsPlaying,
|
||||
"bg-slate-950 outline outline-2 outline-slate-300":
|
||||
thisBlockIsTargetted,
|
||||
},
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -138,10 +138,6 @@ function NodeHeader({
|
||||
} = useParams();
|
||||
const debugStore = useDebugStore();
|
||||
const { closeWorkflowPanel } = useWorkflowPanelStore();
|
||||
const thisBlockIsPlaying =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === blockLabel;
|
||||
const anyBlockIsPlaying =
|
||||
urlBlockLabel !== undefined && urlBlockLabel.length > 0;
|
||||
const workflowSettingsStore = useWorkflowSettingsStore();
|
||||
const [label, setLabel] = useNodeLabelChangeHandler({
|
||||
id: nodeId,
|
||||
@@ -164,6 +160,21 @@ function NodeHeader({
|
||||
});
|
||||
const saveWorkflow = useWorkflowSave();
|
||||
|
||||
const thisBlockIsPlaying =
|
||||
workflowRunIsRunningOrQueued &&
|
||||
urlBlockLabel !== undefined &&
|
||||
urlBlockLabel === blockLabel;
|
||||
|
||||
const thisBlockIsTargetted =
|
||||
urlBlockLabel !== undefined && urlBlockLabel === blockLabel;
|
||||
|
||||
const timerDurationOverride =
|
||||
workflowRun && workflowRun.finished_at
|
||||
? new Date(workflowRun.finished_at).getTime() -
|
||||
new Date(workflowRun.created_at).getTime() +
|
||||
3500
|
||||
: null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!workflowRun || !workflowPermanentId || !workflowRunId) {
|
||||
return;
|
||||
@@ -173,7 +184,7 @@ function NodeHeader({
|
||||
workflowRunId === workflowRun?.workflow_run_id &&
|
||||
statusIsFinalized(workflowRun)
|
||||
) {
|
||||
navigate(`/workflows/${workflowPermanentId}/debug`);
|
||||
// navigate(`/workflows/${workflowPermanentId}/debug`);
|
||||
|
||||
if (statusIsAFailureType(workflowRun)) {
|
||||
toast({
|
||||
@@ -340,10 +351,10 @@ function NodeHeader({
|
||||
|
||||
return (
|
||||
<>
|
||||
{thisBlockIsPlaying && (
|
||||
{thisBlockIsTargetted && (
|
||||
<div className="flex w-full animate-[auto-height_1s_ease-in-out_forwards] items-center justify-between overflow-hidden">
|
||||
<div className="pb-4">
|
||||
<Timer />
|
||||
<Timer override={timerDurationOverride ?? undefined} />
|
||||
</div>
|
||||
<div className="pb-4">{workflowRun?.status ?? "pending"}</div>
|
||||
</div>
|
||||
@@ -370,7 +381,7 @@ function NodeHeader({
|
||||
</div>
|
||||
</div>
|
||||
<div className="pointer-events-auto ml-auto flex items-center gap-2">
|
||||
{thisBlockIsPlaying && workflowRunIsRunningOrQueued && (
|
||||
{thisBlockIsPlaying && (
|
||||
<div className="ml-auto">
|
||||
<button className="rounded p-1 hover:bg-red-500 hover:text-black disabled:opacity-50">
|
||||
{cancelBlock.isPending ? (
|
||||
@@ -388,9 +399,9 @@ function NodeHeader({
|
||||
)}
|
||||
{debugStore.isDebugMode && isDebuggable && (
|
||||
<button
|
||||
disabled={anyBlockIsPlaying}
|
||||
disabled={workflowRunIsRunningOrQueued}
|
||||
className={cn("rounded p-1 disabled:opacity-50", {
|
||||
"hover:bg-muted": anyBlockIsPlaying,
|
||||
"hover:bg-muted": workflowRunIsRunningOrQueued,
|
||||
})}
|
||||
>
|
||||
{runBlock.isPending ? (
|
||||
@@ -399,7 +410,7 @@ function NodeHeader({
|
||||
<PlayIcon
|
||||
className={cn("size-6", {
|
||||
"fill-gray-500 text-gray-500":
|
||||
anyBlockIsPlaying || !workflowPermanentId,
|
||||
workflowRunIsRunningOrQueued || !workflowPermanentId,
|
||||
})}
|
||||
onClick={() => {
|
||||
handleOnPlay();
|
||||
@@ -412,7 +423,8 @@ function NodeHeader({
|
||||
<div>
|
||||
<div
|
||||
className={cn("rounded p-1 hover:bg-muted", {
|
||||
"pointer-events-none opacity-50": anyBlockIsPlaying,
|
||||
"pointer-events-none opacity-50":
|
||||
workflowRunIsRunningOrQueued,
|
||||
})}
|
||||
>
|
||||
<NodeActionMenu
|
||||
|
||||
Reference in New Issue
Block a user