{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "latency-meter",
  "title": "AI Latency Meter",
  "description": "Response time visualization with TTFB and total duration display, color-coded performance indicators, animated progress bar, and compact/expanded variants.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/ai/ai-latency-meter/components/elements/ai-latency-meter.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { Clock, Timer, Zap } from \"lucide-react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype LatencyLevel = \"fast\" | \"moderate\" | \"slow\";\n\ninterface AiLatencyMeterContextValue {\n  ttfb?: number;\n  totalDuration?: number;\n  isLoading: boolean;\n  variant: \"compact\" | \"expanded\";\n  level: LatencyLevel;\n}\n\nconst AiLatencyMeterContext =\n  React.createContext<AiLatencyMeterContextValue | null>(null);\n\nfunction useLatencyMeterContext() {\n  const context = React.useContext(AiLatencyMeterContext);\n  if (!context) {\n    throw new Error(\n      \"AiLatencyMeter components must be used within <AiLatencyMeter>\",\n    );\n  }\n  return context;\n}\n\nfunction getLatencyLevel(ms?: number): LatencyLevel {\n  if (ms === undefined) return \"fast\";\n  if (ms < 1000) return \"fast\";\n  if (ms < 3000) return \"moderate\";\n  return \"slow\";\n}\n\ninterface AiLatencyMeterProps {\n  ttfb?: number;\n  totalDuration?: number;\n  isLoading?: boolean;\n  variant?: \"compact\" | \"expanded\";\n  children?: React.ReactNode;\n  className?: string;\n}\n\nfunction AiLatencyMeter({\n  ttfb,\n  totalDuration,\n  isLoading = false,\n  variant = \"expanded\",\n  children,\n  className,\n}: AiLatencyMeterProps) {\n  const level = React.useMemo(\n    () => getLatencyLevel(totalDuration ?? ttfb),\n    [totalDuration, ttfb],\n  );\n\n  const contextValue = React.useMemo(\n    () => ({ ttfb, totalDuration, isLoading, variant, level }),\n    [ttfb, totalDuration, isLoading, variant, level],\n  );\n\n  return (\n    <AiLatencyMeterContext.Provider value={contextValue}>\n      <div\n        data-slot=\"ai-latency-meter\"\n        data-variant={variant}\n        data-loading={isLoading}\n        data-level={level}\n        className={cn(\n          \"rounded-lg border border-border bg-card text-card-foreground overflow-hidden\",\n          variant === \"compact\" && \"inline-flex items-center gap-2 px-3 py-2\",\n          variant === \"expanded\" && \"p-4\",\n          className,\n        )}\n      >\n        {children}\n      </div>\n    </AiLatencyMeterContext.Provider>\n  );\n}\n\ninterface AiLatencyMeterHeaderProps {\n  title?: string;\n  className?: string;\n}\n\nfunction AiLatencyMeterHeader({\n  title = \"Response Time\",\n  className,\n}: AiLatencyMeterHeaderProps) {\n  const { isLoading, level } = useLatencyMeterContext();\n\n  const levelConfig = React.useMemo(() => {\n    const configs: Record<\n      LatencyLevel,\n      { icon: React.ReactNode; color: string }\n    > = {\n      fast: {\n        icon: <Zap className=\"size-4\" />,\n        color: \"text-green-500\",\n      },\n      moderate: {\n        icon: <Clock className=\"size-4\" />,\n        color: \"text-yellow-500\",\n      },\n      slow: {\n        icon: <Timer className=\"size-4\" />,\n        color: \"text-red-500\",\n      },\n    };\n    return configs[level];\n  }, [level]);\n\n  return (\n    <div\n      data-slot=\"ai-latency-meter-header\"\n      className={cn(\"flex items-center gap-2 mb-3\", className)}\n    >\n      <div\n        className={cn(\n          \"flex size-8 shrink-0 items-center justify-center rounded-md bg-muted\",\n          isLoading && \"animate-pulse\",\n        )}\n      >\n        <span className={levelConfig.color}>{levelConfig.icon}</span>\n      </div>\n      <span className=\"font-medium text-sm\">{title}</span>\n    </div>\n  );\n}\n\ninterface AiLatencyMeterBarProps {\n  className?: string;\n}\n\nfunction AiLatencyMeterBar({ className }: AiLatencyMeterBarProps) {\n  const { ttfb, totalDuration, isLoading, level } = useLatencyMeterContext();\n\n  const maxDuration = 5000;\n  const ttfbPercent = React.useMemo(() => {\n    if (!ttfb) return 0;\n    return Math.min((ttfb / maxDuration) * 100, 100);\n  }, [ttfb]);\n\n  const totalPercent = React.useMemo(() => {\n    if (!totalDuration) return 0;\n    return Math.min((totalDuration / maxDuration) * 100, 100);\n  }, [totalDuration]);\n\n  const levelColors = React.useMemo(() => {\n    const colors: Record<LatencyLevel, { bg: string; fill: string }> = {\n      fast: {\n        bg: \"bg-green-100 dark:bg-green-950\",\n        fill: \"bg-green-500\",\n      },\n      moderate: {\n        bg: \"bg-yellow-100 dark:bg-yellow-950\",\n        fill: \"bg-yellow-500\",\n      },\n      slow: {\n        bg: \"bg-red-100 dark:bg-red-950\",\n        fill: \"bg-red-500\",\n      },\n    };\n    return colors[level];\n  }, [level]);\n\n  return (\n    <div\n      data-slot=\"ai-latency-meter-bar\"\n      className={cn(\"space-y-2\", className)}\n    >\n      <div\n        className={cn(\n          \"relative h-3 w-full rounded-full overflow-hidden\",\n          levelColors.bg,\n        )}\n      >\n        {isLoading ? (\n          <div\n            className={cn(\n              \"absolute inset-y-0 left-0 rounded-full animate-pulse\",\n              levelColors.fill,\n            )}\n            style={{ width: \"60%\" }}\n          />\n        ) : (\n          <>\n            {ttfb !== undefined && ttfb > 0 && (\n              <div\n                className=\"absolute inset-y-0 left-0 rounded-full bg-blue-500 opacity-50\"\n                style={{ width: `${ttfbPercent}%` }}\n              />\n            )}\n            {totalDuration !== undefined && totalDuration > 0 && (\n              <div\n                className={cn(\n                  \"absolute inset-y-0 left-0 rounded-full\",\n                  levelColors.fill,\n                )}\n                style={{ width: `${totalPercent}%` }}\n              />\n            )}\n          </>\n        )}\n      </div>\n      <div className=\"flex justify-between text-xs text-muted-foreground\">\n        <span>0ms</span>\n        <span>1s</span>\n        <span>3s</span>\n        <span>5s+</span>\n      </div>\n    </div>\n  );\n}\n\ninterface AiLatencyMeterStatsProps {\n  className?: string;\n}\n\nfunction AiLatencyMeterStats({ className }: AiLatencyMeterStatsProps) {\n  const { ttfb, totalDuration, isLoading } = useLatencyMeterContext();\n\n  const formatDuration = React.useCallback((ms?: number) => {\n    if (ms === undefined) return \"-\";\n    if (ms < 1000) return `${ms}ms`;\n    return `${(ms / 1000).toFixed(2)}s`;\n  }, []);\n\n  return (\n    <div\n      data-slot=\"ai-latency-meter-stats\"\n      className={cn(\"grid grid-cols-2 gap-4 mt-3\", className)}\n    >\n      <div className=\"space-y-1\">\n        <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n          TTFB\n        </span>\n        <p\n          className={cn(\n            \"text-lg font-semibold font-mono\",\n            isLoading && \"animate-pulse text-muted-foreground\",\n          )}\n        >\n          {isLoading ? \"...\" : formatDuration(ttfb)}\n        </p>\n      </div>\n      <div className=\"space-y-1\">\n        <span className=\"text-xs font-medium text-muted-foreground uppercase tracking-wider\">\n          Total\n        </span>\n        <p\n          className={cn(\n            \"text-lg font-semibold font-mono\",\n            isLoading && \"animate-pulse text-muted-foreground\",\n          )}\n        >\n          {isLoading ? \"...\" : formatDuration(totalDuration)}\n        </p>\n      </div>\n    </div>\n  );\n}\n\ninterface AiLatencyMeterCompactProps {\n  className?: string;\n}\n\nfunction AiLatencyMeterCompact({ className }: AiLatencyMeterCompactProps) {\n  const { ttfb, totalDuration, isLoading, level } = useLatencyMeterContext();\n\n  const levelConfig = React.useMemo(() => {\n    const configs: Record<\n      LatencyLevel,\n      { icon: React.ReactNode; color: string; bgColor: string }\n    > = {\n      fast: {\n        icon: <Zap className=\"size-3.5\" />,\n        color: \"text-green-600 dark:text-green-400\",\n        bgColor: \"bg-green-100 dark:bg-green-950\",\n      },\n      moderate: {\n        icon: <Clock className=\"size-3.5\" />,\n        color: \"text-yellow-600 dark:text-yellow-400\",\n        bgColor: \"bg-yellow-100 dark:bg-yellow-950\",\n      },\n      slow: {\n        icon: <Timer className=\"size-3.5\" />,\n        color: \"text-red-600 dark:text-red-400\",\n        bgColor: \"bg-red-100 dark:bg-red-950\",\n      },\n    };\n    return configs[level];\n  }, [level]);\n\n  const formatDuration = React.useCallback((ms?: number) => {\n    if (ms === undefined) return \"-\";\n    if (ms < 1000) return `${ms}ms`;\n    return `${(ms / 1000).toFixed(1)}s`;\n  }, []);\n\n  const displayValue = totalDuration ?? ttfb;\n\n  return (\n    <div\n      data-slot=\"ai-latency-meter-compact\"\n      className={cn(\"flex items-center gap-2\", className)}\n    >\n      <span\n        className={cn(\n          \"inline-flex items-center justify-center size-6 rounded\",\n          levelConfig.bgColor,\n          isLoading && \"animate-pulse\",\n        )}\n      >\n        <span className={levelConfig.color}>{levelConfig.icon}</span>\n      </span>\n      <span\n        className={cn(\n          \"text-sm font-mono font-medium\",\n          isLoading && \"animate-pulse text-muted-foreground\",\n        )}\n      >\n        {isLoading ? \"...\" : formatDuration(displayValue)}\n      </span>\n    </div>\n  );\n}\n\nexport {\n  AiLatencyMeter,\n  AiLatencyMeterHeader,\n  AiLatencyMeterBar,\n  AiLatencyMeterStats,\n  AiLatencyMeterCompact,\n};\nexport type { AiLatencyMeterProps, LatencyLevel };\n",
      "type": "registry:component"
    }
  ],
  "docs": "Compound component for visualizing AI response latency. Performance levels: fast (<1s, green), moderate (<3s, yellow), slow (>3s, red). Shows TTFB (time to first byte) and total duration. Supports compact (inline badge) and expanded (full bar chart) variants with loading animation.",
  "categories": [
    "ai"
  ],
  "type": "registry:ui"
}