{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "usage-table",
  "title": "Usage Table",
  "description": "A usage table component with resource consumption breakdown",
  "registryDependencies": ["table", "card", "utils"],
  "files": [
    {
      "path": "src/registry/billingsdk/usage-table.tsx",
      "content": "\"use client\";\nimport React, { useCallback } from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport {\n  Card,\n  CardContent,\n  CardDescription,\n  CardHeader,\n  CardTitle,\n} from \"@/components/ui/card\";\nimport {\n  Table,\n  TableBody,\n  TableCell,\n  TableCaption,\n  TableHead,\n  TableHeader,\n  TableRow,\n} from \"@/components/ui/table\";\nimport { Button } from \"@/components/ui/button\";\nimport { Download } from \"lucide-react\";\n\nexport interface UsageItem {\n  model: string;\n  inputWithCache: number;\n  inputWithoutCache: number;\n  cacheRead: number;\n  output: number;\n  totalTokens: number;\n  apiCost?: number;\n  costToYou?: number;\n}\n\ninterface UsageTableProps {\n  className?: string;\n  title?: string;\n  description?: string;\n  usageHistory: UsageItem[];\n  showTotal?: boolean;\n}\n\nexport function UsageTable({\n  className,\n  title,\n  description,\n  usageHistory,\n  showTotal = true, // Default to true\n}: UsageTableProps) {\n  const totalRow = showTotal\n    ? usageHistory.reduce(\n        (acc, item) => ({\n          inputWithCache: acc.inputWithCache + item.inputWithCache,\n          inputWithoutCache: acc.inputWithoutCache + item.inputWithoutCache,\n          cacheRead: acc.cacheRead + item.cacheRead,\n          output: acc.output + item.output,\n          totalTokens: acc.totalTokens + item.totalTokens,\n          apiCost: acc.apiCost + (item.apiCost || 0),\n          costToYou: acc.costToYou + (item.costToYou || 0),\n        }),\n        {\n          inputWithCache: 0,\n          inputWithoutCache: 0,\n          cacheRead: 0,\n          output: 0,\n          totalTokens: 0,\n          apiCost: 0,\n          costToYou: 0,\n        },\n      )\n    : null;\n\n  const formatNumber = (num: number) => {\n    return new Intl.NumberFormat().format(num);\n  };\n\n  const formatCurrency = (amount: number) => {\n    return `$${amount.toFixed(2)}`;\n  };\n  const hasApiCost = usageHistory.some(\n    (item) => item.apiCost !== undefined && item.apiCost !== null,\n  );\n  const hasCostToYou = usageHistory.some(\n    (item) => item.costToYou !== undefined && item.costToYou !== null,\n  );\n\n  const exportColumns = [\n    { key: \"model\", label: \"Model\" },\n    { key: \"inputWithCache\", label: \"Input (w/ Cache)\" },\n    { key: \"inputWithoutCache\", label: \"Input (w/o Cache)\" },\n    { key: \"cacheRead\", label: \"Cache Read\" },\n    { key: \"output\", label: \"Output\" },\n    { key: \"totalTokens\", label: \"Total Tokens\" },\n    { key: \"apiCost\", label: \"API Cost\" },\n    { key: \"costToYou\", label: \"Cost to You\" },\n  ] as const;\n  // --- CSV EXPORT LOGIC ---\n  const exportToCsv = useCallback(() => {\n    if (!usageHistory || usageHistory.length === 0) {\n      console.warn(\"No data to export.\");\n      return;\n    }\n    // 1. Filter columns to match what is displayed\n    const columnsToExport = exportColumns.filter((col) => {\n      if (col.key === \"apiCost\") return hasApiCost;\n      if (col.key === \"costToYou\") return hasCostToYou;\n      return true;\n    });\n\n    // 2. Generate Header Row using display labels\n    const headerRow = columnsToExport.map((col) => `\"${col.label}\"`).join(\",\");\n\n    // 3. Helper to format a single data item into a CSV row\n    const getCsvRow = (item: UsageItem & { model: string }): string => {\n      return columnsToExport\n        .map((col) => {\n          const key = col.key as keyof UsageItem;\n          const value = item[key];\n\n          let formattedValue: string;\n\n          if (key === \"model\") {\n            formattedValue = item.model;\n          } else if (key === \"apiCost\" || key === \"costToYou\") {\n            // Apply currency formatting\n            formattedValue = formatCurrency(Number(value ?? 0));\n          } else {\n            formattedValue = formatNumber(Number(value ?? 0)); // Apply number formatting for tokens\n          }\n\n          // Escape double quotes and wrap in quotes for robust CSV\n          const escapedValue = String(formattedValue).replace(/\"/g, '\"\"');\n          return `\"${escapedValue}\"`;\n        })\n        .join(\",\");\n    };\n\n    // 4. Map usage history rows\n    const allRows = usageHistory.map((item) => getCsvRow(item));\n\n    // 5. Conditionally add the total row\n    if (showTotal && totalRow) {\n      const totalItem = {\n        ...totalRow,\n        // Override 'model' key for the Total row label\n        model: \"Total\",\n      } as UsageItem;\n\n      allRows.push(getCsvRow(totalItem));\n    }\n    // 6. Combine all content and trigger download (BOM + CRLF for Excel)\n    const csvContent = [headerRow, ...allRows].join(\"\\r\\n\");\n    const blob = new Blob([\"\\uFEFF\", csvContent], {\n      type: \"text/csv;charset=utf-8;\",\n    });\n    const url = URL.createObjectURL(blob);\n\n    const link = document.createElement(\"a\");\n    link.setAttribute(\"href\", url);\n    link.setAttribute(\"download\", \"usage_summary.csv\");\n\n    document.body.appendChild(link);\n    try {\n      link.click();\n    } finally {\n      document.body.removeChild(link);\n      // Slight delay ensures some browsers finish navigation before revoking\n      setTimeout(() => URL.revokeObjectURL(url), 0);\n    }\n  }, [\n    usageHistory,\n    totalRow,\n    showTotal,\n    hasApiCost,\n    hasCostToYou,\n    formatNumber,\n    formatCurrency,\n  ]);\n  // --- END CSV EXPORT LOGIC ---\n  // Calculate total row if showTotal is true\n  return (\n    <Card className={cn(\"w-full\", className)}>\n      <CardHeader className=\"flex flex-row items-start justify-between space-y-0\">\n        <div className=\"space-y-1\">\n          {title && <CardTitle>{title}</CardTitle>}\n          {description && <CardDescription>{description}</CardDescription>}\n        </div>\n        {/* Export Button on the top right */}\n        {usageHistory.length > 0 && (\n          <Button\n            onClick={exportToCsv}\n            variant=\"outline\"\n            size=\"sm\"\n            className=\"ml-4 h-8\"\n          >\n            <Download className=\"mr-2 h-4 w-4\" />\n            Export to CSV\n          </Button>\n        )}\n      </CardHeader>\n      <CardContent>\n        <div className=\"overflow-x-auto rounded-md border\">\n          <Table>\n            <TableCaption className=\"sr-only\">\n              Model usage summary with token counts and costs\n            </TableCaption>\n            <TableHeader>\n              <TableRow>\n                <TableHead className=\"w-[140px]\">Model</TableHead>\n                <TableHead className=\"text-right\">\n                  <span className=\"hidden sm:inline\">Input (w/ Cache)</span>\n                  <span className=\"sm:hidden\">w/ Cache</span>\n                </TableHead>\n                <TableHead className=\"text-right\">\n                  <span className=\"hidden sm:inline\">Input (w/o Cache)</span>\n                  <span className=\"sm:hidden\">w/o Cache</span>\n                </TableHead>\n                <TableHead className=\"text-right\">\n                  <span className=\"hidden sm:inline\">Cache Read</span>\n                  <span className=\"sm:hidden\">Cache</span>\n                </TableHead>\n                <TableHead className=\"text-right\">Output</TableHead>\n                <TableHead className=\"text-right\">\n                  <span className=\"hidden sm:inline\">Total Tokens</span>\n                  <span className=\"sm:hidden\">Total</span>\n                </TableHead>\n                {hasApiCost && (\n                  <TableHead className=\"text-right\">\n                    <span className=\"hidden sm:inline\">API Cost</span>\n                    <span className=\"sm:hidden\">API</span>\n                  </TableHead>\n                )}\n                {hasCostToYou && (\n                  <TableHead className=\"text-right\">\n                    <span className=\"hidden sm:inline\">Cost to You</span>\n                    <span className=\"sm:hidden\">Cost</span>\n                  </TableHead>\n                )}\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              {usageHistory.length === 0 && (\n                <TableRow>\n                  <TableCell\n                    colSpan={8}\n                    className=\"text-muted-foreground h-24 text-center\"\n                  >\n                    No usage data available\n                  </TableCell>\n                </TableRow>\n              )}\n              {usageHistory.map((item, index) => (\n                <TableRow key={item.model || index}>\n                  <TableCell className=\"font-medium\">{item.model}</TableCell>\n                  <TableCell className=\"text-right\">\n                    {formatNumber(item.inputWithCache)}\n                  </TableCell>\n                  <TableCell className=\"text-right\">\n                    {formatNumber(item.inputWithoutCache)}\n                  </TableCell>\n                  <TableCell className=\"text-right\">\n                    {formatNumber(item.cacheRead)}\n                  </TableCell>\n                  <TableCell className=\"text-right\">\n                    {formatNumber(item.output)}\n                  </TableCell>\n                  <TableCell className=\"text-right\">\n                    {formatNumber(item.totalTokens)}\n                  </TableCell>\n                  {hasApiCost && (\n                    <TableCell className=\"text-right\">\n                      {formatCurrency(item.apiCost || 0)}\n                    </TableCell>\n                  )}\n                  {hasCostToYou && (\n                    <TableCell className=\"text-right\">\n                      {formatCurrency(item.costToYou || 0)}\n                    </TableCell>\n                  )}\n                </TableRow>\n              ))}\n              {showTotal && totalRow && (\n                <TableRow className=\"bg-muted/50 hover:bg-muted/50\">\n                  <TableCell className=\"font-semibold\">Total</TableCell>\n                  <TableCell className=\"text-right font-semibold\">\n                    {formatNumber(totalRow.inputWithCache)}\n                  </TableCell>\n                  <TableCell className=\"text-right font-semibold\">\n                    {formatNumber(totalRow.inputWithoutCache)}\n                  </TableCell>\n                  <TableCell className=\"text-right font-semibold\">\n                    {formatNumber(totalRow.cacheRead)}\n                  </TableCell>\n                  <TableCell className=\"text-right font-semibold\">\n                    {formatNumber(totalRow.output)}\n                  </TableCell>\n                  <TableCell className=\"text-right font-semibold\">\n                    {formatNumber(totalRow.totalTokens)}\n                  </TableCell>\n                  {hasApiCost && (\n                    <TableCell className=\"text-right font-semibold\">\n                      {formatCurrency(totalRow.apiCost || 0)}\n                    </TableCell>\n                  )}\n                  {hasCostToYou && (\n                    <TableCell className=\"text-right font-semibold\">\n                      {formatCurrency(totalRow.costToYou || 0)}\n                    </TableCell>\n                  )}\n                </TableRow>\n              )}\n            </TableBody>\n          </Table>\n        </div>\n      </CardContent>\n    </Card>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/billingsdk/usage-table.tsx"
    },
    {
      "path": "src/registry/billingsdk/demo/usage-table-demo.tsx",
      "content": "import { UsageTable, type UsageItem } from \"@/registry/billingsdk/usage-table\";\n\nexport default function UsageTableDemo() {\n  const usageHistory: UsageItem[] = [\n    {\n      model: \"gpt-5\",\n      inputWithCache: 0,\n      inputWithoutCache: 518131,\n      cacheRead: 1646080,\n      output: 103271,\n      totalTokens: 2267482,\n    },\n    {\n      model: \"claude-3.5-sonnet\",\n      inputWithCache: 176177,\n      inputWithoutCache: 28413,\n      cacheRead: 434612,\n      output: 8326,\n      totalTokens: 647528,\n      costToYou: 1.0,\n    },\n    {\n      model: \"gemini-2.0-flash-exp\",\n      inputWithCache: 176100,\n      inputWithoutCache: 28432,\n      cacheRead: 434612,\n      output: 8326,\n      totalTokens: 647528,\n      apiCost: 1,\n      costToYou: 0,\n    },\n    {\n      model: \"gemini-2.5-pro\",\n      inputWithCache: 176177,\n      inputWithoutCache: 28413,\n      cacheRead: 434612,\n      output: 7000,\n      totalTokens: 647528,\n      apiCost: 1,\n      costToYou: 0,\n    },\n    {\n      model: \"claude-4-sonnet\",\n      inputWithCache: 68415,\n      inputWithoutCache: 902,\n      cacheRead: 864450,\n      output: 12769,\n      totalTokens: 946536,\n      apiCost: 0.71,\n      costToYou: 0.71,\n    },\n    {\n      model: \"claude-3.7-sonnet\",\n      inputWithCache: 68415,\n      inputWithoutCache: 902,\n      cacheRead: 864450,\n      output: 12769,\n      totalTokens: 946536,\n      apiCost: 0.71,\n    },\n    {\n      model: \"auto\",\n      inputWithCache: 84551,\n      inputWithoutCache: 0,\n      cacheRead: 284876,\n      output: 9458,\n      totalTokens: 378885,\n      apiCost: 0.23,\n      costToYou: 0,\n    },\n    {\n      model: \"sonic\",\n      inputWithCache: 0,\n      inputWithoutCache: 149484,\n      cacheRead: 4354855,\n      output: 23569,\n      totalTokens: 4527908,\n      costToYou: 2,\n    },\n  ];\n\n  return (\n    <UsageTable\n      title=\"Usage Summary\"\n      usageHistory={usageHistory}\n      showTotal={true}\n      description=\"Per-model LLM usage with token counts, cache reads, and API cost.\"\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/usage-table-demo.tsx"
    }
  ],
  "type": "registry:block"
}
