@epicbagel/saas-kit 1.2.2 → 1.3.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.
@@ -0,0 +1,143 @@
1
+ "use client";
2
+ import {
3
+ Drawer,
4
+ DrawerClose,
5
+ DrawerContent,
6
+ DrawerDescription,
7
+ DrawerFooter,
8
+ DrawerHeader,
9
+ DrawerTitle,
10
+ DrawerTrigger
11
+ } from "./chunk-4HWCNRK4.js";
12
+ import {
13
+ Dialog,
14
+ DialogClose,
15
+ DialogContent,
16
+ DialogDescription,
17
+ DialogFooter,
18
+ DialogHeader,
19
+ DialogTitle,
20
+ DialogTrigger
21
+ } from "./chunk-3QT5VYZ3.js";
22
+ import {
23
+ cn
24
+ } from "./chunk-G6SRSWXB.js";
25
+
26
+ // src/primitives/responsive-dialog.tsx
27
+ import * as React from "react";
28
+ import { useMediaQuery } from "react-responsive";
29
+ import { jsx } from "react/jsx-runtime";
30
+ var DesktopContext = React.createContext(false);
31
+ var useIsDesktop = () => React.useContext(DesktopContext);
32
+ var DESKTOP_QUERY = { minWidth: 1024 };
33
+ var ResponsiveDialog = ({ children, ...props }) => {
34
+ const isDesktop = useMediaQuery(DESKTOP_QUERY);
35
+ const Root = isDesktop ? Dialog : Drawer;
36
+ return /* @__PURE__ */ jsx(DesktopContext.Provider, { value: isDesktop, children: /* @__PURE__ */ jsx(Root, { ...props, children }) });
37
+ };
38
+ ResponsiveDialog.displayName = "ResponsiveDialog";
39
+ var ResponsiveDialogTrigger = ({
40
+ children,
41
+ ...props
42
+ }) => {
43
+ const Trigger = useIsDesktop() ? DialogTrigger : DrawerTrigger;
44
+ return /* @__PURE__ */ jsx(Trigger, { ...props, children });
45
+ };
46
+ ResponsiveDialogTrigger.displayName = "ResponsiveDialogTrigger";
47
+ var ResponsiveDialogClose = ({
48
+ children,
49
+ ...props
50
+ }) => {
51
+ const Close = useIsDesktop() ? DialogClose : DrawerClose;
52
+ return /* @__PURE__ */ jsx(Close, { ...props, children });
53
+ };
54
+ ResponsiveDialogClose.displayName = "ResponsiveDialogClose";
55
+ var ResponsiveDialogContent = React.forwardRef(({ className, children, presentation = "default", ...props }, ref) => {
56
+ if (useIsDesktop()) {
57
+ return /* @__PURE__ */ jsx(
58
+ DialogContent,
59
+ {
60
+ ref,
61
+ className: cn(
62
+ "flex max-h-[85vh] max-w-md flex-col gap-0 overflow-hidden p-0",
63
+ presentation === "preview" && "max-w-2xl sm:h-[85vh] [&>button:last-child]:hidden",
64
+ className
65
+ ),
66
+ ...props,
67
+ children
68
+ }
69
+ );
70
+ }
71
+ return /* @__PURE__ */ jsx(
72
+ DrawerContent,
73
+ {
74
+ ref,
75
+ className: cn("max-h-[92vh]", className),
76
+ ...props,
77
+ children
78
+ }
79
+ );
80
+ });
81
+ ResponsiveDialogContent.displayName = "ResponsiveDialogContent";
82
+ var ResponsiveDialogHeader = ({
83
+ className,
84
+ visibility = "visible",
85
+ ...props
86
+ }) => {
87
+ const Header = useIsDesktop() ? DialogHeader : DrawerHeader;
88
+ return /* @__PURE__ */ jsx(
89
+ Header,
90
+ {
91
+ className: cn(useIsDesktop() && "p-4", visibility === "screen-reader" && "sr-only", className),
92
+ ...props
93
+ }
94
+ );
95
+ };
96
+ ResponsiveDialogHeader.displayName = "ResponsiveDialogHeader";
97
+ var ResponsiveDialogBody = ({
98
+ className,
99
+ ...props
100
+ }) => /* @__PURE__ */ jsx("div", { className: cn("min-h-0 flex-1 overflow-y-auto px-4 pb-6", className), ...props });
101
+ ResponsiveDialogBody.displayName = "ResponsiveDialogBody";
102
+ var ResponsiveDialogFooter = ({
103
+ className,
104
+ layout = "auto",
105
+ ...props
106
+ }) => {
107
+ const isDesktop = useIsDesktop();
108
+ const Footer = isDesktop ? DialogFooter : DrawerFooter;
109
+ return /* @__PURE__ */ jsx(
110
+ Footer,
111
+ {
112
+ className: cn(
113
+ "mt-0 shrink-0 gap-2 border-t bg-background p-4",
114
+ layout === "fill" ? "flex-row space-x-0 [&>*]:min-w-0 [&>*]:flex-1" : isDesktop ? "flex-row justify-end space-x-0 [&>*]:flex-none" : "grid grid-cols-2 [&>*]:w-full",
115
+ className
116
+ ),
117
+ ...props
118
+ }
119
+ );
120
+ };
121
+ ResponsiveDialogFooter.displayName = "ResponsiveDialogFooter";
122
+ var ResponsiveDialogTitle = React.forwardRef(({ className, visuallyHidden = false, ...props }, ref) => {
123
+ const Title = useIsDesktop() ? DialogTitle : DrawerTitle;
124
+ return /* @__PURE__ */ jsx(Title, { ref, className: cn(visuallyHidden && "sr-only", className), ...props });
125
+ });
126
+ ResponsiveDialogTitle.displayName = "ResponsiveDialogTitle";
127
+ var ResponsiveDialogDescription = React.forwardRef(({ className, visuallyHidden = false, ...props }, ref) => {
128
+ const Description = useIsDesktop() ? DialogDescription : DrawerDescription;
129
+ return /* @__PURE__ */ jsx(Description, { ref, className: cn(visuallyHidden && "sr-only", className), ...props });
130
+ });
131
+ ResponsiveDialogDescription.displayName = "ResponsiveDialogDescription";
132
+
133
+ export {
134
+ ResponsiveDialog,
135
+ ResponsiveDialogTrigger,
136
+ ResponsiveDialogClose,
137
+ ResponsiveDialogContent,
138
+ ResponsiveDialogHeader,
139
+ ResponsiveDialogBody,
140
+ ResponsiveDialogFooter,
141
+ ResponsiveDialogTitle,
142
+ ResponsiveDialogDescription
143
+ };
@@ -0,0 +1,53 @@
1
+ "use client";
2
+ import {
3
+ cn
4
+ } from "./chunk-G6SRSWXB.js";
5
+
6
+ // src/primitives/image.tsx
7
+ import { forwardRef, useEffect, useState } from "react";
8
+ import { jsx } from "react/jsx-runtime";
9
+ var fitStyles = { cover: "object-cover", contain: "object-contain" };
10
+ var radiusStyles = { none: "", sm: "rounded-sm", md: "rounded-md", xl: "rounded-xl", full: "rounded-full" };
11
+ var Image = forwardRef(function Image2({ fit = "cover", frameHeight, radius = "none", width = "auto", align = "start", border = false, shadow = "none", emphasis = "full", loadingIndicator = "none", className, onLoad, onError, src, ...props }, ref) {
12
+ const [loaded, setLoaded] = useState(loadingIndicator === "none" || !src);
13
+ useEffect(() => {
14
+ setLoaded(loadingIndicator === "none" || !src);
15
+ }, [loadingIndicator, src]);
16
+ const loading = loadingIndicator === "pulse" && !loaded;
17
+ return /* @__PURE__ */ jsx(
18
+ "img",
19
+ {
20
+ ref,
21
+ className: cn(
22
+ fitStyles[fit],
23
+ radiusStyles[radius],
24
+ frameHeight === "sm" && "h-16",
25
+ frameHeight === "md" && "h-28",
26
+ width === "full" && "w-full",
27
+ width === "preview" && "w-full max-w-[380px]",
28
+ width === "preview-lg" && "w-full max-w-[440px]",
29
+ align === "center" && "mx-auto",
30
+ border && "border",
31
+ shadow === "subtle" && "shadow-xs",
32
+ loading && "animate-pulse bg-muted",
33
+ emphasis === "soft-until-parent-hover" && "transition-opacity duration-200 md:opacity-70 md:group-hover:opacity-100",
34
+ className
35
+ ),
36
+ src,
37
+ ...props,
38
+ "aria-busy": loading || void 0,
39
+ onLoad: (event) => {
40
+ setLoaded(true);
41
+ onLoad?.(event);
42
+ },
43
+ onError: (event) => {
44
+ setLoaded(true);
45
+ onError?.(event);
46
+ }
47
+ }
48
+ );
49
+ });
50
+
51
+ export {
52
+ Image
53
+ };
@@ -0,0 +1,55 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});"use client";
2
+
3
+
4
+ var _chunkVKIGJNJRcjs = require('./chunk-VKIGJNJR.cjs');
5
+
6
+
7
+ var _chunkKWLSBTB2cjs = require('./chunk-KWLSBTB2.cjs');
8
+
9
+ // src/primitives/code-block.tsx
10
+ var _jsxruntime = require('react/jsx-runtime');
11
+ var HEIGHTS = {
12
+ none: "",
13
+ sm: "max-h-40 overflow-y-auto",
14
+ md: "max-h-64 overflow-y-auto",
15
+ lg: "max-h-96 overflow-y-auto",
16
+ viewport: "min-h-[55vh] max-h-[65vh] overflow-y-auto"
17
+ };
18
+ function CodeBlock({
19
+ children,
20
+ copy,
21
+ copyLabel,
22
+ wrap = false,
23
+ maxHeight = "none",
24
+ className
25
+ }) {
26
+ const copyText = typeof copy === "string" ? copy : children;
27
+ return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: _chunkKWLSBTB2cjs.cn.call(void 0, "relative", className), children: [
28
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
29
+ "pre",
30
+ {
31
+ className: _chunkKWLSBTB2cjs.cn.call(void 0,
32
+ "rounded-md border border-border bg-muted/40 p-3 font-mono text-xs leading-relaxed",
33
+ wrap ? "whitespace-pre-wrap break-words" : "overflow-x-auto",
34
+ HEIGHTS[maxHeight],
35
+ copy ? "pr-12" : void 0
36
+ ),
37
+ children
38
+ }
39
+ ),
40
+ copy ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
41
+ _chunkVKIGJNJRcjs.CopyButton,
42
+ {
43
+ text: copyText,
44
+ label: copyLabel,
45
+ size: "sm",
46
+ variant: "ghost",
47
+ className: "absolute right-1.5 top-1.5"
48
+ }
49
+ ) : null
50
+ ] });
51
+ }
52
+
53
+
54
+
55
+ exports.CodeBlock = CodeBlock;
@@ -15,19 +15,19 @@ var _chunkSUB4BAYEcjs = require('../chunk-SUB4BAYE.cjs');
15
15
 
16
16
 
17
17
 
18
- var _chunkB765DSRGcjs = require('../chunk-B765DSRG.cjs');
19
18
 
20
19
 
21
20
 
22
21
 
22
+ var _chunkXABXUP6Lcjs = require('../chunk-XABXUP6L.cjs');
23
23
 
24
24
 
25
25
 
26
- var _chunkXABXUP6Lcjs = require('../chunk-XABXUP6L.cjs');
26
+ var _chunk2AU7ZRGXcjs = require('../chunk-2AU7ZRGX.cjs');
27
27
 
28
28
 
29
29
 
30
- var _chunk2AU7ZRGXcjs = require('../chunk-2AU7ZRGX.cjs');
30
+ var _chunkB765DSRGcjs = require('../chunk-B765DSRG.cjs');
31
31
 
32
32
 
33
33
 
@@ -36,12 +36,12 @@ var _chunkRIWLAMMEcjs = require('../chunk-RIWLAMME.cjs');
36
36
  require('../chunk-2HV2KKOE.cjs');
37
37
 
38
38
 
39
- var _chunkKENBU3NVcjs = require('../chunk-KENBU3NV.cjs');
40
- require('../chunk-ELPV44S6.cjs');
41
39
 
40
+ var _chunkKADJ56AHcjs = require('../chunk-KADJ56AH.cjs');
42
41
 
43
42
 
44
- var _chunkKADJ56AHcjs = require('../chunk-KADJ56AH.cjs');
43
+ var _chunkKENBU3NVcjs = require('../chunk-KENBU3NV.cjs');
44
+ require('../chunk-ELPV44S6.cjs');
45
45
 
46
46
 
47
47
 
@@ -114,13 +114,32 @@ function useArrivingUserTurnId(messages) {
114
114
  }, [messages]);
115
115
  for (let i = messages.length - 1; i >= 0; i--) {
116
116
  const message = messages[i];
117
- if (message.role === "user") return seen.current.has(message.id) ? void 0 : message.id;
117
+ if (message.role === "user" && message.status !== "running") {
118
+ return seen.current.has(message.id) ? void 0 : message.id;
119
+ }
118
120
  }
119
121
  return void 0;
120
122
  }
123
+ function timelineMarkers(messages, now) {
124
+ let previous = null;
125
+ return messages.map((message) => {
126
+ if (message.status === "running") return null;
127
+ const relativeTime = _chunkH2UFRLLNcjs.formatRelativeLong.call(void 0, message.createdAt, now);
128
+ const opens = relativeTime && relativeTime !== previous ? relativeTime : null;
129
+ previous = relativeTime;
130
+ return opens;
131
+ });
132
+ }
121
133
  function TimelineMarker({ label }) {
122
134
  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 }) });
123
135
  }
136
+ function ChatRunningLine({
137
+ label,
138
+ align = "start",
139
+ workspace
140
+ }) {
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 }) }) }) }) });
142
+ }
124
143
  var ChatThread = _react.forwardRef.call(void 0,
125
144
  function ChatThread2({
126
145
  messages,
@@ -136,7 +155,7 @@ var ChatThread = _react.forwardRef.call(void 0,
136
155
  attachmentAction = "open",
137
156
  className
138
157
  }, ref) {
139
- const timelineNow = /* @__PURE__ */ new Date();
158
+ const markers = timelineMarkers(messages, /* @__PURE__ */ new Date());
140
159
  const anchorId = useArrivingUserTurnId(messages);
141
160
  const workspace = appearance === "workspace";
142
161
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkXABXUP6Lcjs.MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
@@ -155,10 +174,18 @@ var ChatThread = _react.forwardRef.call(void 0,
155
174
  children: [
156
175
  messages.map((m, index) => {
157
176
  const isUser = m.role === "user";
177
+ if (m.status === "running") {
178
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkXABXUP6Lcjs.MessageScrollerItem, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
179
+ ChatRunningLine,
180
+ {
181
+ label: m.text || pendingLabel,
182
+ align: isUser ? "end" : "start",
183
+ workspace
184
+ }
185
+ ) }, m.id);
186
+ }
158
187
  const isFullWidth = m.width === "full";
159
- const relativeTime = _chunkH2UFRLLNcjs.formatRelativeLong.call(void 0, m.createdAt, timelineNow);
160
- const previousRelativeTime = _chunkH2UFRLLNcjs.formatRelativeLong.call(void 0, _optionalChain([messages, 'access', _ => _[index - 1], 'optionalAccess', _2 => _2.createdAt]), timelineNow);
161
- const startsTimelineSection = relativeTime && relativeTime !== previousRelativeTime;
188
+ const marker = markers[index];
162
189
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
163
190
  _chunkXABXUP6Lcjs.MessageScrollerItem,
164
191
  {
@@ -166,7 +193,7 @@ var ChatThread = _react.forwardRef.call(void 0,
166
193
  scrollAnchor: m.id === anchorId,
167
194
  className: _chunkKWLSBTB2cjs.cn.call(void 0, "flex flex-col", workspace ? "gap-5" : "gap-3"),
168
195
  children: [
169
- startsTimelineSection ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TimelineMarker, { label: relativeTime }) : null,
196
+ marker ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, TimelineMarker, { label: marker }) : null,
170
197
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2AU7ZRGXcjs.Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _chunk2AU7ZRGXcjs.MessageContent, { children: [
171
198
  (m.content || m.text) && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
172
199
  _chunkV4YWTRLDcjs.Bubble,
@@ -202,7 +229,7 @@ var ChatThread = _react.forwardRef.call(void 0,
202
229
  m.id
203
230
  );
204
231
  }),
205
- pending && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkXABXUP6Lcjs.MessageScrollerItem, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk2AU7ZRGXcjs.Message, { align: "start", 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: pendingLabel }) }) }) }) }) }),
232
+ pending && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkXABXUP6Lcjs.MessageScrollerItem, { children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, ChatRunningLine, { label: pendingLabel, workspace }) }),
206
233
  footer && // Always painted: content-visibility:auto would contain the
207
234
  // footer's paint and layout, clipping a ring or shadow at its
208
235
  // edge and re-parenting anything fixed inside it.
@@ -258,7 +285,7 @@ function ChatAttachmentList({
258
285
  {
259
286
  "aria-label": `${verb} ${attachment.name}`,
260
287
  title: `${verb} ${attachment.name}`,
261
- onClick: () => _optionalChain([onOpen, 'optionalCall', _3 => _3(attachment)]),
288
+ onClick: () => _optionalChain([onOpen, 'optionalCall', _ => _(attachment)]),
262
289
  className: "rounded-xl focus-visible:ring-2 focus-visible:ring-ring/50"
263
290
  }
264
291
  ),
@@ -403,7 +430,7 @@ function ChatInput({
403
430
  "data-slot": "chat-input-actions",
404
431
  onClick: (event) => {
405
432
  if (!event.currentTarget.contains(event.target)) return;
406
- if (!event.target.closest("button")) _optionalChain([ref, 'access', _4 => _4.current, 'optionalAccess', _5 => _5.focus, 'call', _6 => _6()]);
433
+ if (!event.target.closest("button")) _optionalChain([ref, 'access', _2 => _2.current, 'optionalAccess', _3 => _3.focus, 'call', _4 => _4()]);
407
434
  },
408
435
  className: _chunkKWLSBTB2cjs.cn.call(void 0,
409
436
  "gap-2",
@@ -421,7 +448,7 @@ function ChatInput({
421
448
  children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Plus, { className: _chunkKWLSBTB2cjs.cn.call(void 0, spacious && "h-5 w-5") })
422
449
  }
423
450
  ) }),
424
- /* @__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', _7 => _7.current, 'optionalAccess', _8 => _8.click, 'call', _9 => _9()]), children: [
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: [
425
452
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _lucidereact.Paperclip, { className: "h-4 w-4" }),
426
453
  addFilesLabel
427
454
  ] }) })
@@ -652,14 +679,14 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
652
679
  else if (typeof globalThis.reportError === "function") globalThis.reportError(cause);
653
680
  else console.error(cause);
654
681
  }
655
- window.setTimeout(() => _optionalChain([inputRef, 'access', _10 => _10.current, 'optionalAccess', _11 => _11.focus, 'call', _12 => _12()]), 0);
682
+ window.setTimeout(() => _optionalChain([inputRef, 'access', _8 => _8.current, 'optionalAccess', _9 => _9.focus, 'call', _10 => _10()]), 0);
656
683
  },
657
684
  [attachments, mode, onSend, onSendError, pending]
658
685
  );
659
686
  _react.useImperativeHandle.call(void 0,
660
687
  ref,
661
688
  () => ({
662
- focusInput: () => _optionalChain([inputRef, 'access', _13 => _13.current, 'optionalAccess', _14 => _14.focus, 'call', _15 => _15()]),
689
+ focusInput: () => _optionalChain([inputRef, 'access', _11 => _11.current, 'optionalAccess', _12 => _12.focus, 'call', _13 => _13()]),
663
690
  clearInput: () => setInput("")
664
691
  }),
665
692
  []
@@ -667,7 +694,7 @@ var Chat = _react.forwardRef.call(void 0, function Chat2({
667
694
  const dropFiles = _react.useCallback.call(void 0,
668
695
  (files) => {
669
696
  if (!conversationStarted && mode === "voice") setUserMode("text");
670
- return _optionalChain([onFilesSelected, 'optionalCall', _16 => _16(files)]);
697
+ return _optionalChain([onFilesSelected, 'optionalCall', _14 => _14(files)]);
671
698
  },
672
699
  [conversationStarted, mode, onFilesSelected]
673
700
  );
@@ -34,12 +34,26 @@ type ChatMessage = {
34
34
  content?: ReactNode;
35
35
  /** Files that belong to this turn. */
36
36
  attachments?: ReadonlyArray<ChatAttachment>;
37
+ /**
38
+ * `"running"`: a reply still being worked on. The thread draws the turn as
39
+ * its running line, the one `pending` adds at the end, labelled with `text`
40
+ * (else the thread's `pendingLabel`), where the turn sits in `messages`.
41
+ * Unlike `pending`, it leaves the composer free, so use it for a reply
42
+ * worked on in the background while the user can keep writing; replace it
43
+ * with the reply when that lands. The line is not a message: it has no time
44
+ * marker, files or rich `content`, and the scroller neither anchors nor
45
+ * tracks it.
46
+ */
47
+ status?: "running";
37
48
  };
38
49
  interface ChatThreadProps {
39
50
  messages: ChatMessage[];
40
- /** Show a running line (`pendingLabel`) at the end of the thread. */
51
+ /**
52
+ * Show a running line (`pendingLabel`) at the end of the thread. A turn with
53
+ * `status: "running"` draws the same line where it sits in `messages`.
54
+ */
41
55
  pending?: boolean;
42
- /** The running line while a reply is pending. Defaults to "Thinking…". */
56
+ /** The running line while a reply is pending, and a running turn's without `text`. Defaults to "Thinking…". */
43
57
  pendingLabel?: string;
44
58
  /** Accessible name of the scroll-to-latest button. Defaults to "Scroll to latest message". */
45
59
  scrollButtonLabel?: string;
@@ -80,7 +94,7 @@ interface ChatThreadProps {
80
94
  * button appears whenever the reader is away from the end. Turns with a
81
95
  * `createdAt` are grouped under relative-time markers ("2 days ago"), and a
82
96
  * pending reply shows as a running trace line, which the log announces as it
83
- * appears.
97
+ * appears. A turn with `status: "running"` draws the same line in its place.
84
98
  */
85
99
  declare const ChatThread: React$1.ForwardRefExoticComponent<ChatThreadProps & React$1.RefAttributes<HTMLDivElement>>;
86
100
  interface ChatInputProps {
@@ -170,9 +184,14 @@ interface ChatProps {
170
184
  * failure itself, with `error`.
171
185
  */
172
186
  onSendError?: (error: unknown) => void;
173
- /** Whether an in-flight request is pending. Disables input and shows a running line (`pendingLabel`). */
187
+ /**
188
+ * Whether an in-flight request is pending. Disables input and shows a
189
+ * running line (`pendingLabel`) at the end of the thread. To show that line
190
+ * and leave the composer free, as for a reply worked on in the background,
191
+ * put a turn with `status: "running"` in `messages` instead.
192
+ */
174
193
  pending?: boolean;
175
- /** The running line while a reply is pending. Defaults to "Thinking…". */
194
+ /** The running line while a reply is pending, and a running turn's without `text`. Defaults to "Thinking…". */
176
195
  pendingLabel?: string;
177
196
  /**
178
197
  * Replaces the empty composer while its first message is in flight. Use for
@@ -34,12 +34,26 @@ type ChatMessage = {
34
34
  content?: ReactNode;
35
35
  /** Files that belong to this turn. */
36
36
  attachments?: ReadonlyArray<ChatAttachment>;
37
+ /**
38
+ * `"running"`: a reply still being worked on. The thread draws the turn as
39
+ * its running line, the one `pending` adds at the end, labelled with `text`
40
+ * (else the thread's `pendingLabel`), where the turn sits in `messages`.
41
+ * Unlike `pending`, it leaves the composer free, so use it for a reply
42
+ * worked on in the background while the user can keep writing; replace it
43
+ * with the reply when that lands. The line is not a message: it has no time
44
+ * marker, files or rich `content`, and the scroller neither anchors nor
45
+ * tracks it.
46
+ */
47
+ status?: "running";
37
48
  };
38
49
  interface ChatThreadProps {
39
50
  messages: ChatMessage[];
40
- /** Show a running line (`pendingLabel`) at the end of the thread. */
51
+ /**
52
+ * Show a running line (`pendingLabel`) at the end of the thread. A turn with
53
+ * `status: "running"` draws the same line where it sits in `messages`.
54
+ */
41
55
  pending?: boolean;
42
- /** The running line while a reply is pending. Defaults to "Thinking…". */
56
+ /** The running line while a reply is pending, and a running turn's without `text`. Defaults to "Thinking…". */
43
57
  pendingLabel?: string;
44
58
  /** Accessible name of the scroll-to-latest button. Defaults to "Scroll to latest message". */
45
59
  scrollButtonLabel?: string;
@@ -80,7 +94,7 @@ interface ChatThreadProps {
80
94
  * button appears whenever the reader is away from the end. Turns with a
81
95
  * `createdAt` are grouped under relative-time markers ("2 days ago"), and a
82
96
  * pending reply shows as a running trace line, which the log announces as it
83
- * appears.
97
+ * appears. A turn with `status: "running"` draws the same line in its place.
84
98
  */
85
99
  declare const ChatThread: React$1.ForwardRefExoticComponent<ChatThreadProps & React$1.RefAttributes<HTMLDivElement>>;
86
100
  interface ChatInputProps {
@@ -170,9 +184,14 @@ interface ChatProps {
170
184
  * failure itself, with `error`.
171
185
  */
172
186
  onSendError?: (error: unknown) => void;
173
- /** Whether an in-flight request is pending. Disables input and shows a running line (`pendingLabel`). */
187
+ /**
188
+ * Whether an in-flight request is pending. Disables input and shows a
189
+ * running line (`pendingLabel`) at the end of the thread. To show that line
190
+ * and leave the composer free, as for a reply worked on in the background,
191
+ * put a turn with `status: "running"` in `messages` instead.
192
+ */
174
193
  pending?: boolean;
175
- /** The running line while a reply is pending. Defaults to "Thinking…". */
194
+ /** The running line while a reply is pending, and a running turn's without `text`. Defaults to "Thinking…". */
176
195
  pendingLabel?: string;
177
196
  /**
178
197
  * Replaces the empty composer while its first message is in flight. Use for
@@ -12,10 +12,6 @@ import {
12
12
  Suggestion,
13
13
  Suggestions
14
14
  } from "../chunk-THWIJG5C.js";
15
- import {
16
- Marker,
17
- MarkerContent
18
- } from "../chunk-IW4ZIXXG.js";
19
15
  import {
20
16
  MessageScroller,
21
17
  MessageScrollerButton,
@@ -28,20 +24,24 @@ import {
28
24
  Message,
29
25
  MessageContent
30
26
  } from "../chunk-JZOBIPFC.js";
27
+ import {
28
+ Marker,
29
+ MarkerContent
30
+ } from "../chunk-IW4ZIXXG.js";
31
31
  import {
32
32
  InputGroup,
33
33
  InputGroupAddon,
34
34
  InputGroupTextarea
35
35
  } from "../chunk-MHYAVXOE.js";
36
36
  import "../chunk-6FC65AZQ.js";
37
- import {
38
- LazyMarkdown
39
- } from "../chunk-VY5U4VGV.js";
40
- import "../chunk-INNGGLMZ.js";
41
37
  import {
42
38
  FileDropArea,
43
39
  FileDropzone
44
40
  } from "../chunk-M7FYGAGO.js";
41
+ import {
42
+ LazyMarkdown
43
+ } from "../chunk-VY5U4VGV.js";
44
+ import "../chunk-INNGGLMZ.js";
45
45
  import {
46
46
  DropdownMenu,
47
47
  DropdownMenuContent,
@@ -114,13 +114,32 @@ function useArrivingUserTurnId(messages) {
114
114
  }, [messages]);
115
115
  for (let i = messages.length - 1; i >= 0; i--) {
116
116
  const message = messages[i];
117
- if (message.role === "user") return seen.current.has(message.id) ? void 0 : message.id;
117
+ if (message.role === "user" && message.status !== "running") {
118
+ return seen.current.has(message.id) ? void 0 : message.id;
119
+ }
118
120
  }
119
121
  return void 0;
120
122
  }
123
+ function timelineMarkers(messages, now) {
124
+ let previous = null;
125
+ return messages.map((message) => {
126
+ if (message.status === "running") return null;
127
+ const relativeTime = formatRelativeLong(message.createdAt, now);
128
+ const opens = relativeTime && relativeTime !== previous ? relativeTime : null;
129
+ previous = relativeTime;
130
+ return opens;
131
+ });
132
+ }
121
133
  function TimelineMarker({ label }) {
122
134
  return /* @__PURE__ */ jsx(Marker, { variant: "separator", role: "separator", "aria-label": label, children: /* @__PURE__ */ jsx(MarkerContent, { children: label }) });
123
135
  }
136
+ function ChatRunningLine({
137
+ label,
138
+ align = "start",
139
+ workspace
140
+ }) {
141
+ return /* @__PURE__ */ jsx(Message, { align, children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(Trace, { className: workspace ? void 0 : "px-3.5 py-2", children: /* @__PURE__ */ jsx(TraceLine, { status: "running", children: /* @__PURE__ */ jsx(TraceTitle, { children: label }) }) }) }) });
142
+ }
124
143
  var ChatThread = forwardRef(
125
144
  function ChatThread2({
126
145
  messages,
@@ -136,7 +155,7 @@ var ChatThread = forwardRef(
136
155
  attachmentAction = "open",
137
156
  className
138
157
  }, ref) {
139
- const timelineNow = /* @__PURE__ */ new Date();
158
+ const markers = timelineMarkers(messages, /* @__PURE__ */ new Date());
140
159
  const anchorId = useArrivingUserTurnId(messages);
141
160
  const workspace = appearance === "workspace";
142
161
  return /* @__PURE__ */ jsx(MessageScrollerProvider, { autoScroll: true, children: /* @__PURE__ */ jsxs(
@@ -155,10 +174,18 @@ var ChatThread = forwardRef(
155
174
  children: [
156
175
  messages.map((m, index) => {
157
176
  const isUser = m.role === "user";
177
+ if (m.status === "running") {
178
+ return /* @__PURE__ */ jsx(MessageScrollerItem, { children: /* @__PURE__ */ jsx(
179
+ ChatRunningLine,
180
+ {
181
+ label: m.text || pendingLabel,
182
+ align: isUser ? "end" : "start",
183
+ workspace
184
+ }
185
+ ) }, m.id);
186
+ }
158
187
  const isFullWidth = m.width === "full";
159
- const relativeTime = formatRelativeLong(m.createdAt, timelineNow);
160
- const previousRelativeTime = formatRelativeLong(messages[index - 1]?.createdAt, timelineNow);
161
- const startsTimelineSection = relativeTime && relativeTime !== previousRelativeTime;
188
+ const marker = markers[index];
162
189
  return /* @__PURE__ */ jsxs(
163
190
  MessageScrollerItem,
164
191
  {
@@ -166,7 +193,7 @@ var ChatThread = forwardRef(
166
193
  scrollAnchor: m.id === anchorId,
167
194
  className: cn("flex flex-col", workspace ? "gap-5" : "gap-3"),
168
195
  children: [
169
- startsTimelineSection ? /* @__PURE__ */ jsx(TimelineMarker, { label: relativeTime }) : null,
196
+ marker ? /* @__PURE__ */ jsx(TimelineMarker, { label: marker }) : null,
170
197
  /* @__PURE__ */ jsx(Message, { align: isUser ? "end" : "start", children: /* @__PURE__ */ jsxs(MessageContent, { children: [
171
198
  (m.content || m.text) && /* @__PURE__ */ jsx(
172
199
  Bubble,
@@ -202,7 +229,7 @@ var ChatThread = forwardRef(
202
229
  m.id
203
230
  );
204
231
  }),
205
- pending && /* @__PURE__ */ jsx(MessageScrollerItem, { children: /* @__PURE__ */ jsx(Message, { align: "start", children: /* @__PURE__ */ jsx(MessageContent, { children: /* @__PURE__ */ jsx(Trace, { className: workspace ? void 0 : "px-3.5 py-2", children: /* @__PURE__ */ jsx(TraceLine, { status: "running", children: /* @__PURE__ */ jsx(TraceTitle, { children: pendingLabel }) }) }) }) }) }),
232
+ pending && /* @__PURE__ */ jsx(MessageScrollerItem, { children: /* @__PURE__ */ jsx(ChatRunningLine, { label: pendingLabel, workspace }) }),
206
233
  footer && // Always painted: content-visibility:auto would contain the
207
234
  // footer's paint and layout, clipping a ring or shadow at its
208
235
  // edge and re-parenting anything fixed inside it.