"use client"; import { useState, useEffect, useSyncExternalStore } from "react"; import { takeScreenshot, takeSnapshot, getEnvStatus } from "./actions/browse"; import type { ScreenshotResult, SnapshotResult, Mode, EnvStatus, } from "./actions/browse"; import { ResizablePanelGroup, ResizablePanel, ResizableHandle, } from "@/components/ui/resizable"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Badge } from "@/components/ui/badge"; import { Alert, AlertTitle, AlertDescription } from "@/components/ui/alert"; import { Loader2, Monitor, TriangleAlert, CircleX } from "lucide-react"; const MOBILE_QUERY = "(max-width: 767px)"; const subscribe = (cb: () => void) => { const mql = window.matchMedia(MOBILE_QUERY); mql.addEventListener("change", cb); return () => mql.removeEventListener("change", cb); }; const getSnapshot = () => window.matchMedia(MOBILE_QUERY).matches; const getServerSnapshot = () => false; function useIsMobile() { return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); } type Action = "screenshot" | "snapshot"; function formatError(raw: string): string { let cleaned = raw.replace(/<[^>]*>/g, " ").replace(/\s+/g, " ").trim(); const match = cleaned.match(/(?:error|Error)[:\s]*(.{1,200})/); if (match) cleaned = match[1].trim(); if (cleaned.length > 300) cleaned = cleaned.slice(0, 300) + "..."; return cleaned || raw.slice(0, 300); } function SegmentedControl({ value, onChange, options, }: { value: T; onChange: (v: T) => void; options: { value: T; label: string }[]; }) { return (
{options.map((opt) => ( ))}
); } function EnvBadge({ label, value, status, }: { label: string; value?: string; status: "ok" | "warn" | "missing"; }) { const variant = status === "ok" ? "outline" : status === "warn" ? "secondary" : "destructive"; const icon = status === "ok" ? "\u2713" : status === "warn" ? "\u26A0" : "\u2717"; return ( {icon} {label} {value && {value}} ); } function ErrorDisplay({ error }: { error: string }) { const isHtml = /<[a-z][\s\S]*>/i.test(error); const message = isHtml ? formatError(error) : error; const showRaw = isHtml && error.length > 100; return (
Request failed {message} {showRaw && (
Show raw response
            {error}
          
)}
); } function ModeCard({ selected, onSelect, title, description, badges, }: { selected: boolean; onSelect: () => void; title: string; description: string; badges?: React.ReactNode; }) { return ( ); } export default function Home() { const isMobile = useIsMobile(); const [url, setUrl] = useState("https://example.com"); const [loading, setLoading] = useState(false); const [action, setAction] = useState("screenshot"); const [mode, setMode] = useState("serverless"); const [screenshotResult, setScreenshotResult] = useState(null); const [snapshotResult, setSnapshotResult] = useState(null); const [envStatus, setEnvStatus] = useState(null); useEffect(() => { getEnvStatus().then(setEnvStatus); }, []); function clearResults() { setScreenshotResult(null); setSnapshotResult(null); } async function handleSubmit(e: React.FormEvent) { e.preventDefault(); setLoading(true); setScreenshotResult(null); setSnapshotResult(null); try { if (action === "screenshot") { const result = await takeScreenshot(url, mode); setScreenshotResult(result); } else { const result = await takeSnapshot(url, mode); setSnapshotResult(result); } } catch (err) { const message = err instanceof Error ? err.message : String(err); if (action === "screenshot") { setScreenshotResult({ ok: false, error: message }); } else { setSnapshotResult({ ok: false, error: message }); } } finally { setLoading(false); } } const hasResult = screenshotResult || snapshotResult; const envWarning = envStatus && mode === "serverless" && !envStatus.serverless.isVercel && !envStatus.serverless.hasChromiumPath ? "Running locally without CHROMIUM_PATH. The app will try to use your system Chrome. Set CHROMIUM_PATH if Chrome is not in the default location." : null; const controlsForm = (
{ setUrl(e.target.value); clearResults(); }} placeholder="https://example.com" required />
value={action} onChange={(v) => { setAction(v); clearResults(); }} options={[ { value: "screenshot", label: "Screenshot" }, { value: "snapshot", label: "Snapshot" }, ]} />

{action === "screenshot" ? "Captures a full-page PNG image" : "Returns the accessibility tree"}

{ setMode("serverless"); clearResults(); }} title="Serverless Function" description="Runs @sparticuz/chromium + puppeteer-core directly in a Vercel function." badges={ envStatus && ( ) } /> { setMode("sandbox"); clearResults(); }} title="Vercel Sandbox" description="Ephemeral microVM with agent-browser + Chrome. No binary size limits." badges={ envStatus && ( ) } />
{envWarning && ( Local development {envWarning} )}
); const resultContent = loading ? (

Taking {action}...

) : hasResult ? (
{screenshotResult && (screenshotResult.ok ? (

{screenshotResult.title}

screenshot
{screenshotResult.title}
) : ( ))} {snapshotResult && (snapshotResult.ok ? (

{snapshotResult.title}

snapshot
              {snapshotResult.snapshot}
            
) : ( ))}
) : (

No result yet

Enter a URL and click Run

); return (
agent-browser / Demo
Deploy with Vercel
{isMobile ? (
{controlsForm}
{resultContent}
) : (
{resultContent}
)}
); }