diff --git a/components/TasksPage/Run/RunLogsList.tsx b/components/TasksPage/Run/RunLogsList.tsx index 67dcbad09..5d2cfa2f0 100644 --- a/components/TasksPage/Run/RunLogsList.tsx +++ b/components/TasksPage/Run/RunLogsList.tsx @@ -1,17 +1,13 @@ "use client"; -import { useEffect, useRef } from "react"; +import { useAutoScroll } from "@/hooks/useAutoScroll"; interface RunLogsListProps { logs: string[]; } export default function RunLogsList({ logs }: RunLogsListProps) { - const bottomRef = useRef(null); - - useEffect(() => { - bottomRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [logs.length]); + const containerRef = useAutoScroll(logs.length); if (logs.length === 0) { return ( @@ -22,13 +18,12 @@ export default function RunLogsList({ logs }: RunLogsListProps) { } return ( -
+
{logs.map((log, i) => (

{log}

))} -
); } diff --git a/components/VercelChat/ToolComponents.tsx b/components/VercelChat/ToolComponents.tsx index ac0dbd07e..a9c833963 100644 --- a/components/VercelChat/ToolComponents.tsx +++ b/components/VercelChat/ToolComponents.tsx @@ -129,6 +129,8 @@ import GetChatsResult, { import RunPageSkeleton from "@/components/TasksPage/Run/RunPageSkeleton"; import SandboxCreatedResult from "./tools/sandbox/SandboxCreatedResult"; import RunSandboxCommandResultWithPolling from "./tools/sandbox/RunSandboxCommandResultWithPolling"; +import PromptSandboxStreamProgress from "./tools/sandbox/PromptSandboxStreamProgress"; +import type { SandboxStreamProgress } from "@/lib/sandboxes/sandboxStreamTypes"; type CallToolResult = { content: TextContent[]; @@ -320,6 +322,12 @@ export function getToolCallComponent(part: ToolUIPart) {
); + } else if (toolName === "prompt_sandbox") { + return ( +
+ +
+ ); } // Default for other tools @@ -615,6 +623,12 @@ export function getToolResultComponent(part: ToolUIPart | DynamicToolUIPart) {
); + } else if (toolName === "prompt_sandbox") { + return ( +
+ +
+ ); } // Default generic result for other tools diff --git a/components/VercelChat/tools/sandbox/PromptSandboxStreamProgress.tsx b/components/VercelChat/tools/sandbox/PromptSandboxStreamProgress.tsx new file mode 100644 index 000000000..8acdb08ec --- /dev/null +++ b/components/VercelChat/tools/sandbox/PromptSandboxStreamProgress.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import { Loader, CheckCircle, XCircle } from "lucide-react"; +import type { SandboxStreamProgress } from "@/lib/sandboxes/sandboxStreamTypes"; +import { useAutoScroll } from "@/hooks/useAutoScroll"; + +interface PromptSandboxStreamProgressProps { + progress: SandboxStreamProgress; +} + +const PromptSandboxStreamProgress: React.FC< + PromptSandboxStreamProgressProps +> = ({ progress }) => { + const preRef = useAutoScroll(progress.output); + + if (progress.status === "booting") { + return ( +
+ + Starting sandbox... +
+ ); + } + + if (progress.status === "streaming") { + return ( +
+
+ + Running in sandbox... +
+
+
+            {progress.output || "Waiting for output..."}
+          
+
+
+ ); + } + + if (progress.status === "complete") { + const hasError = progress.exitCode !== undefined && progress.exitCode !== 0; + + return ( +
+
+ {hasError ? ( + + ) : ( + + )} + + {hasError + ? `Sandbox exited with code ${progress.exitCode}` + : "Sandbox complete"} + +
+
+
+            {progress.output || "(no output)"}
+          
+
+
+ ); + } + + if (progress.status === "error") { + return ( +
+
+ + Sandbox error +
+
+
+            {progress.stderr || progress.output || "Unknown error"}
+          
+
+
+ ); + } + + return null; +}; + +export default PromptSandboxStreamProgress; diff --git a/hooks/useAutoScroll.ts b/hooks/useAutoScroll.ts new file mode 100644 index 000000000..a06b671e5 --- /dev/null +++ b/hooks/useAutoScroll.ts @@ -0,0 +1,17 @@ +import { useEffect, useRef } from "react"; + +/** + * Returns a ref to attach to a scrollable container. + * Automatically scrolls to the bottom whenever the dependency changes. + */ +export function useAutoScroll(dep: unknown) { + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + ref.current.scrollTop = ref.current.scrollHeight; + } + }, [dep]); + + return ref; +} diff --git a/lib/sandboxes/sandboxStreamTypes.ts b/lib/sandboxes/sandboxStreamTypes.ts new file mode 100644 index 000000000..2f8366de8 --- /dev/null +++ b/lib/sandboxes/sandboxStreamTypes.ts @@ -0,0 +1,10 @@ +export type SandboxStreamStatus = "booting" | "streaming" | "complete" | "error"; + +export interface SandboxStreamProgress { + status: SandboxStreamStatus; + sandboxId?: string; + output: string; + stderr?: string; + exitCode?: number; + created?: boolean; +}