@epicbagel/saas-kit 1.25.1 → 1.27.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.
@@ -17,24 +17,20 @@ var _chunkPNI354BYcjs = require('../chunk-PNI354BY.cjs');
17
17
  var _chunkM4V7OJNFcjs = require('../chunk-M4V7OJNF.cjs');
18
18
 
19
19
 
20
-
21
- var _chunkB765DSRGcjs = require('../chunk-B765DSRG.cjs');
22
-
20
+ var _chunkVMXKT67Ecjs = require('../chunk-VMXKT67E.cjs');
23
21
 
24
22
 
23
+ var _chunkU7XNNLVPcjs = require('../chunk-U7XNNLVP.cjs');
25
24
 
26
25
 
27
26
 
28
-
29
- var _chunk23IETCLVcjs = require('../chunk-23IETCLV.cjs');
30
- require('../chunk-DMPSKGFM.cjs');
27
+ var _chunkB765DSRGcjs = require('../chunk-B765DSRG.cjs');
31
28
 
32
29
 
33
30
 
34
31
 
35
32
 
36
33
  var _chunkNWDPH2Z7cjs = require('../chunk-NWDPH2Z7.cjs');
37
- require('../chunk-Z2H4LJVG.cjs');
38
34
 
39
35
 
40
36
 
@@ -44,6 +40,14 @@ require('../chunk-2HV2KKOE.cjs');
44
40
 
45
41
 
46
42
 
43
+
44
+
45
+
46
+ var _chunkDHXKCMKYcjs = require('../chunk-DHXKCMKY.cjs');
47
+ require('../chunk-NQK5ZWC7.cjs');
48
+
49
+
50
+
47
51
  var _chunkZLEM2SAUcjs = require('../chunk-ZLEM2SAU.cjs');
48
52
 
49
53
 
@@ -63,6 +67,19 @@ var _chunk2BHI5ZWJcjs = require('../chunk-2BHI5ZWJ.cjs');
63
67
 
64
68
 
65
69
  var _chunkZAITUDYDcjs = require('../chunk-ZAITUDYD.cjs');
70
+
71
+
72
+ var _chunkZBWL7OUWcjs = require('../chunk-ZBWL7OUW.cjs');
73
+
74
+
75
+
76
+
77
+
78
+
79
+
80
+ var _chunk23IETCLVcjs = require('../chunk-23IETCLV.cjs');
81
+ require('../chunk-DMPSKGFM.cjs');
82
+ require('../chunk-Z2H4LJVG.cjs');
66
83
  require('../chunk-U2NES7VR.cjs');
67
84
 
68
85
 
@@ -114,6 +131,7 @@ var _chunkH2UFRLLNcjs = require('../chunk-H2UFRLLN.cjs');
114
131
 
115
132
 
116
133
 
134
+
117
135
  var _react = require('react');
118
136
 
119
137
 
@@ -162,6 +180,24 @@ function timelineMarkers(messages, now) {
162
180
  function TimelineMarker({ label }) {
163
181
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkB765DSRGcjs.Marker, { variant: "separator", role: "separator", "aria-label": label, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkB765DSRGcjs.MarkerContent, { children: label }) });
164
182
  }
183
+ function authorLines(messages, markers) {
184
+ let previous = null;
185
+ return messages.map((message, index) => {
186
+ if (message.status === "running") return false;
187
+ const shown = Boolean(message.author) && (!previous || Boolean(markers[index]) || previous.role !== message.role || _optionalChain([previous, 'access', _ => _.author, 'optionalAccess', _2 => _2.id]) !== message.author.id);
188
+ previous = message;
189
+ return shown;
190
+ });
191
+ }
192
+ function ChatAuthorLine({ author, align }) {
193
+ const avatar = /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkU7XNNLVPcjs.PersonAvatar, { name: author.name, photoUrl: author.avatarUrl, size: 20, fallbackClassName: "text-[9px]" });
194
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { "data-slot": "chat-author", className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex items-center gap-2 text-xs", align === "end" ? "self-end" : "self-start"), children: [
195
+ align === "start" ? avatar : null,
196
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "font-medium text-foreground", children: author.name }),
197
+ author.detail ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "text-muted-foreground", children: author.detail }) : null,
198
+ align === "end" ? avatar : null
199
+ ] });
200
+ }
165
201
  function ChatRunningLine({
166
202
  label,
167
203
  align = "start",
@@ -183,9 +219,13 @@ var ChatThread = _react.forwardRef.call(void 0,
183
219
  onOpenAttachment,
184
220
  attachmentAction = "open",
185
221
  messageActions,
222
+ navigator,
223
+ navigatorLabel,
224
+ navigatorSide,
186
225
  className
187
226
  }, ref) {
188
227
  const markers = timelineMarkers(messages, /* @__PURE__ */ new Date());
228
+ const authorShown = authorLines(messages, markers);
189
229
  const anchorId = useArrivingUserTurnId(messages);
190
230
  const workspace = appearance === "workspace";
191
231
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk23IETCLVcjs.MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
@@ -226,6 +266,7 @@ var ChatThread = _react.forwardRef.call(void 0,
226
266
  children: [
227
267
  marker ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TimelineMarker, { label: marker }) : null,
228
268
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNWDPH2Z7cjs.Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkNWDPH2Z7cjs.MessageContent, { children: [
269
+ m.author && authorShown[index] ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatAuthorLine, { author: m.author, align: isUser ? "end" : "start" }) : null,
229
270
  (m.content || m.text) && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
230
271
  _chunkV4YWTRLDcjs.Bubble,
231
272
  {
@@ -285,7 +326,17 @@ var ChatThread = _react.forwardRef.call(void 0,
285
326
  /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk23IETCLVcjs.MessageScrollerButton, { variant: "outline", className: "rounded-full shadow-sm", children: [
286
327
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.ArrowDown, { className: "h-4 w-4" }),
287
328
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "sr-only", children: scrollButtonLabel })
288
- ] })
329
+ ] }),
330
+ navigator && navigator.length > 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
331
+ _chunkZBWL7OUWcjs.ChatTurnNavigator,
332
+ {
333
+ items: navigator,
334
+ order: messages.map((m) => m.id),
335
+ label: navigatorLabel,
336
+ side: navigatorSide,
337
+ columnWidth: workspace ? 768 : void 0
338
+ }
339
+ ) : null
289
340
  ]
290
341
  }
291
342
  ) });
@@ -308,7 +359,7 @@ function ChatCopyAction({
308
359
  "data-copied": copied || void 0,
309
360
  onClick: async () => {
310
361
  const ok = await copy(text);
311
- _optionalChain([onCopy, 'optionalCall', _ => _(ok)]);
362
+ _optionalChain([onCopy, 'optionalCall', _3 => _3(ok)]);
312
363
  },
313
364
  children: copied ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Check, {}) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Copy, {})
314
365
  }
@@ -353,7 +404,7 @@ function ChatAttachmentList({
353
404
  {
354
405
  "aria-label": `${verb} ${attachment.name}`,
355
406
  title: `${verb} ${attachment.name}`,
356
- onClick: () => _optionalChain([onOpen, 'optionalCall', _2 => _2(attachment)]),
407
+ onClick: () => _optionalChain([onOpen, 'optionalCall', _4 => _4(attachment)]),
357
408
  className: "rounded-xl focus-visible:ring-2 focus-visible:ring-ring/50"
358
409
  }
359
410
  ),
@@ -533,7 +584,7 @@ function ChatInput({
533
584
  "data-slot": "chat-input-actions",
534
585
  onClick: (event) => {
535
586
  if (!event.currentTarget.contains(event.target)) return;
536
- if (!event.target.closest("button")) _optionalChain([ref, 'access', _3 => _3.current, 'optionalAccess', _4 => _4.focus, 'call', _5 => _5()]);
587
+ if (!event.target.closest("button")) _optionalChain([ref, 'access', _5 => _5.current, 'optionalAccess', _6 => _6.focus, 'call', _7 => _7()]);
537
588
  },
538
589
  className: _chunkKWLSBTB2cjs.cn.call(void 0,
539
590
  compact ? "gap-1 px-2 pb-2 pt-0" : "gap-2",
@@ -552,7 +603,7 @@ function ChatInput({
552
603
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Plus, { className: _chunkKWLSBTB2cjs.cn.call(void 0, spacious && "h-5 w-5") })
553
604
  }
554
605
  ) }),
555
- /* @__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: [
606
+ /* @__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', _8 => _8.current, 'optionalAccess', _9 => _9.click, 'call', _10 => _10()]), children: [
556
607
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Paperclip, { className: "h-4 w-4" }),
557
608
  addFilesLabel
558
609
  ] }) })
@@ -719,6 +770,92 @@ function ChatSuggestions({
719
770
  )
720
771
  ] });
721
772
  }
773
+ var DEFAULT_EMPTY_ACTIONS_LABEL = "Pick up where you left off";
774
+ function ChatEmptyActionIcon({ icon }) {
775
+ if (!icon) return null;
776
+ if (_react.isValidElement.call(void 0, icon)) return icon;
777
+ if (typeof icon === "function" || typeof icon === "object" && icon !== null && "render" in icon) {
778
+ const IconComp = icon;
779
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, IconComp, { "aria-hidden": true });
780
+ }
781
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _jsxruntime.Fragment, { children: icon });
782
+ }
783
+ function ChatEmptyActions({
784
+ items,
785
+ label,
786
+ onError,
787
+ className
788
+ }) {
789
+ const labelId = _react.useId.call(void 0, );
790
+ const [running, setRunning] = _react.useState.call(void 0, null);
791
+ if (items.length === 0) return null;
792
+ const showLabel = label.length > 0;
793
+ const pick = async (item) => {
794
+ if (running !== null) return;
795
+ let result;
796
+ try {
797
+ result = item.onSelect();
798
+ } catch (cause) {
799
+ onError(cause);
800
+ return;
801
+ }
802
+ if (!result || typeof result.then !== "function") return;
803
+ setRunning(item.id);
804
+ try {
805
+ await result;
806
+ } catch (cause) {
807
+ onError(cause);
808
+ } finally {
809
+ setRunning(null);
810
+ }
811
+ };
812
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { "data-slot": "chat-empty-actions", className: _chunkKWLSBTB2cjs.cn.call(void 0, "w-full text-left", className), children: [
813
+ showLabel && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { id: labelId, "data-slot": "chat-empty-actions-label", className: "mb-1.5 px-3 text-xs text-muted-foreground", children: label }),
814
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
815
+ "div",
816
+ {
817
+ role: "group",
818
+ "aria-labelledby": showLabel ? labelId : void 0,
819
+ "aria-label": showLabel ? void 0 : DEFAULT_EMPTY_ACTIONS_LABEL,
820
+ className: "flex flex-col gap-0.5",
821
+ children: items.map((item) => {
822
+ const busy = running === item.id;
823
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDHXKCMKYcjs.Item, { asChild: true, size: "sm", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
824
+ "button",
825
+ {
826
+ type: "button",
827
+ "data-slot": "chat-empty-action",
828
+ "data-state": busy ? "loading" : void 0,
829
+ "aria-busy": busy || void 0,
830
+ disabled: item.disabled || running !== null,
831
+ onClick: () => void pick(item),
832
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
833
+ "min-h-11 cursor-pointer flex-nowrap text-left",
834
+ "hover:bg-muted focus-visible:bg-muted",
835
+ "disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:bg-transparent",
836
+ busy && "disabled:opacity-100"
837
+ ),
838
+ children: [
839
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
840
+ _chunkDHXKCMKYcjs.ItemMedia,
841
+ {
842
+ variant: "icon",
843
+ className: "size-8 self-center rounded-md bg-muted text-muted-foreground group-has-data-[slot=item-description]/item:translate-y-0 group-has-data-[slot=item-description]/item:self-center",
844
+ children: busy ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkVMXKT67Ecjs.Spinner, { "aria-hidden": true }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatEmptyActionIcon, { icon: item.icon })
845
+ }
846
+ ),
847
+ /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunkDHXKCMKYcjs.ItemContent, { className: "min-w-0 flex-row flex-wrap items-baseline gap-x-2 gap-y-0.5 md:flex-nowrap", children: [
848
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDHXKCMKYcjs.ItemTitle, { className: "w-auto min-w-0 shrink-0 font-normal text-foreground md:max-w-[60%] md:truncate", children: item.label }),
849
+ item.detail && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkDHXKCMKYcjs.ItemDescription, { className: "line-clamp-1 min-w-0 flex-1 truncate text-xs md:text-sm", children: item.detail })
850
+ ] })
851
+ ]
852
+ }
853
+ ) }, item.id);
854
+ })
855
+ }
856
+ )
857
+ ] });
858
+ }
722
859
  function MobileReplySuggestions({
723
860
  items,
724
861
  onSelect,
@@ -790,11 +927,11 @@ function MobileReplySuggestions({
790
927
  className: "max-h-[min(85dvh,40rem)] overflow-hidden pb-[env(safe-area-inset-bottom)]",
791
928
  onOpenAutoFocus: (event) => {
792
929
  event.preventDefault();
793
- _optionalChain([closeRef, 'access', _9 => _9.current, 'optionalAccess', _10 => _10.focus, 'call', _11 => _11({ preventScroll: true })]);
930
+ _optionalChain([closeRef, 'access', _11 => _11.current, 'optionalAccess', _12 => _12.focus, 'call', _13 => _13({ preventScroll: true })]);
794
931
  },
795
932
  onCloseAutoFocus: (event) => {
796
933
  event.preventDefault();
797
- if (_optionalChain([triggerRef, 'access', _12 => _12.current, 'optionalAccess', _13 => _13.isConnected]) && !triggerRef.current.disabled) {
934
+ if (_optionalChain([triggerRef, 'access', _14 => _14.current, 'optionalAccess', _15 => _15.isConnected]) && !triggerRef.current.disabled) {
798
935
  triggerRef.current.focus({ preventScroll: true });
799
936
  }
800
937
  },
@@ -874,6 +1011,8 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
874
1011
  emptySubtitle,
875
1012
  examplePrompts = [],
876
1013
  examplePromptsLabel = "Try",
1014
+ emptyActions = [],
1015
+ emptyActionsLabel = DEFAULT_EMPTY_ACTIONS_LABEL,
877
1016
  examplePromptsVariant = "chips",
878
1017
  examplePromptsLoading = false,
879
1018
  examplePromptsLoadingLabel = "Loading suggestions\u2026",
@@ -921,6 +1060,9 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
921
1060
  onOpenAttachment,
922
1061
  attachmentAction,
923
1062
  messageActions,
1063
+ navigator,
1064
+ navigatorLabel,
1065
+ navigatorSide,
924
1066
  className
925
1067
  }, ref) {
926
1068
  const isMobile = _chunkR35FT53Gcjs.useIsMobile.call(void 0, );
@@ -951,7 +1093,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
951
1093
  }
952
1094
  reportSendError(cause);
953
1095
  }
954
- window.setTimeout(() => _optionalChain([inputRef, 'access', _14 => _14.current, 'optionalAccess', _15 => _15.focus, 'call', _16 => _16()]), 0);
1096
+ window.setTimeout(() => _optionalChain([inputRef, 'access', _16 => _16.current, 'optionalAccess', _17 => _17.focus, 'call', _18 => _18()]), 0);
955
1097
  },
956
1098
  [attachments, mode, onSend, pending, reportSendError]
957
1099
  );
@@ -964,14 +1106,14 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
964
1106
  } catch (cause) {
965
1107
  reportSendError(cause);
966
1108
  }
967
- if (focusInput) window.setTimeout(() => _optionalChain([inputRef, 'access', _17 => _17.current, 'optionalAccess', _18 => _18.focus, 'call', _19 => _19()]), 0);
1109
+ if (focusInput) window.setTimeout(() => _optionalChain([inputRef, 'access', _19 => _19.current, 'optionalAccess', _20 => _20.focus, 'call', _21 => _21()]), 0);
968
1110
  },
969
1111
  [onSend, pending, reportSendError]
970
1112
  );
971
1113
  _react.useImperativeHandle.call(void 0,
972
1114
  ref,
973
1115
  () => ({
974
- focusInput: () => _optionalChain([inputRef, 'access', _20 => _20.current, 'optionalAccess', _21 => _21.focus, 'call', _22 => _22()]),
1116
+ focusInput: () => _optionalChain([inputRef, 'access', _22 => _22.current, 'optionalAccess', _23 => _23.focus, 'call', _24 => _24()]),
975
1117
  clearInput: () => setInput("")
976
1118
  }),
977
1119
  []
@@ -979,7 +1121,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
979
1121
  const dropFiles = _react.useCallback.call(void 0,
980
1122
  (files) => {
981
1123
  if (!conversationStarted && mode === "voice") setUserMode("text");
982
- return _optionalChain([onFilesSelected, 'optionalCall', _23 => _23(files)]);
1124
+ return _optionalChain([onFilesSelected, 'optionalCall', _25 => _25(files)]);
983
1125
  },
984
1126
  [conversationStarted, mode, onFilesSelected]
985
1127
  );
@@ -1110,6 +1252,15 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1110
1252
  }
1111
1253
  ),
1112
1254
  error && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatError, { children: error }),
1255
+ !pending && emptyActions.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1256
+ ChatEmptyActions,
1257
+ {
1258
+ items: emptyActions,
1259
+ label: emptyActionsLabel,
1260
+ onError: reportSendError,
1261
+ className: "mt-3"
1262
+ }
1263
+ ),
1113
1264
  !pending && examplePromptsLoading && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ExamplePromptsLoading, { label: examplePromptsLoadingLabel }),
1114
1265
  !pending && !examplePromptsLoading && examplePrompts.length > 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
1115
1266
  ChatSuggestions,
@@ -1144,6 +1295,9 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1144
1295
  onOpenAttachment,
1145
1296
  attachmentAction,
1146
1297
  messageActions,
1298
+ navigator,
1299
+ navigatorLabel,
1300
+ navigatorSide,
1147
1301
  className: _chunkKWLSBTB2cjs.cn.call(void 0,
1148
1302
  "mb-3",
1149
1303
  application && "min-h-0 flex-1 px-4 py-4 md:px-0 md:py-0",
@@ -1187,7 +1341,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1187
1341
  closeLabel: replySuggestionsCloseLabel,
1188
1342
  disabled: pending
1189
1343
  },
1190
- JSON.stringify([_optionalChain([messages, 'access', _24 => _24.at, 'call', _25 => _25(-1), 'optionalAccess', _26 => _26.id]), replySuggestions])
1344
+ JSON.stringify([_optionalChain([messages, 'access', _26 => _26.at, 'call', _27 => _27(-1), 'optionalAccess', _28 => _28.id]), replySuggestions])
1191
1345
  ) : void 0,
1192
1346
  value: input,
1193
1347
  onChange: setInput,
@@ -1226,4 +1380,5 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
1226
1380
 
1227
1381
 
1228
1382
 
1229
- exports.Chat = Chat; exports.ChatCopyAction = ChatCopyAction; exports.ChatInput = ChatInput; exports.ChatThread = ChatThread;
1383
+
1384
+ exports.Chat = Chat; exports.ChatCopyAction = ChatCopyAction; exports.ChatInput = ChatInput; exports.ChatThread = ChatThread; exports.authorLines = authorLines;
@@ -1,7 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React$1 from 'react';
3
3
  import { ReactNode } from 'react';
4
+ import { LucideIcon } from 'lucide-react';
4
5
  import { VoiceSessionControlsProps } from './voice-session-controls.cjs';
6
+ import { ChatNavigatorItem } from './chat-turn-navigator.cjs';
5
7
  import { MarkdownComponents } from './markdown.cjs';
6
8
  import './button.cjs';
7
9
  import 'class-variance-authority/types';
@@ -10,6 +12,17 @@ import 'react-markdown';
10
12
  import './rich-text.cjs';
11
13
 
12
14
  type ChatRole = "user" | "assistant";
15
+ /** A turn's author, as the thread names them above their turns. */
16
+ interface ChatMessageAuthor {
17
+ /** Stable for the person: consecutive turns with the same id are named once. */
18
+ id: string;
19
+ name: string;
20
+ /** Their photo; initials stand in without one. */
21
+ avatarUrl?: string | null;
22
+ /** A quiet note after the name, such as how the turn arrived ("by email"). */
23
+ detail?: string;
24
+ }
25
+
13
26
  type ChatAttachment = {
14
27
  id: string;
15
28
  name: string;
@@ -38,6 +51,12 @@ type ChatMessage = {
38
51
  content?: ReactNode;
39
52
  /** Files that belong to this turn. */
40
53
  attachments?: ReadonlyArray<ChatAttachment>;
54
+ /**
55
+ * Who wrote the turn. The thread names them, with their avatar, above the
56
+ * first of a run of turns by the same author, so a conversation with
57
+ * several people in it says who said what. Turns without one are unnamed.
58
+ */
59
+ author?: ChatMessageAuthor;
41
60
  /**
42
61
  * `"running"`: a reply still being worked on. The thread draws the turn as
43
62
  * its running line, the one `pending` adds at the end, labelled with `text`
@@ -105,8 +124,24 @@ interface ChatThreadProps {
105
124
  * pointer reaches it; return `null` for none.
106
125
  */
107
126
  messageActions?: (message: ChatMessage) => ReactNode;
127
+ /**
128
+ * Places to jump to, drawn as a rail of ticks in the thread's gutter (see
129
+ * `ChatTurnNavigator`): the one in view marked, a preview of each on hover
130
+ * or focus. It shows only where the gutter has room; omit it for none.
131
+ */
132
+ navigator?: readonly ChatNavigatorItem[];
133
+ /** The rail's landmark name. */
134
+ navigatorLabel?: string;
135
+ /** The gutter the rail sits in. */
136
+ navigatorSide?: "start" | "end";
108
137
  className?: string;
109
138
  }
139
+ /**
140
+ * Which turns name their author: the first of each run of turns by the same
141
+ * author on the same side. A time marker, a turn by someone else or a turn
142
+ * with no author between them starts a new run; a running line doesn't.
143
+ */
144
+ declare function authorLines(messages: ReadonlyArray<ChatMessage>, markers: ReadonlyArray<string | null>): boolean[];
110
145
  /**
111
146
  * Scrollable message thread, built on shadcn's headless `MessageScroller`
112
147
  * (streaming-aware auto-scroll, anchored turns) + the `Message`/`Bubble`
@@ -225,6 +260,24 @@ interface ChatInputProps {
225
260
  * device-aware default modes.
226
261
  */
227
262
  declare function ChatInput({ value, onChange, onSend, pending, disabled, placeholder, size, uploadingLabel, voice, voiceSession, voiceTitle, voiceLang, submitLabel, autoFocus, className, inputRef, attachments, onFilesSelected, onRemoveAttachment, accept, multiple, addFilesLabel, appearance, compactOnBlur, header, helperText, privacyNote, }: ChatInputProps): react_jsx_runtime.JSX.Element;
263
+ /** A next step offered in the empty state, under the composer (`emptyActions`). */
264
+ interface ChatEmptyAction {
265
+ /** Stable key. */
266
+ id: string;
267
+ /** What picking it does, e.g. "Review 3 new people". */
268
+ label: ReactNode;
269
+ /** Where it applies, muted after the label, e.g. "Head of Sales · Northwind". One line, truncated. */
270
+ detail?: ReactNode;
271
+ /** Leading icon: a Lucide component or an element. */
272
+ icon?: LucideIcon | ReactNode;
273
+ /**
274
+ * Called when picked. While a returned promise is pending, that row shows a
275
+ * spinner and every row is disabled; a rejection re-enables them and is
276
+ * reported.
277
+ */
278
+ onSelect: () => void | Promise<void>;
279
+ disabled?: boolean;
280
+ }
228
281
  interface ChatProps {
229
282
  /** Conversation messages. */
230
283
  messages: ChatMessage[];
@@ -270,6 +323,21 @@ interface ChatProps {
270
323
  examplePrompts?: ReadonlyArray<string>;
271
324
  /** Label above the example prompts. Defaults to "Try". */
272
325
  examplePromptsLabel?: string;
326
+ /**
327
+ * Next steps under the empty-state composer, before `examplePrompts`,
328
+ * ChatGPT-style: a quiet list of rows, each an icon, what picking it does
329
+ * and where it applies (`ChatEmptyAction`). Picking one calls its
330
+ * `onSelect`; nothing is sent here. While a returned promise is pending
331
+ * the row shows a spinner and every row is disabled. Hidden while
332
+ * `pending`, like the example prompts.
333
+ */
334
+ emptyActions?: ReadonlyArray<ChatEmptyAction>;
335
+ /**
336
+ * The actions' small heading and accessible name. Defaults to "Pick up
337
+ * where you left off"; `""` hides the heading and keeps that default as
338
+ * the list's accessible name.
339
+ */
340
+ emptyActionsLabel?: string;
273
341
  /**
274
342
  * `chips` (the default) wrap onto as many lines as they need; `row` is one
275
343
  * line that scrolls sideways, for a few short prompts where height is
@@ -425,6 +493,15 @@ interface ChatProps {
425
493
  * assistant turns, by default), such as `ChatCopyAction`. See `ChatThread`.
426
494
  */
427
495
  messageActions?: (message: ChatMessage) => ReactNode;
496
+ /**
497
+ * Places in the conversation to jump to, as a rail of ticks in the
498
+ * thread's gutter, each previewed on hover or focus. See `ChatThread`.
499
+ */
500
+ navigator?: readonly ChatNavigatorItem[];
501
+ /** The rail's landmark name. Defaults to "Conversation". */
502
+ navigatorLabel?: string;
503
+ /** The gutter the rail sits in. Defaults to `"start"`. */
504
+ navigatorSide?: "start" | "end";
428
505
  className?: string;
429
506
  }
430
507
  interface ChatHandle {
@@ -457,4 +534,4 @@ interface ChatHandle {
457
534
  */
458
535
  declare const Chat: React$1.ForwardRefExoticComponent<ChatProps & React$1.RefAttributes<ChatHandle>>;
459
536
 
460
- export { Chat, type ChatAttachment, type ChatAttachmentAction, ChatCopyAction, type ChatCopyActionProps, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps };
537
+ export { Chat, type ChatAttachment, type ChatAttachmentAction, ChatCopyAction, type ChatCopyActionProps, type ChatEmptyAction, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatMessageAuthor, ChatNavigatorItem, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps, authorLines };
@@ -1,7 +1,9 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as React$1 from 'react';
3
3
  import { ReactNode } from 'react';
4
+ import { LucideIcon } from 'lucide-react';
4
5
  import { VoiceSessionControlsProps } from './voice-session-controls.js';
6
+ import { ChatNavigatorItem } from './chat-turn-navigator.js';
5
7
  import { MarkdownComponents } from './markdown.js';
6
8
  import './button.js';
7
9
  import 'class-variance-authority/types';
@@ -10,6 +12,17 @@ import 'react-markdown';
10
12
  import './rich-text.js';
11
13
 
12
14
  type ChatRole = "user" | "assistant";
15
+ /** A turn's author, as the thread names them above their turns. */
16
+ interface ChatMessageAuthor {
17
+ /** Stable for the person: consecutive turns with the same id are named once. */
18
+ id: string;
19
+ name: string;
20
+ /** Their photo; initials stand in without one. */
21
+ avatarUrl?: string | null;
22
+ /** A quiet note after the name, such as how the turn arrived ("by email"). */
23
+ detail?: string;
24
+ }
25
+
13
26
  type ChatAttachment = {
14
27
  id: string;
15
28
  name: string;
@@ -38,6 +51,12 @@ type ChatMessage = {
38
51
  content?: ReactNode;
39
52
  /** Files that belong to this turn. */
40
53
  attachments?: ReadonlyArray<ChatAttachment>;
54
+ /**
55
+ * Who wrote the turn. The thread names them, with their avatar, above the
56
+ * first of a run of turns by the same author, so a conversation with
57
+ * several people in it says who said what. Turns without one are unnamed.
58
+ */
59
+ author?: ChatMessageAuthor;
41
60
  /**
42
61
  * `"running"`: a reply still being worked on. The thread draws the turn as
43
62
  * its running line, the one `pending` adds at the end, labelled with `text`
@@ -105,8 +124,24 @@ interface ChatThreadProps {
105
124
  * pointer reaches it; return `null` for none.
106
125
  */
107
126
  messageActions?: (message: ChatMessage) => ReactNode;
127
+ /**
128
+ * Places to jump to, drawn as a rail of ticks in the thread's gutter (see
129
+ * `ChatTurnNavigator`): the one in view marked, a preview of each on hover
130
+ * or focus. It shows only where the gutter has room; omit it for none.
131
+ */
132
+ navigator?: readonly ChatNavigatorItem[];
133
+ /** The rail's landmark name. */
134
+ navigatorLabel?: string;
135
+ /** The gutter the rail sits in. */
136
+ navigatorSide?: "start" | "end";
108
137
  className?: string;
109
138
  }
139
+ /**
140
+ * Which turns name their author: the first of each run of turns by the same
141
+ * author on the same side. A time marker, a turn by someone else or a turn
142
+ * with no author between them starts a new run; a running line doesn't.
143
+ */
144
+ declare function authorLines(messages: ReadonlyArray<ChatMessage>, markers: ReadonlyArray<string | null>): boolean[];
110
145
  /**
111
146
  * Scrollable message thread, built on shadcn's headless `MessageScroller`
112
147
  * (streaming-aware auto-scroll, anchored turns) + the `Message`/`Bubble`
@@ -225,6 +260,24 @@ interface ChatInputProps {
225
260
  * device-aware default modes.
226
261
  */
227
262
  declare function ChatInput({ value, onChange, onSend, pending, disabled, placeholder, size, uploadingLabel, voice, voiceSession, voiceTitle, voiceLang, submitLabel, autoFocus, className, inputRef, attachments, onFilesSelected, onRemoveAttachment, accept, multiple, addFilesLabel, appearance, compactOnBlur, header, helperText, privacyNote, }: ChatInputProps): react_jsx_runtime.JSX.Element;
263
+ /** A next step offered in the empty state, under the composer (`emptyActions`). */
264
+ interface ChatEmptyAction {
265
+ /** Stable key. */
266
+ id: string;
267
+ /** What picking it does, e.g. "Review 3 new people". */
268
+ label: ReactNode;
269
+ /** Where it applies, muted after the label, e.g. "Head of Sales · Northwind". One line, truncated. */
270
+ detail?: ReactNode;
271
+ /** Leading icon: a Lucide component or an element. */
272
+ icon?: LucideIcon | ReactNode;
273
+ /**
274
+ * Called when picked. While a returned promise is pending, that row shows a
275
+ * spinner and every row is disabled; a rejection re-enables them and is
276
+ * reported.
277
+ */
278
+ onSelect: () => void | Promise<void>;
279
+ disabled?: boolean;
280
+ }
228
281
  interface ChatProps {
229
282
  /** Conversation messages. */
230
283
  messages: ChatMessage[];
@@ -270,6 +323,21 @@ interface ChatProps {
270
323
  examplePrompts?: ReadonlyArray<string>;
271
324
  /** Label above the example prompts. Defaults to "Try". */
272
325
  examplePromptsLabel?: string;
326
+ /**
327
+ * Next steps under the empty-state composer, before `examplePrompts`,
328
+ * ChatGPT-style: a quiet list of rows, each an icon, what picking it does
329
+ * and where it applies (`ChatEmptyAction`). Picking one calls its
330
+ * `onSelect`; nothing is sent here. While a returned promise is pending
331
+ * the row shows a spinner and every row is disabled. Hidden while
332
+ * `pending`, like the example prompts.
333
+ */
334
+ emptyActions?: ReadonlyArray<ChatEmptyAction>;
335
+ /**
336
+ * The actions' small heading and accessible name. Defaults to "Pick up
337
+ * where you left off"; `""` hides the heading and keeps that default as
338
+ * the list's accessible name.
339
+ */
340
+ emptyActionsLabel?: string;
273
341
  /**
274
342
  * `chips` (the default) wrap onto as many lines as they need; `row` is one
275
343
  * line that scrolls sideways, for a few short prompts where height is
@@ -425,6 +493,15 @@ interface ChatProps {
425
493
  * assistant turns, by default), such as `ChatCopyAction`. See `ChatThread`.
426
494
  */
427
495
  messageActions?: (message: ChatMessage) => ReactNode;
496
+ /**
497
+ * Places in the conversation to jump to, as a rail of ticks in the
498
+ * thread's gutter, each previewed on hover or focus. See `ChatThread`.
499
+ */
500
+ navigator?: readonly ChatNavigatorItem[];
501
+ /** The rail's landmark name. Defaults to "Conversation". */
502
+ navigatorLabel?: string;
503
+ /** The gutter the rail sits in. Defaults to `"start"`. */
504
+ navigatorSide?: "start" | "end";
428
505
  className?: string;
429
506
  }
430
507
  interface ChatHandle {
@@ -457,4 +534,4 @@ interface ChatHandle {
457
534
  */
458
535
  declare const Chat: React$1.ForwardRefExoticComponent<ChatProps & React$1.RefAttributes<ChatHandle>>;
459
536
 
460
- export { Chat, type ChatAttachment, type ChatAttachmentAction, ChatCopyAction, type ChatCopyActionProps, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps };
537
+ export { Chat, type ChatAttachment, type ChatAttachmentAction, ChatCopyAction, type ChatCopyActionProps, type ChatEmptyAction, type ChatHandle, ChatInput, type ChatInputProps, type ChatMessage, type ChatMessageAuthor, ChatNavigatorItem, type ChatProps, type ChatRole, ChatThread, type ChatThreadProps, authorLines };