mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
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:
parent
7494885093
commit
1dfcdf223e
4 changed files with 33 additions and 40 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
/>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue