feat(lens): keep the live run ambient until View run is clicked

The drawer no longer opens on Run or when entering a running investigation. LiveRun takes reviews as a prop so it can move to a dedicated reviews endpoint.
This commit is contained in:
Ishaan Jaff 2026-10-03 17:31:32 -07:00
parent 7494885093
commit 1dfcdf223e
No known key found for this signature in database
4 changed files with 33 additions and 40 deletions

View file

@ -105,7 +105,7 @@ export function InvestigationDetail({
onCancel={readOnly ? undefined : () => void update((api) => api.cancelRun(lens.id))}
/>
)}
{live && <LiveRun key={live.id} job={live} name={lens.settings.name} />}
{live && <LiveRun key={live.id} job={live} reviews={live.reviews} name={lens.settings.name} />}
{runNow && (
<RunNowDialog
lens={lens}

View file

@ -6,6 +6,7 @@ import { modelsUsed } from "../../model/inbox";
import {
analysisModel,
conclusions,
decidedReviews,
focusedReview,
issueCount,
reviewKey,
@ -15,30 +16,33 @@ import {
import type { Job, Review } from "../../model/types";
import { LiveDrawer } from "./LiveDrawer";
import { LiveStrip } from "./LiveStrip";
import { useDrawerOpen, useStripOpen } from "./useLivePanels";
import { useStripOpen } from "./useLivePanels";
import { useReviewPlayback } from "./useReviewPlayback";
export function LiveRun({ job, name }: { job: Job; name: string }) {
export function LiveRun({ job, reviews, name }: { job: Job; reviews: readonly Review[]; name: string }) {
const live = job.status === "queued" || job.status === "running";
const [stripOpen, setStripOpen] = useStripOpen();
const [drawerOpen, setDrawerOpen] = useDrawerOpen(job.id, live);
const [drawerOpen, setDrawerOpen] = useState(false);
const [pinned, setPinned] = useState<string | null>(null);
const playback = useReviewPlayback(job.reviews, live);
const model = analysisModel([...job.reviews.map((r) => r.model), ...modelsUsed(job.steps), job.settings.model]);
const { review: focused, following } = focusedReview(job.reviews, pinned, playback.current);
const issues = issueCount(job);
const playback = useReviewPlayback(reviews, live);
const withReviews = { ...job, reviews: [...reviews] };
const model = analysisModel([...reviews.map((r) => r.model), ...modelsUsed(job.steps), job.settings.model]);
const { review: focused, following } = focusedReview(reviews, pinned, playback.current);
const issues = issueCount(withReviews);
const reviewed = live ? shownCount(job.reviewed, playback) : job.reviewed;
const decided = decidedReviews(playback, playback.phase.verdict);
const pin = (review: Review) => setPinned(review === playback.current ? null : reviewKey(review));
const open = (review?: Review) => {
setPinned(review && review !== playback.current ? reviewKey(review) : null);
if (review) pin(review);
setDrawerOpen(true);
};
return (
<>
{stripOpen && (
{stripOpen ? (
<LiveStrip
model={model}
state={stripState(job, model)}
state={stripState(withReviews, model)}
playback={playback}
reviewed={reviewed}
selected={job.coverage.selected}
@ -47,8 +51,7 @@ export function LiveRun({ job, name }: { job: Job; name: string }) {
onOpen={open}
onClose={() => setStripOpen(false)}
/>
)}
{!stripOpen && (
) : (
<button
type="button"
onClick={() => setStripOpen(true)}
@ -62,15 +65,17 @@ export function LiveRun({ job, name }: { job: Job; name: string }) {
onClose={() => setDrawerOpen(false)}
name={name}
model={model}
job={job}
status={job.status}
reviewed={reviewed}
playback={playback}
live={live}
focused={focused}
following={following}
phase={playback.phase}
groups={conclusions(job.reviews, job.settings.checks)}
scope={issues.scope === "findings" ? `from the last ${job.reviews.length} reviews` : issues.scope}
onPick={(review) => setPinned(review === playback.current ? null : reviewKey(review))}
groups={conclusions(decided, job.settings.checks)}
decided={decided.length}
scope={job.reviewed > reviews.length ? `From the latest ${reviews.length} of ${job.reviewed} reviewed traces` : ""}
onPick={pin}
onFollow={() => setPinned(null)}
/>
</>

View file

@ -13,16 +13,17 @@ import { outcome, providerOf, queueRows, reviewKey, shortVerdict, type IssueCoun
import type { Playback } from "../../model/live";
import type { Review } from "../../model/types";
const RECENT = 5;
const RECENT = 3;
const MARK = { issue: "●", clear: "✓", unknown: "–" } as const;
const LOGO = { sm: "size-3.5", md: "size-[18px]" } as const;
export function ModelName({ model }: { model: string }) {
export function ModelName({ model, size = "sm" }: { model: string; size?: keyof typeof LOGO }) {
if (!model) return null;
const provider = providerOf(model);
return (
<span data-testid="live-model" className="inline-flex shrink-0 items-center gap-1.5 text-foreground">
{provider && <ProviderLogo provider={provider} className="size-4 shrink-0" />}
<span className="font-mono text-[11px]">{model}</span>
{provider && <ProviderLogo provider={provider} className={cn("shrink-0", LOGO[size])} />}
<span className={cn("font-mono", size === "md" ? "text-[12px]" : "text-[11px]")}>{model}</span>
</span>
);
}
@ -31,7 +32,7 @@ function RecentLine({ review, now, onOpen }: { review: Review; now: number; onOp
const result = outcome(review);
const issue = result === "issue";
return (
<li className="motion-safe:animate-in motion-safe:fade-in motion-safe:duration-300">
<li className="motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-top-1 motion-safe:duration-300">
<button
type="button"
onClick={() => onOpen(review)}
@ -40,7 +41,7 @@ function RecentLine({ review, now, onOpen }: { review: Review; now: number; onOp
<span aria-label={result} className={issue ? "text-[#e5484d]" : ""}>
{MARK[result]}
</span>
<span className="truncate">{review.agent || review.name}</span>
<span className="truncate text-foreground">{review.agent || review.name}</span>
<span className={cn("truncate", issue && "text-[#e5484d]")}>{shortVerdict(review)}</span>
<span className="tabular-nums">{agoLabel(Date.parse(review.at), now)}</span>
</button>
@ -80,7 +81,7 @@ export function LiveStrip({
return (
<section
aria-label="Live trace results"
className="flex flex-wrap gap-x-6 gap-y-2 rounded-md bg-muted/30 px-3 py-2 text-[12px] text-muted-foreground"
className="flex flex-wrap items-start gap-x-6 gap-y-2 rounded-md bg-muted/30 px-3 py-2 text-[12px] text-muted-foreground"
>
<div className="flex min-w-[16rem] flex-col gap-1">
<ModelName model={model} />
@ -107,10 +108,10 @@ export function LiveStrip({
</ol>
)}
</div>
<div className="flex shrink-0 items-start gap-1">
<Button variant="ghost" size="xs" onClick={() => onOpen()} disabled={!recent.length}>
<div className="flex shrink-0 items-center gap-1">
<Button variant="outline" size="xs" onClick={() => onOpen()} disabled={!recent.length}>
<PanelRight />
View live
View run
</Button>
<Button variant="ghost" size="icon-xs" aria-label="Hide live trace results" onClick={onClose}>
<X />

View file

@ -3,7 +3,6 @@
import { useCallback, useState } from "react";
const STRIP_KEY = "litellm.lens.liveStrip";
const DRAWER_KEY = "litellm.lens.liveDrawerClosed";
function read(key: string): string | null {
try {
@ -29,15 +28,3 @@ export function useStripOpen(): [boolean, (open: boolean) => void] {
}, []);
return [open, update];
}
export function useDrawerOpen(jobId: string, live: boolean): [boolean, (open: boolean) => void] {
const [open, setOpen] = useState(() => live && read(DRAWER_KEY) !== jobId);
const update = useCallback(
(next: boolean) => {
setOpen(next);
if (!next) write(DRAWER_KEY, jobId);
},
[jobId],
);
return [open, update];
}