feat(frontend): smol improvements on new run view (#10854)

## Changes 🏗️

<img width="800" height="790" alt="Screenshot 2025-09-05 at 17 22 36"
src="https://github.com/user-attachments/assets/8b22424c-1968-4c4f-9eed-3d5d5185751d"
/>

- Make a nicer empty state and display it when there are no
runs/schedules
- Rename search param to `executionId` to mirror what was on the old
page
- Reduce polling when execution is happening to 1.5s ( 3.s is too slow
maybe... )
- Make sure the run details page also updates when a run is happening

## Checklist 📋

### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Run the app
  - [x] Tested the above 

### For configuration changes:

None

<details>
  <summary>Examples of configuration changes</summary>

  - Changing ports
  - Adding new services that need to communicate with each other
  - Secrets or environment variable changes
  - New or infrastructure changes such as databases
</details>
This commit is contained in:
Ubbe
2025-09-06 18:02:25 +09:00
committed by GitHub
parent cfc975d39b
commit 3952a1a226
7 changed files with 163 additions and 20 deletions

View File

@@ -5,9 +5,10 @@ import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
import { useAgentRunsView } from "./useAgentRunsView";
import { AgentRunsLoading } from "./components/AgentRunsLoading";
import { RunsSidebar } from "./components/RunsSidebar/RunsSidebar";
import React from "react";
import React, { useMemo, useState } from "react";
import { RunDetails } from "./components/RunDetails/RunDetails";
import { ScheduleDetails } from "./components/ScheduleDetails/ScheduleDetails";
import { EmptyAgentRuns } from "./components/EmptyAgentRuns/EmptyAgentRuns";
export function AgentRunsView() {
const {
@@ -19,6 +20,17 @@ export function AgentRunsView() {
handleSelectRun,
clearSelectedRun,
} = useAgentRunsView();
const [sidebarCounts, setSidebarCounts] = useState({
runsCount: 0,
schedulesCount: 0,
});
const hasAnyItems = useMemo(
() =>
(sidebarCounts.runsCount ?? 0) > 0 ||
(sidebarCounts.schedulesCount ?? 0) > 0,
[sidebarCounts],
);
if (!ready) {
return <AgentRunsLoading />;
@@ -57,27 +69,39 @@ export function AgentRunsView() {
const agent = response.data;
return (
<div className="grid h-screen grid-cols-1 gap-0 pt-6 md:gap-4 lg:grid-cols-[25%_70%]">
<RunsSidebar
agent={agent}
selectedRunId={selectedRun}
onSelectRun={handleSelectRun}
/>
<div
className={
hasAnyItems
? "grid h-screen grid-cols-1 gap-0 pt-6 md:gap-4 lg:grid-cols-[25%_70%]"
: "grid h-screen grid-cols-1 gap-0 pt-6 md:gap-4"
}
>
<div className={hasAnyItems ? "" : "hidden"}>
<RunsSidebar
agent={agent}
selectedRunId={selectedRun}
onSelectRun={handleSelectRun}
onCountsChange={setSidebarCounts}
/>
</div>
{/* Main Content - 70% */}
<div className="p-4">
<Breadcrumbs
items={[
{ name: "My Library", link: "/library" },
{ name: agent.name, link: `/library/agents/${agentId}` },
]}
/>
<div className={!hasAnyItems ? "px-2" : ""}>
<Breadcrumbs
items={[
{ name: "My Library", link: "/library" },
{ name: agent.name, link: `/library/agents/${agentId}` },
]}
/>
</div>
<div className="mt-1">
{selectedRun ? (
selectedRun.startsWith("schedule:") ? (
<ScheduleDetails
agent={agent}
scheduleId={selectedRun.replace("schedule:", "")}
onClearSelectedRun={clearSelectedRun}
/>
) : (
<RunDetails
@@ -87,10 +111,17 @@ export function AgentRunsView() {
onClearSelectedRun={clearSelectedRun}
/>
)
) : (
) : hasAnyItems ? (
<div className="text-gray-600">
Select a run to view its details
</div>
) : (
<EmptyAgentRuns
agentName={agent.name}
creatorName={agent.creator_name || "Unknown"}
description={agent.description}
agent={agent}
/>
)}
</div>
</div>

View File

@@ -0,0 +1,70 @@
import { ShowMoreText } from "@/components/molecules/ShowMoreText/ShowMoreText";
import { RunDetailCard } from "../RunDetailCard/RunDetailCard";
import { Text } from "@/components/atoms/Text/Text";
import { RunAgentModal } from "../RunAgentModal/RunAgentModal";
import { LibraryAgent } from "@/app/api/__generated__/models/libraryAgent";
import { Button } from "@/components/atoms/Button/Button";
import { PlusIcon } from "@phosphor-icons/react";
type Props = {
agentName: string;
creatorName: string;
description: string;
agent: LibraryAgent;
};
export function EmptyAgentRuns({
agentName,
creatorName,
description,
agent,
}: Props) {
const isUnknownCreator = creatorName === "Unknown";
return (
<div className="mt-6 px-2">
<RunDetailCard className="relative min-h-[70vh]">
<div className="absolute left-1/2 top-1/2 flex w-[80%] -translate-x-1/2 -translate-y-1/2 flex-col gap-6 md:w-[60%] lg:w-auto">
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-2">
<Text
variant="h3"
className="truncate text-ellipsis !font-normal"
>
{agentName}
</Text>
{!isUnknownCreator ? (
<Text variant="body-medium">by {creatorName}</Text>
) : null}
</div>
{description ? (
<ShowMoreText
previewLimit={80}
variant="small"
className="mt-4 !text-zinc-700"
>
{description}
</ShowMoreText>
) : null}
</div>
<div className="flex flex-col gap-4">
<Text variant="h4">You dont have any runs</Text>
<Text variant="large">
Get started with creating a run, and youll see information here
</Text>
</div>
<RunAgentModal
triggerSlot={
<Button variant="primary" size="large" className="w-full">
<PlusIcon size={20} /> New Run
</Button>
}
agent={agent}
agentId={agent.id.toString()}
/>
</div>
</RunDetailCard>
</div>
);
}

View File

@@ -1,10 +1,18 @@
import { cn } from "@/lib/utils";
type Props = {
children: React.ReactNode;
className?: string;
};
export function RunDetailCard({ children }: Props) {
export function RunDetailCard({ children, className }: Props) {
return (
<div className="min-h-20 rounded-xlarge border border-slate-50/70 bg-white p-6">
<div
className={cn(
"min-h-20 rounded-xlarge border border-slate-50/70 bg-white p-6",
className,
)}
>
{children}
</div>
);

View File

@@ -2,9 +2,32 @@
import { useGetV1GetExecutionDetails } from "@/app/api/__generated__/endpoints/graphs/graphs";
import type { GetV1GetExecutionDetails200 } from "@/app/api/__generated__/models/getV1GetExecutionDetails200";
import { AgentExecutionStatus } from "@/app/api/__generated__/models/agentExecutionStatus";
export function useRunDetails(graphId: string, runId: string) {
const query = useGetV1GetExecutionDetails(graphId, runId);
const query = useGetV1GetExecutionDetails(graphId, runId, {
query: {
refetchInterval: (q) => {
const isSuccess = q.state.data?.status === 200;
if (!isSuccess) return false;
const status =
q.state.data?.status === 200 ? q.state.data.data.status : undefined;
if (!status) return false;
if (
status === AgentExecutionStatus.RUNNING ||
status === AgentExecutionStatus.QUEUED ||
status === AgentExecutionStatus.INCOMPLETE
)
return 1500;
return false;
},
refetchIntervalInBackground: true,
refetchOnWindowFocus: false,
},
});
const status = query.data?.status;

View File

@@ -1,6 +1,6 @@
"use client";
import React from "react";
import React, { useEffect } from "react";
import {
TabsLine,
TabsLineList,
@@ -23,12 +23,17 @@ interface RunsSidebarProps {
agent: LibraryAgent;
selectedRunId?: string;
onSelectRun: (id: string) => void;
onCountsChange?: (info: {
runsCount: number;
schedulesCount: number;
}) => void;
}
export function RunsSidebar({
agent,
selectedRunId,
onSelectRun,
onCountsChange,
}: RunsSidebarProps) {
const {
runs,
@@ -44,6 +49,10 @@ export function RunsSidebar({
setTabValue,
} = useRunsSidebar({ graphId: agent.graph_id, onSelectRun });
useEffect(() => {
if (onCountsChange) onCountsChange({ runsCount, schedulesCount });
}, [runsCount, schedulesCount, onCountsChange]);
if (error) {
return <ErrorCard responseError={error} />;
}

View File

@@ -8,6 +8,8 @@ import { GraphExecutionsPaginated } from "@/app/api/__generated__/models/graphEx
import type { GraphExecutionJobInfo } from "@/app/api/__generated__/models/graphExecutionJobInfo";
import { useSearchParams } from "next/navigation";
const AGENT_RUNNING_POLL_INTERVAL = 1500;
type Args = {
graphId?: string;
onSelectRun: (runId: string) => void;
@@ -40,7 +42,7 @@ export function useRunsSidebar({ graphId, onSelectRun }: Args) {
(e: { status?: string }) =>
e.status === "RUNNING" || e.status === "QUEUED",
);
return hasActive ? 3000 : false;
return hasActive ? AGENT_RUNNING_POLL_INTERVAL : false;
} catch {
return false;
}

View File

@@ -7,7 +7,7 @@ export function useAgentRunsView() {
const agentId = id as string;
const { data: response, isSuccess, error } = useGetV2GetLibraryAgent(agentId);
const [runParam, setRunParam] = useQueryState("run", parseAsString);
const [runParam, setRunParam] = useQueryState("executionId", parseAsString);
const selectedRun = runParam ?? undefined;
function handleSelectRun(id: string) {