Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions site/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import "./globals.css";
import type { Metadata } from 'next';
import { Inter } from "next/font/google";
import zealtConfig from "@/zealt/config.json";
import { ThemeProvider } from "@/components/theme-provider";
import { ThemeToggle } from "@/components/theme-toggle";

const inter = Inter({ subsets: ["latin"] });

Expand All @@ -18,8 +20,15 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
<html lang="en" className="dark">
<body className={`${inter.className} antialiased`}>{children}</body>
<html lang="en" suppressHydrationWarning>
<body className={`${inter.className} antialiased`}>
<ThemeProvider defaultTheme="dark">
<div className="fixed right-4 top-6 z-50 sm:top-8">
<ThemeToggle />
</div>
{children}
</ThemeProvider>
</body>
</html>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { useEffect, useRef, useState } from "react";
import { useEffect, useMemo, useState } from "react";
import { useTheme } from "next-themes";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Skeleton } from "@/components/ui/skeleton";
Expand All @@ -18,31 +19,33 @@ export function TrajectoryPage({
stderrText,
verifierText,
}: TrajectoryPageProps) {
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
const [iframeLoading, setIframeLoading] = useState(true);
const [activeTab, setActiveTab] = useState("trajectory");
const iframeRef = useRef<HTMLIFrameElement>(null);
const hideSkeletonTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);

const iframeTheme = mounted && resolvedTheme === "light" ? "light" : "dark";

const iframeUrl = useMemo(() => {
const url = new URL(trajectoryUrl);
url.searchParams.set("theme", iframeTheme);
return url.toString();
}, [trajectoryUrl, iframeTheme]);

useEffect(() => {
return () => {
if (hideSkeletonTimeoutRef.current) {
clearTimeout(hideSkeletonTimeoutRef.current);
}
};
setMounted(true);
}, []);

const handleIframeLoad = () => {
if (hideSkeletonTimeoutRef.current) {
clearTimeout(hideSkeletonTimeoutRef.current);
useEffect(() => {
if (!mounted) {
return;
}

hideSkeletonTimeoutRef.current = setTimeout(() => {
setIframeLoading(false);
if (iframeRef.current) {
iframeRef.current.style.opacity = "1";
}
hideSkeletonTimeoutRef.current = null;
}, 300);
setIframeLoading(true);
}, [iframeUrl, mounted]);

const handleIframeLoad = () => {
setIframeLoading(false);
};

const handleIframeError = () => {
Expand Down Expand Up @@ -93,19 +96,20 @@ export function TrajectoryPage({
</div>

<TabsContent value="trajectory" className="relative min-h-0 flex-1 overflow-hidden px-2" forceMount>
{iframeLoading && (
<div className="absolute inset-0 z-10 overflow-auto bg-background/80 backdrop-blur-sm">
<TrajectorySkeleton />
</div>
<div
className={`absolute inset-0 z-10 overflow-auto bg-background/80 transition-opacity duration-420 ease-out delay-220 ${!mounted || iframeLoading ? "opacity-100" : "pointer-events-none opacity-0"}`}
>
<TrajectorySkeleton />
</div>
{mounted && (
<iframe
src={iframeUrl}
className={`h-full w-full border-0 transition-opacity duration-260 ease-out ${iframeLoading ? "opacity-0" : "opacity-100"}`}
title="Trial Details"
onLoad={handleIframeLoad}
onError={handleIframeError}
/>
)}
<iframe
ref={iframeRef}
src={trajectoryUrl}
className="h-full w-full border-0 opacity-0 transition-opacity duration-300"
title="Trial Details"
onLoad={handleIframeLoad}
onError={handleIframeError}
/>
</TabsContent>

<TabsContent value="log" className="min-h-0 flex-1 overflow-hidden" forceMount>
Expand Down
7 changes: 3 additions & 4 deletions site/app/tasks/[name]/[jobId]/trajectory/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ function buildClipUrl(jobName: string, trialName: string, title: string): string
const ownerRepo = getGithubOwnerRepo();
const url = new URL(`/f/raw.githubusercontent.com/${ownerRepo}/refs/heads/main/jobs/${jobName}/${trialName}/agent/pochi/trajectory.jsonl`, getServerBaseUrl());
url.searchParams.set("title", title);
url.searchParams.set("theme", "dark");
return url.toString();
}

Expand Down Expand Up @@ -288,10 +287,10 @@ export default async function TrajectoryRoutePage({
<div className="flex h-screen w-full flex-col overflow-hidden bg-background text-foreground font-sans selection:bg-primary/20">
<div className="fixed inset-0 -z-10 h-full w-full bg-background bg-[radial-gradient(#2a2a2a_1px,transparent_1px)] [background-size:16px_16px] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_70%,transparent_100%)] opacity-20 dark:opacity-40"></div>
<div className="z-40 shrink-0 bg-background/85 backdrop-blur-sm">
<div className="mx-auto w-full max-w-[1400px] px-4 py-4 sm:px-7 lg:px-10">
<div className="mx-auto w-full max-w-[1400px] px-4 py-6 sm:px-7 sm:py-8 lg:px-10">
<div className="flex flex-col gap-1">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between sm:gap-3">
<h1 className="min-w-0 truncate whitespace-nowrap font-bold text-2xl sm:flex-1 sm:pr-4">
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-start sm:gap-2">
<h1 className="min-w-0 truncate whitespace-nowrap font-bold text-2xl">
{headerTitle}
</h1>
<a
Expand Down
3 changes: 3 additions & 0 deletions site/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions site/components/theme-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use client";

import type { ReactNode } from "react";
import { ThemeProvider as NextThemesProvider } from "next-themes";

export type ThemeMode = "light" | "dark" | "system";

export function ThemeProvider({
children,
defaultTheme = "dark",
}: {
children: ReactNode;
defaultTheme?: ThemeMode;
}) {
return (
<NextThemesProvider
attribute="class"
defaultTheme={defaultTheme}
enableSystem
// Keep iframe transparency rendering stable across theme switches.
enableColorScheme={false}
disableTransitionOnChange
>
{children}
</NextThemesProvider>
);
}
32 changes: 32 additions & 0 deletions site/components/theme-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use client";

import * as React from "react";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";

import { Button } from "@/components/ui/button";

export function ThemeToggle() {
const { resolvedTheme, setTheme } = useTheme();
const [mounted, setMounted] = React.useState(false);

React.useEffect(() => {
setMounted(true);
}, []);

const isDark = mounted ? resolvedTheme !== "light" : true;

return (
<Button
type="button"
variant="outline"
size="icon-sm"
aria-label={isDark ? "Switch to light theme" : "Switch to dark theme"}
className="size-8 cursor-pointer rounded-full border-border/70 bg-background/90 shadow-sm backdrop-blur supports-backdrop-filter:bg-background/70"
onClick={() => setTheme(isDark ? "light" : "dark")}
>
{isDark ? <Moon className="h-4 w-4" /> : <Sun className="h-4 w-4" />}
<span className="sr-only">Current theme: {isDark ? "dark" : "light"}</span>
</Button>
);
}
1 change: 1 addition & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"cmdk": "^1.1.1",
"lucide-react": "^0.575.0",
"next": "^16.1.6",
"next-themes": "^0.4.6",
"radix-ui": "^1.4.3",
"react": "^19.2.4",
"react-dom": "^19.2.4",
Expand Down
Loading