Componenteditors0.96 KB gzipZero dependencies

Prompt Diff Compare

Side-by-side prompt diff with highlights.

Preview

Live Preview

Prompt diff

-1 / +5

Before

Design a blue ticket stub for the evening show

After

Design a cream ticket stub with red stamp for the evening gala show

Installation

Tokens guide →

Add to your project via CLI (zero dependencies, code you own):

npx bigbullui add prompt-diff-compare

Or install the full npm package:

npm install bigbullui
import { PromptDiffCompare } from "@/components/ui/prompt-diff-compare";

<PromptDiffCompare />

Usage guidance

Use Prompt Diff Compare when a ticket stub surface needs Side-by-side prompt diff with highlights. Keep props minimal and prefer composition with sibling stubs.

Avoid Prompt Diff Compare for long form flows or dense data grids where a dedicated layout block fits better.

Source code

prompt-diff-compare.tsx

Zero dependencies (only React + utils). Copy and paste directly into your project:

"use client";

import * as React from "react";
import { cn } from "./lib/utils";

export interface PromptDiffCompareProps extends React.HTMLAttributes<HTMLDivElement> {
  before: string;
  after: string;
  beforeLabel?: string;
  afterLabel?: string;
}

function words(text: string): string[] {
  return text.split(/\s+/).filter(Boolean);
}

/** Side-by-side prompt diff with added/removed word highlights. */
export function PromptDiffCompare({
  before,
  after,
  beforeLabel = "Before",
  afterLabel = "After",
  className,
  ...props
}: PromptDiffCompareProps) {
  const beforeWords = React.useMemo(() => words(before), [before]);
  const afterWords = React.useMemo(() => words(after), [after]);
  const afterSet = React.useMemo(() => new Set(afterWords), [afterWords]);
  const beforeSet = React.useMemo(() => new Set(beforeWords), [beforeWords]);
  const removed = beforeWords.filter((w) => !afterSet.has(w)).length;
  const added = afterWords.filter((w) => !beforeSet.has(w)).length;
  const pane = (label: string, list: string[], base: Set<string>, tone: "removed" | "added") => (
    <div className="flex-1 rounded border border-border bg-background p-2.5">
      <p className="font-mono text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground">{label}</p>
      <p className="mt-1.5 text-xs leading-6 text-foreground">
        {list.map((w, i) => {
          const changed = !base.has(w);
          return (
            <span
              key={i}
              className={cn(
                "rounded px-0.5",
                changed && tone === "removed" && "bg-accent/20 text-accent line-through",
                changed && tone === "added" && "bg-primary/20 font-bold",
              )}
            >
              {w}{" "}
            </span>
          );
        })}
      </p>
    </div>
  );
  return (
    <div className={cn("w-full rounded-lg border-2 border-foreground bg-card p-3", className)} {...props}>
      <div className="mb-2 flex items-center justify-between">
        <p className="font-mono text-[10px] font-bold uppercase tracking-[0.2em] text-muted-foreground">
          Prompt diff
        </p>
        <p className="font-mono text-[10px] uppercase tracking-wider text-muted-foreground">
          <span className="text-accent">-{removed}</span> / <span className="font-bold">+{added}</span>
        </p>
      </div>
      <div className="flex flex-col gap-2 sm:flex-row">
        {pane(beforeLabel, beforeWords, afterSet, "removed")}
        {pane(afterLabel, afterWords, beforeSet, "added")}
      </div>
    </div>
  );
}