-

Model Diff View

PreviousNext

A review surface for added, updated, and removed models after provider catalog sync.

Installation

pnpm
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/model-diff-view.json
npm
pnpm dlx shadcn@latest add https://ui.zgi.ai/r/model-diff-view.json
yarn
yarn dlx shadcn@latest add https://ui.zgi.ai/r/model-diff-view.json

Component

components/ui/model-diff-view.tsx
"use client"

import * as React from "react"
import { CirclePlusIcon, PencilIcon, Trash2Icon } from "lucide-react"

import { cn } from "@/lib/utils"
import { Badge } from "@/components/ui/badge"

export type ModelDiffType = "added" | "updated" | "removed"

export interface ModelDiffItem {
  id: string
  name: React.ReactNode
  provider?: React.ReactNode
  type: ModelDiffType
  description?: React.ReactNode
  before?: React.ReactNode
  after?: React.ReactNode
}

export interface ModelDiffViewProps
  extends Omit<React.ComponentProps<"div">, "title"> {
  changes: ModelDiffItem[]
  title?: React.ReactNode
  description?: React.ReactNode
  emptyState?: React.ReactNode
}

const diffConfig = {
  added: {
    label: "Added",
    icon: CirclePlusIcon,
    className: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
  },
  updated: {
    label: "Updated",
    icon: PencilIcon,
    className: "bg-blue-500/10 text-blue-700 dark:text-blue-300",
  },
  removed: {
    label: "Removed",
    icon: Trash2Icon,
    className: "bg-destructive/10 text-destructive",
  },
} satisfies Record<
  ModelDiffType,
  {
    label: string
    icon: React.ComponentType<{ className?: string }>
    className: string
  }
>

export function ModelDiffView({
  changes,
  title = "Model changes",
  description,
  emptyState = "No model changes detected.",
  className,
  ...props
}: ModelDiffViewProps) {
  const counts = changes.reduce(
    (acc, change) => {
      acc[change.type] += 1
      return acc
    },
    { added: 0, updated: 0, removed: 0 }
  )

  return (
    <div className={cn("w-full rounded-lg border p-4", className)} {...props}>
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div>
          <h3 className="text-sm font-medium">{title}</h3>
          {description ? (
            <p className="text-muted-foreground mt-1 text-xs leading-5">
              {description}
            </p>
          ) : null}
        </div>
        <div className="flex flex-wrap gap-1.5">
          {(Object.keys(diffConfig) as ModelDiffType[]).map((type) => {
            const config = diffConfig[type]
            const Icon = config.icon

            return (
              <Badge
                key={type}
                variant="secondary"
                className={cn("h-6 rounded-md border-0", config.className)}
              >
                <Icon className="size-3.5" />
                {counts[type]}
              </Badge>
            )
          })}
        </div>
      </div>

      <div className="mt-4 space-y-2">
        {changes.length ? (
          changes.map((change) => {
            const config = diffConfig[change.type]
            const Icon = config.icon

            return (
              <div
                key={change.id}
                className="hover:bg-accent/30 rounded-md border p-3 transition-colors"
              >
                <div className="flex items-start gap-3">
                  <div
                    className={cn(
                      "mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-md",
                      config.className
                    )}
                  >
                    <Icon className="size-3.5" />
                  </div>
                  <div className="min-w-0 flex-1">
                    <div className="flex min-w-0 flex-wrap items-center gap-2">
                      <span className="truncate text-sm font-medium">
                        {change.name}
                      </span>
                      {change.provider ? (
                        <Badge variant="outline" className="h-5 rounded-sm">
                          {change.provider}
                        </Badge>
                      ) : null}
                      <Badge
                        variant="secondary"
                        className={cn(
                          "h-5 rounded-sm border-0",
                          config.className
                        )}
                      >
                        {config.label}
                      </Badge>
                    </div>
                    {change.description ? (
                      <p className="text-muted-foreground mt-1 text-xs leading-5">
                        {change.description}
                      </p>
                    ) : null}
                    {change.before || change.after ? (
                      <div className="mt-3 grid gap-2 text-xs sm:grid-cols-2">
                        {change.before ? (
                          <div className="bg-muted/60 rounded-md p-2">
                            <div className="text-muted-foreground">Before</div>
                            <div className="mt-1 font-medium">
                              {change.before}
                            </div>
                          </div>
                        ) : null}
                        {change.after ? (
                          <div className="bg-muted/60 rounded-md p-2">
                            <div className="text-muted-foreground">After</div>
                            <div className="mt-1 font-medium">
                              {change.after}
                            </div>
                          </div>
                        ) : null}
                      </div>
                    ) : null}
                  </div>
                </div>
              </div>
            )
          })
        ) : (
          <div className="text-muted-foreground rounded-md border border-dashed p-6 text-center text-sm">
            {emptyState}
          </div>
        )}
      </div>
    </div>
  )
}

Usage

import { ModelDiffView } from "@/components/ui/model-diff-view"
 
export function Example() {
  return (
    <ModelDiffView
      changes={[
        {
          id: "zgi-large",
          name: "zgi-large-2026-06",
          provider: "ZGI",
          type: "added",
          description: "New general-purpose reasoning model is available.",
        },
      ]}
    />
  )
}

Props

PropTypeDefault
changesModelDiffItem[]required
titleReactNode"Model changes"
descriptionReactNode-
emptyStateReactNode"No model changes detected."