@epicbagel/saas-kit 1.3.0 → 1.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 (34) hide show
  1. package/README.md +91 -0
  2. package/dist/chunk-D5EHW3O2.cjs +52 -0
  3. package/dist/chunk-F3DAFAMD.js +271 -0
  4. package/dist/{chunk-JZOBIPFC.js → chunk-FGISSYXG.js} +61 -2
  5. package/dist/chunk-HAP6USTP.cjs +46 -0
  6. package/dist/chunk-HAQOT2CD.js +46 -0
  7. package/dist/{chunk-2AU7ZRGX.cjs → chunk-LAN3K2JI.cjs} +61 -2
  8. package/dist/chunk-TWCJMFST.js +260 -0
  9. package/dist/chunk-U3YFP6ZV.cjs +273 -0
  10. package/dist/chunk-X3PRMR5G.js +52 -0
  11. package/dist/chunk-ZJFYOXSM.cjs +260 -0
  12. package/dist/lib/neutral-theme.cjs +39 -236
  13. package/dist/lib/neutral-theme.d.cts +44 -2
  14. package/dist/lib/neutral-theme.d.ts +44 -2
  15. package/dist/lib/neutral-theme.js +23 -219
  16. package/dist/primitives/chat.cjs +65 -12
  17. package/dist/primitives/chat.d.cts +46 -2
  18. package/dist/primitives/chat.d.ts +46 -2
  19. package/dist/primitives/chat.js +55 -2
  20. package/dist/primitives/field.cjs +12 -239
  21. package/dist/primitives/field.js +16 -243
  22. package/dist/primitives/message.cjs +8 -2
  23. package/dist/primitives/message.d.cts +35 -1
  24. package/dist/primitives/message.d.ts +35 -1
  25. package/dist/primitives/message.js +7 -1
  26. package/dist/primitives/radio-group.cjs +4 -39
  27. package/dist/primitives/radio-group.js +4 -39
  28. package/dist/primitives/theme-selector.cjs +188 -0
  29. package/dist/primitives/theme-selector.d.cts +78 -0
  30. package/dist/primitives/theme-selector.d.ts +78 -0
  31. package/dist/primitives/theme-selector.js +188 -0
  32. package/dist/primitives/toggle-group.cjs +5 -45
  33. package/dist/primitives/toggle-group.js +5 -45
  34. package/package.json +11 -1
@@ -1,225 +1,28 @@
1
- // src/lib/neutral-theme.ts
2
- var SEMANTIC_TOKENS = [
3
- "background",
4
- "foreground",
5
- "card",
6
- "card-foreground",
7
- "popover",
8
- "popover-foreground",
9
- "primary",
10
- "primary-foreground",
11
- "secondary",
12
- "secondary-foreground",
13
- "muted",
14
- "muted-foreground",
15
- "accent",
16
- "accent-foreground",
17
- "destructive",
18
- "destructive-foreground",
19
- "border",
20
- "input",
21
- "ring",
22
- "chart-1",
23
- "chart-2",
24
- "chart-3",
25
- "chart-4",
26
- "chart-5",
27
- "sidebar-background",
28
- "sidebar-foreground",
29
- "sidebar-primary",
30
- "sidebar-primary-foreground",
31
- "sidebar-accent",
32
- "sidebar-accent-foreground",
33
- "sidebar-border",
34
- "sidebar-ring"
35
- ];
36
- function hexToRgb(hex) {
37
- const h = hex.replace("#", "").trim();
38
- const full = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
39
- if (!/^[0-9a-fA-F]{6}$/.test(full)) {
40
- throw new Error(`Not a hex colour: ${hex}`);
41
- }
42
- return [
43
- parseInt(full.slice(0, 2), 16),
44
- parseInt(full.slice(2, 4), 16),
45
- parseInt(full.slice(4, 6), 16)
46
- ];
47
- }
48
- function hexToHsl(hex) {
49
- const [r255, g255, b255] = hexToRgb(hex);
50
- const r = r255 / 255;
51
- const g = g255 / 255;
52
- const b = b255 / 255;
53
- const max = Math.max(r, g, b);
54
- const min = Math.min(r, g, b);
55
- const l = (max + min) / 2;
56
- const d = max - min;
57
- let h = 0;
58
- let s = 0;
59
- if (d !== 0) {
60
- s = d / (1 - Math.abs(2 * l - 1));
61
- if (max === r) h = (g - b) / d % 6;
62
- else if (max === g) h = (b - r) / d + 2;
63
- else h = (r - g) / d + 4;
64
- h *= 60;
65
- if (h < 0) h += 360;
66
- }
67
- const round = (n) => Math.round(n * 10) / 10;
68
- return `${round(h)} ${round(s * 100)}% ${round(l * 100)}%`;
69
- }
70
- function luminance(hex) {
71
- const [r, g, b] = hexToRgb(hex).map((v) => {
72
- const c = v / 255;
73
- return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
74
- });
75
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
76
- }
77
- function contrastRatio(a, b) {
78
- const la = luminance(a);
79
- const lb = luminance(b);
80
- const [hi, lo] = la > lb ? [la, lb] : [lb, la];
81
- return (hi + 0.05) / (lo + 0.05);
82
- }
83
- function bestForeground(bg, candidates) {
84
- let best = candidates[0];
85
- let bestRatio = 0;
86
- for (const c of candidates) {
87
- const ratio = contrastRatio(bg, c);
88
- if (ratio > bestRatio) {
89
- best = c;
90
- bestRatio = ratio;
91
- }
92
- }
93
- return best;
94
- }
95
- var NEUTRAL_RADIUS = "0rem";
96
- function neutral(mode) {
97
- const light = mode === "light";
98
- const danger = "#d71921";
99
- const mono = light ? {
100
- bg: "#ffffff",
101
- fg: "#000000",
102
- surface: "#f5f5f5",
103
- subtle: "#666666",
104
- border: "#e5e5e5",
105
- sidebar: "#fafafa",
106
- strong: "#333333"
107
- } : {
108
- bg: "#000000",
109
- fg: "#ffffff",
110
- surface: "#1a1a1a",
111
- subtle: "#999999",
112
- border: "#333333",
113
- sidebar: "#0d0d0d",
114
- strong: "#cccccc"
115
- };
116
- return {
117
- background: mono.bg,
118
- foreground: mono.fg,
119
- card: mono.bg,
120
- "card-foreground": mono.fg,
121
- popover: mono.bg,
122
- "popover-foreground": mono.fg,
123
- primary: mono.fg,
124
- "primary-foreground": mono.bg,
125
- secondary: mono.surface,
126
- "secondary-foreground": mono.fg,
127
- muted: mono.surface,
128
- "muted-foreground": mono.subtle,
129
- accent: mono.surface,
130
- "accent-foreground": mono.fg,
131
- destructive: danger,
132
- "destructive-foreground": bestForeground(danger, ["#ffffff", "#000000"]),
133
- border: mono.border,
134
- input: mono.border,
135
- ring: mono.fg,
136
- "chart-1": light ? "#333333" : "#cccccc",
137
- "chart-2": light ? "#4d4d4d" : "#b3b3b3",
138
- "chart-3": light ? "#666666" : "#999999",
139
- "chart-4": light ? "#808080" : "#808080",
140
- "chart-5": light ? "#999999" : "#666666",
141
- "sidebar-background": mono.sidebar,
142
- "sidebar-foreground": mono.fg,
143
- "sidebar-primary": mono.fg,
144
- "sidebar-primary-foreground": mono.bg,
145
- "sidebar-accent": mono.surface,
146
- "sidebar-accent-foreground": mono.fg,
147
- "sidebar-border": mono.border,
148
- "sidebar-ring": mono.border
149
- };
150
- }
151
- var NEUTRAL = {
152
- light: neutral("light"),
153
- dark: neutral("dark")
154
- };
155
- function colorAlias(token) {
156
- if (token.startsWith("chart-")) return null;
157
- if (token === "sidebar-background") return "--color-sidebar";
158
- return `--color-${token}`;
159
- }
160
- function paletteDeclarations(tokens, names, radius, { indent = " ", aliasSource = "neutral-theme.ts" } = {}) {
161
- const value = (token) => {
162
- const hex = tokens[token];
163
- if (hex === void 0) throw new Error(`Missing token: ${token}`);
164
- return hexToHsl(hex);
165
- };
166
- const lines = [`${indent}--radius: ${radius};`];
167
- for (const token of names) {
168
- lines.push(`${indent}--${token}: ${value(token)};`);
169
- }
170
- lines.push(`${indent}--sidebar: ${value("sidebar-background")};`);
171
- lines.push(`${indent}/* Tailwind-facing aliases \u2014 see ${aliasSource} */`);
172
- for (const token of names) {
173
- const alias = colorAlias(token);
174
- if (alias) lines.push(`${indent}${alias}: hsl(var(--${token}));`);
175
- }
176
- lines.push(`${indent}--radius-sm: calc(var(--radius) - 4px);`);
177
- lines.push(`${indent}--radius-md: calc(var(--radius) - 2px);`);
178
- lines.push(`${indent}--radius-lg: var(--radius);`);
179
- lines.push(`${indent}--radius-xl: calc(var(--radius) + 4px);`);
180
- lines.push(`${indent}--radius-2xl: calc(var(--radius) + 8px);`);
181
- return lines.join("\n");
182
- }
183
- function paletteBlock(selector, declarations, indent = "") {
184
- const head = selector.split("\n").map((line) => indent + line).join("\n");
185
- return `${head} {
186
- ${declarations}
187
- ${indent}}
188
- `;
189
- }
190
- function layerBlock(name, rules) {
191
- return `@layer ${name} {
192
- ${rules.join("\n")}}
193
- `;
194
- }
195
- var DARK_ROOT_SELECTOR = ":root:where(.dark)";
196
- function baseLayerPalette(light, dark, indent = " ") {
197
- return layerBlock("base", [
198
- paletteBlock(":root", light, indent),
199
- paletteBlock(DARK_ROOT_SELECTOR, dark, indent)
200
- ]);
201
- }
202
- var PALETTE_GENERATED_HEADER = `/* GENERATED FILE \u2014 do not edit.
203
- * Source: src/lib/neutral-theme.ts \xB7 Regenerate: pnpm gen:themes
204
- *
205
- * The neutral palette: light on :root, dark when the root carries the
206
- * \`.dark\` class. It sits in \`@layer base\`, so any unlayered palette rule
207
- * or a host's own :root overrides wins on layer order alone. Imported by
208
- * tokens.css; every --color-* alias and the radius scale are re-declared
209
- * alongside the tokens they read.
210
- */`;
211
- function renderPaletteCss() {
212
- const declarations = (mode) => paletteDeclarations(NEUTRAL[mode], SEMANTIC_TOKENS, NEUTRAL_RADIUS, {
213
- indent: " "
214
- });
215
- return [
216
- PALETTE_GENERATED_HEADER,
217
- baseLayerPalette(declarations("light"), declarations("dark"))
218
- ].join("\n");
219
- }
1
+ import {
2
+ NEUTRAL,
3
+ NEUTRAL_PALETTE,
4
+ NEUTRAL_RADIUS,
5
+ NEUTRAL_THEME_ID,
6
+ PALETTE_GENERATED_HEADER,
7
+ SEMANTIC_TOKENS,
8
+ baseLayerPalette,
9
+ bestForeground,
10
+ colorAlias,
11
+ contrastRatio,
12
+ hexToHsl,
13
+ hexToRgb,
14
+ layerBlock,
15
+ luminance,
16
+ paletteBlock,
17
+ paletteDeclarations,
18
+ paletteStyle,
19
+ renderPaletteCss
20
+ } from "../chunk-F3DAFAMD.js";
220
21
  export {
221
22
  NEUTRAL,
23
+ NEUTRAL_PALETTE,
222
24
  NEUTRAL_RADIUS,
25
+ NEUTRAL_THEME_ID,
223
26
  PALETTE_GENERATED_HEADER,
224
27
  SEMANTIC_TOKENS,
225
28
  baseLayerPalette,
@@ -232,5 +35,6 @@ export {
232
35
  luminance,
233
36
  paletteBlock,
234
37
  paletteDeclarations,
38
+ paletteStyle,
235
39
  renderPaletteCss
236
40
  };
@@ -23,7 +23,9 @@ var _chunkXABXUP6Lcjs = require('../chunk-XABXUP6L.cjs');
23
23
 
24
24
 
25
25
 
26
- var _chunk2AU7ZRGXcjs = require('../chunk-2AU7ZRGX.cjs');
26
+
27
+
28
+ var _chunkLAN3K2JIcjs = require('../chunk-LAN3K2JI.cjs');
27
29
 
28
30
 
29
31
 
@@ -75,6 +77,9 @@ require('../chunk-PQUSJEXL.cjs');
75
77
  var _chunkL4DH72UFcjs = require('../chunk-L4DH72UF.cjs');
76
78
 
77
79
 
80
+ var _chunk4LXN6GLZcjs = require('../chunk-4LXN6GLZ.cjs');
81
+
82
+
78
83
  var _chunkR35FT53Gcjs = require('../chunk-R35FT53G.cjs');
79
84
 
80
85
 
@@ -104,8 +109,14 @@ var _react = require('react');
104
109
 
105
110
 
106
111
 
112
+
113
+
107
114
  var _lucidereact = require('lucide-react');
108
115
  var _jsxruntime = require('react/jsx-runtime');
116
+ function offersActions(message) {
117
+ if (message.status) return false;
118
+ return _nullishCoalesce(message.actions, () => ( message.role === "assistant"));
119
+ }
109
120
  function useArrivingUserTurnId(messages) {
110
121
  const seen = _react.useRef.call(void 0, null);
111
122
  seen.current ??= new Set(messages.map((m) => m.id));
@@ -138,7 +149,7 @@ function ChatRunningLine({
138
149
  align = "start",
139
150
  workspace
140
151
  }) {
141
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2AU7ZRGXcjs.Message, { align, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2AU7ZRGXcjs.MessageContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2BTDBZYNcjs.Trace, { className: workspace ? void 0 : "px-3.5 py-2", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2BTDBZYNcjs.TraceLine, { status: "running", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2BTDBZYNcjs.TraceTitle, { children: label }) }) }) }) });
152
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLAN3K2JIcjs.Message, { align, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLAN3K2JIcjs.MessageContent, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2BTDBZYNcjs.Trace, { className: workspace ? void 0 : "px-3.5 py-2", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2BTDBZYNcjs.TraceLine, { status: "running", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2BTDBZYNcjs.TraceTitle, { children: label }) }) }) }) });
142
153
  }
143
154
  var ChatThread = _react.forwardRef.call(void 0,
144
155
  function ChatThread2({
@@ -153,6 +164,7 @@ var ChatThread = _react.forwardRef.call(void 0,
153
164
  markdownComponents,
154
165
  onOpenAttachment,
155
166
  attachmentAction = "open",
167
+ messageActions,
156
168
  className
157
169
  }, ref) {
158
170
  const markers = timelineMarkers(messages, /* @__PURE__ */ new Date());
@@ -186,6 +198,7 @@ var ChatThread = _react.forwardRef.call(void 0,
186
198
  }
187
199
  const isFullWidth = m.width === "full";
188
200
  const marker = markers[index];
201
+ const actions = messageActions && offersActions(m) ? messageActions(m) : null;
189
202
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
190
203
  _chunkXABXUP6Lcjs.MessageScrollerItem,
191
204
  {
@@ -194,7 +207,7 @@ var ChatThread = _react.forwardRef.call(void 0,
194
207
  className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex flex-col", workspace ? "gap-5" : "gap-3"),
195
208
  children: [
196
209
  marker ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TimelineMarker, { label: marker }) : null,
197
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2AU7ZRGXcjs.Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2AU7ZRGXcjs.MessageContent, { children: [
210
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLAN3K2JIcjs.Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkLAN3K2JIcjs.MessageContent, { children: [
198
211
  (m.content || m.text) && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
199
212
  _chunkV4YWTRLDcjs.Bubble,
200
213
  {
@@ -222,7 +235,21 @@ var ChatThread = _react.forwardRef.call(void 0,
222
235
  uploadingLabel,
223
236
  className: "px-0 pt-0"
224
237
  }
225
- )
238
+ ),
239
+ actions != null && actions !== false ? (
240
+ // Unboxed workspace turns pull the row out by the
241
+ // button's inset, so the first icon lines up with
242
+ // the text above it.
243
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
244
+ _chunkLAN3K2JIcjs.MessageActions,
245
+ {
246
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
247
+ workspace && (isUser ? "-me-2 pointer-coarse:-me-3.5" : "-ms-2 pointer-coarse:-ms-3.5")
248
+ ),
249
+ children: actions
250
+ }
251
+ )
252
+ ) : null
226
253
  ] }) })
227
254
  ]
228
255
  },
@@ -233,7 +260,7 @@ var ChatThread = _react.forwardRef.call(void 0,
233
260
  footer && // Always painted: content-visibility:auto would contain the
234
261
  // footer's paint and layout, clipping a ring or shadow at its
235
262
  // edge and re-parenting anything fixed inside it.
236
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkXABXUP6Lcjs.MessageScrollerItem, { className: "[content-visibility:visible]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2AU7ZRGXcjs.Message, { align: "start", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2AU7ZRGXcjs.MessageContent, { children: footer }) }) })
263
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkXABXUP6Lcjs.MessageScrollerItem, { className: "[content-visibility:visible]", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLAN3K2JIcjs.Message, { align: "start", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLAN3K2JIcjs.MessageContent, { children: footer }) }) })
237
264
  ]
238
265
  }
239
266
  ) }),
@@ -246,6 +273,29 @@ var ChatThread = _react.forwardRef.call(void 0,
246
273
  ) });
247
274
  }
248
275
  );
276
+ function ChatCopyAction({
277
+ message,
278
+ label = "Copy",
279
+ copiedLabel = "Copied",
280
+ onCopy,
281
+ resetAfter = 2e3
282
+ }) {
283
+ const { copied, copy } = _chunk4LXN6GLZcjs.useCopyToClipboard.call(void 0, { resetAfter });
284
+ const text = message.text;
285
+ if (!text) return null;
286
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
287
+ _chunkLAN3K2JIcjs.MessageAction,
288
+ {
289
+ tooltip: copied ? copiedLabel : label,
290
+ "data-copied": copied || void 0,
291
+ onClick: async () => {
292
+ const ok = await copy(text);
293
+ _optionalChain([onCopy, 'optionalCall', _ => _(ok)]);
294
+ },
295
+ children: copied ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Check, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Copy, {})
296
+ }
297
+ );
298
+ }
249
299
  function formatFileSize(size) {
250
300
  if (size === void 0) return "File";
251
301
  if (size < 1024) return `${size} B`;
@@ -285,7 +335,7 @@ function ChatAttachmentList({
285
335
  {
286
336
  "aria-label": `${verb} ${attachment.name}`,
287
337
  title: `${verb} ${attachment.name}`,
288
- onClick: () => _optionalChain([onOpen, 'optionalCall', _ => _(attachment)]),
338
+ onClick: () => _optionalChain([onOpen, 'optionalCall', _2 => _2(attachment)]),
289
339
  className: "rounded-xl focus-visible:ring-2 focus-visible:ring-ring/50"
290
340
  }
291
341
  ),
@@ -430,7 +480,7 @@ function ChatInput({
430
480
  "data-slot": "chat-input-actions",
431
481
  onClick: (event) => {
432
482
  if (!event.currentTarget.contains(event.target)) return;
433
- if (!event.target.closest("button")) _optionalChain([ref, 'access', _2 => _2.current, 'optionalAccess', _3 => _3.focus, 'call', _4 => _4()]);
483
+ if (!event.target.closest("button")) _optionalChain([ref, 'access', _3 => _3.current, 'optionalAccess', _4 => _4.focus, 'call', _5 => _5()]);
434
484
  },
435
485
  className: _chunkKWLSBTB2cjs.cn.call(void 0,
436
486
  "gap-2",
@@ -448,7 +498,7 @@ function ChatInput({
448
498
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Plus, { className: _chunkKWLSBTB2cjs.cn.call(void 0, spacious && "h-5 w-5") })
449
499
  }
450
500
  ) }),
451
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkZAITUDYDcjs.DropdownMenuContent, { align: "start", side: "top", className: "min-w-52", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkZAITUDYDcjs.DropdownMenuItem, { onSelect: () => _optionalChain([fileInputRef, 'access', _5 => _5.current, 'optionalAccess', _6 => _6.click, 'call', _7 => _7()]), children: [
501
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkZAITUDYDcjs.DropdownMenuContent, { align: "start", side: "top", className: "min-w-52", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkZAITUDYDcjs.DropdownMenuItem, { onSelect: () => _optionalChain([fileInputRef, 'access', _6 => _6.current, 'optionalAccess', _7 => _7.click, 'call', _8 => _8()]), children: [
452
502
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Paperclip, { className: "h-4 w-4" }),
453
503
  addFilesLabel
454
504
  ] }) })
@@ -655,6 +705,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
655
705
  markdownComponents,
656
706
  onOpenAttachment,
657
707
  attachmentAction,
708
+ messageActions,
658
709
  className
659
710
  }, ref) {
660
711
  const isMobile = _chunkR35FT53Gcjs.useIsMobile.call(void 0, );
@@ -679,14 +730,14 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
679
730
  else if (typeof globalThis.reportError === "function") globalThis.reportError(cause);
680
731
  else console.error(cause);
681
732
  }
682
- window.setTimeout(() => _optionalChain([inputRef, 'access', _8 => _8.current, 'optionalAccess', _9 => _9.focus, 'call', _10 => _10()]), 0);
733
+ window.setTimeout(() => _optionalChain([inputRef, 'access', _9 => _9.current, 'optionalAccess', _10 => _10.focus, 'call', _11 => _11()]), 0);
683
734
  },
684
735
  [attachments, mode, onSend, onSendError, pending]
685
736
  );
686
737
  _react.useImperativeHandle.call(void 0,
687
738
  ref,
688
739
  () => ({
689
- focusInput: () => _optionalChain([inputRef, 'access', _11 => _11.current, 'optionalAccess', _12 => _12.focus, 'call', _13 => _13()]),
740
+ focusInput: () => _optionalChain([inputRef, 'access', _12 => _12.current, 'optionalAccess', _13 => _13.focus, 'call', _14 => _14()]),
690
741
  clearInput: () => setInput("")
691
742
  }),
692
743
  []
@@ -694,7 +745,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
694
745
  const dropFiles = _react.useCallback.call(void 0,
695
746
  (files) => {
696
747
  if (!conversationStarted && mode === "voice") setUserMode("text");
697
- return _optionalChain([onFilesSelected, 'optionalCall', _14 => _14(files)]);
748
+ return _optionalChain([onFilesSelected, 'optionalCall', _15 => _15(files)]);
698
749
  },
699
750
  [conversationStarted, mode, onFilesSelected]
700
751
  );
@@ -851,6 +902,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
851
902
  markdownComponents,
852
903
  onOpenAttachment,
853
904
  attachmentAction,
905
+ messageActions,
854
906
  className: _chunkKWLSBTB2cjs.cn.call(void 0,
855
907
  "mb-3",
856
908
  application && "min-h-0 flex-1 px-4 py-4 md:px-0 md:py-0",
@@ -905,4 +957,5 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
905
957
 
906
958
 
907
959
 
908
- exports.Chat = Chat; exports.ChatInput = ChatInput; exports.ChatThread = ChatThread;
960
+
961
+ exports.Chat = Chat; exports.ChatCopyAction = ChatCopyAction; exports.ChatInput = ChatInput; exports.ChatThread = ChatThread;
@@ -43,8 +43,20 @@ type ChatMessage = {
43
43
  * with the reply when that lands. The line is not a message: it has no time
44
44
  * marker, files or rich `content`, and the scroller neither anchors nor
45
45
  * tracks it.
46
+ *
47
+ * `"sending"`: a turn still on its way to being stored, such as the user's
48
+ * own turn shown optimistically. It is drawn as any turn, but offers no
49
+ * actions until its stored copy replaces it.
46
50
  */
47
- status?: "running";
51
+ status?: "running" | "sending";
52
+ /**
53
+ * Whether the thread offers this turn the actions `messageActions` returns.
54
+ * By default an assistant turn has them and a user turn doesn't; `false`
55
+ * drops them from an assistant turn that isn't a reply (a progress line),
56
+ * and `true` gives them to a user turn. A running or sending turn never has
57
+ * them, whatever this says.
58
+ */
59
+ actions?: boolean;
48
60
  };
49
61
  interface ChatThreadProps {
50
62
  messages: ChatMessage[];
@@ -80,6 +92,15 @@ interface ChatThreadProps {
80
92
  onOpenAttachment?: (attachment: ChatAttachment) => void;
81
93
  /** What opening a message's file does, for its label and icon. Defaults to `"open"`. */
82
94
  attachmentAction?: ChatAttachmentAction;
95
+ /**
96
+ * The actions under a turn, in a `MessageActions` row: `ChatCopyAction`,
97
+ * then any `MessageAction`s of your own (a thumbs up or down). Called for
98
+ * each turn that offers them (`ChatMessage.actions`: assistant turns, by
99
+ * default), never for the running line, a running or sending turn or the
100
+ * footer. The row always shows, on a phone too, so nothing moves when a
101
+ * pointer reaches it; return `null` for none.
102
+ */
103
+ messageActions?: (message: ChatMessage) => ReactNode;
83
104
  className?: string;
84
105
  }
85
106
  /**
@@ -97,6 +118,24 @@ interface ChatThreadProps {
97
118
  * appears. A turn with `status: "running"` draws the same line in its place.
98
119
  */
99
120
  declare const ChatThread: React$1.ForwardRefExoticComponent<ChatThreadProps & React$1.RefAttributes<HTMLDivElement>>;
121
+ interface ChatCopyActionProps {
122
+ /** The turn whose `text` (its Markdown source) is copied. */
123
+ message: Pick<ChatMessage, "text">;
124
+ /** The tooltip and accessible name. Defaults to "Copy". */
125
+ label?: string;
126
+ /** Both, for a moment after a copy. Defaults to "Copied". */
127
+ copiedLabel?: string;
128
+ /** Called after each copy, with whether the clipboard took the text. */
129
+ onCopy?: (success: boolean) => void;
130
+ /** How long the copied state shows, in ms. Defaults to 2000. */
131
+ resetAfter?: number;
132
+ }
133
+ /**
134
+ * The ready Copy action for `messageActions`: copies the turn's `text`, its
135
+ * Markdown source, and shows a check and "Copied" for a moment. A turn with
136
+ * no `text` (rich `content` only) has nothing to copy, so it draws nothing.
137
+ */
138
+ declare function ChatCopyAction({ message, label, copiedLabel, onCopy, resetAfter, }: ChatCopyActionProps): react_jsx_runtime.JSX.Element | null;
100
139
  interface ChatInputProps {
101
140
  value: string;
102
141
  onChange: (next: string) => void;
@@ -330,6 +369,11 @@ interface ChatProps {
330
369
  onOpenAttachment?: (attachment: ChatAttachment) => void;
331
370
  /** What opening a message's file does, for its label and icon. Defaults to `"open"`. */
332
371
  attachmentAction?: ChatAttachmentAction;
372
+ /**
373
+ * The actions under each turn that offers them (`ChatMessage.actions`:
374
+ * assistant turns, by default), such as `ChatCopyAction`. See `ChatThread`.
375
+ */
376
+ messageActions?: (message: ChatMessage) => ReactNode;
333
377
  className?: string;
334
378
  }
335
379
  interface ChatHandle {
@@ -361,4 +405,4 @@ interface ChatHandle {
361
405
  */
362
406
  declare const Chat: React$1.ForwardRefExoticComponent<ChatProps & React$1.RefAttributes<ChatHandle>>;
363
407
 
364
- export { Chat, type ChatAttachment, type ChatAttachmentAction, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps };
408
+ export { Chat, type ChatAttachment, type ChatAttachmentAction, ChatCopyAction, type ChatCopyActionProps, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps };
@@ -43,8 +43,20 @@ type ChatMessage = {
43
43
  * with the reply when that lands. The line is not a message: it has no time
44
44
  * marker, files or rich `content`, and the scroller neither anchors nor
45
45
  * tracks it.
46
+ *
47
+ * `"sending"`: a turn still on its way to being stored, such as the user's
48
+ * own turn shown optimistically. It is drawn as any turn, but offers no
49
+ * actions until its stored copy replaces it.
46
50
  */
47
- status?: "running";
51
+ status?: "running" | "sending";
52
+ /**
53
+ * Whether the thread offers this turn the actions `messageActions` returns.
54
+ * By default an assistant turn has them and a user turn doesn't; `false`
55
+ * drops them from an assistant turn that isn't a reply (a progress line),
56
+ * and `true` gives them to a user turn. A running or sending turn never has
57
+ * them, whatever this says.
58
+ */
59
+ actions?: boolean;
48
60
  };
49
61
  interface ChatThreadProps {
50
62
  messages: ChatMessage[];
@@ -80,6 +92,15 @@ interface ChatThreadProps {
80
92
  onOpenAttachment?: (attachment: ChatAttachment) => void;
81
93
  /** What opening a message's file does, for its label and icon. Defaults to `"open"`. */
82
94
  attachmentAction?: ChatAttachmentAction;
95
+ /**
96
+ * The actions under a turn, in a `MessageActions` row: `ChatCopyAction`,
97
+ * then any `MessageAction`s of your own (a thumbs up or down). Called for
98
+ * each turn that offers them (`ChatMessage.actions`: assistant turns, by
99
+ * default), never for the running line, a running or sending turn or the
100
+ * footer. The row always shows, on a phone too, so nothing moves when a
101
+ * pointer reaches it; return `null` for none.
102
+ */
103
+ messageActions?: (message: ChatMessage) => ReactNode;
83
104
  className?: string;
84
105
  }
85
106
  /**
@@ -97,6 +118,24 @@ interface ChatThreadProps {
97
118
  * appears. A turn with `status: "running"` draws the same line in its place.
98
119
  */
99
120
  declare const ChatThread: React$1.ForwardRefExoticComponent<ChatThreadProps & React$1.RefAttributes<HTMLDivElement>>;
121
+ interface ChatCopyActionProps {
122
+ /** The turn whose `text` (its Markdown source) is copied. */
123
+ message: Pick<ChatMessage, "text">;
124
+ /** The tooltip and accessible name. Defaults to "Copy". */
125
+ label?: string;
126
+ /** Both, for a moment after a copy. Defaults to "Copied". */
127
+ copiedLabel?: string;
128
+ /** Called after each copy, with whether the clipboard took the text. */
129
+ onCopy?: (success: boolean) => void;
130
+ /** How long the copied state shows, in ms. Defaults to 2000. */
131
+ resetAfter?: number;
132
+ }
133
+ /**
134
+ * The ready Copy action for `messageActions`: copies the turn's `text`, its
135
+ * Markdown source, and shows a check and "Copied" for a moment. A turn with
136
+ * no `text` (rich `content` only) has nothing to copy, so it draws nothing.
137
+ */
138
+ declare function ChatCopyAction({ message, label, copiedLabel, onCopy, resetAfter, }: ChatCopyActionProps): react_jsx_runtime.JSX.Element | null;
100
139
  interface ChatInputProps {
101
140
  value: string;
102
141
  onChange: (next: string) => void;
@@ -330,6 +369,11 @@ interface ChatProps {
330
369
  onOpenAttachment?: (attachment: ChatAttachment) => void;
331
370
  /** What opening a message's file does, for its label and icon. Defaults to `"open"`. */
332
371
  attachmentAction?: ChatAttachmentAction;
372
+ /**
373
+ * The actions under each turn that offers them (`ChatMessage.actions`:
374
+ * assistant turns, by default), such as `ChatCopyAction`. See `ChatThread`.
375
+ */
376
+ messageActions?: (message: ChatMessage) => ReactNode;
333
377
  className?: string;
334
378
  }
335
379
  interface ChatHandle {
@@ -361,4 +405,4 @@ interface ChatHandle {
361
405
  */
362
406
  declare const Chat: React$1.ForwardRefExoticComponent<ChatProps & React$1.RefAttributes<ChatHandle>>;
363
407
 
364
- export { Chat, type ChatAttachment, type ChatAttachmentAction, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps };
408
+ export { Chat, type ChatAttachment, type ChatAttachmentAction, ChatCopyAction, type ChatCopyActionProps, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps };