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
3 changes: 3 additions & 0 deletions .zealt/pending-tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"pending-tasks": 0
}
55 changes: 44 additions & 11 deletions site/app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Github, Terminal, ClipboardList } from "lucide-react";
import { Github, Terminal, ClipboardList, ListTree } from "lucide-react";
import Link from "next/link";
import tasksData from "@/zealt/tasks.json";
import pendingTasksData from "@/zealt/pending-tasks.json";
import zealtConfig from "@/zealt/config.json";
import PendingReviewCard from "@/components/pending-review-card";
import LeaderboardTable, { type LeaderboardEntry } from "./components/leaderboard-table";

type TaskTrial = {
Expand All @@ -14,8 +17,17 @@ type TaskValue = {
trials?: TaskTrial[];
};

type PendingTasksValue = {
'pending-tasks'?: number;
};

export default function Home() {
const totalTasks = Object.keys(tasksData as Record<string, unknown>).length;
const hasTasks = totalTasks > 0;
const pendingSampleCases = Math.max(
0,
Number((pendingTasksData as PendingTasksValue)['pending-tasks'] ?? 0),
);

// Process tasks.json to compute leaderboard stats directly on the server
const statsMap = new Map<string, {
Expand All @@ -40,9 +52,9 @@ export default function Home() {
// Simplify model name
const modelName = trial.model.split('/').pop() || trial.model;
const agentName = trial.agent.charAt(0).toUpperCase() + trial.agent.slice(1);

const key = `${modelName}-${agentName}`;

if (!statsMap.has(key)) {
statsMap.set(key, {
passed: 0,
Expand All @@ -53,7 +65,7 @@ export default function Home() {
agent: agentName
});
}

const stats = statsMap.get(key);
if (!stats) {
return;
Expand Down Expand Up @@ -116,11 +128,15 @@ export default function Home() {
<Github className="w-4 h-4" />
<span>View on GitHub</span>
</a>
<div className="hidden h-4 w-px bg-border sm:block"></div>
<span className="flex w-full sm:w-auto items-center justify-center gap-2">
<ClipboardList className="w-4 h-4" />
<span>Total tasks: {totalTasks}</span>
</span>
{data.length > 0 && (
<>
<div className="hidden h-4 w-px bg-border sm:block"></div>
<span className="flex w-full sm:w-auto items-center justify-center gap-2">
<ClipboardList className="w-4 h-4" />
<span>Total tasks: {totalTasks}</span>
</span>
</>
)}
<div className="hidden h-4 w-px bg-border sm:block"></div>
<span className="flex w-full sm:w-auto items-center justify-center gap-2">
<Terminal className="w-4 h-4" />
Expand All @@ -129,8 +145,25 @@ export default function Home() {
</div>
</div>

{/* Client Component for Interactive Table */}
<LeaderboardTable data={data} />
{!hasTasks ? (
<PendingReviewCard pendingSampleCases={pendingSampleCases} />
) : data.length === 0 ? (
<div className="rounded-2xl border border-dashed border-border bg-card/40 backdrop-blur-sm px-8 py-14 text-center">
<h2 className="text-2xl font-semibold tracking-tight">No evaluation data yet</h2>
<div className="mt-6 flex justify-center">
<Link
href="./tasks"
className="flex items-center justify-center gap-2 px-4 py-2 border border-border bg-card/50 hover:bg-secondary/50 text-foreground rounded-lg text-sm font-medium transition-colors shadow-sm backdrop-blur-sm whitespace-nowrap"
>
<ListTree className="w-4 h-4" />
View Tasks
</Link>
</div>
</div>
) : (
// Client Component for Interactive Table
<LeaderboardTable data={data} />
)}
</div>
</div>
);
Expand Down
102 changes: 90 additions & 12 deletions site/app/tasks/components/tasks-page-client.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState, useMemo, useEffect, useCallback } from "react";
import { useState, useMemo, useEffect, useCallback, type ReactNode } from "react";
import {
Check,
X as XIcon,
Expand Down Expand Up @@ -92,6 +92,23 @@ function useMediaQuery(query: string) {
return matches;
}

type TableWrapperProps = {
hasRows: boolean;
children: ReactNode;
};

function TableWrapper({ hasRows, children }: TableWrapperProps) {
if (!hasRows) {
return (
<div className="text-center py-12 text-muted-foreground flex-1 flex items-center justify-center">
No tasks found matching your filters
</div>
);
}

return <div className="overflow-auto relative custom-scrollbar">{children}</div>;
}

export function TasksPageClient({ tasksData }: TasksPageClientProps) {
const router = useRouter();
const pathname = usePathname();
Expand Down Expand Up @@ -237,7 +254,29 @@ export function TasksPageClient({ tasksData }: TasksPageClientProps) {
return combos;
}, [allCombos, selectedModels.join(","), selectedAgents.join(",")]);

const filteredAndSortedTasks = useMemo(() => {
const noTrials = activeCombos.length === 0;

const tableTasks = useMemo(() => {
const query = searchQuery.trim().toLowerCase();

if (noTrials) {
const filtered = query
? tasksData.filter((task) => task.taskName.toLowerCase().includes(query))
: tasksData;

return [...filtered]
.map((task) => ({
taskName: task.taskName,
comboMap: {} as Record<string, CompactTrial>,
avgDuration: 0,
}))
.sort((a, b) =>
queryOrder === "desc"
? b.taskName.localeCompare(a.taskName)
: a.taskName.localeCompare(b.taskName),
);
}

const result = tasksData
.map((task) => {
const comboMap: Record<string, CompactTrial> = {};
Expand Down Expand Up @@ -300,7 +339,7 @@ export function TasksPageClient({ tasksData }: TasksPageClientProps) {
})
.filter((task) => {
if (!task.hasMatchingTrial) return false;
if (searchQuery && !task.taskName.toLowerCase().includes(searchQuery.toLowerCase())) return false;
if (query && !task.taskName.toLowerCase().includes(query)) return false;
return true;
});

Expand All @@ -314,9 +353,10 @@ export function TasksPageClient({ tasksData }: TasksPageClientProps) {
: b.taskName.localeCompare(a.taskName);
});

return result;
return result.map(({ taskName, comboMap, avgDuration }) => ({ taskName, comboMap, avgDuration }));
}, [
activeCombos,
noTrials,
queryOrder,
querySort,
searchQuery,
Expand Down Expand Up @@ -473,12 +513,50 @@ export function TasksPageClient({ tasksData }: TasksPageClientProps) {
</div>

<div className="rounded-xl border border-border bg-card/50 backdrop-blur-sm shadow-sm overflow-hidden animate-in fade-in duration-500 relative flex flex-col max-h-full pb-1">
{filteredAndSortedTasks.length === 0 ? (
<div className="text-center py-12 text-muted-foreground flex-1 flex items-center justify-center">
No tasks found matching your filters
</div>
{noTrials ? (
<TableWrapper hasRows={tableTasks.length > 0}>
<table className="w-full text-sm text-left border-collapse">
<thead className="sticky top-0 z-30 text-muted-foreground font-medium border-b border-border select-none shadow-sm">
<tr>
<th className="md:sticky left-0 z-40 bg-secondary/95 backdrop-blur md:bg-[#f6f6f6] dark:md:bg-[#0f0f0f] border-r border-border/50 px-3 sm:px-6 py-3 w-[200px] min-w-[200px] max-w-[200px] md:w-[350px] md:min-w-[350px] md:max-w-[350px]">
<div className="flex items-center gap-1 sm:gap-2">
<span className="truncate">Task Name ({tableTasks.length} tasks)</span>
</div>
</th>
<th className="p-0 min-w-[220px] border-0 bg-transparent"></th>
</tr>
</thead>
<tbody className="divide-y divide-border/30">
{tableTasks.map((task, index) => (
<tr key={task.taskName} className="transition-colors duration-200">
<td className="md:sticky left-0 z-20 bg-background border-r border-border/50 p-0 font-mono w-[200px] min-w-[200px] max-w-[200px] md:w-[350px] md:min-w-[350px] md:max-w-[350px] md:shadow-[1px_0_0_rgba(0,0,0,0.05)]">
<button
type="button"
onClick={() => {
setSelectedTask(task.taskName);
setIsInstructionOpen(true);
}}
className="group/task flex items-center gap-2 px-3 sm:px-6 py-2 w-full h-full text-foreground hover:text-primary transition-colors focus:outline-none bg-transparent even:bg-secondary/5 hover:bg-secondary/30 cursor-pointer text-left"
title={`View ${task.taskName} instruction`}
>
<span className="truncate w-full block group-hover/task:underline text-xs md:text-sm">{task.taskName}</span>
</button>
</td>
{index === 0 ? (
<td
rowSpan={tableTasks.length}
className="px-3 sm:px-6 pt-10 pb-2 border-l border-border/50 min-w-[220px] text-xs md:text-sm align-top text-center"
>
<span className="font-medium text-foreground/90">No evaluation data yet</span>
</td>
) : null}
</tr>
))}
</tbody>
</table>
</TableWrapper>
) : (
<div className="overflow-auto relative custom-scrollbar">
<TableWrapper hasRows={tableTasks.length > 0}>
<table className="w-full text-sm text-left border-collapse">
<thead className="sticky top-0 z-30 bg-secondary/95 backdrop-blur text-muted-foreground font-medium border-b border-border select-none shadow-sm">
<tr>
Expand All @@ -487,7 +565,7 @@ export function TasksPageClient({ tasksData }: TasksPageClientProps) {
onClick={() => toggleSort("taskName")}
>
<div className="flex items-center gap-1 sm:gap-2">
<span className="truncate">Task Name ({filteredAndSortedTasks.length} tasks)</span>
<span className="truncate">Task Name ({tableTasks.length} tasks)</span>
{renderSortIcon("taskName")}
</div>
</th>
Expand All @@ -503,7 +581,7 @@ export function TasksPageClient({ tasksData }: TasksPageClientProps) {
</tr>
</thead>
<tbody className="divide-y divide-border/30">
{filteredAndSortedTasks.map((task) => (
{tableTasks.map((task) => (
<tr key={task.taskName} className="hover:bg-secondary/30 even:bg-secondary/5 transition-colors duration-200 group">
<td className="md:sticky left-0 z-20 bg-background border-r border-border/50 p-0 font-mono w-[200px] min-w-[200px] max-w-[200px] md:w-[350px] md:min-w-[350px] md:max-w-[350px] md:shadow-[1px_0_0_rgba(0,0,0,0.05)]">
<button
Expand Down Expand Up @@ -594,7 +672,7 @@ export function TasksPageClient({ tasksData }: TasksPageClientProps) {
))}
</tbody>
</table>
</div>
</TableWrapper>
)}
</div>

Expand Down
31 changes: 30 additions & 1 deletion site/app/tasks/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import tasksDataRaw from "@/zealt/tasks.json";
import pendingTasksData from "@/zealt/pending-tasks.json";
import PendingReviewCard from "@/components/pending-review-card";
import { TasksPageClient, type CompactTask, type CompactTrial } from "./components/tasks-page-client";

type RawTaskTrial = {
Expand All @@ -24,6 +26,10 @@ type RawTaskRecord = {
trials?: RawTaskTrial[];
};

type PendingTasksValue = {
'pending-tasks'?: number;
};

function splitTrialName(trialName: string): { taskName: string; jobId: string } | null {
const separatorIndex = trialName.lastIndexOf("__");
if (separatorIndex <= 0 || separatorIndex >= trialName.length - 2) {
Expand Down Expand Up @@ -98,12 +104,35 @@ function buildCompactTasksData(): CompactTask[] {

export default function TasksPage() {
const compactTasksData = buildCompactTasksData();
const pendingSampleCases = Math.max(
0,
Number((pendingTasksData as PendingTasksValue)['pending-tasks'] ?? 0),
);

return (
<div className="min-h-screen 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>

<TasksPageClient tasksData={compactTasksData} />
{compactTasksData.length === 0 ? (
<div className="container mx-auto px-4 sm:px-8 lg:px-12 py-8 max-w-screen-2xl h-[100dvh] flex flex-col overflow-hidden">
<div className="mb-6 space-y-4 shrink-0">
<div className="flex items-center gap-4">
<a href="/" className="text-sm text-muted-foreground hover:text-foreground transition-colors">
&larr; Back to Leaderboard
</a>
</div>
<div>
<h1 className="text-4xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-b from-foreground to-foreground/50">
Task
</h1>
</div>
</div>

<PendingReviewCard pendingSampleCases={pendingSampleCases} />
</div>
) : (
<TasksPageClient tasksData={compactTasksData} />
)}
</div>
);
}
44 changes: 44 additions & 0 deletions site/components/pending-review-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
type PendingReviewCardProps = {
pendingSampleCases: number;
};

export default function PendingReviewCard({ pendingSampleCases }: PendingReviewCardProps) {
return (
<div className="relative overflow-hidden rounded-2xl border border-primary/30 bg-gradient-to-br from-primary/10 via-card/90 to-background/90 px-5 pb-10 pt-6 text-center shadow-[0_0_0_1px_hsl(var(--primary)/0.18),0_28px_72px_-32px_hsl(var(--foreground)/0.45)] backdrop-blur-sm sm:px-8 sm:pb-14 sm:pt-8">
<div className="pointer-events-none absolute -right-16 -top-16 h-36 w-36 rounded-full bg-primary/20 blur-3xl sm:-right-14 sm:-top-14 sm:h-40 sm:w-40" />
<div className="pointer-events-none absolute -left-20 bottom-0 h-32 w-32 rounded-full bg-foreground/10 blur-3xl sm:h-36 sm:w-36" />

<div className="relative mx-auto mb-8 inline-flex items-center rounded-full border border-primary/40 bg-primary/15 px-3 py-1 text-xs font-semibold tracking-[0.12em] text-foreground sm:text-sm sm:tracking-[0.16em]">
⏳ PIPELINE PENDING REVIEW
</div>

<p className="relative mx-auto max-w-2xl px-4 text-center text-lg font-semibold tracking-wide text-foreground sm:px-5 sm:text-xl">
{pendingSampleCases} sample cases generated — pending review
</p>

<p className="relative mx-auto mt-2 max-w-2xl px-4 text-left text-sm leading-relaxed text-muted-foreground sm:px-5">
Your sample cases are ready. Before moving forward, we do a quick review together to make sure everything looks right and aligns with real-world usage.
</p>

<div className="relative mx-auto mt-8 max-w-2xl rounded-xl bg-primary/10 px-4 py-4 text-left sm:px-5">
<p className="text-base font-semibold text-foreground sm:text-lg text-center">
Next step: join Slack for review
</p>
<p className="mt-1 text-sm leading-relaxed text-muted-foreground">
We&apos;ve sent you a Slack invitation to your registered email with Zealt. Please join and we&apos;ll walk through the next step together.
</p>
<p className="mt-3 text-xs text-muted-foreground">
If you don&apos;t see it, just reach out at{" "}
<a
href="mailto:zealtdev@tabbyml.com"
className="font-normal text-foreground underline decoration-primary/60 underline-offset-4 hover:text-primary transition-colors"
>
zealtdev@tabbyml.com
</a>
.
</p>
</div>

</div>
);
}
Loading
Loading