diff --git a/skyvern-frontend/src/routes/browserSession/BrowserSession.tsx b/skyvern-frontend/src/routes/browserSession/BrowserSession.tsx index e4f10ea1..5ac6e6d0 100644 --- a/skyvern-frontend/src/routes/browserSession/BrowserSession.tsx +++ b/skyvern-frontend/src/routes/browserSession/BrowserSession.tsx @@ -6,10 +6,12 @@ import { getClient } from "@/api/AxiosClient"; import { BrowserStream } from "@/components/BrowserStream"; import { LogoMinimized } from "@/components/LogoMinimized"; import { useCredentialGetter } from "@/hooks/useCredentialGetter"; +import { BrowserSessionVideo } from "./BrowserSessionVideo"; function BrowserSession() { const { browserSessionId } = useParams(); const [hasBrowserSession, setHasBrowserSession] = useState(false); + const [activeTab, setActiveTab] = useState<"stream" | "videos">("stream"); const credentialGetter = useCredentialGetter(); @@ -19,12 +21,14 @@ function BrowserSession() { const client = await getClient(credentialGetter, "sans-api-v1"); try { - await client.get(`/browser_sessions/${browserSessionId}`); + const response = await client.get( + `/browser_sessions/${browserSessionId}`, + ); setHasBrowserSession(true); - return true; + return response.data; } catch (error) { setHasBrowserSession(false); - return false; + return null; } }, }); @@ -60,12 +64,41 @@ function BrowserSession() {
browser session
+ + {/* Tab Navigation */} +
+ + +
+ + {/* Tab Content */}
- + {activeTab === "stream" && ( + + )} + {activeTab === "videos" && }
diff --git a/skyvern-frontend/src/routes/browserSession/BrowserSessionVideo.tsx b/skyvern-frontend/src/routes/browserSession/BrowserSessionVideo.tsx new file mode 100644 index 00000000..da70e327 --- /dev/null +++ b/skyvern-frontend/src/routes/browserSession/BrowserSessionVideo.tsx @@ -0,0 +1,136 @@ +import { useQuery } from "@tanstack/react-query"; +import { useParams } from "react-router-dom"; + +import { getClient } from "@/api/AxiosClient"; +import { useCredentialGetter } from "@/hooks/useCredentialGetter"; + +interface Recording { + url: string; + checksum: string; + filename: string; + modified_at: string; +} + +function BrowserSessionVideo() { + const { browserSessionId } = useParams(); + const credentialGetter = useCredentialGetter(); + + const { + data: browserSession, + isLoading, + error, + } = useQuery({ + queryKey: ["browserSession", browserSessionId], + queryFn: async () => { + const client = await getClient(credentialGetter, "sans-api-v1"); + const response = await client.get( + `/browser_sessions/${browserSessionId}`, + ); + return response.data; + }, + enabled: !!browserSessionId, + }); + + // Extract recordings from browser session data + const recordings = browserSession?.recordings || []; + + if (isLoading) { + return ( +
+
Loading videos...
+
+ ); + } + + if (error) { + return ( +
+
+ Error loading videos: {error.message} +
+
+ ); + } + + if (!recordings || recordings.length === 0) { + return ( +
+
+
+ No recordings available +
+
+ Video recordings will appear here when the browser session is active + and recording +
+
+
+ ); + } + + return ( +
+
+

Browser Session Videos

+

+ Recorded videos from this browser session +

+
+ +
+ {recordings.map((recording: Recording, index: number) => ( +
+
+

+ {recording.filename || `Recording ${index + 1}`} + {recording.modified_at && ( + + ({new Date(recording.modified_at).toLocaleString()}) + + )} +

+
+ + {recording.url ? ( +
+ +
+ + Download video + +
+
+ ) : ( +
+ Video URL not available - video may still be processing +
+ )} + + {recording.checksum && ( +
+ Checksum: {recording.checksum} +
+ )} +
+ ))} +
+
+ ); +} + +export { BrowserSessionVideo };