diff --git a/skyvern-frontend/src/components/FloatingWindow.tsx b/skyvern-frontend/src/components/FloatingWindow.tsx index 06f5b413..e9339809 100644 --- a/skyvern-frontend/src/components/FloatingWindow.tsx +++ b/skyvern-frontend/src/components/FloatingWindow.tsx @@ -6,6 +6,7 @@ * and `re-resizable`; but I don't want to do that until it's worth the effort.) */ +import { OpenInNewWindowIcon } from "@radix-ui/react-icons"; import { ReloadIcon } from "@radix-ui/react-icons"; import { Resizable } from "re-resizable"; import { @@ -77,6 +78,27 @@ function WindowsButton(props: { ); } +/** + * Button to open browser in a new tab. + */ +function BreakoutButton(props: { onClick: () => void }) { + return ( + + + + + + Open In New Tab + + + ); +} + function PowerButton(props: { onClick: () => void }) { return ( @@ -149,6 +171,7 @@ function FloatingWindow({ initialWidth, initialHeight, maximized, + showBreakoutButton, showCloseButton, showMaximizeButton, showMinimizeButton, @@ -157,6 +180,7 @@ function FloatingWindow({ title, zIndex, // -- + onBreakout, onCycle, onFocus, onBlur, @@ -168,6 +192,7 @@ function FloatingWindow({ initialPosition?: { x: number; y: number }; initialWidth?: number; maximized?: boolean; + showBreakoutButton?: boolean; showCloseButton?: boolean; showMaximizeButton?: boolean; showMinimizeButton?: boolean; @@ -176,6 +201,7 @@ function FloatingWindow({ title: string; zIndex?: number; // -- + onBreakout?: () => void; onCycle?: () => void; onFocus?: () => void; onBlur?: () => void; @@ -469,6 +495,10 @@ function FloatingWindow({ }, 1000); }; + const breakout = () => { + onBreakout?.(); + }; + const cycle = () => { onCycle?.(); }; @@ -536,8 +566,8 @@ function FloatingWindow({ pointerEvents: "auto", overflow: "hidden", }} - className={cn("border-2 border-gray-700", { - "hover:border-slate-500": !isMaximized, + className={cn("rounded-xl border border-slate-700", { + "hover:border-slate-600": !isMaximized, })} handleStyles={{ bottomLeft: { @@ -621,7 +651,7 @@ function FloatingWindow({ >
{os === "macOS" ? ( @@ -655,7 +685,12 @@ function FloatingWindow({ )} {showPowerButton && cycle()} />}
-
{title}
+
+ {showBreakoutButton && ( + breakout()} /> + )} + {title} +
{showReloadButton && ( + + + + + ); +} + +export { BrowserIcon }; diff --git a/skyvern-frontend/src/routes/discover/WorkflowTemplates.tsx b/skyvern-frontend/src/routes/discover/WorkflowTemplates.tsx index f77128cc..409e4f02 100644 --- a/skyvern-frontend/src/routes/discover/WorkflowTemplates.tsx +++ b/skyvern-frontend/src/routes/discover/WorkflowTemplates.tsx @@ -37,7 +37,7 @@ function WorkflowTemplates() { testImg } onClick={() => { - navigate(`/workflows/${workflow.workflow_permanent_id}/edit`); + navigate(`/workflows/${workflow.workflow_permanent_id}/debug`); }} /> ); diff --git a/skyvern-frontend/src/routes/workflows/ImportWorkflowButton.tsx b/skyvern-frontend/src/routes/workflows/ImportWorkflowButton.tsx index 3afd39ae..b618752f 100644 --- a/skyvern-frontend/src/routes/workflows/ImportWorkflowButton.tsx +++ b/skyvern-frontend/src/routes/workflows/ImportWorkflowButton.tsx @@ -48,7 +48,7 @@ function ImportWorkflowButton() { queryClient.invalidateQueries({ queryKey: ["workflows"], }); - navigate(`/workflows/${response.data.workflow_permanent_id}/edit`); + navigate(`/workflows/${response.data.workflow_permanent_id}/debug`); }, onError: (error: AxiosError) => { toast({ diff --git a/skyvern-frontend/src/routes/workflows/WorkflowPage.tsx b/skyvern-frontend/src/routes/workflows/WorkflowPage.tsx index d4d715ff..d1f25cb7 100644 --- a/skyvern-frontend/src/routes/workflows/WorkflowPage.tsx +++ b/skyvern-frontend/src/routes/workflows/WorkflowPage.tsx @@ -89,7 +89,7 @@ function WorkflowPage() { /> )} ) : ( <> - + + + + + + + {debugStore.isDebugMode + ? "Turn off Browser" + : "Turn on Browser"} + + + diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index 9c6fc542..475861d4 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -114,8 +114,10 @@ function Workspace({ ]); // ---start fya: https://github.com/frontyardart + const hasForLoopNode = nodes.some((node) => node.type === "loop"); + const initialBrowserPosition = { - x: 600, + x: hasForLoopNode ? 600 : 520, y: 132, }; @@ -143,6 +145,18 @@ function Workspace({ const workflowChangesStore = useWorkflowHasChangesStore(); + /** + * Open a new tab (not window) with the browser session URL. + */ + const handleOnBreakout = () => { + if (activeDebugSession) { + const pbsId = activeDebugSession.browser_session_id; + if (pbsId) { + window.open(`${location.origin}/browser-session/${pbsId}`, "_blank"); + } + } + }; + const handleOnCycle = () => { setOpenDialogue(true); }; @@ -435,8 +449,14 @@ function Workspace({ {/* sub panels */} {workflowPanelState.active && (
{ promote("dropdown"); }} @@ -471,7 +491,7 @@ function Workspace({ }} >
-
+
{workflowRunId && ( promote("browserWindow")} > diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx index 71246f05..d4904de1 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/ActionNode/ActionNode.tsx @@ -34,7 +34,6 @@ import { ParametersMultiSelect } from "../TaskNode/ParametersMultiSelect"; import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"; import { RunEngineSelector } from "@/components/EngineSelector"; import { ModelSelector } from "@/components/ModelSelector"; -import { useDebugStore } from "@/store/useDebugStore"; import { useBlockScriptStore } from "@/store/BlockScriptStore"; import { cn } from "@/util/utils"; import { useParams } from "react-router-dom"; @@ -53,7 +52,7 @@ function ActionNode({ id, data, type }: NodeProps) { const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { editable, debuggable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; const [inputs, setInputs] = useState({ url: data.url, @@ -69,7 +68,6 @@ function ActionNode({ id, data, type }: NodeProps) { engine: data.engine, }); const { blockLabel: urlBlockLabel } = useParams(); - const debugStore = useDebugStore(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = workflowRun && statusIsRunningOrQueued(workflowRun); @@ -77,7 +75,6 @@ function ActionNode({ id, data, type }: NodeProps) { urlBlockLabel !== undefined && urlBlockLabel === label; const thisBlockIsPlaying = workflowRunIsRunningOrQueued && thisBlockIsTargetted; - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const rerender = useRerender({ prefix: "accordian" }); const nodes = useNodes(); @@ -125,7 +122,6 @@ function ActionNode({ id, data, type }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -55,7 +52,6 @@ function CodeBlockNode({ id, data }: NodeProps) { > ) { - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -50,7 +47,6 @@ function DownloadNode({ id, data }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { debuggable, editable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -114,7 +111,6 @@ function ExtractionNode({ id, data, type }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { debuggable, editable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; - const debugStore = useDebugStore(); const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -64,7 +62,6 @@ function FileDownloadNode({ id, data }: NodeProps) { urlBlockLabel !== undefined && urlBlockLabel === label; const thisBlockIsPlaying = workflowRunIsRunningOrQueued && thisBlockIsTargetted; - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const [inputs, setInputs] = useState({ url: data.url, navigationGoal: data.navigationGoal, @@ -122,7 +119,6 @@ function FileDownloadNode({ id, data }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -69,7 +66,6 @@ function FileParserNode({ id, data }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -80,7 +77,6 @@ function FileUploadNode({ id, data }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { debuggable, editable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -119,7 +116,6 @@ function LoginNode({ id, data, type }: NodeProps) { > ) { if (!node) { throw new Error("Node not found"); // not possible } - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -109,7 +106,6 @@ function LoopNode({ id, data }: NodeProps) { > ) { const { blockLabel: urlBlockLabel } = useParams(); - const debugStore = useDebugStore(); const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { editable, debuggable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -58,7 +56,6 @@ function NavigationNode({ id, data, type }: NodeProps) { urlBlockLabel !== undefined && urlBlockLabel === label; const thisBlockIsPlaying = workflowRunIsRunningOrQueued && thisBlockIsTargetted; - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const rerender = useRerender({ prefix: "accordian" }); const [inputs, setInputs] = useState({ allowDownloads: data.allowDownloads, @@ -125,7 +122,6 @@ function NavigationNode({ id, data, type }: NodeProps) { ) { const { updateNodeData } = useReactFlow(); - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -69,7 +66,6 @@ function PDFParserNode({ id, data }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -71,7 +68,6 @@ function SendEmailNode({ id, data }: NodeProps) { > ) { />
-
-
- - + {inputs.useScriptCache && ( +
+
+ + +
+ { + const value = (event.target.value ?? "").trim(); + const v = value.length ? value : null; + handleChange("scriptCacheKey", v); + }} + />
- { - const value = (event.target.value ?? "").trim(); - const v = value.length ? value : null; - handleChange("scriptCacheKey", v); - }} - /> -
+ )}
diff --git a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNode.tsx b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNode.tsx index b0e428be..7167ef3f 100644 --- a/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNode.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/nodes/TaskNode/TaskNode.tsx @@ -36,7 +36,6 @@ import { WorkflowDataSchemaInputGroup } from "@/components/DataSchemaInputGroup/ import { useIsFirstBlockInWorkflow } from "../../hooks/useIsFirstNodeInWorkflow"; import { RunEngineSelector } from "@/components/EngineSelector"; import { ModelSelector } from "@/components/ModelSelector"; -import { useDebugStore } from "@/store/useDebugStore"; import { cn } from "@/util/utils"; import { NodeHeader } from "../components/NodeHeader"; import { useParams } from "react-router-dom"; @@ -48,10 +47,8 @@ function TaskNode({ id, data, type }: NodeProps) { const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { debuggable, editable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -126,7 +123,6 @@ function TaskNode({ id, data, type }: NodeProps) { > ) { - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -82,7 +79,6 @@ function Taskv2Node({ id, data, type }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -72,7 +69,6 @@ function TextPromptNode({ id, data }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { debuggable, editable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -77,7 +74,6 @@ function URLNode({ id, data, type }: NodeProps) { > ) { - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -49,7 +46,6 @@ function UploadNode({ id, data }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); const [facing, setFacing] = useState<"front" | "back">("front"); const blockScriptStore = useBlockScriptStore(); - const { debuggable, editable, label } = data; + const { editable, label } = data; const script = blockScriptStore.scripts[label]; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -109,7 +106,6 @@ function ValidationNode({ id, data, type }: NodeProps) { > ) { const { updateNodeData } = useReactFlow(); - const { debuggable, editable, label } = data; - const debugStore = useDebugStore(); - const elideFromDebugging = debugStore.isDebugMode && !debuggable; + const { editable, label } = data; const { blockLabel: urlBlockLabel } = useParams(); const { data: workflowRun } = useWorkflowRunQuery(); const workflowRunIsRunningOrQueued = @@ -66,7 +63,6 @@ function WaitNode({ id, data, type }: NodeProps) { > onMouseDownCapture?.()} > -
+

Block Library

@@ -355,8 +355,8 @@ function WorkflowNodeLibraryPanel({ tabIndex={0} />
- - + +
{filteredItems.length > 0 ? ( filteredItems.map((item) => ( diff --git a/skyvern-frontend/src/routes/workflows/hooks/useCreateWorkflowMutation.ts b/skyvern-frontend/src/routes/workflows/hooks/useCreateWorkflowMutation.ts index c129b241..4165453d 100644 --- a/skyvern-frontend/src/routes/workflows/hooks/useCreateWorkflowMutation.ts +++ b/skyvern-frontend/src/routes/workflows/hooks/useCreateWorkflowMutation.ts @@ -29,7 +29,7 @@ function useCreateWorkflowMutation() { queryClient.invalidateQueries({ queryKey: ["workflows"], }); - navigate(`/workflows/${response.data.workflow_permanent_id}/edit`); + navigate(`/workflows/${response.data.workflow_permanent_id}/debug`); }, }); }