rift-ds 1.8.0 → 1.10.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 (111) hide show
  1. package/README.md +3 -3
  2. package/bin/templates/chat/app/api/chat/route.ts +3 -0
  3. package/chat/AssistantTurn.js +5 -2
  4. package/chat/ChatAgentPane.d.ts +8 -5
  5. package/chat/ChatAgentPane.js +4 -1
  6. package/chat/ChatArtifactCard.d.ts +13 -0
  7. package/chat/ChatArtifactCard.js +21 -0
  8. package/chat/ChatArtifactPane.d.ts +18 -0
  9. package/chat/ChatArtifactPane.js +43 -0
  10. package/chat/ChatProvider.d.ts +4 -2
  11. package/chat/ChatProvider.js +15 -0
  12. package/chat/ChatWidget.css +213 -25
  13. package/chat/ChatWidget.d.ts +12 -1
  14. package/chat/ChatWidget.js +302 -75
  15. package/chat/DemoWelcome.js +17 -5
  16. package/chat/FollowupSuggestions.d.ts +7 -4
  17. package/chat/FollowupSuggestions.js +5 -3
  18. package/chat/TurnContent.d.ts +3 -1
  19. package/chat/TurnContent.js +9 -2
  20. package/chat/TurnQuestion.d.ts +19 -0
  21. package/chat/TurnQuestion.js +38 -0
  22. package/chat/artifacts.d.ts +5 -0
  23. package/chat/artifacts.js +14 -0
  24. package/chat/context.d.ts +18 -1
  25. package/chat/context.js +2 -0
  26. package/chat/demo-artifacts.d.ts +6 -0
  27. package/chat/demo-artifacts.js +80 -0
  28. package/chat/demo-card-components.d.ts +2 -0
  29. package/chat/demo-card-components.js +7 -0
  30. package/chat/demo-script.js +277 -49
  31. package/chat/demo.css +6 -0
  32. package/chat/demo.d.ts +1 -0
  33. package/chat/demo.js +4 -0
  34. package/chat/followups.d.ts +2 -2
  35. package/chat/followups.js +1 -0
  36. package/chat/index.d.ts +7 -3
  37. package/chat/index.js +23 -2
  38. package/chat/queries.d.ts +12 -3
  39. package/chat/queries.js +8 -3
  40. package/chat/questions.d.ts +97 -0
  41. package/chat/questions.js +140 -0
  42. package/chat/registry.json.d.ts +9 -3
  43. package/chat/registry.json.js +1 -1
  44. package/chat/server.d.ts +19 -0
  45. package/chat/server.js +45 -1
  46. package/chat/sim.d.ts +12 -1
  47. package/chat/sim.js +19 -3
  48. package/chat/types.d.ts +94 -1
  49. package/chat/types.js +3 -0
  50. package/chat/useChat.d.ts +16 -1
  51. package/chat/useChat.js +39 -2
  52. package/components/AgentRail/AgentRail.css +14 -5
  53. package/components/AgentRail/AgentRail.d.ts +2 -0
  54. package/components/AgentRail/AgentRail.js +3 -2
  55. package/components/AlertDialog/AlertDialog.css +1 -1
  56. package/components/AnchorNav/AnchorNav.css +1 -1
  57. package/components/AppSidebar/AppSidebar.css +5 -5
  58. package/components/AppSidebar/AppSidebar.js +3 -2
  59. package/components/ArtifactCard/ArtifactCard.css +106 -0
  60. package/components/ArtifactCard/ArtifactCard.d.ts +27 -0
  61. package/components/ArtifactCard/ArtifactCard.js +29 -0
  62. package/components/ArtifactPanel/ArtifactPanel.css +159 -0
  63. package/components/ArtifactPanel/ArtifactPanel.d.ts +70 -0
  64. package/components/ArtifactPanel/ArtifactPanel.js +131 -0
  65. package/components/Attachment/Attachment.css +3 -1
  66. package/components/BottomNav/BottomNav.css +1 -1
  67. package/components/Chart/Chart.css +1 -0
  68. package/components/ChatThread/ChatThread.css +21 -0
  69. package/components/ChatThread/ChatThread.d.ts +12 -1
  70. package/components/ChatThread/ChatThread.js +40 -1
  71. package/components/ColorPicker/ColorPicker.css +1 -1
  72. package/components/Combobox/Combobox.css +1 -1
  73. package/components/CommandPalette/CommandPalette.css +1 -1
  74. package/components/Composer/Composer.css +1 -1
  75. package/components/ContextMenu/ContextMenu.css +1 -1
  76. package/components/Dialog/Dialog.css +1 -1
  77. package/components/Drawer/Drawer.css +1 -1
  78. package/components/Dropdown/Dropdown.css +1 -1
  79. package/components/DropdownMenu/DropdownMenu.css +1 -1
  80. package/components/FilterBar/FilterBar.css +1 -1
  81. package/components/HoverCard/HoverCard.css +1 -1
  82. package/components/Lightbox/Lightbox.css +1 -1
  83. package/components/ModelPicker/ModelPicker.css +1 -1
  84. package/components/Popover/Popover.css +1 -1
  85. package/components/PromptSuggestions/PromptSuggestions.css +67 -0
  86. package/components/PromptSuggestions/PromptSuggestions.d.ts +8 -0
  87. package/components/PromptSuggestions/PromptSuggestions.js +34 -26
  88. package/components/QuestionCard/QuestionCard.css +358 -0
  89. package/components/QuestionCard/QuestionCard.d.ts +136 -0
  90. package/components/QuestionCard/QuestionCard.js +371 -0
  91. package/components/RichDropdown/RichDropdown.css +1 -1
  92. package/components/SourceChip/SourceChip.css +1 -1
  93. package/components/ThreadPanel/ThreadPanel.css +87 -33
  94. package/components/ThreadPanel/ThreadPanel.js +21 -10
  95. package/components/TimePicker/TimePicker.css +1 -1
  96. package/components/Toast/Toast.css +1 -1
  97. package/components/Tooltip/Tooltip.css +1 -1
  98. package/components/TopAppBar/TopAppBar.css +1 -1
  99. package/components/registry.json +24 -0
  100. package/components/registry.json.d.ts +24 -0
  101. package/components/registry.json.js +1 -1
  102. package/index.d.ts +3 -0
  103. package/index.js +7 -0
  104. package/package.json +5 -1
  105. package/tokens/breakpoints.d.ts +60 -0
  106. package/tokens/breakpoints.js +92 -0
  107. package/tokens/registry.d.ts +1 -1
  108. package/tokens/registry.json +14 -0
  109. package/tokens/registry.json.d.ts +14 -0
  110. package/tokens/registry.json.js +1 -1
  111. package/tokens/tokens-light.css +22 -0
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
- import { useSyncExternalStore, useRef, useEffect, useState, useId } from "react";
3
+ import { useRef, useEffect, useSyncExternalStore, useState, useId, useMemo } from "react";
4
4
  import { AttachmentDropZone } from "../components/Attachment/AttachmentDropZone.js";
5
5
  import { AttachmentGroup } from "../components/Attachment/AttachmentGroup.js";
6
6
  import { useAttachments } from "../components/Attachment/useAttachments.js";
@@ -16,15 +16,19 @@ import { ModelPicker } from "../components/ModelPicker/ModelPicker.js";
16
16
  import { PromptSuggestions } from "../components/PromptSuggestions/PromptSuggestions.js";
17
17
  import { MOTION_SUGGESTIONS_THINK_MS, MOTION_SCROLL_SETTLE_MS } from "../tokens/motion.js";
18
18
  import { AssistantTurn } from "./AssistantTurn.js";
19
+ import { cardArtifact } from "./artifacts.js";
19
20
  import { ChatAgentPane } from "./ChatAgentPane.js";
21
+ import { ChatArtifactPane } from "./ChatArtifactPane.js";
20
22
  import { ChatTabs } from "./ChatTabs.js";
21
23
  import { ChatThreads } from "./ChatThreads.js";
22
- import { useChatWidget } from "./context.js";
24
+ import { useChatWidget, ArtifactSeatContext } from "./context.js";
23
25
  import { subscribeClock, readGreeting, serverGreeting } from "./greeting.js";
24
26
  import { CHAT_PLACEMENTS, chatPlacement } from "./placement.js";
25
- import { THREADS_RAIL_MIN_WIDTH, ASIDE_RAIL_MIN_WIDTH } from "./queries.js";
27
+ import { ASIDE_WITH_THREADS_MIN_WIDTH, ARTIFACT_MIN_COLUMN_WIDTH, THREADS_RAIL_MIN_WIDTH, ASIDE_RAIL_MIN_WIDTH } from "./queries.js";
26
28
  import { fitsChip } from "./suggestions.js";
27
29
  import { TurnCard } from "./TurnContent.js";
30
+ import { isBlockingQuestion } from "./questions.js";
31
+ import { ReplyBody } from "./TurnQuestion.js";
28
32
  import "./ChatWidget.css";
29
33
  const DEFAULT_MAX_ATTACHMENTS = 10;
30
34
  const DEFAULT_MAX_ATTACHMENT_BYTES = 50 * 1024 * 1024;
@@ -38,7 +42,9 @@ function ChatWidget({
38
42
  compact = false,
39
43
  phone = false,
40
44
  title = "Assistant",
45
+ flow = "top-down",
41
46
  placeholder = "Ask anything",
47
+ questionsPlaceholder = "Answer the questions above, or skip them",
42
48
  showStarters = true,
43
49
  logo = null,
44
50
  tagline = "How can I help you today?",
@@ -57,6 +63,7 @@ function ChatWidget({
57
63
  tabs,
58
64
  aside,
59
65
  agent,
66
+ artifacts = false,
60
67
  contextLabel,
61
68
  widgets,
62
69
  widgetsLayout = "fit",
@@ -83,8 +90,19 @@ function ChatWidget({
83
90
  setDraft,
84
91
  starterSeed,
85
92
  starterRevealKey,
86
- setStarterRevealKey
93
+ setStarterRevealKey,
94
+ artifact,
95
+ openArtifact,
96
+ closeArtifact
87
97
  } = useChatWidget();
98
+ const questionsOpen = isBlockingQuestion(turns[turns.length - 1], live !== null);
99
+ const questionsWereOpen = useRef(questionsOpen);
100
+ useEffect(() => {
101
+ if (questionsWereOpen.current && !questionsOpen) {
102
+ composerRef.current?.focus({ preventScroll: true });
103
+ }
104
+ questionsWereOpen.current = questionsOpen;
105
+ }, [questionsOpen]);
88
106
  const lockedValues = modelPolicy?.locked ?? [];
89
107
  const pick = chosenModel && !lockedValues.includes(chosenModel) ? chosenModel : null;
90
108
  const pickerValue = pick ?? modelPolicy?.default ?? defaultModel ?? models?.[0]?.value;
@@ -132,17 +150,21 @@ function ChatWidget({
132
150
  {
133
151
  profile: withAgentMark(agent.profile ?? { name: title, status: "Connected" }, title),
134
152
  tabs: agent.tabs,
135
- overlay: ctx.overlay
153
+ overlay: ctx.overlay,
154
+ onClose: ctx.close
136
155
  }
137
156
  ) : void 0);
138
157
  const tabsNode = tabs === true ? /* @__PURE__ */ jsx(ChatTabs, {}) : tabs || null;
139
- const hasRails = Boolean(renderThreads || renderAside);
158
+ const hasRails = Boolean(renderThreads || renderAside || artifacts);
140
159
  const [threadsOpen, setThreadsOpen] = useState(false);
141
160
  const [asideOpen, setAsideOpen] = useState(Boolean(agent?.defaultOpen));
142
161
  const railsHostRef = useRef(null);
143
162
  const [threadsWide, setThreadsWide] = useState(false);
144
163
  const [asideWide, setAsideWide] = useState(false);
145
- const wasAsideWideRef = useRef(false);
164
+ const [asideRoomy, setAsideRoomy] = useState(false);
165
+ const [artifactEmptyOpen, setArtifactEmptyOpen] = useState(false);
166
+ const [artifactExpandedWanted, setArtifactExpanded] = useState(false);
167
+ const wasAsideWideRef = useRef(true);
146
168
  useEffect(() => {
147
169
  if (!hasRails) return;
148
170
  const host = railsHostRef.current;
@@ -153,20 +175,119 @@ function ChatWidget({
153
175
  const asideRoom = width >= ASIDE_RAIL_MIN_WIDTH;
154
176
  setThreadsWide(wide);
155
177
  setAsideWide(asideRoom);
178
+ setAsideRoomy(width >= ASIDE_WITH_THREADS_MIN_WIDTH);
156
179
  if (wide) setThreadsOpen(false);
157
- if (wasAsideWideRef.current && !asideRoom) setAsideOpen(false);
180
+ if (wasAsideWideRef.current && !asideRoom) {
181
+ setAsideOpen(false);
182
+ setArtifactEmptyOpen(false);
183
+ setArtifactExpanded(false);
184
+ closeArtifact();
185
+ }
158
186
  wasAsideWideRef.current = asideRoom;
159
187
  };
160
188
  measure();
161
189
  const observer = new ResizeObserver(measure);
162
190
  observer.observe(host);
163
191
  return () => observer.disconnect();
164
- }, [hasRails]);
192
+ }, [hasRails, closeArtifact]);
165
193
  const closeThreads = () => setThreadsOpen(false);
194
+ const artifactId = useId();
195
+ const artifactShown = artifacts && (artifact !== null || artifactEmptyOpen);
196
+ const [heldArtifact, setHeldArtifact] = useState(artifact);
197
+ if (artifactShown && artifact !== heldArtifact) setHeldArtifact(artifact);
198
+ const artifactInline = artifactShown && asideWide;
199
+ const artifactExpanded = artifactInline && artifactExpandedWanted;
200
+ const closeArtifactPanel = () => {
201
+ setArtifactExpanded(false);
202
+ setArtifactEmptyOpen(false);
203
+ closeArtifact();
204
+ };
205
+ const [artifactWidth, setArtifactWidth] = useState(null);
206
+ const [artifactResizing, setArtifactResizing] = useState(false);
207
+ const artifactRailRef = useRef(null);
208
+ const artifactDragRef = useRef(null);
209
+ const beginArtifactResize = (e) => {
210
+ if (e.pointerType === "mouse" && e.button !== 0 || artifactDragRef.current) return;
211
+ const rail = artifactRailRef.current;
212
+ const column = railsHostRef.current?.querySelector(":scope > .ds-chat-widget");
213
+ if (!rail || !column) return;
214
+ const width = rail.getBoundingClientRect().width;
215
+ const spare = column.getBoundingClientRect().width - ARTIFACT_MIN_COLUMN_WIDTH;
216
+ artifactDragRef.current = { startX: e.clientX, width, max: width + Math.max(0, spare), next: width };
217
+ e.currentTarget.setPointerCapture(e.pointerId);
218
+ setArtifactResizing(true);
219
+ };
220
+ const moveArtifactResize = (e) => {
221
+ const drag = artifactDragRef.current;
222
+ if (!drag) return;
223
+ const grown = drag.width - (e.clientX - drag.startX);
224
+ drag.next = Math.round(Math.min(drag.max, Math.max(ARTIFACT_MIN_COLUMN_WIDTH, grown)));
225
+ railsHostRef.current?.style.setProperty("--ds-chat-artifact-width", `${drag.next}px`);
226
+ };
227
+ const endArtifactResize = () => {
228
+ const drag = artifactDragRef.current;
229
+ if (!drag) return;
230
+ artifactDragRef.current = null;
231
+ setArtifactWidth(drag.next);
232
+ setArtifactResizing(false);
233
+ };
234
+ const latestArtifact = useMemo(() => {
235
+ for (let index = turns.length - 1; index >= 0; index -= 1) {
236
+ const found = cardArtifact(turns[index].card);
237
+ if (found) return found;
238
+ }
239
+ return null;
240
+ }, [turns]);
241
+ const seenArtifactTurns = useRef(null);
242
+ const asideWideRef = useRef(asideWide);
243
+ useEffect(() => {
244
+ asideWideRef.current = asideWide;
245
+ }, [asideWide]);
246
+ useEffect(() => {
247
+ if (!artifacts) return;
248
+ const carrying = turns.filter((turn) => cardArtifact(turn.card));
249
+ if (seenArtifactTurns.current === null) {
250
+ seenArtifactTurns.current = new Set(carrying.map((turn) => turn.id));
251
+ return;
252
+ }
253
+ for (const turn of carrying) {
254
+ if (seenArtifactTurns.current.has(turn.id)) continue;
255
+ seenArtifactTurns.current.add(turn.id);
256
+ const arrived = cardArtifact(turn.card);
257
+ if (arrived && asideWideRef.current) openArtifact(arrived);
258
+ }
259
+ }, [artifacts, turns, openArtifact]);
260
+ const trailingUp = artifactInline || Boolean(renderAside) && asideOpen && asideWide;
261
+ const threadsInline = threadsWide && !(trailingUp && (!asideRoomy || artifactExpanded));
262
+ const asideRailRef = useRef(null);
263
+ const asideMounted = Boolean(renderAside) && asideWide;
264
+ useEffect(() => {
265
+ if (!asideMounted) return;
266
+ const rail = asideRailRef.current;
267
+ const card = rail?.firstElementChild;
268
+ const pane = card?.firstElementChild;
269
+ if (!rail || !card || !pane) return;
270
+ const measure = () => {
271
+ const width = pane.offsetWidth + (card.offsetWidth - card.clientWidth);
272
+ if (pane.offsetWidth > 0) rail.style.setProperty("--ds-chat-aside-width", `${width}px`);
273
+ else rail.style.removeProperty("--ds-chat-aside-width");
274
+ };
275
+ measure();
276
+ const observer = new ResizeObserver(measure);
277
+ observer.observe(pane);
278
+ return () => observer.disconnect();
279
+ }, [asideMounted]);
166
280
  const asideId = useId();
167
- const asideShown = Boolean(renderAside) && asideOpen;
281
+ const asideShown = Boolean(renderAside) && asideOpen && !artifactShown;
168
282
  const closeAside = () => setAsideOpen(false);
169
- const toggleAside = () => setAsideOpen((current) => !current);
283
+ const toggleAside = () => {
284
+ if (artifactShown) {
285
+ closeArtifactPanel();
286
+ setAsideOpen(true);
287
+ return;
288
+ }
289
+ setAsideOpen((current) => !current);
290
+ };
170
291
  const startersNode = isEmpty && showStarters && starters.length > 0 && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__starters", children: /* @__PURE__ */ jsx(
171
292
  PromptSuggestions,
172
293
  {
@@ -182,8 +303,12 @@ function ChatWidget({
182
303
  }
183
304
  ) });
184
305
  const widgetsNode = isEmpty && widgets && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__widgets", children: widgets });
306
+ const trailingInline = asideShown && asideWide || artifactInline;
307
+ const actionsFloat = (Boolean(renderAside) || artifacts) && asideWide;
308
+ const showNewChat = !(renderThreads && threadsWide);
309
+ const actionCount = Number(showNewChat) + Number(Boolean(renderAside)) + Number(artifacts) + Number(placementEnabled) + Number(fullscreenEnabled) + Number(closeEnabled);
185
310
  const headerActions = /* @__PURE__ */ jsxs(Fragment, { children: [
186
- !(renderThreads && threadsWide) && /* @__PURE__ */ jsx(
311
+ showNewChat && /* @__PURE__ */ jsx(
187
312
  CircularButton,
188
313
  {
189
314
  icon: "edit_square",
@@ -210,6 +335,23 @@ function ChatWidget({
210
335
  onClick: toggleAside
211
336
  }
212
337
  ),
338
+ artifacts && /* @__PURE__ */ jsx(
339
+ CircularButton,
340
+ {
341
+ icon: artifactShown ? "right_panel_close" : "right_panel_open",
342
+ variant: "tertiary",
343
+ className: artifactShown ? "ds-chat-widget__agent-toggle--on" : void 0,
344
+ ariaLabel: artifactShown ? "Hide the artifact panel" : "Show the artifact panel",
345
+ tooltipPosition: "bottom",
346
+ "aria-expanded": artifactShown,
347
+ "aria-controls": artifactShown ? artifactId : void 0,
348
+ onClick: () => {
349
+ if (artifactShown) closeArtifactPanel();
350
+ else if (latestArtifact) openArtifact(latestArtifact);
351
+ else setArtifactEmptyOpen(true);
352
+ }
353
+ }
354
+ ),
213
355
  placementEnabled && /* @__PURE__ */ jsx(
214
356
  "span",
215
357
  {
@@ -292,11 +434,12 @@ function ChatWidget({
292
434
  "data-widgets-wide": widgetsNode && widgetsWidth === "wide" ? "" : void 0,
293
435
  "data-scrolling": widgetsNode && widgetsLayout === "scroll" && rootScrolling || void 0,
294
436
  onScroll: handleRootScroll,
295
- "data-lifted-actions": asideShown && asideWide || void 0,
437
+ "data-lifted-actions": trailingInline || void 0,
296
438
  onKeyDown: (e) => {
297
- if (e.key === "Escape" && (threadsOpen || asideShown && !asideWide)) {
439
+ if (e.key === "Escape" && (threadsOpen || asideShown && !asideWide || artifactShown && !asideWide)) {
298
440
  e.stopPropagation();
299
441
  if (threadsOpen) closeThreads();
442
+ else if (artifactShown) closeArtifactPanel();
300
443
  else closeAside();
301
444
  return;
302
445
  }
@@ -318,7 +461,7 @@ function ChatWidget({
318
461
  ChatHeader,
319
462
  {
320
463
  title: /* @__PURE__ */ jsxs("span", { className: "ds-chat-widget__brand", children: [
321
- renderThreads && !threadsWide && /* @__PURE__ */ jsx(
464
+ renderThreads && !threadsInline && /* @__PURE__ */ jsx(
322
465
  CircularButton,
323
466
  {
324
467
  icon: "menu",
@@ -334,13 +477,20 @@ function ChatWidget({
334
477
  actions: (
335
478
  /* With the agent rail seated inline the cluster moves to the
336
479
  widget's own right edge (see headerActions). */
337
- asideShown && asideWide ? void 0 : headerActions
480
+ !actionsFloat ? headerActions : trailingInline ? void 0 : /* @__PURE__ */ jsx(
481
+ "span",
482
+ {
483
+ className: "ds-chat-widget__actions-spacer",
484
+ style: { "--ds-chat-action-count": actionCount },
485
+ "aria-hidden": "true"
486
+ }
487
+ )
338
488
  )
339
489
  }
340
490
  ),
341
491
  tabsNode && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__tabs-row", children: tabsNode }),
342
492
  /* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__body", children: [
343
- /* @__PURE__ */ jsx(ChatThread, { className: "ds-chat-widget__thread", children: [
493
+ /* @__PURE__ */ jsx(ArtifactSeatContext.Provider, { value: artifacts, children: /* @__PURE__ */ jsx(ChatThread, { className: "ds-chat-widget__thread", flow, children: [
344
494
  ...turns.map(
345
495
  (turn) => turn.bare ? (
346
496
  /* Conversation furniture (a day marker): the injected
@@ -357,14 +507,14 @@ function ChatWidget({
357
507
  turn.content,
358
508
  turn.card && /* @__PURE__ */ jsx(TurnCard, { card: turn.card })
359
509
  ] }),
360
- children: mentions && turn.text ? /* @__PURE__ */ jsx(MentionText, { text: turn.text, sources: mentions }) : turn.text
510
+ children: turn.reply ? /* @__PURE__ */ jsx(ReplyBody, { reply: turn.reply, text: turn.text }) : mentions && turn.text ? /* @__PURE__ */ jsx(MentionText, { text: turn.text, sources: mentions }) : turn.text
361
511
  },
362
512
  turn.id
363
513
  )
364
514
  ) : /* @__PURE__ */ jsx(AssistantTurn, { turn }, turn.id)
365
515
  ),
366
516
  ...live ? [/* @__PURE__ */ jsx(AssistantTurn, { live }, live.id)] : []
367
- ] }),
517
+ ] }) }),
368
518
  isEmpty && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__welcome", children: /* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__greeting", children: [
369
519
  /* @__PURE__ */ jsx("p", { className: "ds-chat-widget__hello", children: greeting }),
370
520
  /* @__PURE__ */ jsx("p", { className: "ds-chat-widget__ask", children: tagline })
@@ -377,7 +527,8 @@ function ChatWidget({
377
527
  Composer,
378
528
  {
379
529
  ref: composerRef,
380
- placeholder,
530
+ placeholder: questionsOpen ? questionsPlaceholder : placeholder,
531
+ disabled: questionsOpen,
381
532
  sendLabel: "Send",
382
533
  aiGlow: true,
383
534
  value: draft,
@@ -434,61 +585,137 @@ function ChatWidget({
434
585
  }
435
586
  );
436
587
  if (!hasRails) return chatColumn;
437
- return /* @__PURE__ */ jsxs("div", { ref: railsHostRef, className: "ds-chat-widget__rails", children: [
438
- renderThreads && threadsWide && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__threads-rail", children: renderThreads({ overlay: false, close: closeThreads }) }),
439
- chatColumn,
440
- asideShown && asideWide && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__rail-actions", children: headerActions }),
441
- renderAside && asideWide && /* @__PURE__ */ jsx(
442
- "div",
443
- {
444
- className: `ds-chat-widget__aside-rail ${asideOpen ? "ds-chat-widget__aside-rail--open" : ""}`,
445
- id: asideId,
446
- inert: !asideOpen || void 0,
447
- children: /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-rail-inner", children: renderAside({ overlay: false, close: closeAside }) })
448
- }
449
- ),
450
- renderThreads && !threadsWide && /* @__PURE__ */ jsxs(
451
- "div",
452
- {
453
- className: `ds-chat-widget__threads-overlay ${threadsOpen ? "ds-chat-widget__threads-overlay--open" : ""}`,
454
- children: [
455
- /* @__PURE__ */ jsx(
456
- "button",
457
- {
458
- type: "button",
459
- className: "ds-chat-widget__rail-scrim",
460
- "aria-label": "Close the chat history",
461
- tabIndex: threadsOpen ? 0 : -1,
462
- onClick: closeThreads
463
- }
464
- ),
465
- /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__threads-sheet", children: renderThreads({ overlay: true, close: closeThreads }) })
466
- ]
467
- }
468
- ),
469
- renderAside && !asideWide && /* @__PURE__ */ jsxs(
470
- "div",
471
- {
472
- className: `ds-chat-widget__aside-overlay ${asideOpen ? "ds-chat-widget__aside-overlay--open" : ""}`,
473
- children: [
474
- /* @__PURE__ */ jsx(
475
- "button",
476
- {
477
- type: "button",
478
- className: "ds-chat-widget__rail-scrim",
479
- "aria-label": "Close the agent panel",
480
- tabIndex: asideOpen ? 0 : -1,
481
- onClick: closeAside
482
- }
483
- ),
484
- /* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__aside-sheet", id: asideId, children: [
485
- /* @__PURE__ */ jsx("span", { className: "ds-chat-widget__aside-grabber", "aria-hidden": "true" }),
486
- /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-sheet-body", children: renderAside({ overlay: true, close: closeAside }) })
487
- ] })
488
- ]
489
- }
490
- )
491
- ] });
588
+ return /* @__PURE__ */ jsxs(
589
+ "div",
590
+ {
591
+ ref: railsHostRef,
592
+ className: "ds-chat-widget__rails",
593
+ "data-artifact-expanded": artifactExpanded || void 0,
594
+ "data-artifact-sized": artifactWidth !== null || artifactResizing || void 0,
595
+ "data-artifact-resizing": artifactResizing || void 0,
596
+ style: artifactWidth !== null ? { "--ds-chat-artifact-width": `${artifactWidth}px` } : void 0,
597
+ onKeyDown: (event) => {
598
+ if (event.key === "Escape" && artifactExpanded) {
599
+ event.stopPropagation();
600
+ setArtifactExpanded(false);
601
+ }
602
+ },
603
+ children: [
604
+ renderThreads && threadsInline && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__threads-rail", children: renderThreads({ overlay: false, close: closeThreads }) }),
605
+ chatColumn,
606
+ actionsFloat && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__rail-actions", children: headerActions }),
607
+ renderAside && asideWide && /* @__PURE__ */ jsx(
608
+ "div",
609
+ {
610
+ ref: asideRailRef,
611
+ className: `ds-chat-widget__aside-rail ${asideShown ? "ds-chat-widget__aside-rail--open" : ""}`,
612
+ id: asideId,
613
+ inert: !asideShown || void 0,
614
+ children: /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-rail-inner", children: renderAside({ overlay: false, close: closeAside }) })
615
+ }
616
+ ),
617
+ artifactInline && !artifactExpanded && /* @__PURE__ */ jsx(
618
+ "div",
619
+ {
620
+ className: `ds-chat-widget__artifact-grip ${artifactResizing ? "ds-chat-widget__artifact-grip--resizing" : ""}`,
621
+ "aria-hidden": "true",
622
+ onPointerDown: beginArtifactResize,
623
+ onPointerMove: moveArtifactResize,
624
+ onPointerUp: endArtifactResize,
625
+ onPointerCancel: endArtifactResize
626
+ }
627
+ ),
628
+ artifacts && asideWide && /* @__PURE__ */ jsx(
629
+ "div",
630
+ {
631
+ ref: artifactRailRef,
632
+ className: `ds-chat-widget__artifact-rail ${artifactShown ? "ds-chat-widget__artifact-rail--open" : ""}`,
633
+ id: artifactId,
634
+ inert: !artifactShown || void 0,
635
+ children: /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__artifact-rail-inner", children: /* @__PURE__ */ jsx(
636
+ ChatArtifactPane,
637
+ {
638
+ artifact: heldArtifact,
639
+ expanded: artifactExpanded,
640
+ onExpandedChange: setArtifactExpanded,
641
+ onClose: closeArtifactPanel
642
+ }
643
+ ) })
644
+ }
645
+ ),
646
+ artifacts && !asideWide && /* @__PURE__ */ jsxs(
647
+ "div",
648
+ {
649
+ className: `ds-chat-widget__aside-overlay ${artifactShown ? "ds-chat-widget__aside-overlay--open" : ""}`,
650
+ children: [
651
+ /* @__PURE__ */ jsx(
652
+ "button",
653
+ {
654
+ type: "button",
655
+ className: "ds-chat-widget__rail-scrim",
656
+ "aria-label": "Close the artifact panel",
657
+ tabIndex: artifactShown ? 0 : -1,
658
+ onClick: closeArtifactPanel
659
+ }
660
+ ),
661
+ /* @__PURE__ */ jsxs(
662
+ "div",
663
+ {
664
+ className: "ds-chat-widget__aside-sheet ds-chat-widget__aside-sheet--artifact",
665
+ id: artifactId,
666
+ children: [
667
+ /* @__PURE__ */ jsx("span", { className: "ds-chat-widget__aside-grabber", "aria-hidden": "true" }),
668
+ /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-sheet-body", children: /* @__PURE__ */ jsx(ChatArtifactPane, { artifact: heldArtifact, onClose: closeArtifactPanel }) })
669
+ ]
670
+ }
671
+ )
672
+ ]
673
+ }
674
+ ),
675
+ renderThreads && !threadsInline && /* @__PURE__ */ jsxs(
676
+ "div",
677
+ {
678
+ className: `ds-chat-widget__threads-overlay ${threadsOpen ? "ds-chat-widget__threads-overlay--open" : ""}`,
679
+ children: [
680
+ /* @__PURE__ */ jsx(
681
+ "button",
682
+ {
683
+ type: "button",
684
+ className: "ds-chat-widget__rail-scrim",
685
+ "aria-label": "Close the chat history",
686
+ tabIndex: threadsOpen ? 0 : -1,
687
+ onClick: closeThreads
688
+ }
689
+ ),
690
+ /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__threads-sheet", children: renderThreads({ overlay: true, close: closeThreads }) })
691
+ ]
692
+ }
693
+ ),
694
+ renderAside && !asideWide && /* @__PURE__ */ jsxs(
695
+ "div",
696
+ {
697
+ className: `ds-chat-widget__aside-overlay ${asideShown ? "ds-chat-widget__aside-overlay--open" : ""}`,
698
+ children: [
699
+ /* @__PURE__ */ jsx(
700
+ "button",
701
+ {
702
+ type: "button",
703
+ className: "ds-chat-widget__rail-scrim",
704
+ "aria-label": "Close the agent panel",
705
+ tabIndex: asideShown ? 0 : -1,
706
+ onClick: closeAside
707
+ }
708
+ ),
709
+ /* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__aside-sheet", id: asideId, children: [
710
+ /* @__PURE__ */ jsx("span", { className: "ds-chat-widget__aside-grabber", "aria-hidden": "true" }),
711
+ /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-sheet-body", children: renderAside({ overlay: true, close: closeAside }) })
712
+ ] })
713
+ ]
714
+ }
715
+ )
716
+ ]
717
+ }
718
+ );
492
719
  }
493
720
  export {
494
721
  ChatWidget
@@ -61,7 +61,7 @@ function DemoBoard({ onAsk }) {
61
61
  /* @__PURE__ */ jsx(WidgetRow, { title: "How is the trial going?", meta: "Ask", onClick: () => onAsk("How is the trial going?") })
62
62
  ] }),
63
63
  /* @__PURE__ */ jsx(Widget, { title: "Projects", count: BOARD_PROJECTS.length, children: BOARD_PROJECTS.map(([name, state, when]) => /* @__PURE__ */ jsx(WidgetRow, { leading: /* @__PURE__ */ jsx(PixelAvatar, { name }), title: name, description: state, meta: when }, name)) }),
64
- /* @__PURE__ */ jsxs(Widget, { title: "To do", count: 4, children: [
64
+ /* @__PURE__ */ jsxs(Widget, { title: "To do", count: 5, children: [
65
65
  /* @__PURE__ */ jsxs(WidgetGroup, { label: "Waiting on you", children: [
66
66
  /* @__PURE__ */ jsx(
67
67
  WidgetRow,
@@ -70,7 +70,7 @@ function DemoBoard({ onAsk }) {
70
70
  title: "Invite the other three",
71
71
  description: "2 of 5 seats filled",
72
72
  meta: "Today",
73
- onClick: () => onAsk("Go ahead and invite the other three")
73
+ onClick: () => onAsk("Invite my team to a workspace")
74
74
  }
75
75
  ),
76
76
  /* @__PURE__ */ jsx(
@@ -80,7 +80,17 @@ function DemoBoard({ onAsk }) {
80
80
  title: "Choose a plan",
81
81
  description: "Trial ends in 9 days",
82
82
  meta: "Oct 19",
83
- onClick: () => onAsk("Which plan fits a team of five?")
83
+ onClick: () => onAsk("Help me pick a plan")
84
+ }
85
+ ),
86
+ /* @__PURE__ */ jsx(
87
+ WidgetRow,
88
+ {
89
+ leading: /* @__PURE__ */ jsx(StatusDot, { variant: "info", size: "sm", "aria-hidden": "true" }),
90
+ title: "Plan the launch kickoff",
91
+ description: "A date, who comes, and how",
92
+ meta: "Oct 21",
93
+ onClick: () => onAsk("Plan our launch kickoff")
84
94
  }
85
95
  )
86
96
  ] }),
@@ -91,7 +101,8 @@ function DemoBoard({ onAsk }) {
91
101
  leading: /* @__PURE__ */ jsx(StatusDot, { variant: "positive", size: "sm", "aria-hidden": "true" }),
92
102
  title: "Shared calendar",
93
103
  description: "Synced an hour ago",
94
- meta: "On"
104
+ meta: "On",
105
+ onClick: () => onAsk("Find a time we can all meet")
95
106
  }
96
107
  ),
97
108
  /* @__PURE__ */ jsx(
@@ -100,7 +111,8 @@ function DemoBoard({ onAsk }) {
100
111
  leading: /* @__PURE__ */ jsx(StatusDot, { variant: "neutral", size: "sm", "aria-hidden": "true" }),
101
112
  title: "Mention notifications",
102
113
  description: "Quiet hours, 6pm to 8am",
103
- meta: "Paused"
114
+ meta: "Paused",
115
+ onClick: () => onAsk("Set up notifications for me")
104
116
  }
105
117
  )
106
118
  ] })
@@ -4,15 +4,16 @@
4
4
  * one pattern for "here is something you could ask", whether the conversation
5
5
  * has started or not. The only difference is where the questions come from.
6
6
  *
7
- * Tapping one sends it as the next message. A tap while a response is already
8
- * streaming is ignored by `send`, the same as pressing enter in the composer
9
- * would be.
7
+ * Tapping one sends it as the next message and keeps the row as the record
8
+ * of that choice: the chip checked, the rest dimmed and inert. A tap while a
9
+ * response is already streaming is ignored by `send`, the same as pressing
10
+ * enter in the composer would be, and leaves the row as it was.
10
11
  *
11
12
  * While the questions are still being generated, `pending` shows the row's
12
13
  * shimmer placeholders in their place: a real loading state, unlike the
13
14
  * welcome starters' staged one.
14
15
  */
15
- export declare function FollowupSuggestions({ turnId, suggestions, pending, entrance, }: {
16
+ export declare function FollowupSuggestions({ turnId, suggestions, pending, entrance, chosen, }: {
16
17
  /** The turn these belong to; makes each suggestion's id unique in the thread. */
17
18
  turnId: string;
18
19
  suggestions: string[];
@@ -20,4 +21,6 @@ export declare function FollowupSuggestions({ turnId, suggestions, pending, entr
20
21
  pending?: boolean;
21
22
  /** Play the chips' staggered pop-in. Off for rows remounted by a reopen, whose suggestions already arrived once. */
22
23
  entrance?: boolean;
24
+ /** The label already sent from this row, which keeps the row as a record. */
25
+ chosen?: string;
23
26
  }): import("react").JSX.Element;
@@ -6,9 +6,10 @@ function FollowupSuggestions({
6
6
  turnId,
7
7
  suggestions,
8
8
  pending = false,
9
- entrance = true
9
+ entrance = true,
10
+ chosen
10
11
  }) {
11
- const { send } = useChatWidget();
12
+ const { chooseFollowup } = useChatWidget();
12
13
  const items = suggestions.map((label, index) => ({
13
14
  id: `${turnId}-followup-${index}`,
14
15
  label
@@ -22,9 +23,10 @@ function FollowupSuggestions({
22
23
  pending,
23
24
  entrance,
24
25
  suggestions: items,
26
+ value: chosen !== void 0 ? items.find((item) => item.label === chosen)?.id : void 0,
25
27
  onValueChange: (id) => {
26
28
  const item = items.find((candidate) => candidate.id === id);
27
- if (item) send(item.label);
29
+ if (item) chooseFollowup(turnId, item.label);
28
30
  }
29
31
  }
30
32
  );
@@ -2,7 +2,9 @@ import { ChatCard } from './types';
2
2
  /**
3
3
  * A turn's `card`, drawn through the provider's renderers. A kind with no
4
4
  * renderer draws nothing: a card is an enrichment of an answer that already
5
- * stands in text, so an unknown kind degrades to the words alone.
5
+ * stands in text, so an unknown kind degrades to the words alone. The one
6
+ * built-in kind is the artifact, drawn as its tile unless the host has
7
+ * taken the kind over with a renderer of its own.
6
8
  */
7
9
  export declare function TurnCard({ card }: {
8
10
  card: ChatCard;
@@ -1,10 +1,17 @@
1
1
  "use client";
2
2
  import { jsx, Fragment } from "react/jsx-runtime";
3
- import { useChatWidget } from "./context.js";
3
+ import { useContext } from "react";
4
+ import { cardArtifact } from "./artifacts.js";
5
+ import { ChatArtifactCard } from "./ChatArtifactCard.js";
6
+ import { useChatWidget, ArtifactSeatContext } from "./context.js";
4
7
  function TurnCard({ card }) {
5
8
  const { renderers } = useChatWidget();
9
+ const artifactSeat = useContext(ArtifactSeatContext);
6
10
  const render = renderers[card.kind];
7
- if (!render) return null;
11
+ if (!render) {
12
+ const artifact = artifactSeat ? cardArtifact(card) : null;
13
+ return artifact ? /* @__PURE__ */ jsx(ChatArtifactCard, { artifact }) : null;
14
+ }
8
15
  return /* @__PURE__ */ jsx(Fragment, { children: render(card.data) });
9
16
  }
10
17
  export {