{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chatgpt-source-chip",
  "title": "ChatGPT Source Chip",
  "description": "An inline citation pill with a hover card.",
  "dependencies": [
    "cn"
  ],
  "registryDependencies": [
    "avatar",
    "badge",
    "button",
    "collapsible",
    "hover-card",
    "separator",
    "sheet"
  ],
  "files": [
    {
      "path": "registry/notra/chatgpt/types/chatgpt.ts",
      "content": "import type { ComponentProps, ReactNode } from \"react\";\n\nimport type { Avatar } from \"@/components/ui/avatar\";\nimport type { Button } from \"@/components/ui/button\";\nimport type { Collapsible } from \"@/components/ui/collapsible\";\nimport type { Sheet } from \"@/components/ui/sheet\";\n\nexport type ChatgptModelId =\n  | \"latest\"\n  | \"sol\"\n  | \"terra\"\n  | \"luna\"\n  | \"gpt-5.5\"\n  | \"gpt-5.4\"\n  | \"gpt-5.4-mini\";\n\nexport type ChatgptEffortId =\n  | \"instant\"\n  | \"medium\"\n  | \"high\"\n  | \"extra-high\"\n  | \"pro\";\n\nexport type ChatgptMessageRole = \"user\" | \"assistant\";\n\nexport interface ChatgptModelOption {\n  description?: string;\n  id: ChatgptModelId;\n  label: string;\n  shortLabel: string;\n}\n\nexport interface ChatgptEffortOption {\n  id: ChatgptEffortId;\n  label: string;\n}\n\nexport interface ChatgptActivitySite {\n  domain: string;\n  favicon?: string;\n  label: string;\n}\n\nexport interface ChatgptActivitySource {\n  domain: string;\n  favicon?: string;\n  href?: string;\n  id: string;\n  publisher: string;\n  snippet: string;\n  timeLabel: string;\n  title: string;\n}\n\nexport interface ChatgptThreadProps extends ComponentProps<\"div\"> {\n  autoScroll?: boolean;\n  footer?: ReactNode;\n}\n\nexport interface ChatgptMessageProps extends ComponentProps<\"div\"> {\n  actions?: ReactNode;\n  from: ChatgptMessageRole;\n  reasoning?: ReactNode;\n}\n\nexport interface ChatgptReasoningProps extends Omit<\n  ComponentProps<typeof Collapsible>,\n  \"children\"\n> {\n  children?: ReactNode;\n  complete?: boolean;\n  search?: ReactNode;\n  seconds: number;\n}\n\nexport interface ChatgptSearchProps extends Omit<\n  ComponentProps<typeof Button>,\n  \"children\"\n> {\n  sites?: readonly ChatgptActivitySite[];\n  websites: number;\n}\n\nexport interface ChatgptActivityProps extends Omit<\n  ComponentProps<typeof Sheet>,\n  \"children\"\n> {\n  className?: string;\n  seconds: number;\n  sites: readonly ChatgptActivitySite[];\n  sourceCount?: number;\n  sources: readonly ChatgptActivitySource[];\n  websites: number;\n}\n\nexport interface ChatgptFaviconProps extends Omit<\n  ComponentProps<typeof Avatar>,\n  \"children\"\n> {\n  domain: string;\n  src?: string;\n}\n\nexport interface ChatgptThinkingProps extends ComponentProps<\"div\"> {\n  label?: ReactNode;\n}\n\nexport interface ChatgptAppCallProps extends ComponentProps<\"div\"> {\n  label?: ReactNode;\n  logo?: ReactNode;\n  name: ReactNode;\n}\n\nexport interface ChatgptActionsProps extends ComponentProps<\"div\"> {\n  onMore?: () => void;\n  onRedo?: () => void;\n  onShare?: () => void;\n  text: string;\n}\n\nexport interface ChatgptActionButtonProps extends ComponentProps<\n  typeof Button\n> {\n  label: string;\n}\n\nexport interface ChatgptModelSelectorProps extends Omit<\n  ComponentProps<typeof Button>,\n  \"children\"\n> {\n  effort: ChatgptEffortId;\n  efforts?: readonly ChatgptEffortOption[];\n  model: ChatgptModelId;\n  models?: readonly ChatgptModelOption[];\n  onEffortChange?: (effort: ChatgptEffortId) => void;\n  onModelChange?: (model: ChatgptModelId) => void;\n}\n\nexport interface ChatgptComposerProps extends Omit<\n  ComponentProps<\"form\">,\n  \"onSubmit\"\n> {\n  busy?: boolean;\n  defaultEffort?: ChatgptEffortId;\n  defaultModel?: ChatgptModelId;\n  effort?: ChatgptEffortId;\n  efforts?: readonly ChatgptEffortOption[];\n  model?: ChatgptModelId;\n  models?: readonly ChatgptModelOption[];\n  onEffortChange?: (effort: ChatgptEffortId) => void;\n  onModelChange?: (model: ChatgptModelId) => void;\n  onSend?: (text: string) => void;\n  onStop?: () => void;\n  placeholder?: string;\n}\n\nexport interface ChatgptStorySearch {\n  sites: readonly ChatgptActivitySite[];\n  sourceCount?: number;\n  sources: readonly ChatgptActivitySource[];\n  websites: number;\n}\n\nexport type ChatgptStoryReasoningStep =\n  | { kind: \"search\"; search: ChatgptStorySearch }\n  | { kind: \"text\"; muted?: boolean; text: string };\n\nexport interface ChatgptStoryReasoning {\n  search?: ChatgptStorySearch;\n  seconds: number;\n  steps?: readonly ChatgptStoryReasoningStep[];\n  text: string;\n}\n\nexport interface ChatgptStoryMessage {\n  from: ChatgptMessageRole;\n  id: string;\n  reasoning?: ChatgptStoryReasoning;\n  text: string;\n}\n\nexport interface ChatgptPlayback {\n  completeIds: ReadonlySet<string>;\n  messages: ChatgptStoryMessage[];\n  play: () => Promise<void>;\n  playing: boolean;\n  send: (text: string, reply: string) => Promise<void>;\n  stop: () => void;\n  thinking: boolean;\n}\n\nexport interface ChatgptTextSegment {\n  offset: number;\n  text: string;\n}\n\nexport interface ChatgptSourceChipProps extends Omit<\n  ComponentProps<\"a\">,\n  \"children\" | \"href\"\n> {\n  sources: readonly ChatgptActivitySource[];\n}\n\nexport type ChatgptPlaybackPhase =\n  | { status: \"idle\" }\n  | { status: \"playing\"; source: \"script\" | \"send\"; thinking: boolean };\n",
      "type": "registry:lib",
      "target": "components/chatgpt/types/chatgpt.ts"
    },
    {
      "path": "registry/notra/chatgpt/components/chatgpt-favicon.tsx",
      "content": "import { cn } from \"cn\";\n\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\n\nimport { CHATGPT_FAVICON_URL } from \"../constants/chatgpt\";\nimport type { ChatgptFaviconProps } from \"../types/chatgpt\";\n\nconst faviconSrc = (domain: string) =>\n  `${CHATGPT_FAVICON_URL}?domain=${encodeURIComponent(domain)}&sz=64`;\n\nexport const ChatgptFavicon = ({\n  className,\n  domain,\n  src,\n  ...props\n}: ChatgptFaviconProps) => (\n  <Avatar\n    className={cn(\"bg-chatgpt-hover size-4 after:hidden\", className)}\n    data-slot=\"chatgpt-favicon\"\n    {...props}\n  >\n    <AvatarImage alt=\"\" src={src ?? faviconSrc(domain)} />\n    <AvatarFallback className=\"bg-chatgpt-hover\" />\n  </Avatar>\n);\n",
      "type": "registry:component",
      "target": "components/chatgpt/components/chatgpt-favicon.tsx"
    },
    {
      "path": "registry/notra/chatgpt/constants/chatgpt.ts",
      "content": "export const CHATGPT_TOOLTIP_DELAY_MS = 200;\n\nexport const CHATGPT_COPIED_RESET_MS = 1500;\n\nexport const CHATGPT_VISIBLE_SITES = 3;\n\nexport const CHATGPT_SCROLL_STICK_THRESHOLD_PX = 48;\n\nexport const CHATGPT_FAVICON_URL = \"https://www.google.com/s2/favicons\";\n\nexport const CHATGPT_PLAYBACK_TIMING = {\n  afterAssistantMs: 480,\n  afterInstantMs: 180,\n  afterUserMs: 420,\n  reducedMaxMs: 70,\n  thinkingMs: 1100,\n  thinkingWithReasoningMs: 1600,\n  tokenMs: 28,\n} as const;\n\nexport const CHATGPT_PRO_SPARKLES = [\n  { delay: 0, size: 4, x: -30, y: -26 },\n  { delay: 30, size: 3, x: 0, y: -34 },\n  { delay: 10, size: 4, x: 30, y: -24 },\n  { delay: 50, size: 3, x: -38, y: 2 },\n  { delay: 20, size: 3, x: 38, y: 4 },\n  { delay: 40, size: 4, x: -28, y: 26 },\n  { delay: 0, size: 3, x: 2, y: 34 },\n  { delay: 60, size: 4, x: 28, y: 26 },\n  { delay: 80, size: 2, x: -18, y: -14 },\n  { delay: 70, size: 2, x: 18, y: 16 },\n] as const;\n\nexport const CHATGPT_PRO_TWINKLES = [\n  { delay: 0, duration: 3000, size: 2, x: 6, y: 32 },\n  { delay: 900, duration: 3600, size: 3, x: 15, y: 66 },\n  { delay: 400, duration: 2800, size: 2, x: 24, y: 28 },\n  { delay: 1500, duration: 3400, size: 2, x: 33, y: 60 },\n  { delay: 700, duration: 3200, size: 3, x: 43, y: 36 },\n  { delay: 2100, duration: 3800, size: 2, x: 52, y: 70 },\n  { delay: 300, duration: 3000, size: 2, x: 61, y: 30 },\n  { delay: 1200, duration: 3500, size: 3, x: 70, y: 62 },\n  { delay: 1800, duration: 2900, size: 2, x: 79, y: 34 },\n  { delay: 600, duration: 3300, size: 2, x: 87, y: 64 },\n] as const;\n\nexport const CHATGPT_CHIP_HOVER_OPEN_DELAY_MS = 150;\n\nexport const CHATGPT_CHIP_HOVER_CLOSE_DELAY_MS = 120;\n",
      "type": "registry:lib",
      "target": "components/chatgpt/constants/chatgpt.ts"
    },
    {
      "path": "registry/notra/chatgpt/components/chatgpt-source-chip.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"cn\";\n\nimport { badgeVariants } from \"@/components/ui/badge\";\nimport {\n  HoverCard,\n  HoverCardContent,\n  HoverCardTrigger,\n} from \"@/components/ui/hover-card\";\nimport { Separator } from \"@/components/ui/separator\";\n\nimport {\n  CHATGPT_CHIP_HOVER_CLOSE_DELAY_MS,\n  CHATGPT_CHIP_HOVER_OPEN_DELAY_MS,\n} from \"../constants/chatgpt\";\nimport type { ChatgptSourceChipProps } from \"../types/chatgpt\";\nimport { ChatgptFavicon } from \"./chatgpt-favicon\";\n\nexport const ChatgptSourceChip = ({\n  className,\n  rel = \"noopener noreferrer\",\n  sources,\n  target = \"_blank\",\n  ...props\n}: ChatgptSourceChipProps) => {\n  const [first] = sources;\n  if (!first) {\n    return null;\n  }\n  const extra = sources.length - 1;\n\n  return (\n    <HoverCard>\n      <HoverCardTrigger\n        aria-label={\n          extra > 0 ? `${first.publisher} (+${extra})` : first.publisher\n        }\n        className={cn(\n          badgeVariants({ variant: \"secondary\" }),\n          \"bg-chatgpt-hover text-chatgpt-muted hover:bg-chatgpt-border hover:text-chatgpt-fg focus-visible:outline-chatgpt-focus mx-0.5 h-5 gap-1 rounded-full border-0 px-2 align-middle text-xs leading-none font-normal no-underline focus-visible:ring-0 focus-visible:outline-2 focus-visible:outline-solid motion-reduce:transition-none\",\n          className\n        )}\n        closeDelay={CHATGPT_CHIP_HOVER_CLOSE_DELAY_MS}\n        data-slot=\"chatgpt-source-chip\"\n        delay={CHATGPT_CHIP_HOVER_OPEN_DELAY_MS}\n        href={first.href ?? `https://${first.domain}`}\n        rel={rel}\n        target={target}\n        {...props}\n      >\n        {first.publisher}\n        {extra > 0 ? <span>+{extra}</span> : null}\n      </HoverCardTrigger>\n      <HoverCardContent\n        align=\"start\"\n        className=\"bg-chatgpt-popover font-chatgpt text-chatgpt-fg shadow-chatgpt-menu w-80 max-w-[calc(100vw-2rem)] gap-0 rounded-2xl p-0 ring-0\"\n        sideOffset={8}\n      >\n        <ul className=\"flex flex-col\">\n          {sources.slice(0, 3).map((source, index) => (\n            <li key={source.id}>\n              {index > 0 ? <Separator className=\"bg-chatgpt-border\" /> : null}\n              <div className=\"flex flex-col gap-1 p-3\">\n                <div className=\"text-chatgpt-muted flex items-center gap-1.5 text-xs leading-4\">\n                  <ChatgptFavicon\n                    className=\"size-4\"\n                    domain={source.domain}\n                    src={source.favicon}\n                  />\n                  {source.publisher}\n                </div>\n                <p className=\"text-[0.9375rem] leading-5 font-medium\">\n                  {source.title}\n                </p>\n                <p className=\"text-chatgpt-muted line-clamp-2 text-[0.8125rem] leading-[1.15rem]\">\n                  {source.snippet}\n                </p>\n              </div>\n            </li>\n          ))}\n        </ul>\n      </HoverCardContent>\n    </HoverCard>\n  );\n};\n",
      "type": "registry:component",
      "target": "components/chatgpt/components/chatgpt-source-chip.tsx"
    }
  ],
  "cssVars": {
    "theme": {
      "color-chatgpt-bg": "var(--chatgpt-bg)",
      "color-chatgpt-fg": "var(--chatgpt-fg)",
      "color-chatgpt-muted": "var(--chatgpt-muted)",
      "color-chatgpt-hover": "var(--chatgpt-hover)",
      "color-chatgpt-border": "var(--chatgpt-border)",
      "color-chatgpt-bubble": "var(--chatgpt-bubble)",
      "color-chatgpt-composer": "var(--chatgpt-composer)",
      "color-chatgpt-composer-border": "var(--chatgpt-composer-border)",
      "color-chatgpt-popover": "var(--chatgpt-popover)",
      "color-chatgpt-tooltip": "var(--chatgpt-tooltip)",
      "color-chatgpt-tooltip-fg": "var(--chatgpt-tooltip-fg)",
      "color-chatgpt-send": "var(--chatgpt-send)",
      "color-chatgpt-send-hover": "var(--chatgpt-send-hover)",
      "color-chatgpt-send-fg": "var(--chatgpt-send-fg)",
      "color-chatgpt-focus": "var(--chatgpt-focus)",
      "color-chatgpt-search": "var(--chatgpt-search)",
      "shadow-chatgpt-composer": "var(--chatgpt-composer-shadow)",
      "shadow-chatgpt-menu": "var(--chatgpt-menu-shadow)",
      "font-chatgpt": "ui-sans-serif, -apple-system, system-ui, \"Segoe UI\", Helvetica, \"Apple Color Emoji\", Arial, sans-serif, \"Segoe UI Emoji\", \"Segoe UI Symbol\"",
      "color-chatgpt-slider-track": "var(--chatgpt-slider-track)",
      "color-chatgpt-slider-fill": "var(--chatgpt-slider-fill)",
      "color-chatgpt-slider-thumb": "var(--chatgpt-slider-thumb)",
      "color-chatgpt-slider-dot": "var(--chatgpt-slider-dot)",
      "color-chatgpt-slider-dot-fill": "var(--chatgpt-slider-dot-fill)",
      "color-chatgpt-pro": "var(--chatgpt-pro)"
    },
    "light": {
      "chatgpt-bg": "#ffffff",
      "chatgpt-fg": "#0d0d0d",
      "chatgpt-muted": "#5d5d5d",
      "chatgpt-hover": "#f3f3f3",
      "chatgpt-border": "#e5e5e5",
      "chatgpt-bubble": "#f4f4f4",
      "chatgpt-composer": "#ffffff",
      "chatgpt-composer-border": "#00000014",
      "chatgpt-composer-shadow": "0 1px 2px rgb(0 0 0 / 0.04), 0 2px 8px rgb(0 0 0 / 0.04)",
      "chatgpt-popover": "#ffffff",
      "chatgpt-menu-shadow": "0 0 0 1px rgb(0 0 0 / 0.05), 0 4px 16px rgb(0 0 0 / 0.08)",
      "chatgpt-tooltip": "#0d0d0d",
      "chatgpt-tooltip-fg": "#ffffff",
      "chatgpt-send": "#2563eb",
      "chatgpt-send-hover": "#1d4ed8",
      "chatgpt-send-fg": "#ffffff",
      "chatgpt-focus": "#2563eb",
      "chatgpt-search": "#e4543a",
      "chatgpt-slider-track": "#e4e4e4",
      "chatgpt-slider-fill": "#3b82f6",
      "chatgpt-slider-thumb": "#ffffff",
      "chatgpt-slider-dot": "#a3a3a3",
      "chatgpt-slider-dot-fill": "rgb(255 255 255 / 60%)",
      "chatgpt-pro": "#7c3aed",
      "chatgpt-pro-gradient": "linear-gradient(90deg, #2b3fd6 0%, #6a5be2 25%, #b58cf5 50%, #6a5be2 75%, #2b3fd6 100%)"
    },
    "dark": {
      "chatgpt-bg": "#212121",
      "chatgpt-fg": "#ececec",
      "chatgpt-muted": "#afafaf",
      "chatgpt-hover": "#303030",
      "chatgpt-border": "#3a3a3a",
      "chatgpt-bubble": "#303030",
      "chatgpt-composer": "#303030",
      "chatgpt-composer-border": "#ffffff1a",
      "chatgpt-composer-shadow": "0 1px 2px rgb(0 0 0 / 0.2)",
      "chatgpt-popover": "#353535",
      "chatgpt-menu-shadow": "0 0 0 1px rgb(255 255 255 / 0.08), 0 8px 24px rgb(0 0 0 / 0.4)",
      "chatgpt-tooltip": "#000000",
      "chatgpt-tooltip-fg": "#ffffff",
      "chatgpt-send": "#2563eb",
      "chatgpt-send-hover": "#1d4ed8",
      "chatgpt-send-fg": "#ffffff",
      "chatgpt-focus": "#2563eb",
      "chatgpt-search": "#e4543a",
      "chatgpt-slider-track": "#454545",
      "chatgpt-slider-fill": "#3b82f6",
      "chatgpt-slider-thumb": "#ffffff",
      "chatgpt-slider-dot": "#8c8c8c",
      "chatgpt-slider-dot-fill": "rgb(255 255 255 / 55%)",
      "chatgpt-pro": "#b794f6",
      "chatgpt-pro-gradient": "linear-gradient(90deg, #2b3fd6 0%, #6a5be2 25%, #b58cf5 50%, #6a5be2 75%, #2b3fd6 100%)"
    }
  },
  "type": "registry:component"
}