From 41b341f3d8af843d6309b13fbfa2a7a53a3d95aa Mon Sep 17 00:00:00 2001 From: Jonathan Dobson Date: Tue, 2 Sep 2025 10:07:08 -0400 Subject: [PATCH] Jon/debugger layout (#3340) --- .../src/components/BrowserStream.tsx | 4 +- .../src/components/FloatingWindow.tsx | 2 +- skyvern-frontend/src/components/Tip.tsx | 31 ++ .../src/components/browser-stream.css | 1 - .../tasks/detail/ActionTypePillMinimal.tsx | 36 ++ .../routes/workflows/debugger/DebuggerRun.tsx | 2 +- .../workflows/debugger/DebuggerRunMinimal.tsx | 27 ++ .../debugger/DebuggerRunTimeline.tsx | 81 +--- .../debugger/DebuggerRunTimelineMinimal.tsx | 64 +++ .../routes/workflows/editor/FlowRenderer.tsx | 8 +- .../src/routes/workflows/editor/Workspace.tsx | 394 ++++++++++-------- .../workflows/editor/workspace-styles.css | 32 ++ .../workflowRun/ActionCardMinimal.tsx | 43 ++ .../workflowRun/ItemStatusIndicator.tsx | 35 ++ .../workflowRun/ThoughtCardMinimal.tsx | 17 + .../WorkflowRunTimelineBlockItemMinimal.tsx | 76 ++++ 16 files changed, 604 insertions(+), 249 deletions(-) create mode 100644 skyvern-frontend/src/components/Tip.tsx create mode 100644 skyvern-frontend/src/routes/tasks/detail/ActionTypePillMinimal.tsx create mode 100644 skyvern-frontend/src/routes/workflows/debugger/DebuggerRunMinimal.tsx create mode 100644 skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimelineMinimal.tsx create mode 100644 skyvern-frontend/src/routes/workflows/editor/workspace-styles.css create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/ActionCardMinimal.tsx create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/ItemStatusIndicator.tsx create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/ThoughtCardMinimal.tsx create mode 100644 skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItemMinimal.tsx diff --git a/skyvern-frontend/src/components/BrowserStream.tsx b/skyvern-frontend/src/components/BrowserStream.tsx index d0bf8c86..8724da89 100644 --- a/skyvern-frontend/src/components/BrowserStream.tsx +++ b/skyvern-frontend/src/components/BrowserStream.tsx @@ -328,7 +328,7 @@ function BrowserStream({ return (
)} {!isVncConnected && ( -
+
Hm, working on the connection... Hang tight, we're almost there... diff --git a/skyvern-frontend/src/components/FloatingWindow.tsx b/skyvern-frontend/src/components/FloatingWindow.tsx index fc0f7d75..3cf256a1 100644 --- a/skyvern-frontend/src/components/FloatingWindow.tsx +++ b/skyvern-frontend/src/components/FloatingWindow.tsx @@ -756,4 +756,4 @@ function FloatingWindow({ ); } -export { FloatingWindow }; +export { BreakoutButton, FloatingWindow, PowerButton, ReloadButton }; diff --git a/skyvern-frontend/src/components/Tip.tsx b/skyvern-frontend/src/components/Tip.tsx new file mode 100644 index 00000000..77ca700b --- /dev/null +++ b/skyvern-frontend/src/components/Tip.tsx @@ -0,0 +1,31 @@ +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; + +function Tip({ + asChild = true, + children, + content, +}: { + asChild?: boolean; + children: React.ReactNode; + content: string | null; +}) { + if (content === null) { + return children; + } + + return ( + + + {children} + {content} + + + ); +} + +export { Tip }; diff --git a/skyvern-frontend/src/components/browser-stream.css b/skyvern-frontend/src/components/browser-stream.css index 52f54b28..85999982 100644 --- a/skyvern-frontend/src/components/browser-stream.css +++ b/skyvern-frontend/src/components/browser-stream.css @@ -3,7 +3,6 @@ width: 100%; height: 100%; min-height: 0; - padding: 0.5rem; overflow: visible; transition: padding 0.2s ease-in-out; diff --git a/skyvern-frontend/src/routes/tasks/detail/ActionTypePillMinimal.tsx b/skyvern-frontend/src/routes/tasks/detail/ActionTypePillMinimal.tsx new file mode 100644 index 00000000..ef873b25 --- /dev/null +++ b/skyvern-frontend/src/routes/tasks/detail/ActionTypePillMinimal.tsx @@ -0,0 +1,36 @@ +import { ActionType, ReadableActionTypes } from "@/api/types"; +import { + CheckCircledIcon, + CursorArrowIcon, + InputIcon, + QuestionMarkIcon, +} from "@radix-ui/react-icons"; +import { Tip } from "@/components/Tip"; + +type Props = { + actionType: ActionType; +}; + +const icons: Partial> = { + click: , + complete: , + input_text: , +}; + +function ActionTypePillMinimal({ actionType }: Props) { + const icon = icons[actionType] ?? ; + + if (!icon) { + return null; + } + + return ( + +
+ {icon} +
+
+ ); +} + +export { ActionTypePillMinimal }; diff --git a/skyvern-frontend/src/routes/workflows/debugger/DebuggerRun.tsx b/skyvern-frontend/src/routes/workflows/debugger/DebuggerRun.tsx index bc090a0c..2551d59e 100644 --- a/skyvern-frontend/src/routes/workflows/debugger/DebuggerRun.tsx +++ b/skyvern-frontend/src/routes/workflows/debugger/DebuggerRun.tsx @@ -6,7 +6,7 @@ function DebuggerRun() { const workflowFailureReason = workflowRun?.failure_reason ? (
+
+ +
+ + ) : null; + + return ( +
+ {workflowFailureReason} +
+ +
+
+ ); +} + +export { DebuggerRunMinimal }; diff --git a/skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimeline.tsx b/skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimeline.tsx index 39597eb8..4a3ca154 100644 --- a/skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimeline.tsx +++ b/skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimeline.tsx @@ -1,4 +1,3 @@ -import { useParams } from "react-router-dom"; import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; import { Skeleton } from "@/components/ui/skeleton"; import { statusIsFinalized } from "@/routes/tasks/types"; @@ -18,7 +17,6 @@ import { WorkflowRunOverviewActiveElement, } from "@/routes/workflows/workflowRun/WorkflowRunOverview"; import { WorkflowRunTimelineBlockItem } from "@/routes/workflows/workflowRun/WorkflowRunTimelineBlockItem"; -import { useWorkflowQuery } from "../hooks/useWorkflowQuery"; type Props = { activeItem: WorkflowRunOverviewActiveElement; @@ -27,28 +25,12 @@ type Props = { onBlockItemSelected: (item: WorkflowRunBlock) => void; }; -function Step({ n, children }: { n: number; children: React.ReactNode }) { - return ( -
-
- {n} -
-
- {n} -
-
{children}
-
- ); -} - function DebuggerRunTimeline({ activeItem, onObserverThoughtCardSelected, onActionItemSelected, onBlockItemSelected, }: Props) { - const { workflowPermanentId } = useParams(); - const { data: workflow } = useWorkflowQuery({ workflowPermanentId }!); const { data: workflowRun, isLoading: workflowRunIsLoading } = useWorkflowRunQuery(); @@ -59,67 +41,8 @@ function DebuggerRunTimeline({ return ; } - const blocks = workflow?.workflow_definition.blocks ?? []; - - const getStarted = - blocks.length === 0 ? ( -
- Hi! ๐Ÿ‘‹ To get started, add a block to your workflow. You can do that by - clicking the round plus button beneath the Start block, on the left -
- ) : null; - - const runABlock = ( -
- To run a single block, click the play button on that block. Skyvern will - run the block in the browser, live! -
- ); - - const adjustBrowser = ( -
- Need to adjust the browser to test your block again? You can click around - in the browser to bring Skyvern to any page (manually!) -
- ); - - const parameters = ( -
- Want Skyvern to do different things based on your inputs? Use Parameters - to specify them and reference them using {"{{ }}"} syntax! -
- ); - - const addBlocks = ( -
- Not finished? Add a block to your workflow by clicking the round plus - button before or after any other block. -
- ); - - const steps = [ - getStarted, - runABlock, - adjustBrowser, - getStarted === null ? parameters : null, - getStarted === null ? addBlocks : null, - ].filter((step) => step); - if (!workflowRun || !workflowRunTimeline) { - return ( -
-
-
- Build & Debug Complex Browser Automations -
- {steps.map((step, index) => ( - - {step} - - ))} -
-
- ); + return null; } const workflowRunIsFinalized = statusIsFinalized(workflowRun); @@ -132,7 +55,7 @@ function DebuggerRunTimeline({ }, 0); return ( -
+
Actions: {numberOfActions} diff --git a/skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimelineMinimal.tsx b/skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimelineMinimal.tsx new file mode 100644 index 00000000..274fe826 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/debugger/DebuggerRunTimelineMinimal.tsx @@ -0,0 +1,64 @@ +import { ScrollArea, ScrollAreaViewport } from "@/components/ui/scroll-area"; +import { Skeleton } from "@/components/ui/skeleton"; +import { statusIsFinalized } from "@/routes/tasks/types"; +import { useWorkflowRunQuery } from "../hooks/useWorkflowRunQuery"; +import { useWorkflowRunTimelineQuery } from "../hooks/useWorkflowRunTimelineQuery"; +import { isBlockItem, isThoughtItem } from "../types/workflowRunTypes"; +import { ThoughtCardMinimal } from "@/routes/workflows/workflowRun/ThoughtCardMinimal"; +import { WorkflowRunTimelineBlockItemMinimal } from "@/routes/workflows/workflowRun/WorkflowRunTimelineBlockItemMinimal"; + +function DebuggerRunTimelineMinimal() { + const { data: workflowRun, isLoading: workflowRunIsLoading } = + useWorkflowRunQuery(); + + const { data: workflowRunTimeline, isLoading: workflowRunTimelineIsLoading } = + useWorkflowRunTimelineQuery(); + + if (workflowRunIsLoading || workflowRunTimelineIsLoading) { + return ; + } + + if (!workflowRun || !workflowRunTimeline) { + return null; + } + + const workflowRunIsFinalized = statusIsFinalized(workflowRun); + + return ( +
+ {!workflowRunIsFinalized && workflowRunTimeline.length === 0 && ( + + )} + + +
+ {workflowRunIsFinalized && workflowRunTimeline.length === 0 && ( +
-
+ )} + {workflowRunTimeline?.map((timelineItem) => { + if (isBlockItem(timelineItem)) { + return ( + + ); + } + if (isThoughtItem(timelineItem)) { + return ( + + ); + } + })} +
+
+
+
+ ); +} + +export { DebuggerRunTimelineMinimal }; diff --git a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx index 077636b9..74588db7 100644 --- a/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/FlowRenderer.tsx @@ -220,6 +220,7 @@ function convertToParametersYAML( } type Props = { + hideBackground?: boolean; nodes: Array; edges: Array; setNodes: (nodes: Array) => void; @@ -235,6 +236,7 @@ type Props = { }; function FlowRenderer({ + hideBackground = false, nodes, edges, setNodes, @@ -648,7 +650,7 @@ function FlowRenderer({ onEdgesChange={onEdgesChange} nodeTypes={nodeTypes} edgeTypes={edgeTypes} - colorMode="dark" + // colorMode="dark" fitView={true} fitViewOptions={{ maxZoom: 1, @@ -668,7 +670,9 @@ function FlowRenderer({ zoomOnPinch={!flowIsConstrained} zoomOnScroll={!flowIsConstrained} > - + {!hideBackground && ( + + )} diff --git a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx index 84883232..d82cf714 100644 --- a/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx +++ b/skyvern-frontend/src/routes/workflows/editor/Workspace.tsx @@ -1,7 +1,12 @@ import { AxiosError } from "axios"; import { useEffect, useRef, useState } from "react"; import { nanoid } from "nanoid"; -import { ReloadIcon } from "@radix-ui/react-icons"; +import { + ChevronRightIcon, + ChevronLeftIcon, + GlobeIcon, + ReloadIcon, +} from "@radix-ui/react-icons"; import { useParams, useSearchParams } from "react-router-dom"; import { useEdgesState, useNodesState, Edge } from "@xyflow/react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; @@ -10,7 +15,6 @@ import { getClient } from "@/api/AxiosClient"; import { DebugSessionApiResponse } from "@/api/types"; import { useCredentialGetter } from "@/hooks/useCredentialGetter"; import { useMountEffect } from "@/hooks/useMountEffect"; -import { useRanker } from "../hooks/useRanker"; import { useDebugSessionQuery } from "../hooks/useDebugSessionQuery"; import { useBlockScriptsQuery } from "@/routes/workflows/hooks/useBlockScriptsQuery"; import { useCacheKeyValuesQuery } from "../hooks/useCacheKeyValuesQuery"; @@ -19,6 +23,11 @@ import { useSidebarStore } from "@/store/SidebarStore"; import { AnimatedWave } from "@/components/AnimatedWave"; import { Button } from "@/components/ui/button"; +import { + BreakoutButton, + PowerButton, + ReloadButton, +} from "@/components/FloatingWindow"; import { Dialog, DialogContent, @@ -28,21 +37,20 @@ 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 { DebuggerRun } from "@/routes/workflows/debugger/DebuggerRun"; +import { DebuggerRunMinimal } from "@/routes/workflows/debugger/DebuggerRunMinimal"; import { useWorkflowRunQuery } from "@/routes/workflows/hooks/useWorkflowRunQuery"; -import { DebuggerRunOutput } from "@/routes/workflows/debugger/DebuggerRunOutput"; -import { DebuggerPostRunParameters } from "@/routes/workflows/debugger/DebuggerPostRunParameters"; import { useWorkflowPanelStore } from "@/store/WorkflowPanelStore"; import { useWorkflowHasChangesStore, useWorkflowSave, } from "@/store/WorkflowHasChangesStore"; +import { cn } from "@/util/utils"; + import { FlowRenderer, type FlowRendererProps } from "./FlowRenderer"; import { AppNode, isWorkflowBlockNode, WorkflowBlockNode } from "./nodes"; import { WorkflowNodeLibraryPanel } from "./panels/WorkflowNodeLibraryPanel"; @@ -60,6 +68,8 @@ import { } from "./workflowEditorUtils"; import { constructCacheKeyValue } from "./utils"; +import "./workspace-styles.css"; + const Constants = { NewBrowserCooldown: 30000, } as const; @@ -85,10 +95,10 @@ function Workspace({ showBrowser = false, workflow, }: Props) { - const { blockLabel, workflowPermanentId, workflowRunId } = useParams(); + const { blockLabel, workflowPermanentId } = useParams(); const [searchParams, setSearchParams] = useSearchParams(); const cacheKeyValueParam = searchParams.get("cache-key-value"); - const [content, setContent] = useState("actions"); + const [timelineMode, setTimelineMode] = useState("narrow"); const [cacheKeyValueFilter, setCacheKeyValueFilter] = useState( null, ); @@ -102,7 +112,6 @@ function Workspace({ const { data: workflowRun } = useWorkflowRunQuery(); const isFinalized = workflowRun ? statusIsFinalized(workflowRun) : null; const interactor = workflowRun && isFinalized === false ? "agent" : "human"; - const browserTitle = interactor === "agent" ? `Browser [๐Ÿค–]` : `Browser [๐Ÿ‘ค]`; const [openCycleBrowserDialogue, setOpenCycleBrowserDialogue] = useState(false); @@ -116,36 +125,12 @@ function Workspace({ const [activeDebugSession, setActiveDebugSession] = useState(null); const [showPowerButton, setShowPowerButton] = useState(true); + const [reloadKey, setReloadKey] = useState(0); + const [isReloading, setIsReloading] = useState(false); const credentialGetter = useCredentialGetter(); const queryClient = useQueryClient(); const [shouldFetchDebugSession, setShouldFetchDebugSession] = useState(false); const blockScriptStore = useBlockScriptStore(); - const { rankedItems, promote } = useRanker([ - "browserWindow", - "header", - "dropdown", - "history", - "infiniteCanvas", - ]); - const [hideControlButtons, setHideControlButtons] = useState(false); - - // ---start fya: https://github.com/frontyardart - const hasForLoopNode = nodes.some((node) => node.type === "loop"); - - const initialBrowserPosition = { - x: hasForLoopNode ? 600 : 520, - y: 132, - }; - - const windowWidth = window.innerWidth; - const rightPadding = 567; - const initialWidth = Math.max( - 512, - windowWidth - initialBrowserPosition.x - rightPadding, - ); - const initialHeight = (initialWidth / 16) * 9; - // ---end fya - const cacheKey = workflow?.cache_key ?? ""; const [cacheKeyValue, setCacheKeyValue] = useState( @@ -156,6 +141,18 @@ function Workspace({ : constructCacheKeyValue(cacheKey, workflow), ); + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Escape") { + setTimelineMode("narrow"); + } + }; + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); + useEffect(() => { const currentUrlValue = searchParams.get("cache-key-value"); const targetValue = cacheKeyValue === "" ? null : cacheKeyValue; @@ -202,10 +199,13 @@ function Workspace({ const workflowChangesStore = useWorkflowHasChangesStore(); + const showBreakoutButton = + activeDebugSession && activeDebugSession.browser_session_id; + /** * Open a new tab (not window) with the browser session URL. */ - const handleOnBreakout = () => { + const breakout = () => { if (activeDebugSession) { const pbsId = activeDebugSession.browser_session_id; if (pbsId) { @@ -214,10 +214,23 @@ function Workspace({ } }; - const handleOnCycle = () => { + const cycle = () => { setOpenCycleBrowserDialogue(true); }; + const reload = () => { + if (isReloading) { + return; + } + + setReloadKey((prev) => prev + 1); + setIsReloading(true); + + setTimeout(() => { + setIsReloading(false); + }, 1000); + }; + useMountEffect(() => { setCollapsed(true); workflowChangesStore.setHasChanges(false); @@ -459,7 +472,6 @@ function Workspace({ active: true, content: "cacheKeyValues", }); - promote("dropdown"); } function toggleCacheKeyValuesPanel() { @@ -468,7 +480,6 @@ function Workspace({ workflowPanelState.content === "cacheKeyValues" ) { closeWorkflowPanel(); - promote("header"); } else { openCacheKeyValuesPanel(); } @@ -581,13 +592,7 @@ function Workspace({ {/* header panel */} -
{ - promote("header"); - }} - > +
{ @@ -666,13 +669,12 @@ function Workspace({ }} onRun={() => { closeWorkflowPanel(); - promote("header"); }} />
- {/* sub panels */} - {workflowPanelState.active && ( + {/* sub panels in design mode */} + {!showBrowser && workflowPanelState.active && (
{ - promote("dropdown"); }} + onMouseDownCapture={() => {}} > {workflowPanelState.content === "cacheKeyValues" && ( { - promote("dropdown"); - }} onPaginate={(page) => { setPage(page); }} @@ -709,17 +705,10 @@ function Workspace({ /> )} {workflowPanelState.content === "parameters" && ( - { - promote("dropdown"); - }} - /> + )} {workflowPanelState.content === "nodeLibrary" && ( { - promote("dropdown"); - }} onNodeClick={(props) => { addNode(props); }} @@ -728,110 +717,189 @@ function Workspace({
)} - {showBrowser && ( +
+ {/* infinite canvas */}
{ - closeWorkflowPanel(); - promote("history"); - }} + className={cn("skyvern-split-left h-full w-[33rem]", { + "w-full": !showBrowser, + })} > -
-
- {workflowRunId && ( - setContent(value)} - value={content} - options={[ - { - label: "Actions", - value: "actions", - }, - { - label: "Inputs", - value: "inputs", - }, - { - label: "Outputs", - value: "outputs", - }, - ]} - /> - )} -
- {(!workflowRunId || content === "actions") && } - {workflowRunId && content === "inputs" && ( - + +
+ + {/* divider if browser is in play */} + {showBrowser && ( +
+ )} + + {/* browser & timeline & sub-panels in debug mode */} + {showBrowser && ( +
+ {/* sub panels */} + {workflowPanelState.active && ( +
+ {workflowPanelState.content === "cacheKeyValues" && ( + { + setToDeleteCacheKeyValue(cacheKeyValue); + setOpenConfirmCacheKeyValueDeleteDialogue(true); + }} + onPaginate={(page) => { + setPage(page); + }} + onSelect={(cacheKeyValue) => { + setCacheKeyValue(cacheKeyValue); + setCacheKeyValueFilter(""); + closeWorkflowPanel(); + }} + /> )} - {workflowRunId && content === "outputs" && ( - + {workflowPanelState.content === "parameters" && ( + )} + {workflowPanelState.content === "nodeLibrary" && ( + { + addNode(props); + }} + /> + )} +
+ )} + + {/* browser & timeline */} +
+ {/* browser */} +
+
+ {activeDebugSession && + activeDebugSession.browser_session_id && + !cycleBrowser.isPending ? ( + + ) : ( +
+ Connecting to your browser... + +
+ )} +
+
+
+ Live Browser +
+ {showBreakoutButton && ( + breakout()} /> + )} +
+ {showPowerButton && cycle()} />} + reload()} + /> +
+
+
+ + {/* timeline */} +
+
{ + if (timelineMode === "narrow") { + setTimelineMode("wide"); + } + }} + > + {/* timeline wide */} +
+
+ +
+
+ + {/* divider */} +
+ + {/* slide indicator */} +
{ + e.stopPropagation(); + setTimelineMode( + timelineMode === "wide" ? "narrow" : "wide", + ); + }} + > + {timelineMode === "narrow" && } + {timelineMode === "wide" && } +
+ + {/* timeline narrow */} +
+ +
+
-
- )} - - {/* infinite canvas */} - promote("infiniteCanvas")} - zIndex={rankedItems.infiniteCanvas} - /> - - {/* browser */} - {showBrowser && ( - promote("browserWindow")} - onMinimize={() => { - setHideControlButtons(true); - }} - onMaximize={() => { - setHideControlButtons(false); - }} - onRestore={() => { - setHideControlButtons(false); - }} - > - {activeDebugSession && - activeDebugSession.browser_session_id && - !cycleBrowser.isPending ? ( - - ) : ( -
- Connecting to your browser... - -
- )} -
- )} + )} +
); } diff --git a/skyvern-frontend/src/routes/workflows/editor/workspace-styles.css b/skyvern-frontend/src/routes/workflows/editor/workspace-styles.css new file mode 100644 index 00000000..89af84ba --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/editor/workspace-styles.css @@ -0,0 +1,32 @@ +.vertical-line-gradient { + background: linear-gradient( + to bottom, + transparent 0%, + rgba(51, 65, 85, 0.3) 20%, + rgba(51, 65, 85, 1) 50%, + rgba(51, 65, 85, 0.3) 80%, + transparent 100% + ); +} + +.vertical-gradient-error { + background: linear-gradient( + to bottom, + transparent 0%, + rgba(51, 0, 0, 0.3) 5%, + rgba(51, 0, 0, 0.4) 50%, + rgba(51, 0, 0, 0.3) 95%, + transparent 100% + ); +} + +.vertical-gradient-success { + background: linear-gradient( + to bottom, + transparent 0%, + rgba(0, 51, 0, 0.3) 5%, + rgba(0, 51, 0, 0.4) 50%, + rgba(0, 51, 0, 0.3) 95%, + transparent 100% + ); +} diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/ActionCardMinimal.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/ActionCardMinimal.tsx new file mode 100644 index 00000000..25e1cf59 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/ActionCardMinimal.tsx @@ -0,0 +1,43 @@ +import { LightningBoltIcon } from "@radix-ui/react-icons"; +import { ActionsApiResponse, Status } from "@/api/types"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { ActionTypePillMinimal } from "@/routes/tasks/detail/ActionTypePillMinimal"; +import { ItemStatusIndicator } from "./ItemStatusIndicator"; + +type Props = { + action: ActionsApiResponse; +}; + +function ActionCardMinimal({ action }: Props) { + const success = + action.status === Status.Completed || action.status === Status.Skipped; + + return ( + +
+ + {action.created_by === "script" && ( + + + +
+ +
+
+ + Code Execution + +
+
+ )} +
+
+ ); +} + +export { ActionCardMinimal }; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/ItemStatusIndicator.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/ItemStatusIndicator.tsx new file mode 100644 index 00000000..bfbdd7db --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/ItemStatusIndicator.tsx @@ -0,0 +1,35 @@ +import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"; + +interface Props { + children: React.ReactNode; + offset?: string; + failure?: boolean; + success?: boolean; +} + +function ItemStatusIndicator({ + children, + offset = "-0.6rem", + failure, + success, +}: Props) { + return ( +
+ {children} + {success && ( + + )} + {failure && ( + + )} +
+ ); +} + +export { ItemStatusIndicator }; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/ThoughtCardMinimal.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/ThoughtCardMinimal.tsx new file mode 100644 index 00000000..c9f91ee9 --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/ThoughtCardMinimal.tsx @@ -0,0 +1,17 @@ +import { ObserverThought } from "../types/workflowRunTypes"; +import { Tip } from "@/components/Tip"; +import { BrainIcon } from "@/components/icons/BrainIcon"; + +type Props = { + thought: ObserverThought; +}; + +function ThoughtCardMinimal({ thought }: Props) { + return ( + + + + ); +} + +export { ThoughtCardMinimal }; diff --git a/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItemMinimal.tsx b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItemMinimal.tsx new file mode 100644 index 00000000..8d3b3dce --- /dev/null +++ b/skyvern-frontend/src/routes/workflows/workflowRun/WorkflowRunTimelineBlockItemMinimal.tsx @@ -0,0 +1,76 @@ +import { Tip } from "@/components/Tip"; +import { workflowBlockTitle } from "../editor/nodes/types"; +import { WorkflowBlockIcon } from "../editor/nodes/WorkflowBlockIcon"; +import { + isBlockItem, + isThoughtItem, + WorkflowRunBlock, + WorkflowRunTimelineItem, +} from "../types/workflowRunTypes"; +import { ActionCardMinimal } from "./ActionCardMinimal"; +import { Status } from "@/api/types"; +import { ThoughtCardMinimal } from "./ThoughtCardMinimal"; +import { ItemStatusIndicator } from "./ItemStatusIndicator"; + +type Props = { + block: WorkflowRunBlock; + subItems: Array; +}; + +function WorkflowRunTimelineBlockItemMinimal({ block, subItems }: Props) { + const actions = block.actions ?? []; + const showStatusIndicator = block.status !== null; + const showSuccessIndicator = + showStatusIndicator && block.status === Status.Completed; + const showFailureIndicator = + showStatusIndicator && + (block.status === Status.Failed || + block.status === Status.Terminated || + block.status === Status.TimedOut || + block.status === Status.Canceled); + + return ( +
+ + + + + + + {actions.length > 0 && ( +
+ {actions.map((action) => { + return ; + })} +
+ )} + {subItems.map((item) => { + if (isBlockItem(item)) { + return ( + + ); + } + if (isThoughtItem(item)) { + return ( + + ); + } + })} +
+ ); +} + +export { WorkflowRunTimelineBlockItemMinimal };