@mieweb/ui 0.10.0-dev.29 → 0.10.0-dev.30

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 (118) hide show
  1. package/dist/brands/index.cjs +7 -7
  2. package/dist/brands/index.js +2 -2
  3. package/dist/{chunk-JYZW2PZS.cjs → chunk-254QK722.cjs} +7 -7
  4. package/dist/chunk-254QK722.cjs.map +1 -0
  5. package/dist/{chunk-FBLVPM2K.js → chunk-2JICT4A2.js} +26 -10
  6. package/dist/chunk-2JICT4A2.js.map +1 -0
  7. package/dist/{chunk-S45EERW3.cjs → chunk-35Y4ILCU.cjs} +4 -4
  8. package/dist/{chunk-S45EERW3.cjs.map → chunk-35Y4ILCU.cjs.map} +1 -1
  9. package/dist/{chunk-2BARVD4C.js → chunk-3RXZWHCU.js} +3 -3
  10. package/dist/{chunk-2BARVD4C.js.map → chunk-3RXZWHCU.js.map} +1 -1
  11. package/dist/{chunk-MWVW4APN.cjs → chunk-7UTBDFFL.cjs} +4 -4
  12. package/dist/{chunk-MWVW4APN.cjs.map → chunk-7UTBDFFL.cjs.map} +1 -1
  13. package/dist/{chunk-FJ7YPVKV.js → chunk-7X6R3JIA.js} +3 -3
  14. package/dist/{chunk-FJ7YPVKV.js.map → chunk-7X6R3JIA.js.map} +1 -1
  15. package/dist/{chunk-SYRLCTDV.js → chunk-7ZV67VZE.js} +3 -3
  16. package/dist/{chunk-SYRLCTDV.js.map → chunk-7ZV67VZE.js.map} +1 -1
  17. package/dist/chunk-BNH4SXJB.js +874 -0
  18. package/dist/chunk-BNH4SXJB.js.map +1 -0
  19. package/dist/{chunk-N2CJJHHB.cjs → chunk-BUT6MF5K.cjs} +27 -11
  20. package/dist/chunk-BUT6MF5K.cjs.map +1 -0
  21. package/dist/{chunk-6VFDHJSO.cjs → chunk-COM7RNZU.cjs} +2 -2
  22. package/dist/{chunk-6VFDHJSO.cjs.map → chunk-COM7RNZU.cjs.map} +1 -1
  23. package/dist/{chunk-SQHMGT4W.js → chunk-GW4RFJZ5.js} +7 -7
  24. package/dist/chunk-GW4RFJZ5.js.map +1 -0
  25. package/dist/{chunk-JID7AZTO.cjs → chunk-H3ROMJYI.cjs} +4 -4
  26. package/dist/{chunk-JID7AZTO.cjs.map → chunk-H3ROMJYI.cjs.map} +1 -1
  27. package/dist/{chunk-DZFGTWYF.js → chunk-JBIGSP2T.js} +3 -3
  28. package/dist/{chunk-DZFGTWYF.js.map → chunk-JBIGSP2T.js.map} +1 -1
  29. package/dist/{chunk-33DEVA3P.js → chunk-JHG3ELS2.js} +2 -2
  30. package/dist/{chunk-33DEVA3P.js.map → chunk-JHG3ELS2.js.map} +1 -1
  31. package/dist/{chunk-WWJIW2IH.cjs → chunk-JSXIB6EJ.cjs} +4 -4
  32. package/dist/{chunk-WWJIW2IH.cjs.map → chunk-JSXIB6EJ.cjs.map} +1 -1
  33. package/dist/{chunk-H5YXAMVH.js → chunk-MLAWXPRY.js} +44 -2
  34. package/dist/chunk-MLAWXPRY.js.map +1 -0
  35. package/dist/{chunk-AAYXCZMV.cjs → chunk-NKV76R65.cjs} +4 -4
  36. package/dist/{chunk-AAYXCZMV.cjs.map → chunk-NKV76R65.cjs.map} +1 -1
  37. package/dist/{chunk-PYJDVWXM.js → chunk-NUB5HRKS.js} +3 -3
  38. package/dist/{chunk-PYJDVWXM.js.map → chunk-NUB5HRKS.js.map} +1 -1
  39. package/dist/{chunk-LL6IWKI3.js → chunk-OW3SQJ7P.js} +3 -3
  40. package/dist/{chunk-LL6IWKI3.js.map → chunk-OW3SQJ7P.js.map} +1 -1
  41. package/dist/{chunk-OUZDYJ33.cjs → chunk-QURJVPRH.cjs} +44 -2
  42. package/dist/chunk-QURJVPRH.cjs.map +1 -0
  43. package/dist/{chunk-TZJQB4P2.js → chunk-RM2HPQIC.js} +72 -14
  44. package/dist/chunk-RM2HPQIC.js.map +1 -0
  45. package/dist/{chunk-P4VGVJVI.js → chunk-SD7ONW6S.js} +3 -3
  46. package/dist/{chunk-P4VGVJVI.js.map → chunk-SD7ONW6S.js.map} +1 -1
  47. package/dist/chunk-WXFGHDTV.cjs +896 -0
  48. package/dist/chunk-WXFGHDTV.cjs.map +1 -0
  49. package/dist/chunk-XDU65GLW.js +21 -0
  50. package/dist/chunk-XDU65GLW.js.map +1 -0
  51. package/dist/{chunk-MYHV2ND3.cjs → chunk-XRVMYT5G.cjs} +72 -13
  52. package/dist/chunk-XRVMYT5G.cjs.map +1 -0
  53. package/dist/{chunk-I4LXSHKO.cjs → chunk-YRHBSSQ3.cjs} +4 -4
  54. package/dist/{chunk-I4LXSHKO.cjs.map → chunk-YRHBSSQ3.cjs.map} +1 -1
  55. package/dist/chunk-YW2AKBLS.cjs +23 -0
  56. package/dist/chunk-YW2AKBLS.cjs.map +1 -0
  57. package/dist/{chunk-B2X732V3.cjs → chunk-ZTMYKAVU.cjs} +4 -4
  58. package/dist/{chunk-B2X732V3.cjs.map → chunk-ZTMYKAVU.cjs.map} +1 -1
  59. package/dist/components/Collapsible/index.cjs +5 -5
  60. package/dist/components/Collapsible/index.js +2 -2
  61. package/dist/components/MediaEditor/index.cjs +8 -8
  62. package/dist/components/MediaEditor/index.js +3 -3
  63. package/dist/components/MediaFeed/index.cjs +26 -0
  64. package/dist/components/MediaFeed/index.cjs.map +1 -0
  65. package/dist/components/MediaFeed/index.d.cts +104 -0
  66. package/dist/components/MediaFeed/index.d.ts +104 -0
  67. package/dist/components/MediaFeed/index.js +17 -0
  68. package/dist/components/MediaFeed/index.js.map +1 -0
  69. package/dist/components/MediaPlayer/index.cjs +5 -5
  70. package/dist/components/MediaPlayer/index.d.cts +18 -0
  71. package/dist/components/MediaPlayer/index.d.ts +18 -0
  72. package/dist/components/MediaPlayer/index.js +2 -2
  73. package/dist/components/RadialExplorer/index.cjs +3 -3
  74. package/dist/components/RadialExplorer/index.js +2 -2
  75. package/dist/components/SliderCalculator/index.cjs +4 -4
  76. package/dist/components/SliderCalculator/index.js +2 -2
  77. package/dist/components/SuperChat/index.cjs +654 -292
  78. package/dist/components/SuperChat/index.cjs.map +1 -1
  79. package/dist/components/SuperChat/index.d.cts +35 -6
  80. package/dist/components/SuperChat/index.d.ts +35 -6
  81. package/dist/components/SuperChat/index.js +648 -286
  82. package/dist/components/SuperChat/index.js.map +1 -1
  83. package/dist/components/SuperChat/plugins/index.d.cts +2 -2
  84. package/dist/components/SuperChat/plugins/index.d.ts +2 -2
  85. package/dist/components/ThemeProvider/index.cjs +9 -9
  86. package/dist/components/ThemeProvider/index.js +3 -3
  87. package/dist/components/Tooltip/index.cjs +3 -3
  88. package/dist/components/Tooltip/index.js +2 -2
  89. package/dist/esheet.cjs +6 -6
  90. package/dist/esheet.js +3 -3
  91. package/dist/globe.cjs +2 -2
  92. package/dist/globe.js +1 -1
  93. package/dist/hooks/index.cjs +4 -4
  94. package/dist/hooks/index.js +2 -2
  95. package/dist/index.cjs +164 -214
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/index.d.cts +120 -1
  98. package/dist/index.d.ts +120 -1
  99. package/dist/index.js +25 -82
  100. package/dist/index.js.map +1 -1
  101. package/dist/styles.css +1 -1
  102. package/dist/tailwind-preset.cjs +4 -4
  103. package/dist/tailwind-preset.js +1 -1
  104. package/dist/{types-DOxdTwmq.d.cts → types-Cx1FHLq2.d.cts} +130 -1
  105. package/dist/{types-DOxdTwmq.d.ts → types-Cx1FHLq2.d.ts} +130 -1
  106. package/package.json +1 -1
  107. package/dist/chunk-6HFFWEM3.cjs +0 -22
  108. package/dist/chunk-6HFFWEM3.cjs.map +0 -1
  109. package/dist/chunk-FBLVPM2K.js.map +0 -1
  110. package/dist/chunk-H5YXAMVH.js.map +0 -1
  111. package/dist/chunk-HB7C7NB5.js +0 -20
  112. package/dist/chunk-HB7C7NB5.js.map +0 -1
  113. package/dist/chunk-JYZW2PZS.cjs.map +0 -1
  114. package/dist/chunk-MYHV2ND3.cjs.map +0 -1
  115. package/dist/chunk-N2CJJHHB.cjs.map +0 -1
  116. package/dist/chunk-OUZDYJ33.cjs.map +0 -1
  117. package/dist/chunk-SQHMGT4W.js.map +0 -1
  118. package/dist/chunk-TZJQB4P2.js.map +0 -1
@@ -1,295 +1,110 @@
1
1
  import { TextRenderContext } from '../../chunk-I6CY5C6A.js';
2
2
  export { TextRenderContext, useTextRenderContext } from '../../chunk-I6CY5C6A.js';
3
- import { ChatBubble, MCPToolCallDisplay, AITypingIndicator, SparklesIcon, CloseIcon, JumpToBottomButton } from '../../chunk-TZJQB4P2.js';
3
+ import { ChatBubble, MCPToolCallDisplay, AITypingIndicator, SparklesIcon, ButtonGroup, CloseIcon, JumpToBottomButton } from '../../chunk-RM2HPQIC.js';
4
4
  import { notifyComposerMigrationOnce, ChatComposer } from '../../chunk-CRS2CQWM.js';
5
+ import { MediaFeed } from '../../chunk-BNH4SXJB.js';
6
+ import '../../chunk-RISZWWEQ.js';
7
+ import { MediaPlayer } from '../../chunk-2JICT4A2.js';
5
8
  import { Dropdown, DropdownSubmenu, DropdownItem } from '../../chunk-E2PBPQGS.js';
6
9
  import '../../chunk-VVDE4CHU.js';
7
10
  import { Avatar } from '../../chunk-HZ4MBUC3.js';
8
11
  import '../../chunk-IQ4KQJAO.js';
9
12
  import { useStickToBottom, useStreamEndedBelowFold } from '../../chunk-BM35CACB.js';
13
+ import '../../chunk-GW4RFJZ5.js';
14
+ import '../../chunk-ENMXHZNE.js';
10
15
  import '../../chunk-6NZXWKUE.js';
11
16
  import '../../chunk-3IMM2PM4.js';
17
+ import '../../chunk-4SMSH4OY.js';
12
18
  import '../../chunk-T4ME7QCT.js';
13
19
  import '../../chunk-62ZOA6PX.js';
14
20
  import '../../chunk-WSNMQQ4P.js';
21
+ import '../../chunk-VSQF22GL.js';
15
22
  import '../../chunk-BKSPDTX4.js';
16
- import { usePrefersReducedMotion } from '../../chunk-HB7C7NB5.js';
23
+ import { usePrefersReducedMotion } from '../../chunk-XDU65GLW.js';
24
+ import { Button } from '../../chunk-7YO7DNUV.js';
17
25
  import { Badge } from '../../chunk-BMSG4NBH.js';
26
+ import '../../chunk-GVM6DTR5.js';
18
27
  import { AnimatedPresence, Animated } from '../../chunk-F4N67NAF.js';
19
28
  import { cn } from '../../chunk-TQONQSJO.js';
20
29
  import '../../chunk-PX6F3LHL.js';
21
30
  import * as React3 from 'react';
31
+ import { createElement } from 'react';
32
+ import { Ellipsis, Check, Clipboard, Pencil, Play, ArrowLeft } from 'lucide-react';
33
+ import { cva } from 'class-variance-authority';
34
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
22
35
  import Markdown, { defaultUrlTransform } from 'react-markdown';
23
36
  import remarkGfm from 'remark-gfm';
24
37
  import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
25
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
26
- import { cva } from 'class-variance-authority';
27
- import { Ellipsis, Check, Clipboard, Pencil } from 'lucide-react';
28
38
  import { useVirtualizer } from '@tanstack/react-virtual';
29
39
 
30
- function remarkPreserveLineBreaks() {
31
- const splitNode = (node) => {
32
- if (!node.children) return;
33
- const next = [];
34
- for (const child of node.children) {
35
- if (child.type === "text" && child.value && child.value.includes("\n")) {
36
- const segments = child.value.split("\n");
37
- segments.forEach((segment, i) => {
38
- if (i > 0) next.push({ type: "break" });
39
- if (segment) next.push({ type: "text", value: segment });
40
- });
41
- } else {
42
- splitNode(child);
43
- next.push(child);
44
- }
45
- }
46
- node.children = next;
47
- };
48
- return (tree) => splitNode(tree);
49
- }
50
- function baseSanitizeSchema() {
51
- const schema = JSON.parse(JSON.stringify(defaultSchema));
52
- const attributes = schema.attributes ?? {};
53
- const allowClass = (tag) => {
54
- const existing = attributes[tag] ?? [];
55
- const withoutClass = existing.filter(
56
- (a) => !(Array.isArray(a) && a[0] === "className")
57
- );
58
- attributes[tag] = [...withoutClass, "className"];
59
- };
60
- allowClass("code");
61
- allowClass("pre");
62
- allowClass("span");
63
- allowClass("div");
64
- schema.attributes = attributes;
65
- return schema;
66
- }
67
- function mergeSanitizeSchema(base, extra) {
68
- if (!extra) return base;
69
- const out = { ...base };
70
- if (Array.isArray(extra.tagNames)) {
71
- const a = base.tagNames ?? [];
72
- out.tagNames = Array.from(/* @__PURE__ */ new Set([...a, ...extra.tagNames]));
73
- }
74
- if (extra.attributes && typeof extra.attributes === "object") {
75
- const baseAttrs = {
76
- ...base.attributes ?? {}
77
- };
78
- for (const [tag, attrs] of Object.entries(
79
- extra.attributes
80
- )) {
81
- baseAttrs[tag] = [...baseAttrs[tag] ?? [], ...attrs];
82
- }
83
- out.attributes = baseAttrs;
84
- }
85
- if (extra.protocols && typeof extra.protocols === "object") {
86
- const baseProtocols = {
87
- ...base.protocols ?? {}
88
- };
89
- for (const [attr, protocols] of Object.entries(
90
- extra.protocols
91
- )) {
92
- baseProtocols[attr] = Array.from(
93
- /* @__PURE__ */ new Set([...baseProtocols[attr] ?? [], ...protocols])
94
- );
95
- }
96
- out.protocols = baseProtocols;
97
- }
98
- for (const [k, v] of Object.entries(extra)) {
99
- if (k === "tagNames" || k === "attributes" || k === "protocols") continue;
100
- out[k] = v;
101
- }
102
- return out;
103
- }
104
- function baseComponents() {
105
- return {
106
- a: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
107
- "a",
108
- {
109
- ...props,
110
- className: cn(
111
- "text-primary-800 hover:text-primary-900 dark:text-primary-300 underline underline-offset-2",
112
- props.className
113
- ),
114
- target: props.target ?? "_blank",
115
- rel: props.rel ?? "noopener noreferrer",
116
- children
117
- }
118
- ),
119
- pre: ({ node: _node, ...props }) => /* @__PURE__ */ jsx(
120
- "pre",
121
- {
122
- ...props,
123
- tabIndex: 0,
124
- className: cn(
125
- "overflow-x-auto rounded-lg bg-neutral-900 p-3 text-sm text-neutral-100 dark:bg-neutral-950",
126
- props.className
127
- )
128
- }
129
- ),
130
- code: ({ node: _node, ...props }) => {
131
- const isBlock = typeof props.className === "string" && props.className.includes("language-");
132
- return /* @__PURE__ */ jsx(
133
- "code",
134
- {
135
- ...props,
136
- className: cn(
137
- !isBlock && "rounded bg-neutral-200 px-1 py-0.5 text-[0.85em] text-neutral-800 dark:bg-neutral-700 dark:text-neutral-100",
138
- props.className
139
- )
140
- }
141
- );
142
- },
143
- input: ({ node: _node, ...props }) => {
144
- if (props.type === "checkbox") {
145
- return /* @__PURE__ */ jsx(
146
- "input",
40
+ function MessageMedia({
41
+ attachment,
42
+ onOpen,
43
+ openLabel,
44
+ playLabel,
45
+ errorLabel,
46
+ retryLabel,
47
+ launchId
48
+ }) {
49
+ const playable = attachment.kind !== "image";
50
+ const actionLabel = playable ? playLabel : openLabel;
51
+ return /* @__PURE__ */ jsxs(
52
+ "div",
53
+ {
54
+ "data-slot": "superchat-message-media",
55
+ className: "mt-3 flex w-72 max-w-full flex-col gap-2",
56
+ children: [
57
+ attachment.title && /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: attachment.title }),
58
+ attachment.kind === "image" && attachment.src ? /* @__PURE__ */ jsx(
59
+ "img",
147
60
  {
148
- ...props,
149
- "aria-label": props["aria-label"] ?? (props["aria-labelledby"] ? void 0 : "Task list item")
61
+ src: attachment.src,
62
+ alt: attachment.alt ?? attachment.title ?? "",
63
+ loading: "lazy",
64
+ className: "max-h-72 w-full rounded-lg object-contain"
150
65
  }
151
- );
152
- }
153
- return /* @__PURE__ */ jsx("input", { ...props });
154
- },
155
- table: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("div", { className: "my-2 overflow-x-auto", children: /* @__PURE__ */ jsx(
156
- "table",
157
- {
158
- ...props,
159
- className: cn(
160
- "w-full border-collapse text-sm [&_td]:border [&_td]:border-neutral-200 [&_td]:px-2 [&_td]:py-1 dark:[&_td]:border-neutral-700 [&_th]:border [&_th]:border-neutral-200 [&_th]:px-2 [&_th]:py-1 [&_th]:text-start dark:[&_th]:border-neutral-700",
161
- props.className
162
- )
163
- }
164
- ) }),
165
- // Tailwind's preflight resets list markers/padding; restore them explicitly
166
- // so bullets/numbers show without depending on the typography plugin.
167
- ul: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ul", { ...props, className: cn("list-disc ps-5", props.className) }),
168
- ol: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ol", { ...props, className: cn("list-decimal ps-5", props.className) }),
169
- li: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("li", { ...props, className: cn("ps-1", props.className) }),
170
- // Preflight also flattens headings to body size/weight; restore a sensible
171
- // hierarchy without depending on the typography plugin.
172
- h1: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
173
- "h1",
174
- {
175
- ...props,
176
- className: cn("mt-3 mb-1 text-xl font-semibold", props.className),
177
- children
178
- }
179
- ),
180
- h2: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
181
- "h2",
182
- {
183
- ...props,
184
- className: cn("mt-3 mb-1 text-lg font-semibold", props.className),
185
- children
186
- }
187
- ),
188
- h3: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
189
- "h3",
190
- {
191
- ...props,
192
- className: cn("mt-2 mb-1 text-base font-semibold", props.className),
193
- children
194
- }
195
- ),
196
- h4: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
197
- "h4",
198
- {
199
- ...props,
200
- className: cn("mt-2 mb-1 text-sm font-semibold", props.className),
201
- children
202
- }
203
- ),
204
- h5: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
205
- "h5",
206
- {
207
- ...props,
208
- className: cn("mt-2 mb-1 text-sm font-semibold", props.className),
209
- children
210
- }
211
- ),
212
- h6: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
213
- "h6",
214
- {
215
- ...props,
216
- className: cn(
217
- "mt-2 mb-1 text-xs font-semibold tracking-wide uppercase",
218
- props.className
219
- ),
220
- children
221
- }
222
- ),
223
- hr: ({ node: _node, ...props }) => /* @__PURE__ */ jsx(
224
- "hr",
225
- {
226
- ...props,
227
- className: cn(
228
- "my-2 border-neutral-300 dark:border-neutral-600",
229
- props.className
230
- )
231
- }
232
- )
233
- };
234
- }
235
- function createMarkdownRenderer(options = {}) {
236
- const { plugins = [], trusted = false } = options;
237
- const remarkPlugins = [
238
- remarkGfm,
239
- remarkPreserveLineBreaks,
240
- ...plugins.flatMap((p) => p.remarkPlugins ?? [])
241
- ];
242
- const pluginRehype = plugins.flatMap(
243
- (p) => p.rehypePlugins ?? []
244
- );
245
- const sanitizeSchema = plugins.reduce(
246
- (acc, p) => mergeSanitizeSchema(acc, p.sanitizeSchema),
247
- baseSanitizeSchema()
248
- );
249
- const rehypePlugins = trusted ? pluginRehype : [...pluginRehype, [rehypeSanitize, sanitizeSchema]];
250
- const components = plugins.reduce(
251
- (acc, p) => ({ ...acc, ...p.components }),
252
- baseComponents()
253
- );
254
- const MarkdownRenderer = (text, ctx) => /* @__PURE__ */ jsx(
255
- MarkdownContent,
256
- {
257
- text,
258
- messageId: ctx.messageId,
259
- streaming: ctx.streaming,
260
- remarkPlugins,
261
- rehypePlugins,
262
- components
263
- },
264
- ctx.messageId
66
+ ) : (attachment.kind === "video" || attachment.kind === "audio") && attachment.src ? (
67
+ // An opaque themed surface: the player's semi-transparent states
68
+ // (error, loading) must never compose onto a colored chat bubble.
69
+ /* @__PURE__ */ jsx("div", { className: "bg-background overflow-hidden rounded-lg", children: /* @__PURE__ */ jsx(
70
+ MediaPlayer,
71
+ {
72
+ src: attachment.src,
73
+ kind: attachment.kind,
74
+ poster: attachment.poster,
75
+ "aria-label": attachment.alt ?? attachment.title ?? openLabel,
76
+ controls: !onOpen,
77
+ preload: "metadata",
78
+ labels: { error: errorLabel, retry: retryLabel },
79
+ className: "max-h-72"
80
+ }
81
+ ) })
82
+ ) : attachment.poster ? /* @__PURE__ */ jsx(
83
+ "img",
84
+ {
85
+ src: attachment.poster,
86
+ alt: attachment.alt ?? attachment.title ?? "",
87
+ loading: "lazy",
88
+ className: "max-h-72 w-full rounded-lg object-contain"
89
+ }
90
+ ) : null,
91
+ attachment.caption && /* @__PURE__ */ jsx("p", { className: "text-sm", children: attachment.caption }),
92
+ onOpen && /* @__PURE__ */ jsx(ButtonGroup, { children: /* @__PURE__ */ jsx(
93
+ Button,
94
+ {
95
+ "data-media-launch-id": launchId,
96
+ variant: "secondary",
97
+ size: "sm",
98
+ leftIcon: playable ? /* @__PURE__ */ jsx(Play, { className: "size-4", "aria-hidden": "true" }) : void 0,
99
+ "aria-label": playable && attachment.title ? `${playLabel} ${attachment.title}` : actionLabel,
100
+ onClick: () => onOpen(playable),
101
+ children: actionLabel
102
+ }
103
+ ) })
104
+ ]
105
+ }
265
106
  );
266
- return MarkdownRenderer;
267
107
  }
268
- var transformUrl = (url, key, node) => {
269
- if (key === "src" && node.tagName === "img" && (/^data:image\//i.test(url) || /^blob:/i.test(url))) {
270
- return url;
271
- }
272
- return defaultUrlTransform(url);
273
- };
274
- var MarkdownContent = React3.memo(function MarkdownContent2({
275
- text,
276
- messageId,
277
- streaming,
278
- remarkPlugins,
279
- rehypePlugins,
280
- components
281
- }) {
282
- return /* @__PURE__ */ jsx(TextRenderContext.Provider, { value: { messageId, streaming }, children: /* @__PURE__ */ jsx(
283
- Markdown,
284
- {
285
- remarkPlugins,
286
- rehypePlugins,
287
- urlTransform: transformUrl,
288
- components,
289
- children: text
290
- }
291
- ) });
292
- });
293
108
  function formatTime(time) {
294
109
  return new Date(time).toLocaleTimeString(void 0, {
295
110
  hour: "numeric",
@@ -652,10 +467,15 @@ var MessageRow = React3.memo(function MessageRow2({
652
467
  onReferenceClick,
653
468
  editable,
654
469
  onMessageEdited,
655
- defaultCopyFormat = "rich"
470
+ defaultCopyFormat = "rich",
471
+ onOpenMedia,
472
+ openMediaLabel = "Open in media feed",
473
+ playMediaLabel = "Play",
474
+ mediaErrorLabel,
475
+ mediaRetryLabel
656
476
  }) {
657
477
  const streaming = message.status === "streaming";
658
- const hasBody = !!message.text || (message.content?.length ?? 0) > 0;
478
+ const hasBody = !!message.text || (message.content?.length ?? 0) > 0 || (message.media?.length ?? 0) > 0;
659
479
  const [isEditing, setIsEditing] = React3.useState(false);
660
480
  const [draft, setDraft] = React3.useState(message.text ?? "");
661
481
  const [editMinWidth, setEditMinWidth] = React3.useState();
@@ -1016,6 +836,19 @@ ${block.text}
1016
836
  streaming,
1017
837
  role: participant?.kind === "human" ? "user" : "assistant"
1018
838
  }) }),
839
+ message.media?.map((attachment) => /* @__PURE__ */ jsx(
840
+ MessageMedia,
841
+ {
842
+ attachment,
843
+ openLabel: openMediaLabel,
844
+ playLabel: playMediaLabel,
845
+ errorLabel: mediaErrorLabel,
846
+ retryLabel: mediaRetryLabel,
847
+ launchId: JSON.stringify([message.id, attachment.id]),
848
+ onOpen: onOpenMedia ? (play) => onOpenMedia(message.id, attachment.id, play) : void 0
849
+ },
850
+ attachment.id
851
+ )),
1019
852
  streaming && !hasBody && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center", children: /* @__PURE__ */ jsx(AITypingIndicator, {}) })
1020
853
  ] })
1021
854
  }
@@ -1025,22 +858,306 @@ ${block.text}
1025
858
  ]
1026
859
  }
1027
860
  )
1028
- ]
1029
- }
1030
- );
1031
- });
1032
- var sidebarItem = cva(
1033
- "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-start text-sm transition-colors",
1034
- {
1035
- variants: {
1036
- active: {
1037
- true: "bg-primary-100 text-primary-900 dark:bg-primary-900/40 dark:text-primary-100",
1038
- false: "text-neutral-700 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800"
861
+ ]
862
+ }
863
+ );
864
+ });
865
+ var sidebarItem = cva(
866
+ "flex w-full items-center gap-2 rounded-lg px-3 py-2 text-start text-sm transition-colors",
867
+ {
868
+ variants: {
869
+ active: {
870
+ true: "bg-primary-100 text-primary-900 dark:bg-primary-900/40 dark:text-primary-100",
871
+ false: "text-neutral-700 hover:bg-neutral-100 dark:text-neutral-200 dark:hover:bg-neutral-800"
872
+ }
873
+ },
874
+ defaultVariants: { active: false }
875
+ }
876
+ );
877
+
878
+ // src/components/SuperChat/media.ts
879
+ function getConversationMediaItems(conversation) {
880
+ const participants = new Map(
881
+ conversation.participants.map((participant) => [
882
+ participant.id,
883
+ participant
884
+ ])
885
+ );
886
+ return [...conversation.thread].sort(byTime).flatMap(
887
+ (message) => (message.media ?? []).map((attachment) => ({
888
+ // Tuple encoding prevents collisions when ids themselves contain a
889
+ // delimiter. Including the conversation keeps selection scoped to it.
890
+ id: JSON.stringify([conversation.id, message.id, attachment.id]),
891
+ conversationId: conversation.id,
892
+ message,
893
+ attachment,
894
+ participant: participants.get(message.participantId)
895
+ }))
896
+ );
897
+ }
898
+ function remarkPreserveLineBreaks() {
899
+ const splitNode = (node) => {
900
+ if (!node.children) return;
901
+ const next = [];
902
+ for (const child of node.children) {
903
+ if (child.type === "text" && child.value && child.value.includes("\n")) {
904
+ const segments = child.value.split("\n");
905
+ segments.forEach((segment, i) => {
906
+ if (i > 0) next.push({ type: "break" });
907
+ if (segment) next.push({ type: "text", value: segment });
908
+ });
909
+ } else {
910
+ splitNode(child);
911
+ next.push(child);
912
+ }
913
+ }
914
+ node.children = next;
915
+ };
916
+ return (tree) => splitNode(tree);
917
+ }
918
+ function baseSanitizeSchema() {
919
+ const schema = JSON.parse(JSON.stringify(defaultSchema));
920
+ const attributes = schema.attributes ?? {};
921
+ const allowClass = (tag) => {
922
+ const existing = attributes[tag] ?? [];
923
+ const withoutClass = existing.filter(
924
+ (a) => !(Array.isArray(a) && a[0] === "className")
925
+ );
926
+ attributes[tag] = [...withoutClass, "className"];
927
+ };
928
+ allowClass("code");
929
+ allowClass("pre");
930
+ allowClass("span");
931
+ allowClass("div");
932
+ schema.attributes = attributes;
933
+ return schema;
934
+ }
935
+ function mergeSanitizeSchema(base, extra) {
936
+ if (!extra) return base;
937
+ const out = { ...base };
938
+ if (Array.isArray(extra.tagNames)) {
939
+ const a = base.tagNames ?? [];
940
+ out.tagNames = Array.from(/* @__PURE__ */ new Set([...a, ...extra.tagNames]));
941
+ }
942
+ if (extra.attributes && typeof extra.attributes === "object") {
943
+ const baseAttrs = {
944
+ ...base.attributes ?? {}
945
+ };
946
+ for (const [tag, attrs] of Object.entries(
947
+ extra.attributes
948
+ )) {
949
+ baseAttrs[tag] = [...baseAttrs[tag] ?? [], ...attrs];
950
+ }
951
+ out.attributes = baseAttrs;
952
+ }
953
+ if (extra.protocols && typeof extra.protocols === "object") {
954
+ const baseProtocols = {
955
+ ...base.protocols ?? {}
956
+ };
957
+ for (const [attr, protocols] of Object.entries(
958
+ extra.protocols
959
+ )) {
960
+ baseProtocols[attr] = Array.from(
961
+ /* @__PURE__ */ new Set([...baseProtocols[attr] ?? [], ...protocols])
962
+ );
963
+ }
964
+ out.protocols = baseProtocols;
965
+ }
966
+ for (const [k, v] of Object.entries(extra)) {
967
+ if (k === "tagNames" || k === "attributes" || k === "protocols") continue;
968
+ out[k] = v;
969
+ }
970
+ return out;
971
+ }
972
+ function baseComponents() {
973
+ return {
974
+ a: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
975
+ "a",
976
+ {
977
+ ...props,
978
+ className: cn(
979
+ "text-primary-800 hover:text-primary-900 dark:text-primary-300 underline underline-offset-2",
980
+ props.className
981
+ ),
982
+ target: props.target ?? "_blank",
983
+ rel: props.rel ?? "noopener noreferrer",
984
+ children
985
+ }
986
+ ),
987
+ pre: ({ node: _node, ...props }) => /* @__PURE__ */ jsx(
988
+ "pre",
989
+ {
990
+ ...props,
991
+ tabIndex: 0,
992
+ className: cn(
993
+ "overflow-x-auto rounded-lg bg-neutral-900 p-3 text-sm text-neutral-100 dark:bg-neutral-950",
994
+ props.className
995
+ )
996
+ }
997
+ ),
998
+ code: ({ node: _node, ...props }) => {
999
+ const isBlock = typeof props.className === "string" && props.className.includes("language-");
1000
+ return /* @__PURE__ */ jsx(
1001
+ "code",
1002
+ {
1003
+ ...props,
1004
+ className: cn(
1005
+ !isBlock && "rounded bg-neutral-200 px-1 py-0.5 text-[0.85em] text-neutral-800 dark:bg-neutral-700 dark:text-neutral-100",
1006
+ props.className
1007
+ )
1008
+ }
1009
+ );
1010
+ },
1011
+ input: ({ node: _node, ...props }) => {
1012
+ if (props.type === "checkbox") {
1013
+ return /* @__PURE__ */ jsx(
1014
+ "input",
1015
+ {
1016
+ ...props,
1017
+ "aria-label": props["aria-label"] ?? (props["aria-labelledby"] ? void 0 : "Task list item")
1018
+ }
1019
+ );
1020
+ }
1021
+ return /* @__PURE__ */ jsx("input", { ...props });
1022
+ },
1023
+ table: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("div", { className: "my-2 overflow-x-auto", children: /* @__PURE__ */ jsx(
1024
+ "table",
1025
+ {
1026
+ ...props,
1027
+ className: cn(
1028
+ "w-full border-collapse text-sm [&_td]:border [&_td]:border-neutral-200 [&_td]:px-2 [&_td]:py-1 dark:[&_td]:border-neutral-700 [&_th]:border [&_th]:border-neutral-200 [&_th]:px-2 [&_th]:py-1 [&_th]:text-start dark:[&_th]:border-neutral-700",
1029
+ props.className
1030
+ )
1031
+ }
1032
+ ) }),
1033
+ // Tailwind's preflight resets list markers/padding; restore them explicitly
1034
+ // so bullets/numbers show without depending on the typography plugin.
1035
+ ul: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ul", { ...props, className: cn("list-disc ps-5", props.className) }),
1036
+ ol: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ol", { ...props, className: cn("list-decimal ps-5", props.className) }),
1037
+ li: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("li", { ...props, className: cn("ps-1", props.className) }),
1038
+ // Preflight also flattens headings to body size/weight; restore a sensible
1039
+ // hierarchy without depending on the typography plugin.
1040
+ h1: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
1041
+ "h1",
1042
+ {
1043
+ ...props,
1044
+ className: cn("mt-3 mb-1 text-xl font-semibold", props.className),
1045
+ children
1046
+ }
1047
+ ),
1048
+ h2: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
1049
+ "h2",
1050
+ {
1051
+ ...props,
1052
+ className: cn("mt-3 mb-1 text-lg font-semibold", props.className),
1053
+ children
1054
+ }
1055
+ ),
1056
+ h3: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
1057
+ "h3",
1058
+ {
1059
+ ...props,
1060
+ className: cn("mt-2 mb-1 text-base font-semibold", props.className),
1061
+ children
1062
+ }
1063
+ ),
1064
+ h4: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
1065
+ "h4",
1066
+ {
1067
+ ...props,
1068
+ className: cn("mt-2 mb-1 text-sm font-semibold", props.className),
1069
+ children
1070
+ }
1071
+ ),
1072
+ h5: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
1073
+ "h5",
1074
+ {
1075
+ ...props,
1076
+ className: cn("mt-2 mb-1 text-sm font-semibold", props.className),
1077
+ children
1078
+ }
1079
+ ),
1080
+ h6: ({ node: _node, children, ...props }) => /* @__PURE__ */ jsx(
1081
+ "h6",
1082
+ {
1083
+ ...props,
1084
+ className: cn(
1085
+ "mt-2 mb-1 text-xs font-semibold tracking-wide uppercase",
1086
+ props.className
1087
+ ),
1088
+ children
1089
+ }
1090
+ ),
1091
+ hr: ({ node: _node, ...props }) => /* @__PURE__ */ jsx(
1092
+ "hr",
1093
+ {
1094
+ ...props,
1095
+ className: cn(
1096
+ "my-2 border-neutral-300 dark:border-neutral-600",
1097
+ props.className
1098
+ )
1039
1099
  }
1100
+ )
1101
+ };
1102
+ }
1103
+ function createMarkdownRenderer(options = {}) {
1104
+ const { plugins = [], trusted = false } = options;
1105
+ const remarkPlugins = [
1106
+ remarkGfm,
1107
+ remarkPreserveLineBreaks,
1108
+ ...plugins.flatMap((p) => p.remarkPlugins ?? [])
1109
+ ];
1110
+ const pluginRehype = plugins.flatMap(
1111
+ (p) => p.rehypePlugins ?? []
1112
+ );
1113
+ const sanitizeSchema = plugins.reduce(
1114
+ (acc, p) => mergeSanitizeSchema(acc, p.sanitizeSchema),
1115
+ baseSanitizeSchema()
1116
+ );
1117
+ const rehypePlugins = trusted ? pluginRehype : [...pluginRehype, [rehypeSanitize, sanitizeSchema]];
1118
+ const components = plugins.reduce(
1119
+ (acc, p) => ({ ...acc, ...p.components }),
1120
+ baseComponents()
1121
+ );
1122
+ const MarkdownRenderer = (text, ctx) => /* @__PURE__ */ jsx(
1123
+ MarkdownContent,
1124
+ {
1125
+ text,
1126
+ messageId: ctx.messageId,
1127
+ streaming: ctx.streaming,
1128
+ remarkPlugins,
1129
+ rehypePlugins,
1130
+ components
1040
1131
  },
1041
- defaultVariants: { active: false }
1132
+ ctx.messageId
1133
+ );
1134
+ return MarkdownRenderer;
1135
+ }
1136
+ var transformUrl = (url, key, node) => {
1137
+ if (key === "src" && node.tagName === "img" && (/^data:image\//i.test(url) || /^blob:/i.test(url))) {
1138
+ return url;
1042
1139
  }
1043
- );
1140
+ return defaultUrlTransform(url);
1141
+ };
1142
+ var MarkdownContent = React3.memo(function MarkdownContent2({
1143
+ text,
1144
+ messageId,
1145
+ streaming,
1146
+ remarkPlugins,
1147
+ rehypePlugins,
1148
+ components
1149
+ }) {
1150
+ return /* @__PURE__ */ jsx(TextRenderContext.Provider, { value: { messageId, streaming }, children: /* @__PURE__ */ jsx(
1151
+ Markdown,
1152
+ {
1153
+ remarkPlugins,
1154
+ rehypePlugins,
1155
+ urlTransform: transformUrl,
1156
+ components,
1157
+ children: text
1158
+ }
1159
+ ) });
1160
+ });
1044
1161
  function VirtualThread({
1045
1162
  items,
1046
1163
  participantById,
@@ -1051,6 +1168,11 @@ function VirtualThread({
1051
1168
  editable,
1052
1169
  onMessageEdited,
1053
1170
  defaultCopyFormat,
1171
+ onOpenMedia,
1172
+ openMediaLabel,
1173
+ playMediaLabel,
1174
+ mediaErrorLabel,
1175
+ mediaRetryLabel,
1054
1176
  scrollRef,
1055
1177
  contentRef,
1056
1178
  containerProps
@@ -1099,7 +1221,12 @@ function VirtualThread({
1099
1221
  onReferenceClick,
1100
1222
  editable,
1101
1223
  onMessageEdited,
1102
- defaultCopyFormat
1224
+ defaultCopyFormat,
1225
+ onOpenMedia,
1226
+ openMediaLabel,
1227
+ playMediaLabel,
1228
+ mediaErrorLabel,
1229
+ mediaRetryLabel
1103
1230
  }
1104
1231
  ) })
1105
1232
  },
@@ -1111,6 +1238,11 @@ function VirtualThread({
1111
1238
  }
1112
1239
  function SuperChat({
1113
1240
  conversation,
1241
+ view,
1242
+ defaultView = "thread",
1243
+ onViewChange,
1244
+ mediaFeedProps,
1245
+ mediaLabels,
1114
1246
  currentParticipantId,
1115
1247
  renderPlugins,
1116
1248
  renderTextContent,
@@ -1129,6 +1261,114 @@ function SuperChat({
1129
1261
  onBack
1130
1262
  }) {
1131
1263
  const headingId = React3.useId();
1264
+ const [internalView, setInternalView] = React3.useState({
1265
+ conversationId: conversation.id,
1266
+ view: defaultView
1267
+ });
1268
+ const activeView = view ?? (internalView.conversationId === conversation.id ? internalView.view : "thread");
1269
+ const labels = {
1270
+ backToConversation: "Back to conversation",
1271
+ openMedia: "Open in media feed",
1272
+ playMedia: "Play",
1273
+ unknownAuthor: "Unknown",
1274
+ ...mediaLabels
1275
+ };
1276
+ const mediaItems = React3.useMemo(() => {
1277
+ const items = getConversationMediaItems(conversation);
1278
+ return order === "desc" ? items.reverse() : items;
1279
+ }, [conversation, order]);
1280
+ const [mediaSelection, setMediaSelection] = React3.useState();
1281
+ const [manualPlaybackRequest, setManualPlaybackRequest] = React3.useState();
1282
+ const playbackRequestCounter = React3.useRef(0);
1283
+ const mediaFeedRef = React3.useRef(null);
1284
+ const backToConversationRef = React3.useRef(null);
1285
+ const focusMediaOnOpenRef = React3.useRef(false);
1286
+ const focusThreadOnBackRef = React3.useRef(false);
1287
+ const mediaReturnTargetRef = React3.useRef(void 0);
1288
+ const mediaContextRef = React3.useRef({
1289
+ conversation,
1290
+ view,
1291
+ onViewChange,
1292
+ mediaFeedProps,
1293
+ mediaItems
1294
+ });
1295
+ mediaContextRef.current = {
1296
+ conversation,
1297
+ view,
1298
+ onViewChange,
1299
+ mediaFeedProps,
1300
+ mediaItems
1301
+ };
1302
+ const setView = React3.useCallback((next) => {
1303
+ const context = mediaContextRef.current;
1304
+ if (context.view === void 0)
1305
+ setInternalView({ conversationId: context.conversation.id, view: next });
1306
+ context.onViewChange?.(next, { conversation: context.conversation });
1307
+ }, []);
1308
+ const handleMediaSelection = React3.useCallback((item) => {
1309
+ setMediaSelection({ conversationId: item.conversationId, itemId: item.id });
1310
+ mediaContextRef.current.mediaFeedProps?.onActiveItemChange?.(item);
1311
+ }, []);
1312
+ const handleOpenMedia = React3.useCallback(
1313
+ (messageId, attachmentId, play = false) => {
1314
+ const item = mediaContextRef.current.mediaItems.find(
1315
+ (candidate) => candidate.message.id === messageId && candidate.attachment.id === attachmentId
1316
+ );
1317
+ if (!item) return;
1318
+ handleMediaSelection(item);
1319
+ mediaReturnTargetRef.current = {
1320
+ conversationId: item.conversationId,
1321
+ messageId,
1322
+ attachmentId
1323
+ };
1324
+ focusMediaOnOpenRef.current = true;
1325
+ setManualPlaybackRequest(
1326
+ play ? {
1327
+ conversationId: item.conversationId,
1328
+ itemId: item.id,
1329
+ requestId: ++playbackRequestCounter.current
1330
+ } : void 0
1331
+ );
1332
+ setView("media");
1333
+ },
1334
+ [handleMediaSelection, setView]
1335
+ );
1336
+ const handleBackToConversation = React3.useCallback(() => {
1337
+ focusMediaOnOpenRef.current = false;
1338
+ focusThreadOnBackRef.current = true;
1339
+ setManualPlaybackRequest(void 0);
1340
+ setView("thread");
1341
+ }, [setView]);
1342
+ const hostPlaybackRequest = mediaFeedProps?.playbackRequest;
1343
+ const hostPlaybackKey = hostPlaybackRequest ? `${hostPlaybackRequest.itemId}\0${hostPlaybackRequest.requestId}` : void 0;
1344
+ React3.useEffect(() => {
1345
+ if (hostPlaybackKey === void 0) return;
1346
+ setManualPlaybackRequest(void 0);
1347
+ }, [hostPlaybackKey]);
1348
+ React3.useEffect(() => {
1349
+ if (internalView.conversationId !== conversation.id) {
1350
+ setInternalView({ conversationId: conversation.id, view: "thread" });
1351
+ }
1352
+ if (mediaReturnTargetRef.current?.conversationId !== conversation.id) {
1353
+ mediaReturnTargetRef.current = void 0;
1354
+ focusMediaOnOpenRef.current = false;
1355
+ focusThreadOnBackRef.current = false;
1356
+ setManualPlaybackRequest(void 0);
1357
+ }
1358
+ }, [conversation.id, internalView.conversationId]);
1359
+ React3.useEffect(() => {
1360
+ if (activeView !== "media" || !focusMediaOnOpenRef.current) return;
1361
+ const target = mediaFeedRef.current?.querySelector(
1362
+ 'article[tabindex="0"]'
1363
+ ) ?? mediaFeedRef.current?.querySelector('[role="feed"]');
1364
+ if (target) focusMediaOnOpenRef.current = false;
1365
+ (target ?? backToConversationRef.current)?.focus({ preventScroll: true });
1366
+ }, [
1367
+ activeView,
1368
+ mediaFeedProps?.loading,
1369
+ mediaFeedProps?.error,
1370
+ mediaItems.length
1371
+ ]);
1132
1372
  React3.useEffect(() => {
1133
1373
  notifyComposerMigrationOnce("SuperChat");
1134
1374
  }, []);
@@ -1148,12 +1388,36 @@ function SuperChat({
1148
1388
  anchorToTurnStart,
1149
1389
  followIfPinned,
1150
1390
  stopFollowing
1151
- } = useStickToBottom({ disabled: order === "desc" });
1391
+ } = useStickToBottom({
1392
+ disabled: order === "desc" || activeView === "media"
1393
+ });
1394
+ React3.useEffect(() => {
1395
+ if (activeView !== "thread") return;
1396
+ setManualPlaybackRequest(void 0);
1397
+ if (!focusThreadOnBackRef.current) return;
1398
+ focusThreadOnBackRef.current = false;
1399
+ const source = mediaReturnTargetRef.current;
1400
+ const launchId = source ? JSON.stringify([source.messageId, source.attachmentId]) : void 0;
1401
+ const launcher = source?.conversationId === conversation.id ? Array.from(
1402
+ threadRef.current?.querySelectorAll(
1403
+ "[data-media-launch-id]"
1404
+ ) ?? []
1405
+ ).find((button) => button.dataset.mediaLaunchId === launchId) : void 0;
1406
+ (launcher ?? threadRef.current)?.focus({ preventScroll: true });
1407
+ }, [activeView, conversation.id, threadRef]);
1152
1408
  const [hasNewBelow, setHasNewBelow] = React3.useState(false);
1153
1409
  const [turnStartId, setTurnStartId] = React3.useState(null);
1154
1410
  const [turnMinHeight, setTurnMinHeight] = React3.useState();
1155
1411
  const anchoredTurnRef = React3.useRef(null);
1412
+ const threadSnapshotRef = React3.useRef(void 0);
1413
+ const atBottomRef = React3.useRef(isAtBottom);
1414
+ atBottomRef.current = isAtBottom;
1415
+ const pendingMediaOwnSendRef = React3.useRef(false);
1416
+ const pendingMediaAppendRef = React3.useRef(false);
1156
1417
  React3.useEffect(() => {
1418
+ threadSnapshotRef.current = void 0;
1419
+ pendingMediaOwnSendRef.current = false;
1420
+ pendingMediaAppendRef.current = false;
1157
1421
  setTurnStartId(null);
1158
1422
  anchoredTurnRef.current = null;
1159
1423
  if (order === "desc") {
@@ -1187,6 +1451,7 @@ function SuperChat({
1187
1451
  if (threadLength === prevThreadLengthRef.current) return;
1188
1452
  const grew = threadLength > prevThreadLengthRef.current;
1189
1453
  prevThreadLengthRef.current = threadLength;
1454
+ if (grew && activeView === "media") pendingMediaAppendRef.current = true;
1190
1455
  if (order === "desc") {
1191
1456
  const el = threadRef.current;
1192
1457
  if (el) el.scrollTop = 0;
@@ -1202,6 +1467,7 @@ function SuperChat({
1202
1467
  }
1203
1468
  }
1204
1469
  if (turnStart) {
1470
+ if (activeView === "media") pendingMediaOwnSendRef.current = true;
1205
1471
  if (virtualized) {
1206
1472
  scrollToBottom("auto");
1207
1473
  } else {
@@ -1218,7 +1484,36 @@ function SuperChat({
1218
1484
  } else {
1219
1485
  followIfPinned("auto");
1220
1486
  }
1221
- }, [threadLength, order, isAtBottom, conversation.id]);
1487
+ }, [threadLength, order, isAtBottom, conversation.id, activeView]);
1488
+ React3.useLayoutEffect(() => {
1489
+ if (activeView !== "thread") return;
1490
+ const container = threadRef.current;
1491
+ if (!container) return;
1492
+ const snapshot = threadSnapshotRef.current;
1493
+ const sameThread = snapshot?.conversationId === conversation.id && snapshot.order === order;
1494
+ const ownSend = pendingMediaOwnSendRef.current;
1495
+ const appended = pendingMediaAppendRef.current;
1496
+ pendingMediaOwnSendRef.current = false;
1497
+ pendingMediaAppendRef.current = false;
1498
+ if (order === "desc") {
1499
+ container.scrollTop = sameThread && !appended ? snapshot.scrollTop : 0;
1500
+ } else if (ownSend && !virtualized) ; else if (sameThread && !ownSend && (!snapshot.atBottom || !appended)) {
1501
+ container.scrollTop = snapshot.scrollTop;
1502
+ if (!snapshot.atBottom) stopFollowing();
1503
+ } else {
1504
+ scrollToBottom("auto");
1505
+ if ([...conversation.thread].sort(byTime).at(-1)?.status === "streaming")
1506
+ stopFollowing();
1507
+ }
1508
+ return () => {
1509
+ threadSnapshotRef.current = {
1510
+ conversationId: conversation.id,
1511
+ order,
1512
+ scrollTop: container.scrollTop,
1513
+ atBottom: atBottomRef.current
1514
+ };
1515
+ };
1516
+ }, [conversation.id, order, activeView, virtualized]);
1222
1517
  React3.useLayoutEffect(() => {
1223
1518
  if (!turnStartId || anchoredTurnRef.current === turnStartId) return;
1224
1519
  const container = threadRef.current;
@@ -1240,7 +1535,8 @@ function SuperChat({
1240
1535
  turnMinHeight,
1241
1536
  anchorToTurnStart,
1242
1537
  prefersReducedMotion,
1243
- threadRef
1538
+ threadRef,
1539
+ activeView
1244
1540
  ]);
1245
1541
  React3.useEffect(() => {
1246
1542
  prevMessageIdsRef.current = new Set(
@@ -1293,7 +1589,12 @@ function SuperChat({
1293
1589
  onReferenceClick,
1294
1590
  editable,
1295
1591
  onMessageEdited: handleMessageEdited,
1296
- defaultCopyFormat
1592
+ defaultCopyFormat,
1593
+ onOpenMedia: handleOpenMedia,
1594
+ openMediaLabel: labels.openMedia,
1595
+ playMediaLabel: labels.playMedia,
1596
+ mediaErrorLabel: mediaFeedProps?.labels?.mediaError,
1597
+ mediaRetryLabel: mediaFeedProps?.labels?.retry
1297
1598
  },
1298
1599
  m.id
1299
1600
  );
@@ -1426,7 +1727,53 @@ function SuperChat({
1426
1727
  ]
1427
1728
  }
1428
1729
  ),
1429
- /* @__PURE__ */ jsxs(
1730
+ activeView === "media" ? /* @__PURE__ */ jsxs(
1731
+ "div",
1732
+ {
1733
+ "data-slot": "superchat-media-view",
1734
+ className: "flex min-h-0 flex-1 flex-col",
1735
+ children: [
1736
+ /* @__PURE__ */ jsx("div", { className: "px-4 pt-2", children: /* @__PURE__ */ jsx(ButtonGroup, { className: "justify-start", children: /* @__PURE__ */ jsx(
1737
+ Button,
1738
+ {
1739
+ ref: backToConversationRef,
1740
+ variant: "ghost",
1741
+ size: "sm",
1742
+ leftIcon: /* @__PURE__ */ jsx(
1743
+ ArrowLeft,
1744
+ {
1745
+ className: "size-4 rtl:rotate-180",
1746
+ "aria-hidden": "true"
1747
+ }
1748
+ ),
1749
+ onClick: handleBackToConversation,
1750
+ children: labels.backToConversation
1751
+ }
1752
+ ) }) }),
1753
+ /* @__PURE__ */ createElement(
1754
+ MediaFeed,
1755
+ {
1756
+ ...mediaFeedProps,
1757
+ ref: mediaFeedRef,
1758
+ key: conversation.id,
1759
+ items: mediaItems,
1760
+ getId: (item) => item.id,
1761
+ getMedia: (item) => item.attachment,
1762
+ getTitle: (item) => item.attachment.title ?? "",
1763
+ getCaption: (item) => item.attachment.caption ?? item.message.text,
1764
+ getAuthor: (item) => ({
1765
+ name: item.participant?.name ?? labels.unknownAuthor,
1766
+ avatar: item.participant?.avatar
1767
+ }),
1768
+ activeItemId: mediaFeedProps?.activeItemId ?? (mediaSelection?.conversationId === conversation.id ? mediaSelection.itemId : void 0),
1769
+ onActiveItemChange: handleMediaSelection,
1770
+ playbackRequest: manualPlaybackRequest?.conversationId === conversation.id ? manualPlaybackRequest : mediaFeedProps?.playbackRequest,
1771
+ className: cn("min-h-0 flex-1", mediaFeedProps?.className)
1772
+ }
1773
+ )
1774
+ ]
1775
+ }
1776
+ ) : /* @__PURE__ */ jsxs(
1430
1777
  "div",
1431
1778
  {
1432
1779
  "data-slot": "superchat-thread-viewport",
@@ -1444,6 +1791,11 @@ function SuperChat({
1444
1791
  editable,
1445
1792
  onMessageEdited: handleMessageEdited,
1446
1793
  defaultCopyFormat,
1794
+ onOpenMedia: handleOpenMedia,
1795
+ openMediaLabel: labels.openMedia,
1796
+ playMediaLabel: labels.playMedia,
1797
+ mediaErrorLabel: mediaFeedProps?.labels?.mediaError,
1798
+ mediaRetryLabel: mediaFeedProps?.labels?.retry,
1447
1799
  scrollRef: threadRef,
1448
1800
  contentRef: threadContentRef,
1449
1801
  containerProps: {
@@ -1615,6 +1967,11 @@ function SuperChatConversations({
1615
1967
  }
1616
1968
  function SuperChatInbox({
1617
1969
  conversations,
1970
+ view,
1971
+ defaultView,
1972
+ onViewChange,
1973
+ mediaFeedProps,
1974
+ mediaLabels,
1618
1975
  activeConversationId,
1619
1976
  defaultActiveConversationId,
1620
1977
  currentParticipantId,
@@ -1676,6 +2033,11 @@ function SuperChatInbox({
1676
2033
  SuperChat,
1677
2034
  {
1678
2035
  conversation: active,
2036
+ view,
2037
+ defaultView,
2038
+ onViewChange,
2039
+ mediaFeedProps,
2040
+ mediaLabels,
1679
2041
  currentParticipantId,
1680
2042
  renderPlugins,
1681
2043
  renderTextContent,
@@ -1711,6 +2073,6 @@ function SuperChatInbox({
1711
2073
  );
1712
2074
  }
1713
2075
 
1714
- export { SuperChat, SuperChatConversations, SuperChatInbox, createMarkdownRenderer };
2076
+ export { SuperChat, SuperChatConversations, SuperChatInbox, createMarkdownRenderer, getConversationMediaItems };
1715
2077
  //# sourceMappingURL=index.js.map
1716
2078
  //# sourceMappingURL=index.js.map