feat(lens): refine onboarding motion and linked examples

This commit is contained in:
moe-berri 2026-10-03 19:10:25 -07:00
parent 28dd5b512e
commit 290fc203ac
3 changed files with 295 additions and 114 deletions

View file

@ -0,0 +1,108 @@
import { useId } from "react";
import styles from "./LensIntroduction.module.css";
const streams = [
{
id: "runs",
color: "text-sky-500 dark:text-sky-400",
path: "M -32 66 C 24 66 32 22 96 22 S 192 86 256 86 S 368 28 422 28 C 464 28 472 54 500 54 C 558 54 584 86 646 86 S 756 22 818 22 S 924 86 986 86 S 1040 54 1056 54",
},
{
id: "steps",
color: "text-violet-500 dark:text-violet-400",
path: "M -32 30 C 40 30 58 78 120 78 S 218 24 284 24 S 390 76 444 76 C 470 76 480 54 503 54",
},
{
id: "tools",
color: "text-amber-500 dark:text-amber-400",
path: "M -32 54 C 26 54 68 68 128 68 S 230 38 288 38 S 396 62 448 62 C 474 62 484 54 503 54",
},
];
export function GatewayFlow() {
const id = useId();
return (
<div className="mt-5 sm:mt-6">
<div className="grid grid-cols-3 gap-3 text-xs">
<div>
<p className="font-semibold">Agent swarms</p>
<p className="mt-0.5 leading-4 text-muted-foreground">Every run, every recorded step</p>
</div>
<div className="text-center">
<p className="font-semibold">LiteLLM gateway</p>
<p className="mt-0.5 leading-4 text-muted-foreground">One place, your infrastructure</p>
</div>
<div className="text-right">
<p className="font-semibold">Lens</p>
<p className="mt-0.5 leading-4 text-muted-foreground">Findings to improve your agents</p>
</div>
</div>
<svg
aria-hidden="true"
viewBox="0 0 1000 108"
className={`${styles.flowGraphic} mt-2 h-auto w-full`}
focusable="false"
>
<defs>
<pattern id={`${id}-field`} width="12" height="12" patternUnits="userSpaceOnUse">
<circle cx="6" cy="6" r="1.15" className="fill-muted-foreground/15" />
</pattern>
<linearGradient id={`${id}-fade`}>
<stop offset="0" stopColor="white" stopOpacity="0" />
<stop offset="0.06" stopColor="white" />
<stop offset="0.94" stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0" />
</linearGradient>
<mask id={`${id}-edges`}>
<rect width="1000" height="108" fill={`url(#${id}-fade)`} />
</mask>
<radialGradient id={`${id}-glow`} className="text-indigo-500 dark:text-indigo-400">
<stop stopColor="currentColor" stopOpacity="0.2" />
<stop offset="1" stopColor="currentColor" stopOpacity="0" />
</radialGradient>
<linearGradient id={`${id}-gate`} x1="0" y1="0" x2="0" y2="1">
<stop stopColor="currentColor" className="text-sky-400" />
<stop offset="0.5" stopColor="currentColor" className="text-indigo-500 dark:text-indigo-300" />
<stop offset="1" stopColor="currentColor" className="text-violet-400" />
</linearGradient>
{streams.map((stream) => (
<linearGradient
key={stream.id}
id={`${id}-${stream.id}`}
x1="470"
x2="530"
y1="0"
y2="0"
gradientUnits="userSpaceOnUse"
className={stream.color}
>
<stop stopColor="currentColor" />
<stop offset="1" stopColor="currentColor" className="text-indigo-500 dark:text-indigo-400" />
</linearGradient>
))}
</defs>
<g mask={`url(#${id}-edges)`}>
<rect width="1000" height="108" fill={`url(#${id}-field)`} />
<ellipse cx="500" cy="54" rx="58" ry="54" fill={`url(#${id}-glow)`} className={styles.gatewayGlow} />
{streams.map((stream) => (
<g key={stream.id} fill="none" stroke={`url(#${id}-${stream.id})`} strokeLinecap="round">
<path d={stream.path} strokeWidth="1" opacity="0.13" />
<path d={stream.path} strokeWidth="2.4" className={styles.trails} />
<path d={stream.path} strokeWidth="3.6" className={styles.packets} />
</g>
))}
</g>
<rect x="497" y="10" width="6" height="88" rx="3" className="fill-background/90" />
<path
d="M 490 10 H 497 V 98 H 490 M 510 10 H 503 V 98 H 510"
fill="none"
stroke={`url(#${id}-gate)`}
strokeWidth="1.4"
opacity="0.75"
/>
<circle cx="500" cy="54" r="4" className="fill-background stroke-indigo-400" strokeWidth="1.4" />
<circle cx="500" cy="54" r="1.6" className="fill-indigo-500 dark:fill-indigo-300" />
</svg>
</div>
);
}

View file

@ -1,18 +1,117 @@
.flow {
animation: gateway-flow 10s linear infinite;
.packets {
stroke-dasharray: 0 16;
animation: signal-flow 14s linear infinite;
}
@keyframes gateway-flow {
.trails {
stroke-dasharray: 6 42;
stroke-dashoffset: -10;
opacity: 0.3;
animation: signal-trails 14s linear infinite;
}
.gatewayGlow {
opacity: 0.7;
transition: opacity 300ms ease;
}
.flowGraphic:hover .gatewayGlow {
opacity: 1;
}
.sampleCard {
transition:
border-color 200ms ease,
box-shadow 200ms ease;
}
.sampleCard:hover,
.sampleCard:focus-within {
border-color: color-mix(in oklab, var(--color-indigo-500) 22%, var(--border));
box-shadow: 0 4px 20px -16px color-mix(in oklab, var(--color-indigo-500) 40%, transparent);
}
.examples {
--evidence-tint: color-mix(in oklab, var(--color-rose-400) 6%, transparent);
--evidence-outline: color-mix(in oklab, var(--color-rose-400) 32%, transparent);
}
.evidenceLink {
cursor: pointer;
border-radius: 4px;
outline-offset: 4px;
transition: background-color 200ms ease;
}
.evidenceLink:focus-visible {
outline: 2px solid var(--ring);
}
.evidenceLink:hover {
background: var(--evidence-tint);
}
.evidenceOutput,
.evidenceFinding,
.evidenceBar {
transition:
background-color 200ms ease,
box-shadow 200ms ease;
}
.evidenceFinding {
border-radius: 6px;
}
.examples:is(
[data-evidence-active="true"],
:has([data-evidence-link]:hover),
:has([data-evidence-link]:focus-visible)
) {
.evidenceOutput {
box-shadow: inset 0 0 0 1px var(--evidence-outline);
}
.evidenceFinding {
background: var(--evidence-tint);
box-shadow: 0 0 0 7px var(--evidence-tint);
}
.evidenceBar {
box-shadow: 0 0 10px 2px var(--evidence-outline);
}
}
@keyframes signal-flow {
from {
transform: translateX(0);
stroke-dashoffset: 0;
}
to {
transform: translateX(240px);
stroke-dashoffset: -144;
}
}
@keyframes signal-trails {
from {
stroke-dashoffset: -10;
}
to {
stroke-dashoffset: -154;
}
}
@media (prefers-reduced-motion: reduce) {
.flow {
.packets,
.trails {
animation: none;
}
.gatewayGlow,
.sampleCard,
.evidenceLink,
.evidenceOutput,
.evidenceFinding,
.evidenceBar {
transition: none;
}
}

View file

@ -1,77 +1,8 @@
import { useId } from "react";
import { useState } from "react";
import { ArrowRight, ArrowUpRight, ChevronDown, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import styles from "./LensIntroduction.module.css";
const swarmColors = ["fill-violet-400", "fill-sky-400", "fill-amber-400", "fill-rose-400", "fill-indigo-400"];
function flowDot(index: number) {
const column = (index % 124) - 24;
const row = Math.floor(index / 124);
const phase = (column + 24) % 24;
const wave = 2.5 + 2.6 * Math.sin(((phase + 5) / 24) * Math.PI * 2);
if (Math.abs(row - wave) > 0.9) return null;
return {
x: column * 10 + 5,
y: row * 10 + 5,
color: swarmColors[(phase + row) % swarmColors.length],
};
}
const flowDots = Array.from({ length: 744 }, (_, index) => flowDot(index)).filter((dot) => dot !== null);
function GatewayFlow() {
const patternId = useId();
const beforeGateway = `${patternId}-before`;
const afterGateway = `${patternId}-after`;
return (
<div className="mt-5 sm:mt-6">
<div className="grid grid-cols-3 gap-3 text-xs">
<div>
<p className="font-semibold">Agent swarms</p>
<p className="mt-0.5 leading-4 text-muted-foreground">Every run, every recorded step</p>
</div>
<div className="text-center">
<p className="font-semibold">LiteLLM gateway</p>
<p className="mt-0.5 leading-4 text-muted-foreground">One place, your infrastructure</p>
</div>
<div className="text-right">
<p className="font-semibold">Lens</p>
<p className="mt-0.5 leading-4 text-muted-foreground">Findings to improve your agents</p>
</div>
</div>
<svg aria-hidden="true" viewBox="0 0 1000 60" className="mt-2 h-auto w-full" focusable="false">
<defs>
<pattern id={patternId} width="10" height="10" patternUnits="userSpaceOnUse">
<circle cx="5" cy="5" r="2.3" className="fill-muted-foreground/10" />
</pattern>
<clipPath id={beforeGateway}>
<rect width="609" height="60" />
</clipPath>
<clipPath id={afterGateway}>
<rect x="609" width="391" height="60" />
</clipPath>
</defs>
<rect width="1000" height="60" fill={`url(#${patternId})`} />
<g clipPath={`url(#${beforeGateway})`}>
<g className={styles.flow}>
{flowDots.map((dot) => (
<circle key={`${dot.x}-${dot.y}`} cx={dot.x} cy={dot.y} r="2.3" className={dot.color} />
))}
</g>
</g>
<g clipPath={`url(#${afterGateway})`} className="fill-indigo-600 dark:fill-indigo-400">
<g className={styles.flow}>
{flowDots.map((dot) => (
<circle key={`${dot.x}-${dot.y}`} cx={dot.x} cy={dot.y} r="2.3" />
))}
</g>
</g>
<path d="M 615 1 H 609 V 59 H 615" fill="none" stroke="currentColor" strokeWidth="1.8" />
</svg>
</div>
);
}
import { GatewayFlow } from "./GatewayFlow";
const traceSteps = [
{ name: "research_agent", start: 0, duration: 14.2, failed: false },
@ -84,9 +15,14 @@ const traceSteps = [
const sampleAnswer = "The column store is 40% faster than the row store for your workload.";
function TraceExample() {
type EvidenceProps = { highlighted: boolean; onHighlight: () => void };
function TraceExample({ highlighted, onHighlight }: EvidenceProps) {
return (
<section aria-labelledby="lens-sample-trace" className="min-w-0 rounded-xl border bg-card p-4">
<section
aria-labelledby="lens-sample-trace"
className={`${styles.sampleCard} min-w-0 rounded-xl border bg-card p-4`}
>
<div className="flex flex-wrap items-center justify-between gap-2 text-sm">
<h3 id="lens-sample-trace" className="font-semibold">
Tracing
@ -94,38 +30,59 @@ function TraceExample() {
<p className="text-xs text-muted-foreground">Sample trace · 14.2s</p>
</div>
<ol className="mt-3 space-y-1.5 text-xs leading-5 sm:text-[13px]" aria-label="Sample trace timeline">
{traceSteps.map((item, index) => (
<li
key={`${item.name}-${item.start}`}
className="grid grid-cols-[minmax(0,1fr)_minmax(48px,1fr)_2.5rem] items-center gap-2 sm:gap-3"
>
<span className={`flex min-w-0 items-center gap-1 ${index > 0 ? "pl-2" : ""}`}>
{index === 0 ? (
<ChevronDown aria-hidden="true" className="size-3 shrink-0" />
) : (
<ChevronRight aria-hidden="true" className="size-3 shrink-0" />
)}
<span className="truncate" title={item.name}>
{item.name}
{traceSteps.map((item, index) => {
const linked = item.failed || item.name === "answer";
const rowClass =
"grid w-full grid-cols-[minmax(0,1fr)_minmax(48px,1fr)_2.5rem] items-center gap-2 text-left sm:gap-3";
const content = (
<>
<span className={`flex min-w-0 items-center gap-1 ${index > 0 ? "pl-2" : ""}`}>
{index === 0 ? (
<ChevronDown aria-hidden="true" className="size-3 shrink-0" />
) : (
<ChevronRight aria-hidden="true" className="size-3 shrink-0" />
)}
<span className="truncate" title={item.name}>
{item.name}
</span>
{item.failed && <span className="sr-only">Failed</span>}
</span>
{item.failed && <span className="sr-only">Failed</span>}
</span>
<span aria-hidden="true" className="relative h-2.5 overflow-hidden rounded-sm bg-muted/70">
<span
className={`absolute inset-y-0 rounded-sm ${item.failed ? "bg-rose-400" : "bg-indigo-600 dark:bg-indigo-400"}`}
style={{ left: `${(item.start / 14.2) * 100}%`, width: `${(item.duration / 14.2) * 100}%` }}
/>
</span>
<span className="text-right tabular-nums text-muted-foreground">{item.duration}s</span>
</li>
))}
<span aria-hidden="true" className="relative h-2.5 overflow-hidden rounded-sm bg-muted/70">
<span
className={`absolute inset-y-0 rounded-sm ${linked ? styles.evidenceBar : ""} ${item.failed ? "bg-rose-400" : "bg-indigo-600 dark:bg-indigo-400"}`}
style={{ left: `${(item.start / 14.2) * 100}%`, width: `${(item.duration / 14.2) * 100}%` }}
/>
</span>
<span className="text-right tabular-nums text-muted-foreground">{item.duration}s</span>
</>
);
return (
<li key={`${item.name}-${item.start}`}>
{linked ? (
<button
type="button"
className={`${rowClass} ${styles.evidenceLink}`}
data-evidence-link=""
aria-label={`${item.name}, ${item.duration} seconds${item.failed ? ", failed" : ""}. Highlight related finding`}
aria-controls="lens-linked-finding"
aria-pressed={highlighted}
onClick={onHighlight}
>
{content}
</button>
) : (
<div className={rowClass}>{content}</div>
)}
</li>
);
})}
</ol>
<div className="mt-3 grid gap-3 border-t pt-3 sm:grid-cols-2">
<div className="rounded-lg bg-muted/60 p-2.5">
<div id="lens-benchmark-output" className={`${styles.evidenceOutput} rounded-lg bg-muted/60 p-2.5`}>
<p className="text-xs text-muted-foreground">run_benchmark · output</p>
<p className="mt-1.5 text-[13px] leading-[18px]">Error: benchmark runner unavailable (503)</p>
</div>
<div className="rounded-lg bg-muted/60 p-2.5">
<div id="lens-answer-output" className={`${styles.evidenceOutput} rounded-lg bg-muted/60 p-2.5`}>
<p className="text-xs text-muted-foreground">answer · output</p>
<p className="mt-1.5 text-[13px] leading-[18px]">“{sampleAnswer}”</p>
</div>
@ -134,9 +91,12 @@ function TraceExample() {
);
}
function FindingExamples() {
function FindingExamples({ highlighted, onHighlight }: EvidenceProps) {
return (
<section aria-labelledby="lens-sample-findings" className="min-w-0 rounded-xl border bg-card p-4">
<section
aria-labelledby="lens-sample-findings"
className={`${styles.sampleCard} min-w-0 rounded-xl border bg-card p-4`}
>
<div className="flex flex-wrap items-center justify-between gap-2 text-sm">
<h3 id="lens-sample-findings" className="font-semibold">
Lens findings
@ -144,10 +104,19 @@ function FindingExamples() {
<p className="text-xs text-muted-foreground">500 sample runs reviewed</p>
</div>
<div className="mt-3 divide-y border-t">
<article className="py-3">
<h4 className="flex items-start gap-2 text-sm font-semibold">
<span aria-hidden="true" className="mt-1.5 size-2 shrink-0 rounded-full bg-rose-500" />
Claims numbers it never measured
<article id="lens-linked-finding" className={`${styles.evidenceFinding} py-3`}>
<h4 className="text-sm font-semibold">
<button
type="button"
className={`${styles.evidenceLink} flex w-full items-start gap-2 text-left`}
data-evidence-link=""
aria-controls="lens-benchmark-output lens-answer-output"
aria-pressed={highlighted}
onClick={onHighlight}
>
<span aria-hidden="true" className="mt-1.5 size-2 shrink-0 rounded-full bg-rose-500" />
Claims numbers it never measured
</button>
</h4>
<div className="ml-4 mt-1.5 space-y-1.5 text-[13px] leading-[18px]">
<p className="text-muted-foreground">Reports a speedup even though the benchmark failed.</p>
@ -177,6 +146,8 @@ function FindingExamples() {
}
export function LensIntroduction({ onStart, onDemo }: { onStart: () => void; onDemo?: () => void }) {
const [highlighted, setHighlighted] = useState(false);
const toggleEvidence = () => setHighlighted((current) => !current);
return (
<section
aria-labelledby="lens-introduction"
@ -210,9 +181,12 @@ export function LensIntroduction({ onStart, onDemo }: { onStart: () => void; onD
</a>
</div>
<GatewayFlow />
<div className="mt-4 grid items-stretch gap-3 md:grid-cols-2">
<TraceExample />
<FindingExamples />
<div
className={`${styles.examples} mt-4 grid items-stretch gap-3 md:grid-cols-2`}
data-evidence-active={highlighted}
>
<TraceExample highlighted={highlighted} onHighlight={toggleEvidence} />
<FindingExamples highlighted={highlighted} onHighlight={toggleEvidence} />
</div>
</section>
);