File size: 2,640 Bytes
7c5ed63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import { useState } from 'react';

import { CopyButton } from './CopyButton';

interface ImagePreviewProps {
  url: string;
  /** Optional label rendered as the header chip (e.g. `image_url`). */
  label?: string;
}

/** Inline preview for an image ContentPart URL.
 *  Renders the actual `<img>` for `data:image/*` and `http(s)://` URLs;
 *  falls back to the raw URL for any other scheme.
 *  Click "expand" to lift the height cap to 80vh; click "open in tab"
 *  to view the full asset in a new browser tab. */
export function ImagePreview({ url, label = 'image_url' }: ImagePreviewProps) {
  const [open, setOpen] = useState(false);
  const [failed, setFailed] = useState(false);
  const supported =
    url.startsWith('data:image/') || /^https?:\/\//.test(url) || url.startsWith('/');
  const sizeLabel = url.startsWith('data:image/')
    ? `${url.length.toLocaleString()} chars`
    : new URL(url, window.location.href).hostname;

  if (!supported) {
    return (
      <div className="border border-border bg-surface-0 p-2">
        <div className="mb-1 font-mono text-[10px] text-fg-3">{label} (unsupported scheme)</div>
        <span className="break-all font-mono text-[12px] text-fg-1">{url}</span>
      </div>
    );
  }

  return (
    <div className="border border-border bg-surface-0 p-2">
      <div className="mb-1 flex items-center justify-between gap-2">
        <span className="font-mono text-[10px] text-fg-3">
          {label}
          <span className="ml-2 text-fg-3">· {sizeLabel}</span>
        </span>
        <span className="flex items-center gap-2">
          <CopyButton value={url} label="copy url" />
          <a
            href={url}
            target="_blank"
            rel="noreferrer"
            className="font-mono text-[10px] text-fg-3 hover:text-fg-1"
          >
            open in tab ↗
          </a>
          <button
            type="button"
            onClick={() => {
              setOpen((v) => !v);
            }}
            className="font-mono text-[10px] text-fg-3 hover:text-fg-1"
          >
            {open ? 'shrink' : 'expand'}
          </button>
        </span>
      </div>
      {failed ? (
        <div className="font-mono text-[11px] text-[var(--color-sev-error)]">
          failed to load image
        </div>
      ) : (
        <img
          src={url}
          alt="content image"
          loading="lazy"
          onError={() => {
            setFailed(true);
          }}
          className={
            'block max-w-full object-contain ' +
            (open ? 'max-h-[80vh]' : 'max-h-[220px]')
          }
        />
      )}
    </div>
  );
}