@elabs-ai/components-ai 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +4 -6
  2. package/dist/_lazy-engine-boundary.js +1 -1
  3. package/dist/_persona-rive.js.map +1 -1
  4. package/dist/_streamdown-i18n.js +2 -2
  5. package/dist/agent.js +2 -2
  6. package/dist/asset-preview.js +5 -5
  7. package/dist/attachments.js +1 -1
  8. package/dist/audio-player.js +1 -2
  9. package/dist/{chunk-7RL6KZDF.js → chunk-24M6XWVJ.js} +25 -17
  10. package/dist/chunk-24M6XWVJ.js.map +1 -0
  11. package/dist/{chunk-VJZJWNEC.js → chunk-3KXIWGHW.js} +3 -3
  12. package/dist/{chunk-TWPHAJNM.js → chunk-3UHR3RLZ.js} +12 -6
  13. package/dist/chunk-3UHR3RLZ.js.map +1 -0
  14. package/dist/{chunk-V4DORGWJ.js → chunk-5AMSNG2T.js} +2 -2
  15. package/dist/chunk-D7BZMRI3.js +2 -0
  16. package/dist/chunk-D7BZMRI3.js.map +1 -0
  17. package/dist/{chunk-PQBF6ZIQ.js → chunk-DI645WR4.js} +4 -4
  18. package/dist/{chunk-NCL2UZKI.js → chunk-FFA7OPEH.js} +2 -2
  19. package/dist/{chunk-34CQ2VHM.js → chunk-FO44KY7Y.js} +10 -7
  20. package/dist/chunk-FO44KY7Y.js.map +1 -0
  21. package/dist/{chunk-RB7G65JC.js → chunk-GP7ZZCID.js} +7 -5
  22. package/dist/chunk-GP7ZZCID.js.map +1 -0
  23. package/dist/{chunk-6PB2MXCI.js → chunk-HLODND65.js} +2 -2
  24. package/dist/{chunk-ADOYAGGK.js → chunk-IFVO7LQJ.js} +2 -2
  25. package/dist/{chunk-2XOIG5TG.js → chunk-JSEV46SH.js} +18 -12
  26. package/dist/chunk-JSEV46SH.js.map +1 -0
  27. package/dist/{chunk-CZP3RHC4.js → chunk-LY7O4BMP.js} +2 -2
  28. package/dist/{chunk-AOOIAFDT.js → chunk-OCZNA5SO.js} +2 -2
  29. package/dist/{chunk-S3ISPW3A.js → chunk-PWQPVOZJ.js} +1 -1
  30. package/dist/chunk-PWQPVOZJ.js.map +1 -0
  31. package/dist/chunk-RLV64EWK.js +41 -0
  32. package/dist/chunk-RLV64EWK.js.map +1 -0
  33. package/dist/chunk-SSES3F4Q.js +126 -0
  34. package/dist/chunk-SSES3F4Q.js.map +1 -0
  35. package/dist/{chunk-3XNPMDZY.js → chunk-TBCMCF5O.js} +9 -56
  36. package/dist/chunk-TBCMCF5O.js.map +1 -0
  37. package/dist/{chunk-UHLVPAHG.js → chunk-WXUXFDJQ.js} +2 -2
  38. package/dist/{chunk-X47QMM6K.js → chunk-YLN7E4OV.js} +2 -2
  39. package/dist/code-block.js +1 -1
  40. package/dist/diff-view.js +2 -2
  41. package/dist/gallery.js +1 -1
  42. package/dist/generated-image.js +9 -0
  43. package/dist/generated-image.js.map +1 -0
  44. package/dist/grouped-parts.js +6 -6
  45. package/dist/image.js +4 -4
  46. package/dist/index.d.ts +92 -150
  47. package/dist/index.js +67 -65
  48. package/dist/markdown-view.js +3 -3
  49. package/dist/message.js +3 -3
  50. package/dist/model-provider-logo.js +1 -1
  51. package/dist/persona.js +2 -2
  52. package/dist/queue.js +1 -1
  53. package/dist/reasoning.js +3 -3
  54. package/dist/sandbox.js +3 -3
  55. package/dist/tool.js +2 -2
  56. package/package.json +8 -13
  57. package/schemas/a2ui-surface.v1.schema.json +1 -1
  58. package/src/__contract__/{image.contract.test.tsx → attachments.contract.test.tsx} +7 -7
  59. package/src/__contract__/generated-image.contract.test.tsx +49 -0
  60. package/src/_lazy-boundary-conformance.ts +12 -18
  61. package/src/_lazy-engine-boundary.tsx +1 -2
  62. package/src/_lazy-mermaid-absent.test.ts +2 -2
  63. package/src/_media-fixtures.ts +47 -0
  64. package/src/_persona-rive.tsx +1 -2
  65. package/src/a2ui/catalog.source.json +1 -1
  66. package/src/asset-preview.stories.tsx +12 -0
  67. package/src/asset-preview.test.tsx +14 -0
  68. package/src/asset-preview.tsx +4 -2
  69. package/src/attachments.stories.tsx +147 -0
  70. package/src/attachments.test.tsx +104 -0
  71. package/src/attachments.tsx +28 -20
  72. package/src/audio-player.stories.tsx +10 -45
  73. package/src/audio-player.test.tsx +166 -61
  74. package/src/audio-player.tsx +175 -266
  75. package/src/chat.stories.tsx +75 -3
  76. package/src/code-block.test.tsx +32 -0
  77. package/src/code-block.tsx +22 -5
  78. package/src/gallery.tsx +13 -77
  79. package/src/generated-image.stories.tsx +67 -0
  80. package/src/generated-image.test.tsx +107 -0
  81. package/src/generated-image.tsx +57 -0
  82. package/src/image.tsx +2 -115
  83. package/src/index.ts +1 -0
  84. package/src/model-provider-logo.tsx +20 -18
  85. package/src/queue.test.tsx +24 -0
  86. package/src/queue.tsx +8 -5
  87. package/dist/_audio-player-media-chrome.js +0 -85
  88. package/dist/_audio-player-media-chrome.js.map +0 -1
  89. package/dist/chunk-2XOIG5TG.js.map +0 -1
  90. package/dist/chunk-34CQ2VHM.js.map +0 -1
  91. package/dist/chunk-3XNPMDZY.js.map +0 -1
  92. package/dist/chunk-7RL6KZDF.js.map +0 -1
  93. package/dist/chunk-NSCLRHES.js +0 -102
  94. package/dist/chunk-NSCLRHES.js.map +0 -1
  95. package/dist/chunk-RB7G65JC.js.map +0 -1
  96. package/dist/chunk-S3ISPW3A.js.map +0 -1
  97. package/dist/chunk-TWPHAJNM.js.map +0 -1
  98. package/dist/chunk-V6E5DYZD.js +0 -80
  99. package/dist/chunk-V6E5DYZD.js.map +0 -1
  100. package/src/_audio-player-media-chrome.tsx +0 -233
  101. package/src/image.stories.tsx +0 -67
  102. package/src/image.test.tsx +0 -105
  103. /package/dist/{chunk-VJZJWNEC.js.map → chunk-3KXIWGHW.js.map} +0 -0
  104. /package/dist/{chunk-V4DORGWJ.js.map → chunk-5AMSNG2T.js.map} +0 -0
  105. /package/dist/{chunk-PQBF6ZIQ.js.map → chunk-DI645WR4.js.map} +0 -0
  106. /package/dist/{chunk-NCL2UZKI.js.map → chunk-FFA7OPEH.js.map} +0 -0
  107. /package/dist/{chunk-6PB2MXCI.js.map → chunk-HLODND65.js.map} +0 -0
  108. /package/dist/{chunk-ADOYAGGK.js.map → chunk-IFVO7LQJ.js.map} +0 -0
  109. /package/dist/{chunk-CZP3RHC4.js.map → chunk-LY7O4BMP.js.map} +0 -0
  110. /package/dist/{chunk-AOOIAFDT.js.map → chunk-OCZNA5SO.js.map} +0 -0
  111. /package/dist/{chunk-UHLVPAHG.js.map → chunk-WXUXFDJQ.js.map} +0 -0
  112. /package/dist/{chunk-X47QMM6K.js.map → chunk-YLN7E4OV.js.map} +0 -0
@@ -1,13 +1,13 @@
1
1
  "use client";
2
2
  import {
3
3
  MarkdownView
4
- } from "./chunk-V4DORGWJ.js";
4
+ } from "./chunk-5AMSNG2T.js";
5
5
  import {
6
6
  useAssetPreviewRenderer
7
7
  } from "./chunk-NPUCMAG2.js";
8
8
  import {
9
9
  CodeBlock
10
- } from "./chunk-TWPHAJNM.js";
10
+ } from "./chunk-3UHR3RLZ.js";
11
11
  import {
12
12
  Artifact,
13
13
  ArtifactActions,
@@ -19,6 +19,7 @@ import {
19
19
  // src/asset-preview.tsx
20
20
  import {
21
21
  Button,
22
+ Image,
22
23
  Table,
23
24
  TableBody,
24
25
  TableCell,
@@ -161,12 +162,13 @@ var AssetPreview = ({
161
162
  }
162
163
  case "image": {
163
164
  body = /* @__PURE__ */ jsx(
164
- "img",
165
+ Image,
165
166
  {
166
167
  src: content || asset.path,
167
168
  alt: asset.name,
169
+ fit: "contain",
168
170
  loading: "lazy",
169
- className: "max-w-full rounded-md"
171
+ className: "rounded-md"
170
172
  }
171
173
  );
172
174
  break;
@@ -210,4 +212,4 @@ var AssetPreview = ({
210
212
  export {
211
213
  AssetPreview
212
214
  };
213
- //# sourceMappingURL=chunk-RB7G65JC.js.map
215
+ //# sourceMappingURL=chunk-GP7ZZCID.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/asset-preview.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * AssetPreview — type-keyed preview of one produced asset (#193, research 04\n * §5 ASSET-4). Reuses the `Artifact` chrome for the header/actions; the body\n * switches on `ContextAsset.type`:\n *\n * markdown → `MarkdownView` (the branded renderer — a document, never\n * Shiki source; the ASSET-2 fix)\n * code/sql → `CodeBlock` (Shiki)\n * csv → a small token-styled table + row-count summary\n * image → an image preview\n *\n * A Preview / Raw toggle serves users who want the source (Raw = `CodeBlock`,\n * soft-wrapped for narrow embeds). Selection state lives in the\n * `ContextPanelProvider` — the tree and this preview are the two levels of the\n * drill-in.\n *\n * Formats beyond that switch — a PDF, a spreadsheet, a video — arrive through\n * `renderPreview` (prop, or injected once on `ContextPanelProvider`), because\n * `@elabs-ai/components-viewer` is a layer PEER of this package and\n * neither may import the other (ADR 0024 §6). A renderer that returns `null`\n * declines, and everything below runs exactly as before.\n */\nimport {\n Button,\n Image,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n useLocale,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport type { BundledLanguage } from \"shiki\";\nimport {\n Artifact,\n ArtifactActions,\n ArtifactContent,\n ArtifactHeader,\n ArtifactTitle,\n} from \"./artifact\";\nimport { CodeBlock } from \"./code-block\";\nimport type { AssetPreviewRenderer, ContextAsset } from \"./context-panel\";\nimport { useAssetPreviewRenderer } from \"./context-panel\";\nimport { MarkdownView } from \"./markdown-view\";\n\nexport type AssetPreviewMode = \"preview\" | \"raw\";\n\nconst DEFAULT_CODE_LANGUAGE: BundledLanguage = \"typescript\";\n\n/** Raw-mode (and code-preview) Shiki language for an asset. */\nfunction assetLanguage(asset: ContextAsset): BundledLanguage {\n switch (asset.type) {\n case \"markdown\":\n return \"markdown\";\n case \"sql\":\n return \"sql\";\n case \"csv\":\n return \"csv\";\n case \"code\":\n return (asset.language as BundledLanguage) ?? DEFAULT_CODE_LANGUAGE;\n default:\n return DEFAULT_CODE_LANGUAGE;\n }\n}\n\ninterface ParsedCsv {\n header: string[];\n rows: string[][];\n}\n\n/**\n * A small RFC 4180-ish CSV tokenizer: handles quoted fields (commas and\n * newlines inside quotes don't end the field/row) and the `\"\"` escaped-quote\n * convention. Single pass over the raw string — no line pre-split, so a\n * quoted field spanning multiple lines survives intact. Still preview-grade\n * (unquoted cells are trimmed for the glance table), not a full CSV grammar.\n */\nfunction parseCsvRows(content: string): string[][] {\n const rows: string[][] = [];\n let row: string[] = [];\n let field = \"\";\n let wasQuoted = false;\n let inQuotes = false;\n\n const endField = () => {\n row.push(wasQuoted ? field : field.trim());\n field = \"\";\n wasQuoted = false;\n };\n const endRow = () => {\n endField();\n rows.push(row);\n row = [];\n };\n\n for (let i = 0; i < content.length; i++) {\n const ch = content[i];\n\n if (inQuotes) {\n if (ch === '\"') {\n if (content[i + 1] === '\"') {\n field += '\"';\n i += 1;\n } else {\n inQuotes = false;\n }\n } else {\n field += ch;\n }\n continue;\n }\n\n if (ch === '\"' && field === \"\") {\n inQuotes = true;\n wasQuoted = true;\n continue;\n }\n\n if (ch === \",\") {\n endField();\n continue;\n }\n\n if (ch === \"\\r\") {\n continue;\n }\n\n if (ch === \"\\n\") {\n endRow();\n continue;\n }\n\n field += ch;\n }\n\n // A trailing field/row with no terminating newline.\n if (field !== \"\" || row.length > 0) {\n endRow();\n }\n\n return rows;\n}\n\nfunction parseCsv(content: string): ParsedCsv {\n const rows = parseCsvRows(content.trim()).filter((r) => !(r.length === 1 && r[0] === \"\"));\n const [header = [], ...rest] = rows;\n return { header, rows: rest };\n}\n\nconst CsvPreview = ({ content }: { content: string }) => {\n const { t } = useLocale();\n const { header, rows } = useMemo(() => parseCsv(content), [content]);\n return (\n <div className=\"flex flex-col gap-2\">\n <Table className=\"text-body tabular-nums\">\n <TableHeader>\n <TableRow>\n {header.map((cell) => (\n <TableHead key={cell}>{cell}</TableHead>\n ))}\n </TableRow>\n </TableHeader>\n <TableBody>\n {rows.map((row, rowIndex) => (\n // Rows have no stable identity in raw CSV — index is the key.\n // oxlint-disable-next-line eslint-plugin-react(no-array-index-key)\n <TableRow key={`row-${rowIndex}`}>\n {row.map((cell, cellIndex) => (\n // oxlint-disable-next-line eslint-plugin-react(no-array-index-key)\n <TableCell key={`cell-${rowIndex}-${cellIndex}`}>{cell}</TableCell>\n ))}\n </TableRow>\n ))}\n </TableBody>\n </Table>\n <p className=\"text-meta text-muted-foreground\">\n {t(\"ai.assetPreview.rowCount\", { count: rows.length })}\n </p>\n </div>\n );\n};\n\nexport interface AssetPreviewProps extends HTMLAttributes<HTMLDivElement> {\n asset: ContextAsset;\n /** Initial mode. Default `\"preview\"`. */\n defaultMode?: AssetPreviewMode;\n /**\n * Draw this asset's preview body yourself; return `null` to decline and get\n * the built-in rendering. Wins over a renderer injected on\n * `ContextPanelProvider`, so one rail-wide default can still be overridden\n * for a single preview.\n */\n renderPreview?: AssetPreviewRenderer;\n}\n\nexport const AssetPreview = ({\n asset,\n defaultMode = \"preview\",\n renderPreview,\n className,\n ...props\n}: AssetPreviewProps) => {\n const { t } = useLocale();\n const [mode, setMode] = useState<AssetPreviewMode>(defaultMode);\n const injectedRenderer = useAssetPreviewRenderer();\n const content = asset.content ?? \"\";\n // Raw source only exists for text assets with content.\n const hasRaw = asset.type !== \"image\" && content.length > 0;\n const showRaw = mode === \"raw\" && hasRaw;\n\n // Raw is \"show me the source\", so an injection never intercepts it.\n const render = renderPreview ?? injectedRenderer;\n const injected = !showRaw && render ? render(asset) : null;\n\n let body: ReactNode;\n if (injected !== null) {\n body = injected;\n } else if (asset.type !== \"image\" && content.length === 0) {\n body = <p className=\"text-body text-muted-foreground\">{t(\"ai.assetPreview.noPreview\")}</p>;\n } else if (showRaw) {\n body = <CodeBlock code={content} language={assetLanguage(asset)} wrap />;\n } else {\n switch (asset.type) {\n case \"markdown\": {\n // Constrained heading rung inside the narrow rail (research 09 §G.2).\n body = <MarkdownView baseHeadingLevel={2}>{content}</MarkdownView>;\n break;\n }\n case \"code\":\n case \"sql\": {\n body = <CodeBlock code={content} language={assetLanguage(asset)} showLineNumbers wrap />;\n break;\n }\n case \"csv\": {\n body = <CsvPreview content={content} />;\n break;\n }\n case \"image\": {\n body = (\n <Image\n src={content || asset.path}\n alt={asset.name}\n fit=\"contain\"\n loading=\"lazy\"\n className=\"rounded-md\"\n />\n );\n break;\n }\n }\n }\n\n return (\n <Artifact data-slot=\"asset-preview\" className={className} {...props}>\n <ArtifactHeader>\n <ArtifactTitle className=\"min-w-0 truncate\">{asset.name}</ArtifactTitle>\n {hasRaw ? (\n <ArtifactActions>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={!showRaw}\n className={cn(!showRaw && \"bg-accent text-accent-foreground\")}\n onClick={() => setMode(\"preview\")}\n >\n {t(\"ai.assetPreview.preview\")}\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n aria-pressed={showRaw}\n className={cn(showRaw && \"bg-accent text-accent-foreground\")}\n onClick={() => setMode(\"raw\")}\n >\n {t(\"ai.assetPreview.raw\")}\n </Button>\n </ArtifactActions>\n ) : null}\n </ArtifactHeader>\n <ArtifactContent>{body}</ArtifactContent>\n </Artifact>\n );\n};\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  highlightCode
4
- } from "./chunk-TWPHAJNM.js";
4
+ } from "./chunk-3UHR3RLZ.js";
5
5
  import {
6
6
  Shimmer
7
7
  } from "./chunk-3YPT3V7L.js";
@@ -479,4 +479,4 @@ export {
479
479
  diffRowVariants,
480
480
  DiffView
481
481
  };
482
- //# sourceMappingURL=chunk-6PB2MXCI.js.map
482
+ //# sourceMappingURL=chunk-HLODND65.js.map
@@ -4,7 +4,7 @@ import {
4
4
  useStreamdownMermaidOptions,
5
5
  useStreamdownPlugins,
6
6
  useStreamdownTranslations
7
- } from "./chunk-PQBF6ZIQ.js";
7
+ } from "./chunk-DI645WR4.js";
8
8
  import {
9
9
  stripSanitizerOverrides,
10
10
  warnOnTrustedPluginSlots
@@ -479,4 +479,4 @@ export {
479
479
  MessageResponse,
480
480
  MessageToolbar
481
481
  };
482
- //# sourceMappingURL=chunk-ADOYAGGK.js.map
482
+ //# sourceMappingURL=chunk-IFVO7LQJ.js.map
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  // src/model-provider-logo.tsx
4
+ import { Image } from "@elabs-ai/components-ui";
4
5
  import { cn } from "@elabs-ai/components-ui/lib/cn";
5
6
  import { resolveThemeIsDark } from "@elabs-ai/components-tokens";
6
7
  import { BotIcon } from "lucide-react";
7
8
  import { useEffect, useState } from "react";
8
- import { Fragment, jsx } from "react/jsx-runtime";
9
+ import { jsx } from "react/jsx-runtime";
9
10
  function useIsDarkTheme() {
10
11
  const [isDark, setIsDark] = useState(false);
11
12
  useEffect(() => {
@@ -28,22 +29,27 @@ var ModelProviderLogo = ({
28
29
  onError,
29
30
  ...props
30
31
  }) => {
31
- const [failed, setFailed] = useState(false);
32
32
  const isDark = useIsDarkTheme();
33
- if (failed) {
34
- return /* @__PURE__ */ jsx(Fragment, { children: fallback ?? /* @__PURE__ */ jsx(BotIcon, { "aria-label": `${provider} logo`, className: cn("size-3", className), role: "img" }) });
35
- }
33
+ const label = `${provider} logo`;
36
34
  return /* @__PURE__ */ jsx(
37
- "img",
35
+ Image,
38
36
  {
39
37
  ...props,
40
- alt: `${provider} logo`,
38
+ "data-slot": "model-provider-logo",
39
+ alt: label,
41
40
  className: cn("size-3", isDark && "invert", className),
41
+ fallback: fallback ?? /* @__PURE__ */ jsx(
42
+ BotIcon,
43
+ {
44
+ "data-slot": "model-provider-logo",
45
+ "aria-label": label,
46
+ className: cn("size-3", className),
47
+ role: "img"
48
+ }
49
+ ),
42
50
  height: 12,
43
- onError: (event) => {
44
- setFailed(true);
45
- onError?.(event);
46
- },
51
+ onError,
52
+ showSkeleton: false,
47
53
  src: src ?? `${MODEL_PROVIDER_LOGO_BASE_URL}/${provider}.svg`,
48
54
  width: 12
49
55
  }
@@ -69,4 +75,4 @@ export {
69
75
  ModelProviderLogo,
70
76
  ModelProviderLogoGroup
71
77
  };
72
- //# sourceMappingURL=chunk-2XOIG5TG.js.map
78
+ //# sourceMappingURL=chunk-JSEV46SH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/model-provider-logo.tsx"],"sourcesContent":["\"use client\";\n\nimport { Image } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { resolveThemeIsDark } from \"@elabs-ai/components-tokens\";\nimport { BotIcon } from \"lucide-react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { useEffect, useState } from \"react\";\n\n/**\n * Tracks the ACTIVE theme's own `color-scheme` (`resolveThemeIsDark`), not the\n * two shipped `light`/`dark` themes — a consumer-authored dark theme still\n * gets an inverted (visible) logo with no registration step (theming.md:\n * \"Use semantic tokens, not `dark:` — `@custom-variant dark` covers only the\n * themes shipped HERE\"). Mirrors `Toaster`'s `useDocumentThemeMode`\n * (`packages/ui/src/components/sonner/sonner.tsx`).\n */\nfunction useIsDarkTheme(): boolean {\n const [isDark, setIsDark] = useState(false);\n useEffect(() => {\n if (typeof document === \"undefined\") return;\n const el = document.documentElement;\n const read = () => setIsDark(resolveThemeIsDark(el));\n read();\n const obs = new MutationObserver(read);\n obs.observe(el, { attributes: true, attributeFilter: [\"data-theme\"] });\n return () => obs.disconnect();\n }, []);\n return isDark;\n}\n\n/**\n * Where provider logos are fetched from by default.\n *\n * This is a REMOTE origin: a deployment with a restrictive `img-src` blocks it.\n * Self-host the SVGs and pass `src` (or set a `fallback`) — see\n * `docs/CSP-AND-NETWORK.md`.\n */\nexport const MODEL_PROVIDER_LOGO_BASE_URL = \"https://models.dev/logos\";\n\nexport type ModelProviderLogoProps = Omit<ComponentProps<\"img\">, \"alt\"> & {\n /**\n * Override the logo URL — point at a self-hosted or bundled asset when a CSP\n * blocks `models.dev`. Defaults to `${MODEL_PROVIDER_LOGO_BASE_URL}/<provider>.svg`.\n */\n src?: string;\n /**\n * Rendered when the logo fails to load (blocked, offline, or unknown\n * provider). Defaults to a neutral Lucide glyph, so the row never collapses\n * to a broken image.\n */\n fallback?: ReactNode;\n provider:\n | \"moonshotai-cn\"\n | \"lucidquery\"\n | \"moonshotai\"\n | \"zai-coding-plan\"\n | \"alibaba\"\n | \"xai\"\n | \"vultr\"\n | \"nvidia\"\n | \"upstage\"\n | \"groq\"\n | \"github-copilot\"\n | \"mistral\"\n | \"vercel\"\n | \"nebius\"\n | \"deepseek\"\n | \"alibaba-cn\"\n | \"google-vertex-anthropic\"\n | \"venice\"\n | \"chutes\"\n | \"cortecs\"\n | \"github-models\"\n | \"togetherai\"\n | \"azure\"\n | \"baseten\"\n | \"huggingface\"\n | \"opencode\"\n | \"fastrouter\"\n | \"google\"\n | \"google-vertex\"\n | \"cloudflare-workers-ai\"\n | \"inception\"\n | \"wandb\"\n | \"openai\"\n | \"zhipuai-coding-plan\"\n | \"perplexity\"\n | \"openrouter\"\n | \"zenmux\"\n | \"v0\"\n | \"iflowcn\"\n | \"synthetic\"\n | \"deepinfra\"\n | \"zhipuai\"\n | \"submodel\"\n | \"zai\"\n | \"inference\"\n | \"requesty\"\n | \"morph\"\n | \"lmstudio\"\n | \"anthropic\"\n | \"aihubmix\"\n | \"fireworks-ai\"\n | \"modelscope\"\n | \"llama\"\n | \"scaleway\"\n | \"amazon-bedrock\"\n | \"cerebras\"\n // oxlint-disable-next-line typescript-eslint(ban-types) -- intentional pattern for autocomplete-friendly string union\n | (string & {});\n};\n\n/**\n * The mark of an AI model provider (`anthropic`, `openai`, `google`, …), sized\n * for a row in a model list.\n *\n * **`ModelProviderLogo` vs `ServiceLogo` (`@elabs-ai/components-icons`).** An AI\n * provider mark with a zero-config remote default — name the provider and a logo\n * appears, no registration step — is this component. Any other third-party\n * service mark, resolved from a registry the app supplies and never fetched, is\n * `ServiceLogo`. The two are not interchangeable: `ServiceLogo` has no\n * per-instance `src` and no `onError`, and this one reaches a remote origin by\n * default (see `docs/CSP-AND-NETWORK.md`).\n */\nexport const ModelProviderLogo = ({\n provider,\n className,\n src,\n fallback,\n onError,\n ...props\n}: ModelProviderLogoProps) => {\n const isDark = useIsDarkTheme();\n const label = `${provider} logo`;\n\n // A blocked/missing logo must not leave a broken-image glyph in the row:\n // ui `Image` renders `fallback` AS the root on a terminal error (or at once\n // when there is no `src`).\n return (\n <Image\n {...props}\n data-slot=\"model-provider-logo\"\n alt={label}\n className={cn(\"size-3\", isDark && \"invert\", className)}\n fallback={\n fallback ?? (\n <BotIcon\n data-slot=\"model-provider-logo\"\n aria-label={label}\n className={cn(\"size-3\", className)}\n role=\"img\"\n />\n )\n }\n height={12}\n onError={onError}\n showSkeleton={false}\n // AFTER the spread, so the caller's `src` wins via the destructured\n // default rather than being silently overwritten.\n src={src ?? `${MODEL_PROVIDER_LOGO_BASE_URL}/${provider}.svg`}\n width={12}\n />\n );\n};\n\nexport type ModelProviderLogoGroupProps = ComponentProps<\"div\">;\n\nexport const ModelProviderLogoGroup = ({ className, ...props }: ModelProviderLogoGroupProps) => {\n // Same active-theme signal as the single logo above (not the two shipped\n // `light`/`dark` themes) — the backdrop swaps to `--foreground` so a\n // transparent-background SVG still reads against a dark chip in ANY dark\n // theme a consumer registers.\n const isDark = useIsDarkTheme();\n return (\n <div\n className={cn(\n \"flex shrink-0 items-center -space-x-1 [&>img]:rounded-full [&>img]:p-px [&>img]:ring-1\",\n isDark ? \"[&>img]:bg-foreground\" : \"[&>img]:bg-background\",\n className,\n )}\n {...props}\n />\n );\n};\n"],"mappings":";;;AAEA,SAAS,aAAa;AACtB,SAAS,UAAU;AACnB,SAAS,0BAA0B;AACnC,SAAS,eAAe;AAExB,SAAS,WAAW,gBAAgB;AA4I1B;AAlIV,SAAS,iBAA0B;AACjC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,YAAU,MAAM;AACd,QAAI,OAAO,aAAa,YAAa;AACrC,UAAM,KAAK,SAAS;AACpB,UAAM,OAAO,MAAM,UAAU,mBAAmB,EAAE,CAAC;AACnD,SAAK;AACL,UAAM,MAAM,IAAI,iBAAiB,IAAI;AACrC,QAAI,QAAQ,IAAI,EAAE,YAAY,MAAM,iBAAiB,CAAC,YAAY,EAAE,CAAC;AACrE,WAAO,MAAM,IAAI,WAAW;AAAA,EAC9B,GAAG,CAAC,CAAC;AACL,SAAO;AACT;AASO,IAAM,+BAA+B;AAuFrC,IAAM,oBAAoB,CAAC;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA8B;AAC5B,QAAM,SAAS,eAAe;AAC9B,QAAM,QAAQ,GAAG,QAAQ;AAKzB,SACE;AAAA,IAAC;AAAA;AAAA,MACE,GAAG;AAAA,MACJ,aAAU;AAAA,MACV,KAAK;AAAA,MACL,WAAW,GAAG,UAAU,UAAU,UAAU,SAAS;AAAA,MACrD,UACE,YACE;AAAA,QAAC;AAAA;AAAA,UACC,aAAU;AAAA,UACV,cAAY;AAAA,UACZ,WAAW,GAAG,UAAU,SAAS;AAAA,UACjC,MAAK;AAAA;AAAA,MACP;AAAA,MAGJ,QAAQ;AAAA,MACR;AAAA,MACA,cAAc;AAAA,MAGd,KAAK,OAAO,GAAG,4BAA4B,IAAI,QAAQ;AAAA,MACvD,OAAO;AAAA;AAAA,EACT;AAEJ;AAIO,IAAM,yBAAyB,CAAC,EAAE,WAAW,GAAG,MAAM,MAAmC;AAK9F,QAAM,SAAS,eAAe;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,SAAS,0BAA0B;AAAA,QACnC;AAAA,MACF;AAAA,MACC,GAAG;AAAA;AAAA,EACN;AAEJ;","names":[]}
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  CodeBlock
4
- } from "./chunk-TWPHAJNM.js";
4
+ } from "./chunk-3UHR3RLZ.js";
5
5
 
6
6
  // src/agent.tsx
7
7
  import {
@@ -71,4 +71,4 @@ export {
71
71
  AgentTool,
72
72
  AgentOutput
73
73
  };
74
- //# sourceMappingURL=chunk-CZP3RHC4.js.map
74
+ //# sourceMappingURL=chunk-LY7O4BMP.js.map
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  CodeBlock
4
- } from "./chunk-TWPHAJNM.js";
4
+ } from "./chunk-3UHR3RLZ.js";
5
5
 
6
6
  // src/tool.tsx
7
7
  import { Skeleton, StatusBadge, useLocale } from "@elabs-ai/components-ui";
@@ -170,4 +170,4 @@ export {
170
170
  ToolInput,
171
171
  ToolOutput
172
172
  };
173
- //# sourceMappingURL=chunk-AOOIAFDT.js.map
173
+ //# sourceMappingURL=chunk-OCZNA5SO.js.map
@@ -25,4 +25,4 @@ var LazyEngineBoundary = class extends Component {
25
25
  export {
26
26
  LazyEngineBoundary
27
27
  };
28
- //# sourceMappingURL=chunk-S3ISPW3A.js.map
28
+ //# sourceMappingURL=chunk-PWQPVOZJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/_lazy-engine-boundary.tsx"],"sourcesContent":["\"use client\";\n\nimport { Component, type ErrorInfo, type ReactNode } from \"react\";\n\nexport interface LazyEngineBoundaryProps {\n children: ReactNode;\n /**\n * Called once a wrapped `lazy()` import rejects (an optional peer that is\n * not installed, or any other load-time failure). Return what to render in\n * its place — the boundary owns no default visual, since the right\n * stand-in differs by surface (an orb placeholder for `Persona`).\n */\n renderMissing: (error: unknown) => ReactNode;\n}\n\ninterface LazyEngineBoundaryState {\n // Wrapped in an object so `{ value: undefined }` (a boundary that caught\n // an error whose value happens to be `undefined`) is distinguishable from\n // \"nothing caught yet\" (`error: null`).\n error: { value: unknown } | null;\n}\n\n/**\n * Catches a `React.lazy()` load failure — an optional peer dependency that is\n * not installed (issue #33), or any other dynamic-import rejection — and\n * renders a caller-supplied fallback instead of unmounting the tree.\n *\n * `Suspense` alone does not do this: it only covers the PENDING state. A\n * REJECTED lazy import throws during render, and with no boundary above it\n * that throw propagates to the nearest ancestor boundary React DOES find —\n * by default, the whole app. This is the one place in `@elabs-ai/components-ai`\n * that needs a real render-phase error boundary; Mermaid and the interactive\n * terminal fail via an awaited promise instead (Streamdown's `errorComponent`\n * and a `.catch()` on the mount effect, respectively), which don't need one.\n */\nexport class LazyEngineBoundary extends Component<\n LazyEngineBoundaryProps,\n LazyEngineBoundaryState\n> {\n override state: LazyEngineBoundaryState = { error: null };\n\n static getDerivedStateFromError(error: unknown): LazyEngineBoundaryState {\n return { error: { value: error } };\n }\n\n override componentDidCatch(error: unknown, info: ErrorInfo): void {\n console.error(\n \"[@elabs-ai/components-ai] a lazy engine failed to load:\",\n error,\n info.componentStack,\n );\n }\n\n override render(): ReactNode {\n if (this.state.error) {\n return this.props.renderMissing(this.state.error.value);\n }\n return this.props.children;\n }\n}\n"],"mappings":";;;AAEA,SAAS,iBAAiD;AAiCnD,IAAM,qBAAN,cAAiC,UAGtC;AAAA,EACS,QAAiC,EAAE,OAAO,KAAK;AAAA,EAExD,OAAO,yBAAyB,OAAyC;AACvE,WAAO,EAAE,OAAO,EAAE,OAAO,MAAM,EAAE;AAAA,EACnC;AAAA,EAES,kBAAkB,OAAgB,MAAuB;AAChE,YAAQ;AAAA,MACN;AAAA,MACA;AAAA,MACA,KAAK;AAAA,IACP;AAAA,EACF;AAAA,EAES,SAAoB;AAC3B,QAAI,KAAK,MAAM,OAAO;AACpB,aAAO,KAAK,MAAM,cAAc,KAAK,MAAM,MAAM,KAAK;AAAA,IACxD;AACA,WAAO,KAAK,MAAM;AAAA,EACpB;AACF;","names":[]}
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ // src/generated-image.tsx
4
+ import { Image } from "@elabs-ai/components-ui";
5
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
6
+ import { forwardRef } from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ var GeneratedImage = forwardRef(
9
+ function GeneratedImage2({
10
+ base64,
11
+ uint8Array: _uint8Array,
12
+ mediaType,
13
+ alt = "",
14
+ showSkeleton,
15
+ width,
16
+ height,
17
+ className,
18
+ ...props
19
+ }, ref) {
20
+ return /* @__PURE__ */ jsx(
21
+ Image,
22
+ {
23
+ ...props,
24
+ ref,
25
+ "data-slot": "generated-image",
26
+ src: `data:${mediaType};base64,${base64}`,
27
+ alt,
28
+ width,
29
+ height,
30
+ showSkeleton: showSkeleton ?? Boolean(width && height),
31
+ className: cn("rounded-md", className)
32
+ }
33
+ );
34
+ }
35
+ );
36
+ GeneratedImage.displayName = "GeneratedImage";
37
+
38
+ export {
39
+ GeneratedImage
40
+ };
41
+ //# sourceMappingURL=chunk-RLV64EWK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/generated-image.tsx"],"sourcesContent":["\"use client\";\n\nimport { Image, type ImageProps } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport type { Experimental_GeneratedImage } from \"ai\";\nimport { forwardRef } from \"react\";\n\nexport type GeneratedImageProps = Experimental_GeneratedImage &\n Omit<ImageProps, \"src\" | \"alt\" | \"aspectRatio\" | \"showSkeleton\"> & {\n /** Accessible text. Omit (or `\"\"`) for a decorative image. @default \"\" */\n alt?: string;\n /**\n * Show a `Skeleton` in the reserved `width` × `height` box until the base64\n * payload decodes, so it does not pop in. Without both dimensions there is\n * no box to reserve, so the default is off in that case.\n * @default Boolean(width && height)\n */\n showSkeleton?: boolean;\n };\n\n/**\n * An AI-SDK generated image (`Experimental_GeneratedImage`) rendered through\n * the ui `Image` primitive as a base64 data URL — decode-aware skeleton,\n * `ImageOff` fallback on a terminal error.\n */\nexport const GeneratedImage = forwardRef<HTMLImageElement, GeneratedImageProps>(\n function GeneratedImage(\n {\n base64,\n uint8Array: _uint8Array,\n mediaType,\n alt = \"\",\n showSkeleton,\n width,\n height,\n className,\n ...props\n },\n ref,\n ) {\n return (\n <Image\n {...props}\n ref={ref}\n data-slot=\"generated-image\"\n src={`data:${mediaType};base64,${base64}`}\n alt={alt}\n width={width}\n height={height}\n showSkeleton={showSkeleton ?? Boolean(width && height)}\n className={cn(\"rounded-md\", className)}\n />\n );\n },\n);\n\nGeneratedImage.displayName = \"GeneratedImage\";\n"],"mappings":";;;AAEA,SAAS,aAA8B;AACvC,SAAS,UAAU;AAEnB,SAAS,kBAAkB;AAoCrB;AAhBC,IAAM,iBAAiB;AAAA,EAC5B,SAASA,gBACP;AAAA,IACE;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA,MAAM;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAG;AAAA,EACL,GACA,KACA;AACA,WACE;AAAA,MAAC;AAAA;AAAA,QACE,GAAG;AAAA,QACJ;AAAA,QACA,aAAU;AAAA,QACV,KAAK,QAAQ,SAAS,WAAW,MAAM;AAAA,QACvC;AAAA,QACA;AAAA,QACA;AAAA,QACA,cAAc,gBAAgB,QAAQ,SAAS,MAAM;AAAA,QACrD,WAAW,GAAG,cAAc,SAAS;AAAA;AAAA,IACvC;AAAA,EAEJ;AACF;AAEA,eAAe,cAAc;","names":["GeneratedImage"]}
@@ -0,0 +1,126 @@
1
+ "use client";
2
+
3
+ // src/audio-player.tsx
4
+ import {
5
+ MediaPlayer,
6
+ MediaPlayerControls,
7
+ MediaPlayerElement,
8
+ MediaPlayerError,
9
+ MediaPlayerLoading,
10
+ MediaPlayerMuteButton,
11
+ MediaPlayerPlayButton,
12
+ MediaPlayerSeekButton,
13
+ MediaPlayerTime,
14
+ MediaPlayerTimeSlider,
15
+ MediaPlayerVolumeSlider,
16
+ useLocale,
17
+ useMediaPlayer
18
+ } from "@elabs-ai/components-ui";
19
+ import { jsx, jsxs } from "react/jsx-runtime";
20
+ function AudioPlayerErrorRung() {
21
+ const { t } = useLocale();
22
+ const { state } = useMediaPlayer();
23
+ if (!state.error) return null;
24
+ return /* @__PURE__ */ jsx(MediaPlayerError, { title: t("ai.audioPlayer.renderError") });
25
+ }
26
+ var AudioPlayer = ({
27
+ keyboardShortcuts,
28
+ noHotkeys,
29
+ keyboardControl,
30
+ children,
31
+ // Accepted and ignored (former legacy controller attributes).
32
+ autohide: _autohide,
33
+ autohideOverControls: _autohideOverControls,
34
+ breakpoints: _breakpoints,
35
+ breakpointsComputed: _breakpointsComputed,
36
+ defaultDuration: _defaultDuration,
37
+ defaultStreamType: _defaultStreamType,
38
+ defaultSubtitles: _defaultSubtitles,
39
+ gesturesDisabled: _gesturesDisabled,
40
+ keysUsed: _keysUsed,
41
+ liveEdgeOffset: _liveEdgeOffset,
42
+ noAutohide: _noAutohide,
43
+ noAutoSeekToLive: _noAutoSeekToLive,
44
+ noDefaultStore: _noDefaultStore,
45
+ noMutedPref: _noMutedPref,
46
+ noSubtitlesLangPref: _noSubtitlesLangPref,
47
+ noVolumePref: _noVolumePref,
48
+ resolvedLang: _resolvedLang,
49
+ userInteractive: _userInteractive,
50
+ ...props
51
+ }) => /* @__PURE__ */ jsxs(
52
+ MediaPlayer,
53
+ {
54
+ kind: "audio",
55
+ "data-slot": "media-player",
56
+ keyboardShortcuts: (keyboardShortcuts ?? true) && !noHotkeys && keyboardControl !== false,
57
+ ...props,
58
+ children: [
59
+ children,
60
+ /* @__PURE__ */ jsx(MediaPlayerLoading, {}),
61
+ /* @__PURE__ */ jsx(AudioPlayerErrorRung, {})
62
+ ]
63
+ }
64
+ );
65
+ var AudioPlayerElement = ({ ref, ...props }) => {
66
+ const { src, data, ...rest } = props;
67
+ return /* @__PURE__ */ jsx(
68
+ MediaPlayerElement,
69
+ {
70
+ "data-slot": "media-player-element",
71
+ ...rest,
72
+ ref,
73
+ src: data ? `data:${data.mediaType};base64,${data.base64}` : src
74
+ }
75
+ );
76
+ };
77
+ var AudioPlayerControlBar = (props) => /* @__PURE__ */ jsx(MediaPlayerControls, { "data-slot": "media-player-controls", ...props });
78
+ var AudioPlayerPlayButton = (props) => /* @__PURE__ */ jsx(MediaPlayerPlayButton, { "data-slot": "media-player-play-button", ...props });
79
+ var AudioPlayerSeekBackwardButton = ({
80
+ offset,
81
+ seekOffset,
82
+ ...props
83
+ }) => /* @__PURE__ */ jsx(
84
+ MediaPlayerSeekButton,
85
+ {
86
+ "data-slot": "media-player-seek-button",
87
+ ...props,
88
+ offset: -Math.abs(offset ?? seekOffset ?? 10)
89
+ }
90
+ );
91
+ var AudioPlayerSeekForwardButton = ({
92
+ offset,
93
+ seekOffset,
94
+ mediaController: _mediaController,
95
+ mediaCurrentTime: _mediaCurrentTime,
96
+ noTooltip: _noTooltip,
97
+ preventClick: _preventClick,
98
+ ...props
99
+ }) => /* @__PURE__ */ jsx(
100
+ MediaPlayerSeekButton,
101
+ {
102
+ "data-slot": "media-player-seek-button",
103
+ ...props,
104
+ offset: Math.abs(offset ?? seekOffset ?? 10)
105
+ }
106
+ );
107
+ var AudioPlayerTimeDisplay = (props) => /* @__PURE__ */ jsx(MediaPlayerTime, { "data-slot": "media-player-time", ...props, mode: "current" });
108
+ var AudioPlayerTimeRange = (props) => /* @__PURE__ */ jsx(MediaPlayerTimeSlider, { "data-slot": "media-player-time-slider", ...props });
109
+ var AudioPlayerDurationDisplay = (props) => /* @__PURE__ */ jsx(MediaPlayerTime, { "data-slot": "media-player-time", ...props, mode: "duration" });
110
+ var AudioPlayerMuteButton = (props) => /* @__PURE__ */ jsx(MediaPlayerMuteButton, { "data-slot": "media-player-mute-button", ...props });
111
+ var AudioPlayerVolumeRange = (props) => /* @__PURE__ */ jsx(MediaPlayerVolumeSlider, { "data-slot": "media-player-volume-slider", ...props });
112
+
113
+ export {
114
+ AudioPlayer,
115
+ AudioPlayerElement,
116
+ AudioPlayerControlBar,
117
+ AudioPlayerPlayButton,
118
+ AudioPlayerSeekBackwardButton,
119
+ AudioPlayerSeekForwardButton,
120
+ AudioPlayerTimeDisplay,
121
+ AudioPlayerTimeRange,
122
+ AudioPlayerDurationDisplay,
123
+ AudioPlayerMuteButton,
124
+ AudioPlayerVolumeRange
125
+ };
126
+ //# sourceMappingURL=chunk-SSES3F4Q.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/audio-player.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n MediaPlayer,\n MediaPlayerControls,\n MediaPlayerElement,\n MediaPlayerError,\n MediaPlayerLoading,\n MediaPlayerMuteButton,\n MediaPlayerPlayButton,\n MediaPlayerSeekButton,\n MediaPlayerTime,\n MediaPlayerTimeSlider,\n MediaPlayerVolumeSlider,\n useLocale,\n useMediaPlayer,\n type MediaPlayerButtonProps,\n type MediaPlayerControlsProps,\n type MediaPlayerElementProps,\n type MediaPlayerProps,\n type MediaPlayerTimeProps,\n type MediaPlayerTimeSliderProps,\n type MediaPlayerVolumeSliderProps,\n} from \"@elabs-ai/components-ui\";\nimport type { Experimental_SpeechResult as SpeechResult } from \"ai\";\nimport type { ComponentProps, HTMLAttributes, Ref } from \"react\";\n\n/**\n * `AudioPlayer*` — the chat-facing names for spoken agent replies, as thin\n * presets over ui's `MediaPlayer*` compound parts (ADR 0041). Each preset\n * emits the SHARED slot of the ui part it wraps (`media-player*`, spelled out\n * so this module declares what it renders); the former `audio-player*`\n * selectors are gone (maintainer decision 2026-09-23, ADR 0041 §7). The ui\n * part underneath owns the behaviour, labels and styling.\n */\n\n/**\n * @deprecated The shared base of the former custom-element parts. Each part now\n * has its own prop type (`AudioPlayerPlayButtonProps`, …); removed in the next\n * major.\n */\nexport type AudioPlayerPartProps = HTMLAttributes<HTMLElement>;\n\n/**\n * The former custom-element controller attributes. Kept so existing\n * call sites typecheck; `noHotkeys`/`keyboardControl` still map onto\n * `keyboardShortcuts`, every other member is accepted and ignored. All are\n * removed in the next major.\n */\ninterface AudioPlayerLegacyProps {\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n autohide?: string;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n autohideOverControls?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n breakpoints?: string;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n breakpointsComputed?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n defaultDuration?: number;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n defaultStreamType?: string;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n defaultSubtitles?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n gesturesDisabled?: boolean;\n /** @deprecated Use `keyboardShortcuts`. `false` turns the player's keyboard map off. */\n keyboardControl?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n keysUsed?: string;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n liveEdgeOffset?: number;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n noAutohide?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n noAutoSeekToLive?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n noDefaultStore?: boolean;\n /** @deprecated Use `keyboardShortcuts={false}`. */\n noHotkeys?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n noMutedPref?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n noSubtitlesLangPref?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n noVolumePref?: boolean;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n resolvedLang?: string;\n /** @deprecated Ignored (legacy controller attribute); removed in the next major. */\n userInteractive?: boolean;\n}\n\nexport interface AudioPlayerProps extends Omit<MediaPlayerProps, \"kind\">, AudioPlayerLegacyProps {}\n\n/** Replaces the controls with the compact error panel once playback has terminally failed. */\nfunction AudioPlayerErrorRung() {\n const { t } = useLocale();\n const { state } = useMediaPlayer();\n if (!state.error) return null;\n return <MediaPlayerError title={t(\"ai.audioPlayer.renderError\")} />;\n}\n\n/**\n * The player root (`MediaPlayer kind=\"audio\"`): transparent, so put it in the\n * bubble or card that is its surface. Compose `AudioPlayerElement` plus an\n * `AudioPlayerControlBar` inside.\n */\nexport const AudioPlayer = ({\n keyboardShortcuts,\n noHotkeys,\n keyboardControl,\n children,\n // Accepted and ignored (former legacy controller attributes).\n autohide: _autohide,\n autohideOverControls: _autohideOverControls,\n breakpoints: _breakpoints,\n breakpointsComputed: _breakpointsComputed,\n defaultDuration: _defaultDuration,\n defaultStreamType: _defaultStreamType,\n defaultSubtitles: _defaultSubtitles,\n gesturesDisabled: _gesturesDisabled,\n keysUsed: _keysUsed,\n liveEdgeOffset: _liveEdgeOffset,\n noAutohide: _noAutohide,\n noAutoSeekToLive: _noAutoSeekToLive,\n noDefaultStore: _noDefaultStore,\n noMutedPref: _noMutedPref,\n noSubtitlesLangPref: _noSubtitlesLangPref,\n noVolumePref: _noVolumePref,\n resolvedLang: _resolvedLang,\n userInteractive: _userInteractive,\n ...props\n}: AudioPlayerProps) => (\n <MediaPlayer\n kind=\"audio\"\n data-slot=\"media-player\"\n keyboardShortcuts={(keyboardShortcuts ?? true) && !noHotkeys && keyboardControl !== false}\n {...props}\n >\n {children}\n <MediaPlayerLoading />\n <AudioPlayerErrorRung />\n </MediaPlayer>\n);\n\nexport type AudioPlayerElementProps = Omit<ComponentProps<\"audio\">, \"src\"> &\n (\n | {\n data: SpeechResult[\"audio\"];\n }\n | {\n src: string;\n }\n );\n\n/**\n * The `<audio>` element. Pass a URL as `src`, or an AI SDK speech result's\n * `audio` as `data` (converted to a base64 data URL).\n */\nexport const AudioPlayerElement = ({ ref, ...props }: AudioPlayerElementProps) => {\n const { src, data, ...rest } = props as typeof props & {\n src?: string;\n data?: SpeechResult[\"audio\"];\n };\n return (\n <MediaPlayerElement\n data-slot=\"media-player-element\"\n {...(rest as MediaPlayerElementProps)}\n ref={ref as Ref<HTMLMediaElement> | undefined}\n src={data ? `data:${data.mediaType};base64,${data.base64}` : src}\n />\n );\n};\n\nexport type AudioPlayerControlBarProps = MediaPlayerControlsProps;\n\nexport const AudioPlayerControlBar = (props: AudioPlayerControlBarProps) => (\n <MediaPlayerControls data-slot=\"media-player-controls\" {...props} />\n);\n\nexport type AudioPlayerPlayButtonProps = MediaPlayerButtonProps;\n\nexport const AudioPlayerPlayButton = (props: AudioPlayerPlayButtonProps) => (\n <MediaPlayerPlayButton data-slot=\"media-player-play-button\" {...props} />\n);\n\n/** Props shared by the two seek presets. */\ninterface AudioPlayerSeekPresetProps extends MediaPlayerButtonProps {\n /** Seconds to skip, as a positive distance; the button sets the direction. Default 10. */\n offset?: number;\n /** @deprecated Use `offset`. */\n seekOffset?: number;\n}\n\nexport type AudioPlayerSeekBackwardButtonProps = AudioPlayerSeekPresetProps;\n\nexport const AudioPlayerSeekBackwardButton = ({\n offset,\n seekOffset,\n ...props\n}: AudioPlayerSeekBackwardButtonProps) => (\n <MediaPlayerSeekButton\n data-slot=\"media-player-seek-button\"\n {...props}\n offset={-Math.abs(offset ?? seekOffset ?? 10)}\n />\n);\n\nexport interface AudioPlayerSeekForwardButtonProps extends AudioPlayerSeekPresetProps {\n /** @deprecated Ignored (legacy attribute); removed in the next major. */\n mediaController?: string;\n /** @deprecated Ignored (legacy attribute); removed in the next major. */\n mediaCurrentTime?: number;\n /** @deprecated Ignored (legacy attribute); removed in the next major. */\n noTooltip?: boolean;\n /** @deprecated Ignored (legacy attribute); removed in the next major. */\n preventClick?: boolean;\n}\n\nexport const AudioPlayerSeekForwardButton = ({\n offset,\n seekOffset,\n mediaController: _mediaController,\n mediaCurrentTime: _mediaCurrentTime,\n noTooltip: _noTooltip,\n preventClick: _preventClick,\n ...props\n}: AudioPlayerSeekForwardButtonProps) => (\n <MediaPlayerSeekButton\n data-slot=\"media-player-seek-button\"\n {...props}\n offset={Math.abs(offset ?? seekOffset ?? 10)}\n />\n);\n\nexport type AudioPlayerTimeDisplayProps = Omit<MediaPlayerTimeProps, \"mode\">;\n\n/** The current playback position. */\nexport const AudioPlayerTimeDisplay = (props: AudioPlayerTimeDisplayProps) => (\n <MediaPlayerTime data-slot=\"media-player-time\" {...props} mode=\"current\" />\n);\n\nexport type AudioPlayerTimeRangeProps = MediaPlayerTimeSliderProps;\n\nexport const AudioPlayerTimeRange = (props: AudioPlayerTimeRangeProps) => (\n <MediaPlayerTimeSlider data-slot=\"media-player-time-slider\" {...props} />\n);\n\nexport type AudioPlayerDurationDisplayProps = Omit<MediaPlayerTimeProps, \"mode\">;\n\n/** The total duration. */\nexport const AudioPlayerDurationDisplay = (props: AudioPlayerDurationDisplayProps) => (\n <MediaPlayerTime data-slot=\"media-player-time\" {...props} mode=\"duration\" />\n);\n\nexport type AudioPlayerMuteButtonProps = MediaPlayerButtonProps;\n\nexport const AudioPlayerMuteButton = (props: AudioPlayerMuteButtonProps) => (\n <MediaPlayerMuteButton data-slot=\"media-player-mute-button\" {...props} />\n);\n\nexport type AudioPlayerVolumeRangeProps = MediaPlayerVolumeSliderProps;\n\nexport const AudioPlayerVolumeRange = (props: AudioPlayerVolumeRangeProps) => (\n <MediaPlayerVolumeSlider data-slot=\"media-player-volume-slider\" {...props} />\n);\n"],"mappings":"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","names":[]}
@@ -14,6 +14,7 @@ import {
14
14
  DialogContent,
15
15
  DialogDescription,
16
16
  DialogTitle,
17
+ Image,
17
18
  Skeleton,
18
19
  downloadUrl
19
20
  } from "@elabs-ai/components-ui";
@@ -41,48 +42,6 @@ function useGallery() {
41
42
  function triggerDownload(image) {
42
43
  void downloadUrl(image.downloadUrl ?? image.src, image.downloadName);
43
44
  }
44
- function GalleryImg({
45
- src,
46
- alt,
47
- className,
48
- loading,
49
- showSkeleton = true
50
- }) {
51
- const [failed, setFailed] = useState(false);
52
- const [loaded, setLoaded] = useState(false);
53
- const imgRef = useRef(null);
54
- useEffect(() => {
55
- if (imgRef.current?.complete) setLoaded(true);
56
- }, []);
57
- if (failed) {
58
- return /* @__PURE__ */ jsx(
59
- "div",
60
- {
61
- className: cn(
62
- "flex flex-col items-center justify-center gap-1 bg-muted text-muted-foreground",
63
- className
64
- ),
65
- ...alt ? { role: "img", "aria-label": alt } : { "aria-hidden": true },
66
- children: /* @__PURE__ */ jsx(ImageOff, { className: "size-6", "aria-hidden": "true" })
67
- }
68
- );
69
- }
70
- return /* @__PURE__ */ jsxs(Fragment, { children: [
71
- showSkeleton && !loaded ? /* @__PURE__ */ jsx(Skeleton, { className: "absolute inset-0 size-full rounded-none" }) : null,
72
- /* @__PURE__ */ jsx(
73
- "img",
74
- {
75
- ref: imgRef,
76
- src,
77
- alt,
78
- loading,
79
- onError: () => setFailed(true),
80
- onLoad: () => setLoaded(true),
81
- className
82
- }
83
- )
84
- ] });
85
- }
86
45
  function GalleryDownloadButton({
87
46
  image,
88
47
  className,
@@ -182,15 +141,7 @@ function GallerySingle() {
182
141
  const image = images[0];
183
142
  if (!image) return null;
184
143
  return /* @__PURE__ */ jsxs(GalleryTilt, { children: [
185
- /* @__PURE__ */ jsx(AspectRatio, { ratio: aspectRatio, className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
186
- GalleryImg,
187
- {
188
- src: image.src,
189
- alt: image.alt,
190
- loading: "lazy",
191
- className: "size-full object-cover"
192
- }
193
- ) }),
144
+ /* @__PURE__ */ jsx(AspectRatio, { ratio: aspectRatio, className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(Image, { src: image.src, alt: image.alt, fit: "cover", loading: "lazy", showSkeleton: true }) }),
194
145
  /* @__PURE__ */ jsxs("div", { className: "absolute end-2 top-2 z-10 flex items-center gap-1", children: [
195
146
  hideDownload ? null : /* @__PURE__ */ jsx(GalleryDownloadButton, { image, className: HOVER_TOOLBAR }),
196
147
  /* @__PURE__ */ jsx(
@@ -221,12 +172,13 @@ function GalleryTile({ image, index }) {
221
172
  "aria-label": label,
222
173
  className: "block w-full rounded-lg focus-ring",
223
174
  children: /* @__PURE__ */ jsx(AspectRatio, { ratio: aspectRatio, className: "overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
224
- GalleryImg,
175
+ Image,
225
176
  {
226
177
  src: image.thumbnailSrc ?? image.src,
227
178
  alt: "",
179
+ fit: "cover",
228
180
  loading: "lazy",
229
- className: "size-full object-cover"
181
+ showSkeleton: true
230
182
  }
231
183
  ) })
232
184
  }
@@ -344,12 +296,13 @@ function GalleryCarousel() {
344
296
  "aria-label": `${index + 1} of ${images.length}`,
345
297
  children: /* @__PURE__ */ jsxs("div", { className: "group/slide relative flex items-center justify-center px-4", children: [
346
298
  /* @__PURE__ */ jsx(
347
- GalleryImg,
299
+ Image,
348
300
  {
349
301
  src: image.src,
350
302
  alt: image.alt,
303
+ fit: "contain",
351
304
  showSkeleton: false,
352
- className: "max-h-[80vh] w-auto max-w-full object-contain"
305
+ className: "max-h-[80vh] w-auto max-w-full"
353
306
  }
354
307
  ),
355
308
  hideDownload ? null : /* @__PURE__ */ jsx(
@@ -475,4 +428,4 @@ var Gallery = forwardRef(function Gallery2({
475
428
  export {
476
429
  Gallery
477
430
  };
478
- //# sourceMappingURL=chunk-3XNPMDZY.js.map
431
+ //# sourceMappingURL=chunk-TBCMCF5O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/gallery.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Gallery — an OUTPUT-oriented image gallery for AI results / chat surfaces.\n *\n * A `Card` that renders a single image, or a thumbnail grid that collapses the\n * overflow into a \"+N more\" tile, and opens a unified lightbox `Dialog` with a\n * `Carousel` and a right-side metadata panel that tracks the active slide.\n *\n * The image surfaces in this package differ by ROLE, and all of them compose\n * the ui `Image` primitive (ADR 0041) rather than drawing their own `<img>`:\n * - `attachments.tsx` — composer INPUT (removable, file-typed thumbnails).\n * - `asset-preview.tsx` — a SINGLE asset in the context rail (type-switched).\n * - `generated-image.tsx` — an AI-SDK generated (base64) image.\n * Gallery is the multi-image lightbox: expand / download / carousel / metadata.\n *\n * The data model is framework-agnostic (plain `{ src, alt, … }`) — it does NOT\n * couple to the AI-SDK message model. Map a `UIMessage` part onto `GalleryImage`\n * in the app or a thin adapter.\n */\n\nimport {\n AspectRatio,\n Button,\n Card,\n Carousel,\n CarouselContent,\n CarouselItem,\n CarouselNext,\n CarouselPrevious,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogTitle,\n Image,\n Skeleton,\n downloadUrl,\n type CarouselApi,\n} from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { useLocale } from \"@elabs-ai/components-ui\";\nimport { useReducedMotion } from \"@elabs-ai/components-tokens\";\nimport { Download, ImageOff, Maximize2 } from \"lucide-react\";\nimport {\n createContext,\n forwardRef,\n use,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\n\n/** One labelled metadata row rendered in the detail panel `<dl>`. */\nexport interface GalleryMetaItem {\n /** Field label (rendered muted, in the `<dt>`). */\n label: string;\n /** Field value — a string in the common case; `ReactNode` allows a link/badge. */\n value: ReactNode;\n}\n\n/** A single gallery image plus its optional metadata. */\nexport interface GalleryImage {\n /** Full-resolution source shown in the lightbox and standalone tile. */\n src: string;\n /** Required alt text (a11y). Pass `\"\"` to mark the image decorative. */\n alt: string;\n /** Cheaper thumbnail for the grid; falls back to `src`. */\n thumbnailSrc?: string;\n /** Headline shown in the panel; also feeds the dialog's accessible name. */\n title?: string;\n /** Long-form copy shown under the title in the detail panel. */\n description?: string;\n /** Structured key/value rows rendered as a `<dl>` in the detail panel. */\n meta?: GalleryMetaItem[];\n /** Overrides `src` for the download (e.g. a signed full-res URL). */\n downloadUrl?: string;\n /** Suggested download filename. */\n downloadName?: string;\n}\n\nexport interface GalleryProps extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /** Images to display. `0` → empty state, `1` → standalone, `≥2` → grid. */\n images: GalleryImage[];\n /** Thumbnails to show before collapsing the rest into a \"+N more\" tile. @default 5 */\n maxVisible?: number;\n /** Aspect ratio for grid tiles and the standalone image. @default 1 (square) */\n aspectRatio?: number;\n /** Hide every download affordance (e.g. rights-restricted assets). @default false */\n hideDownload?: boolean;\n /**\n * The set is still arriving (loading-states.md `loading` signal). Renders\n * skeleton tiles in the grid footprint instead of the empty state, and fills\n * the gap up to `expectedCount` (or `maxVisible`) while images stream in.\n * @default false\n */\n loading?: boolean;\n /**\n * Total images expected once the set finishes arriving. When\n * `images.length < expectedCount`, the arrived tiles render alongside\n * skeleton tiles for the missing ones, so the grid fills in place (no reflow).\n */\n expectedCount?: number;\n /**\n * Replace the default metadata body in the detail panel. Receives the\n * currently-active image, so it stays in sync with the carousel. The panel\n * chrome + title heading remain.\n */\n renderMeta?: (image: GalleryImage, index: number) => ReactNode;\n}\n\ninterface GalleryContextValue {\n images: GalleryImage[];\n maxVisible: number;\n aspectRatio: number;\n hideDownload: boolean;\n /** The set is still arriving — render skeleton tiles to fill the gap. */\n incoming: boolean;\n /** How many skeleton tiles to append after the arrived images. */\n skeletonCount: number;\n renderMeta?: GalleryProps[\"renderMeta\"];\n state: { open: boolean; activeIndex: number };\n actions: {\n open: (index: number) => void;\n close: () => void;\n setActiveIndex: (index: number) => void;\n setOpen: (open: boolean) => void;\n };\n}\n\nconst GalleryContext = createContext<GalleryContextValue | null>(null);\n\nfunction useGallery(): GalleryContextValue {\n const ctx = use(GalleryContext);\n if (!ctx) throw new Error(\"useGallery must be used within <Gallery />\");\n return ctx;\n}\n\nfunction triggerDownload(image: GalleryImage): void {\n void downloadUrl(image.downloadUrl ?? image.src, image.downloadName);\n}\n\n/** Round, frosted icon button that downloads `image`. */\nfunction GalleryDownloadButton({\n image,\n className,\n tabIndex,\n}: {\n image: GalleryImage;\n className?: string;\n /** Off-screen carousel slides pass -1 so they're not phantom tab stops. */\n tabIndex?: number;\n}) {\n const { t } = useLocale();\n\n return (\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"outline\"\n tabIndex={tabIndex}\n aria-label={t(\"ai.gallery.downloadImage\")}\n className={cn(\"rounded-full bg-background/80 backdrop-blur-sm\", className)}\n onClick={(event) => {\n event.stopPropagation();\n triggerDownload(image);\n }}\n >\n <Download aria-hidden=\"true\" />\n </Button>\n );\n}\n\nconst HOVER_TOOLBAR =\n \"opacity-0 transition-opacity duration-fast ease-standard group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none\";\n\n// \"Comes closer\" hover for a tile: a gentle scale-up + drop shadow, plus a 3D\n// tilt that tracks the cursor. Movement (tilt + scale) is gated by\n// useReducedMotion — the shadow still responds (\"reduced != none\", like Card).\n// The wrapper carries `group relative` (toolbar reveal + absolute children) and\n// NO overflow, so the shadow and the inner control's focus ring aren't clipped.\nconst TILT_PERSPECTIVE = 800; // px — larger = subtler tilt\nconst TILT_MAX_DEG = 6;\nconst TILT_SCALE = 1.04;\nconst tiltTransform = (rotateX: number, rotateY: number, scale: number) =>\n `perspective(${TILT_PERSPECTIVE}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale(${scale})`;\nconst TILT_REST = tiltTransform(0, 0, 1);\n\nfunction GalleryTilt({ className, children }: { className?: string; children: ReactNode }) {\n const reduced = useReducedMotion();\n const ref = useRef<HTMLDivElement>(null);\n const rect = useRef<DOMRect | null>(null);\n const raf = useRef<number | null>(null);\n\n useEffect(\n () => () => {\n if (raf.current != null) cancelAnimationFrame(raf.current);\n },\n [],\n );\n\n // Reduced motion: no tilt/scale; the shadow alone signals the lift.\n if (reduced) {\n return (\n <div\n className={cn(\n \"group relative rounded-lg transition-shadow duration-base ease-standard hover:z-10 hover:shadow-xl\",\n className,\n )}\n >\n {children}\n </div>\n );\n }\n\n const write = (transform: string) => {\n if (ref.current) ref.current.style.transform = transform;\n };\n\n return (\n <div\n ref={ref}\n style={{ transform: TILT_REST }}\n onPointerEnter={() => {\n // Cache geometry once per hover (no per-move layout read); grow on enter.\n rect.current = ref.current?.getBoundingClientRect() ?? null;\n write(tiltTransform(0, 0, TILT_SCALE));\n }}\n onPointerMove={(event) => {\n const r = rect.current;\n if (!r) return;\n const px = (event.clientX - r.left) / r.width - 0.5;\n const py = (event.clientY - r.top) / r.height - 0.5;\n if (raf.current != null) cancelAnimationFrame(raf.current);\n raf.current = requestAnimationFrame(() =>\n write(tiltTransform(-py * TILT_MAX_DEG, px * TILT_MAX_DEG, TILT_SCALE)),\n );\n }}\n onPointerLeave={() => {\n if (raf.current != null) cancelAnimationFrame(raf.current);\n write(TILT_REST);\n }}\n className={cn(\n \"group relative rounded-lg transition-[transform,box-shadow] duration-base ease-standard hover:z-10 hover:shadow-2xl\",\n className,\n )}\n >\n {children}\n </div>\n );\n}\n\nfunction GalleryEmpty() {\n const { t } = useLocale();\n return (\n <div className=\"flex min-h-32 flex-col items-center justify-center gap-2 p-6 text-center text-muted-foreground\">\n <ImageOff className=\"size-6\" aria-hidden=\"true\" />\n <p className=\"text-body\">{t(\"ai.gallery.noImages\")}</p>\n </div>\n );\n}\n\n/** The single-image branch — a framed image with an always-on expand button. */\nfunction GallerySingle() {\n const { t } = useLocale();\n const { images, aspectRatio, hideDownload, actions } = useGallery();\n const image = images[0];\n if (!image) return null;\n\n return (\n <GalleryTilt>\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n {/* `showSkeleton` frames the image `size-full` inside the reserved box. */}\n <Image src={image.src} alt={image.alt} fit=\"cover\" loading=\"lazy\" showSkeleton />\n </AspectRatio>\n <div className=\"absolute end-2 top-2 z-10 flex items-center gap-1\">\n {hideDownload ? null : <GalleryDownloadButton image={image} className={HOVER_TOOLBAR} />}\n <Button\n type=\"button\"\n size=\"icon-sm\"\n variant=\"outline\"\n aria-label={t(\"ai.gallery.expandImage\")}\n className=\"rounded-full bg-background/80 backdrop-blur-sm\"\n onClick={() => actions.open(0)}\n >\n <Maximize2 aria-hidden=\"true\" />\n </Button>\n </div>\n </GalleryTilt>\n );\n}\n\n/** One thumbnail in the grid — a button that opens the lightbox at `index`. */\nfunction GalleryTile({ image, index }: { image: GalleryImage; index: number }) {\n const { images, aspectRatio, hideDownload, actions } = useGallery();\n const total = images.length;\n const label = image.title ?? (image.alt || `Image ${index + 1} of ${total}`);\n\n return (\n <GalleryTilt>\n <button\n type=\"button\"\n onClick={() => actions.open(index)}\n aria-label={label}\n className=\"block w-full rounded-lg focus-ring\"\n >\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n {/* Decorative: the button carries the accessible name. */}\n <Image\n src={image.thumbnailSrc ?? image.src}\n alt=\"\"\n fit=\"cover\"\n loading=\"lazy\"\n showSkeleton\n />\n </AspectRatio>\n </button>\n {hideDownload ? null : (\n <GalleryDownloadButton\n image={image}\n className={cn(\"absolute end-2 top-2 z-10\", HOVER_TOOLBAR)}\n />\n )}\n </GalleryTilt>\n );\n}\n\n/** The \"+N more\" tile — same footprint as a thumbnail; opens the carousel. */\nfunction GalleryOverflowTile() {\n const { images, maxVisible, aspectRatio, actions } = useGallery();\n const total = images.length;\n const count = total - maxVisible;\n\n return (\n <GalleryTilt>\n <button\n type=\"button\"\n onClick={() => actions.open(maxVisible)}\n aria-label={`Show all ${total} images`}\n className=\"block w-full rounded-lg border border-border bg-muted focus-ring\"\n >\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n <span className=\"flex size-full items-center justify-center text-subtitle text-muted-foreground transition-colors duration-fast ease-standard group-hover:text-foreground motion-reduce:transition-none\">\n +{count} more\n </span>\n </AspectRatio>\n </button>\n </GalleryTilt>\n );\n}\n\n/** A grid tile placeholder — same footprint as a thumbnail, shown while loading. */\nfunction GallerySkeletonTile() {\n const { aspectRatio } = useGallery();\n return (\n <AspectRatio ratio={aspectRatio} className=\"overflow-hidden rounded-lg\">\n <Skeleton className=\"size-full rounded-none\" />\n </AspectRatio>\n );\n}\n\n/**\n * The all-skeleton grid for the loading-from-empty case (no images yet).\n * Decorative — the loading announcement comes from the persistent live region on\n * the always-mounted `Card` (so it fires reliably; ARIA22).\n */\nfunction GallerySkeletonGrid() {\n const { skeletonCount } = useGallery();\n return (\n <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\" aria-hidden=\"true\">\n {Array.from({ length: Math.max(1, skeletonCount) }).map((_, i) => (\n <GallerySkeletonTile key={`skeleton-${i}`} />\n ))}\n </div>\n );\n}\n\nfunction GalleryGrid() {\n const { images, maxVisible, incoming, skeletonCount } = useGallery();\n const total = images.length;\n\n // While the set is still arriving, render the arrived tiles plus skeleton\n // tiles for the missing ones — no \"+N more\" collapse until the set settles.\n if (incoming) {\n return (\n <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {images.map((image, index) => (\n <GalleryTile key={`${image.src}-${index}`} image={image} index={index} />\n ))}\n {Array.from({ length: skeletonCount }).map((_, i) => (\n <GallerySkeletonTile key={`skeleton-${i}`} />\n ))}\n </div>\n );\n }\n\n const hasOverflow = total > maxVisible;\n const visible = hasOverflow ? images.slice(0, maxVisible) : images;\n\n return (\n <div className=\"grid grid-cols-2 gap-2 sm:grid-cols-3\">\n {visible.map((image, index) => (\n <GalleryTile key={`${image.src}-${index}`} image={image} index={index} />\n ))}\n {hasOverflow ? <GalleryOverflowTile /> : null}\n </div>\n );\n}\n\n/** Default detail-panel body: description + a `<dl>` of `meta` rows. */\nfunction GalleryDetailPanel({\n image,\n index,\n renderMeta,\n}: {\n image: GalleryImage;\n index: number;\n renderMeta?: GalleryProps[\"renderMeta\"];\n}) {\n const { t } = useLocale();\n const custom = renderMeta?.(image, index);\n if (custom != null) {\n return <div className=\"flex flex-col gap-4\">{custom}</div>;\n }\n\n const hasMeta = image.meta != null && image.meta.length > 0;\n const hasBody = Boolean(image.description) || hasMeta;\n\n if (!hasBody) {\n return <p className=\"text-body text-muted-foreground\">{t(\"ai.gallery.noDetails\")}</p>;\n }\n\n return (\n <div className=\"flex flex-col gap-4\">\n {image.description ? (\n <p className=\"text-body text-muted-foreground\">{image.description}</p>\n ) : null}\n {hasMeta ? (\n <dl className=\"flex flex-col gap-3\">\n {image.meta?.map((item, i) => (\n <div key={`${item.label}-${i}`} className=\"flex flex-col gap-0.5\">\n <dt className=\"text-meta text-muted-foreground\">{item.label}</dt>\n <dd className=\"text-body text-foreground\">{item.value}</dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n );\n}\n\n/** The lightbox carousel — one slide per image; syncs `activeIndex` on select. */\nfunction GalleryCarousel() {\n const { t } = useLocale();\n const { images, state, actions, hideDownload } = useGallery();\n const [api, setApi] = useState<CarouselApi>();\n const showArrows = images.length > 1;\n const { setActiveIndex } = actions;\n // Capture the open-at index ONCE (the modal remounts the carousel each open).\n // Tying opts.startIndex to the live activeIndex hands Embla a changed options\n // object on every slide (it deep-compares) → constant reInit → the native\n // slide animation never plays. A stable opts keeps Embla's scroll intact;\n // activeIndex flows one-way (carousel select → panel), never back into opts.\n const [startIndex] = useState(() => state.activeIndex);\n const opts = useMemo(() => ({ startIndex, loop: false }), [startIndex]);\n\n useEffect(() => {\n if (!api) return;\n const onSelect = () => setActiveIndex(api.selectedScrollSnap());\n onSelect();\n api.on(\"select\", onSelect);\n api.on(\"reInit\", onSelect);\n return () => {\n api.off(\"select\", onSelect);\n api.off(\"reInit\", onSelect);\n };\n }, [api, setActiveIndex]);\n\n // The dialog animates in (zoom-in-95); re-measure once laid out so Embla\n // aligns and lands on the opened slide.\n useEffect(() => {\n if (!api) return;\n const raf = requestAnimationFrame(() => api.reInit());\n return () => cancelAnimationFrame(raf);\n }, [api]);\n\n return (\n <Carousel\n setApi={setApi}\n opts={opts}\n className=\"relative w-full\"\n aria-label={t(\"ai.gallery.label\")}\n >\n <CarouselContent>\n {images.map((image, index) => (\n <CarouselItem\n key={`${image.src}-${index}`}\n aria-label={`${index + 1} of ${images.length}`}\n >\n <div className=\"group/slide relative flex items-center justify-center px-4\">\n {/* The slide is not sized to the image — no box to reserve. */}\n <Image\n src={image.src}\n alt={image.alt}\n fit=\"contain\"\n showSkeleton={false}\n className=\"max-h-[80vh] w-auto max-w-full\"\n />\n {hideDownload ? null : (\n <GalleryDownloadButton\n image={image}\n // Off-screen slides stay in the DOM (Embla); keep only the\n // active slide's download button in the tab order.\n tabIndex={index === state.activeIndex ? 0 : -1}\n className=\"absolute end-14 top-3 opacity-0 transition-opacity duration-fast ease-standard group-hover/slide:opacity-100 group-focus-within/slide:opacity-100 motion-reduce:transition-none\"\n />\n )}\n </div>\n </CarouselItem>\n ))}\n </CarouselContent>\n {showArrows ? (\n <>\n <CarouselPrevious className=\"start-4\" />\n <CarouselNext className=\"end-4\" />\n </>\n ) : null}\n </Carousel>\n );\n}\n\nfunction GalleryModal() {\n const { images, state, actions, renderMeta } = useGallery();\n const total = images.length;\n const active = images[state.activeIndex];\n\n const fallbackTitle = active\n ? active.alt || `Image ${state.activeIndex + 1} of ${total}`\n : \"Image preview\";\n\n return (\n <Dialog open={state.open} onOpenChange={actions.setOpen}>\n <DialogContent\n size=\"full\"\n className=\"grid grid-rows-[minmax(0,3fr)_minmax(0,2fr)] gap-0 overflow-hidden p-0 md:grid-cols-[minmax(0,1fr)_22rem] md:grid-rows-1\"\n >\n <div className=\"relative flex min-h-0 items-center justify-center overflow-hidden bg-muted\">\n {active ? <GalleryCarousel /> : null}\n </div>\n <aside className=\"flex min-h-0 flex-col overflow-y-auto border-border bg-card p-6 max-md:border-t md:border-s\">\n {active?.title ? (\n <DialogTitle className=\"pe-8 text-title\">{active.title}</DialogTitle>\n ) : (\n <DialogTitle className=\"sr-only\">{fallbackTitle}</DialogTitle>\n )}\n <DialogDescription className=\"sr-only\">\n {total > 1\n ? `Image ${state.activeIndex + 1} of ${total}. Use the arrow keys to browse.`\n : \"Image preview\"}\n </DialogDescription>\n {active ? (\n <div className=\"mt-4 flex-1\">\n <GalleryDetailPanel\n image={active}\n index={state.activeIndex}\n renderMeta={renderMeta}\n />\n </div>\n ) : null}\n </aside>\n {/* Carousel navigation only changes the title's text, which doesn't\n re-announce. A polite live region orients screen-reader users on each\n slide change. (sr-only is position:absolute, so it adds no grid cell.) */}\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {active && total > 1\n ? `${active.title ?? active.alt} — image ${state.activeIndex + 1} of ${total}`\n : \"\"}\n </span>\n </DialogContent>\n </Dialog>\n );\n}\n\nexport const Gallery = forwardRef<HTMLDivElement, GalleryProps>(function Gallery(\n {\n images,\n maxVisible = 5,\n aspectRatio = 1,\n hideDownload = false,\n loading = false,\n expectedCount,\n renderMeta,\n className,\n ...props\n },\n ref,\n) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n\n const openAt = useCallback((index: number) => {\n setActiveIndex(index);\n setOpen(true);\n }, []);\n const close = useCallback(() => setOpen(false), []);\n\n // The set is \"incoming\" while it's still arriving — either an explicit\n // `loading` flag, or fewer images than the `expectedCount`. Fill the grid up\n // to the target (capped at the visible footprint) with skeleton tiles.\n const target = expectedCount ?? (loading ? maxVisible : undefined);\n const incoming = target !== undefined && images.length < target;\n const skeletonCount = incoming ? Math.max(0, Math.min(target, maxVisible) - images.length) : 0;\n\n // Loading announcement: the live region must exist BEFORE its text appears\n // (ARIA22), so the `sr-only` status span below is always mounted and its label\n // is set one tick later via this effect — reliable on NVDA + Chrome.\n const loadingFromEmpty = incoming && images.length === 0;\n const [liveMsg, setLiveMsg] = useState(\"\");\n useEffect(() => {\n setLiveMsg(loadingFromEmpty ? \"Loading images\" : \"\");\n }, [loadingFromEmpty]);\n\n const value = useMemo<GalleryContextValue>(\n () => ({\n images,\n maxVisible,\n aspectRatio,\n hideDownload,\n incoming,\n skeletonCount,\n renderMeta,\n state: { open, activeIndex },\n actions: { open: openAt, close, setActiveIndex, setOpen },\n }),\n [\n images,\n maxVisible,\n aspectRatio,\n hideDownload,\n incoming,\n skeletonCount,\n renderMeta,\n open,\n activeIndex,\n openAt,\n close,\n ],\n );\n\n let body: ReactNode;\n if (images.length === 0) {\n body = incoming ? <GallerySkeletonGrid /> : <GalleryEmpty />;\n } else if (incoming) {\n body = <GalleryGrid />;\n } else if (images.length === 1) {\n body = <GallerySingle />;\n } else {\n body = <GalleryGrid />;\n }\n\n return (\n <GalleryContext.Provider value={value}>\n <Card ref={ref} className={cn(\"p-2\", className)} {...props}>\n <span className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {liveMsg}\n </span>\n {body}\n </Card>\n {images.length > 0 ? <GalleryModal /> : null}\n </GalleryContext.Provider>\n );\n});\n"],"mappings":"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","names":["Gallery"]}