rift-ds 1.8.0 → 1.9.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.
- package/README.md +3 -3
- package/chat/ChatAgentPane.d.ts +8 -5
- package/chat/ChatAgentPane.js +4 -1
- package/chat/ChatArtifactCard.d.ts +13 -0
- package/chat/ChatArtifactCard.js +21 -0
- package/chat/ChatArtifactPane.d.ts +18 -0
- package/chat/ChatArtifactPane.js +43 -0
- package/chat/ChatProvider.d.ts +4 -2
- package/chat/ChatProvider.js +15 -0
- package/chat/ChatWidget.css +208 -21
- package/chat/ChatWidget.d.ts +3 -1
- package/chat/ChatWidget.js +287 -73
- package/chat/TurnContent.d.ts +3 -1
- package/chat/TurnContent.js +9 -2
- package/chat/artifacts.d.ts +5 -0
- package/chat/artifacts.js +14 -0
- package/chat/context.d.ts +18 -1
- package/chat/context.js +2 -0
- package/chat/demo-artifacts.d.ts +6 -0
- package/chat/demo-artifacts.js +80 -0
- package/chat/demo-card-components.d.ts +2 -0
- package/chat/demo-card-components.js +7 -0
- package/chat/demo-script.js +24 -2
- package/chat/demo.css +6 -0
- package/chat/demo.d.ts +1 -0
- package/chat/demo.js +4 -0
- package/chat/index.d.ts +6 -3
- package/chat/index.js +12 -2
- package/chat/queries.d.ts +10 -1
- package/chat/queries.js +5 -1
- package/chat/registry.json.d.ts +6 -1
- package/chat/registry.json.js +1 -1
- package/chat/types.d.ts +36 -0
- package/chat/types.js +2 -0
- package/components/AgentRail/AgentRail.css +14 -5
- package/components/AgentRail/AgentRail.d.ts +2 -0
- package/components/AgentRail/AgentRail.js +3 -2
- package/components/ArtifactCard/ArtifactCard.css +106 -0
- package/components/ArtifactCard/ArtifactCard.d.ts +27 -0
- package/components/ArtifactCard/ArtifactCard.js +29 -0
- package/components/ArtifactPanel/ArtifactPanel.css +159 -0
- package/components/ArtifactPanel/ArtifactPanel.d.ts +70 -0
- package/components/ArtifactPanel/ArtifactPanel.js +131 -0
- package/components/ThreadPanel/ThreadPanel.css +87 -33
- package/components/ThreadPanel/ThreadPanel.js +21 -10
- package/components/registry.json +16 -0
- package/components/registry.json.d.ts +16 -0
- package/components/registry.json.js +1 -1
- package/index.d.ts +2 -0
- package/index.js +4 -0
- package/package.json +1 -1
package/chat/ChatWidget.js
CHANGED
|
@@ -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 { useSyncExternalStore, useRef, useEffect, 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,13 +16,15 @@ 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";
|
|
28
30
|
import "./ChatWidget.css";
|
|
@@ -57,6 +59,7 @@ function ChatWidget({
|
|
|
57
59
|
tabs,
|
|
58
60
|
aside,
|
|
59
61
|
agent,
|
|
62
|
+
artifacts = false,
|
|
60
63
|
contextLabel,
|
|
61
64
|
widgets,
|
|
62
65
|
widgetsLayout = "fit",
|
|
@@ -83,7 +86,10 @@ function ChatWidget({
|
|
|
83
86
|
setDraft,
|
|
84
87
|
starterSeed,
|
|
85
88
|
starterRevealKey,
|
|
86
|
-
setStarterRevealKey
|
|
89
|
+
setStarterRevealKey,
|
|
90
|
+
artifact,
|
|
91
|
+
openArtifact,
|
|
92
|
+
closeArtifact
|
|
87
93
|
} = useChatWidget();
|
|
88
94
|
const lockedValues = modelPolicy?.locked ?? [];
|
|
89
95
|
const pick = chosenModel && !lockedValues.includes(chosenModel) ? chosenModel : null;
|
|
@@ -132,17 +138,21 @@ function ChatWidget({
|
|
|
132
138
|
{
|
|
133
139
|
profile: withAgentMark(agent.profile ?? { name: title, status: "Connected" }, title),
|
|
134
140
|
tabs: agent.tabs,
|
|
135
|
-
overlay: ctx.overlay
|
|
141
|
+
overlay: ctx.overlay,
|
|
142
|
+
onClose: ctx.close
|
|
136
143
|
}
|
|
137
144
|
) : void 0);
|
|
138
145
|
const tabsNode = tabs === true ? /* @__PURE__ */ jsx(ChatTabs, {}) : tabs || null;
|
|
139
|
-
const hasRails = Boolean(renderThreads || renderAside);
|
|
146
|
+
const hasRails = Boolean(renderThreads || renderAside || artifacts);
|
|
140
147
|
const [threadsOpen, setThreadsOpen] = useState(false);
|
|
141
148
|
const [asideOpen, setAsideOpen] = useState(Boolean(agent?.defaultOpen));
|
|
142
149
|
const railsHostRef = useRef(null);
|
|
143
150
|
const [threadsWide, setThreadsWide] = useState(false);
|
|
144
151
|
const [asideWide, setAsideWide] = useState(false);
|
|
145
|
-
const
|
|
152
|
+
const [asideRoomy, setAsideRoomy] = useState(false);
|
|
153
|
+
const [artifactEmptyOpen, setArtifactEmptyOpen] = useState(false);
|
|
154
|
+
const [artifactExpandedWanted, setArtifactExpanded] = useState(false);
|
|
155
|
+
const wasAsideWideRef = useRef(true);
|
|
146
156
|
useEffect(() => {
|
|
147
157
|
if (!hasRails) return;
|
|
148
158
|
const host = railsHostRef.current;
|
|
@@ -153,20 +163,119 @@ function ChatWidget({
|
|
|
153
163
|
const asideRoom = width >= ASIDE_RAIL_MIN_WIDTH;
|
|
154
164
|
setThreadsWide(wide);
|
|
155
165
|
setAsideWide(asideRoom);
|
|
166
|
+
setAsideRoomy(width >= ASIDE_WITH_THREADS_MIN_WIDTH);
|
|
156
167
|
if (wide) setThreadsOpen(false);
|
|
157
|
-
if (wasAsideWideRef.current && !asideRoom)
|
|
168
|
+
if (wasAsideWideRef.current && !asideRoom) {
|
|
169
|
+
setAsideOpen(false);
|
|
170
|
+
setArtifactEmptyOpen(false);
|
|
171
|
+
setArtifactExpanded(false);
|
|
172
|
+
closeArtifact();
|
|
173
|
+
}
|
|
158
174
|
wasAsideWideRef.current = asideRoom;
|
|
159
175
|
};
|
|
160
176
|
measure();
|
|
161
177
|
const observer = new ResizeObserver(measure);
|
|
162
178
|
observer.observe(host);
|
|
163
179
|
return () => observer.disconnect();
|
|
164
|
-
}, [hasRails]);
|
|
180
|
+
}, [hasRails, closeArtifact]);
|
|
165
181
|
const closeThreads = () => setThreadsOpen(false);
|
|
182
|
+
const artifactId = useId();
|
|
183
|
+
const artifactShown = artifacts && (artifact !== null || artifactEmptyOpen);
|
|
184
|
+
const [heldArtifact, setHeldArtifact] = useState(artifact);
|
|
185
|
+
if (artifactShown && artifact !== heldArtifact) setHeldArtifact(artifact);
|
|
186
|
+
const artifactInline = artifactShown && asideWide;
|
|
187
|
+
const artifactExpanded = artifactInline && artifactExpandedWanted;
|
|
188
|
+
const closeArtifactPanel = () => {
|
|
189
|
+
setArtifactExpanded(false);
|
|
190
|
+
setArtifactEmptyOpen(false);
|
|
191
|
+
closeArtifact();
|
|
192
|
+
};
|
|
193
|
+
const [artifactWidth, setArtifactWidth] = useState(null);
|
|
194
|
+
const [artifactResizing, setArtifactResizing] = useState(false);
|
|
195
|
+
const artifactRailRef = useRef(null);
|
|
196
|
+
const artifactDragRef = useRef(null);
|
|
197
|
+
const beginArtifactResize = (e) => {
|
|
198
|
+
if (e.pointerType === "mouse" && e.button !== 0 || artifactDragRef.current) return;
|
|
199
|
+
const rail = artifactRailRef.current;
|
|
200
|
+
const column = railsHostRef.current?.querySelector(":scope > .ds-chat-widget");
|
|
201
|
+
if (!rail || !column) return;
|
|
202
|
+
const width = rail.getBoundingClientRect().width;
|
|
203
|
+
const spare = column.getBoundingClientRect().width - ARTIFACT_MIN_COLUMN_WIDTH;
|
|
204
|
+
artifactDragRef.current = { startX: e.clientX, width, max: width + Math.max(0, spare), next: width };
|
|
205
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
206
|
+
setArtifactResizing(true);
|
|
207
|
+
};
|
|
208
|
+
const moveArtifactResize = (e) => {
|
|
209
|
+
const drag = artifactDragRef.current;
|
|
210
|
+
if (!drag) return;
|
|
211
|
+
const grown = drag.width - (e.clientX - drag.startX);
|
|
212
|
+
drag.next = Math.round(Math.min(drag.max, Math.max(ARTIFACT_MIN_COLUMN_WIDTH, grown)));
|
|
213
|
+
railsHostRef.current?.style.setProperty("--ds-chat-artifact-width", `${drag.next}px`);
|
|
214
|
+
};
|
|
215
|
+
const endArtifactResize = () => {
|
|
216
|
+
const drag = artifactDragRef.current;
|
|
217
|
+
if (!drag) return;
|
|
218
|
+
artifactDragRef.current = null;
|
|
219
|
+
setArtifactWidth(drag.next);
|
|
220
|
+
setArtifactResizing(false);
|
|
221
|
+
};
|
|
222
|
+
const latestArtifact = useMemo(() => {
|
|
223
|
+
for (let index = turns.length - 1; index >= 0; index -= 1) {
|
|
224
|
+
const found = cardArtifact(turns[index].card);
|
|
225
|
+
if (found) return found;
|
|
226
|
+
}
|
|
227
|
+
return null;
|
|
228
|
+
}, [turns]);
|
|
229
|
+
const seenArtifactTurns = useRef(null);
|
|
230
|
+
const asideWideRef = useRef(asideWide);
|
|
231
|
+
useEffect(() => {
|
|
232
|
+
asideWideRef.current = asideWide;
|
|
233
|
+
}, [asideWide]);
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (!artifacts) return;
|
|
236
|
+
const carrying = turns.filter((turn) => cardArtifact(turn.card));
|
|
237
|
+
if (seenArtifactTurns.current === null) {
|
|
238
|
+
seenArtifactTurns.current = new Set(carrying.map((turn) => turn.id));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
for (const turn of carrying) {
|
|
242
|
+
if (seenArtifactTurns.current.has(turn.id)) continue;
|
|
243
|
+
seenArtifactTurns.current.add(turn.id);
|
|
244
|
+
const arrived = cardArtifact(turn.card);
|
|
245
|
+
if (arrived && asideWideRef.current) openArtifact(arrived);
|
|
246
|
+
}
|
|
247
|
+
}, [artifacts, turns, openArtifact]);
|
|
248
|
+
const trailingUp = artifactInline || Boolean(renderAside) && asideOpen && asideWide;
|
|
249
|
+
const threadsInline = threadsWide && !(trailingUp && (!asideRoomy || artifactExpanded));
|
|
250
|
+
const asideRailRef = useRef(null);
|
|
251
|
+
const asideMounted = Boolean(renderAside) && asideWide;
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
if (!asideMounted) return;
|
|
254
|
+
const rail = asideRailRef.current;
|
|
255
|
+
const card = rail?.firstElementChild;
|
|
256
|
+
const pane = card?.firstElementChild;
|
|
257
|
+
if (!rail || !card || !pane) return;
|
|
258
|
+
const measure = () => {
|
|
259
|
+
const width = pane.offsetWidth + (card.offsetWidth - card.clientWidth);
|
|
260
|
+
if (pane.offsetWidth > 0) rail.style.setProperty("--ds-chat-aside-width", `${width}px`);
|
|
261
|
+
else rail.style.removeProperty("--ds-chat-aside-width");
|
|
262
|
+
};
|
|
263
|
+
measure();
|
|
264
|
+
const observer = new ResizeObserver(measure);
|
|
265
|
+
observer.observe(pane);
|
|
266
|
+
return () => observer.disconnect();
|
|
267
|
+
}, [asideMounted]);
|
|
166
268
|
const asideId = useId();
|
|
167
|
-
const asideShown = Boolean(renderAside) && asideOpen;
|
|
269
|
+
const asideShown = Boolean(renderAside) && asideOpen && !artifactShown;
|
|
168
270
|
const closeAside = () => setAsideOpen(false);
|
|
169
|
-
const toggleAside = () =>
|
|
271
|
+
const toggleAside = () => {
|
|
272
|
+
if (artifactShown) {
|
|
273
|
+
closeArtifactPanel();
|
|
274
|
+
setAsideOpen(true);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
setAsideOpen((current) => !current);
|
|
278
|
+
};
|
|
170
279
|
const startersNode = isEmpty && showStarters && starters.length > 0 && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__starters", children: /* @__PURE__ */ jsx(
|
|
171
280
|
PromptSuggestions,
|
|
172
281
|
{
|
|
@@ -182,8 +291,12 @@ function ChatWidget({
|
|
|
182
291
|
}
|
|
183
292
|
) });
|
|
184
293
|
const widgetsNode = isEmpty && widgets && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__widgets", children: widgets });
|
|
294
|
+
const trailingInline = asideShown && asideWide || artifactInline;
|
|
295
|
+
const actionsFloat = (Boolean(renderAside) || artifacts) && asideWide;
|
|
296
|
+
const showNewChat = !(renderThreads && threadsWide);
|
|
297
|
+
const actionCount = Number(showNewChat) + Number(Boolean(renderAside)) + Number(artifacts) + Number(placementEnabled) + Number(fullscreenEnabled) + Number(closeEnabled);
|
|
185
298
|
const headerActions = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
186
|
-
|
|
299
|
+
showNewChat && /* @__PURE__ */ jsx(
|
|
187
300
|
CircularButton,
|
|
188
301
|
{
|
|
189
302
|
icon: "edit_square",
|
|
@@ -210,6 +323,23 @@ function ChatWidget({
|
|
|
210
323
|
onClick: toggleAside
|
|
211
324
|
}
|
|
212
325
|
),
|
|
326
|
+
artifacts && /* @__PURE__ */ jsx(
|
|
327
|
+
CircularButton,
|
|
328
|
+
{
|
|
329
|
+
icon: artifactShown ? "right_panel_close" : "right_panel_open",
|
|
330
|
+
variant: "tertiary",
|
|
331
|
+
className: artifactShown ? "ds-chat-widget__agent-toggle--on" : void 0,
|
|
332
|
+
ariaLabel: artifactShown ? "Hide the artifact panel" : "Show the artifact panel",
|
|
333
|
+
tooltipPosition: "bottom",
|
|
334
|
+
"aria-expanded": artifactShown,
|
|
335
|
+
"aria-controls": artifactShown ? artifactId : void 0,
|
|
336
|
+
onClick: () => {
|
|
337
|
+
if (artifactShown) closeArtifactPanel();
|
|
338
|
+
else if (latestArtifact) openArtifact(latestArtifact);
|
|
339
|
+
else setArtifactEmptyOpen(true);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
),
|
|
213
343
|
placementEnabled && /* @__PURE__ */ jsx(
|
|
214
344
|
"span",
|
|
215
345
|
{
|
|
@@ -292,11 +422,12 @@ function ChatWidget({
|
|
|
292
422
|
"data-widgets-wide": widgetsNode && widgetsWidth === "wide" ? "" : void 0,
|
|
293
423
|
"data-scrolling": widgetsNode && widgetsLayout === "scroll" && rootScrolling || void 0,
|
|
294
424
|
onScroll: handleRootScroll,
|
|
295
|
-
"data-lifted-actions":
|
|
425
|
+
"data-lifted-actions": trailingInline || void 0,
|
|
296
426
|
onKeyDown: (e) => {
|
|
297
|
-
if (e.key === "Escape" && (threadsOpen || asideShown && !asideWide)) {
|
|
427
|
+
if (e.key === "Escape" && (threadsOpen || asideShown && !asideWide || artifactShown && !asideWide)) {
|
|
298
428
|
e.stopPropagation();
|
|
299
429
|
if (threadsOpen) closeThreads();
|
|
430
|
+
else if (artifactShown) closeArtifactPanel();
|
|
300
431
|
else closeAside();
|
|
301
432
|
return;
|
|
302
433
|
}
|
|
@@ -318,7 +449,7 @@ function ChatWidget({
|
|
|
318
449
|
ChatHeader,
|
|
319
450
|
{
|
|
320
451
|
title: /* @__PURE__ */ jsxs("span", { className: "ds-chat-widget__brand", children: [
|
|
321
|
-
renderThreads && !
|
|
452
|
+
renderThreads && !threadsInline && /* @__PURE__ */ jsx(
|
|
322
453
|
CircularButton,
|
|
323
454
|
{
|
|
324
455
|
icon: "menu",
|
|
@@ -334,13 +465,20 @@ function ChatWidget({
|
|
|
334
465
|
actions: (
|
|
335
466
|
/* With the agent rail seated inline the cluster moves to the
|
|
336
467
|
widget's own right edge (see headerActions). */
|
|
337
|
-
|
|
468
|
+
!actionsFloat ? headerActions : trailingInline ? void 0 : /* @__PURE__ */ jsx(
|
|
469
|
+
"span",
|
|
470
|
+
{
|
|
471
|
+
className: "ds-chat-widget__actions-spacer",
|
|
472
|
+
style: { "--ds-chat-action-count": actionCount },
|
|
473
|
+
"aria-hidden": "true"
|
|
474
|
+
}
|
|
475
|
+
)
|
|
338
476
|
)
|
|
339
477
|
}
|
|
340
478
|
),
|
|
341
479
|
tabsNode && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__tabs-row", children: tabsNode }),
|
|
342
480
|
/* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__body", children: [
|
|
343
|
-
/* @__PURE__ */ jsx(ChatThread, { className: "ds-chat-widget__thread", children: [
|
|
481
|
+
/* @__PURE__ */ jsx(ArtifactSeatContext.Provider, { value: artifacts, children: /* @__PURE__ */ jsx(ChatThread, { className: "ds-chat-widget__thread", children: [
|
|
344
482
|
...turns.map(
|
|
345
483
|
(turn) => turn.bare ? (
|
|
346
484
|
/* Conversation furniture (a day marker): the injected
|
|
@@ -364,7 +502,7 @@ function ChatWidget({
|
|
|
364
502
|
) : /* @__PURE__ */ jsx(AssistantTurn, { turn }, turn.id)
|
|
365
503
|
),
|
|
366
504
|
...live ? [/* @__PURE__ */ jsx(AssistantTurn, { live }, live.id)] : []
|
|
367
|
-
] }),
|
|
505
|
+
] }) }),
|
|
368
506
|
isEmpty && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__welcome", children: /* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__greeting", children: [
|
|
369
507
|
/* @__PURE__ */ jsx("p", { className: "ds-chat-widget__hello", children: greeting }),
|
|
370
508
|
/* @__PURE__ */ jsx("p", { className: "ds-chat-widget__ask", children: tagline })
|
|
@@ -434,61 +572,137 @@ function ChatWidget({
|
|
|
434
572
|
}
|
|
435
573
|
);
|
|
436
574
|
if (!hasRails) return chatColumn;
|
|
437
|
-
return /* @__PURE__ */ jsxs(
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
"
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
575
|
+
return /* @__PURE__ */ jsxs(
|
|
576
|
+
"div",
|
|
577
|
+
{
|
|
578
|
+
ref: railsHostRef,
|
|
579
|
+
className: "ds-chat-widget__rails",
|
|
580
|
+
"data-artifact-expanded": artifactExpanded || void 0,
|
|
581
|
+
"data-artifact-sized": artifactWidth !== null || artifactResizing || void 0,
|
|
582
|
+
"data-artifact-resizing": artifactResizing || void 0,
|
|
583
|
+
style: artifactWidth !== null ? { "--ds-chat-artifact-width": `${artifactWidth}px` } : void 0,
|
|
584
|
+
onKeyDown: (event) => {
|
|
585
|
+
if (event.key === "Escape" && artifactExpanded) {
|
|
586
|
+
event.stopPropagation();
|
|
587
|
+
setArtifactExpanded(false);
|
|
588
|
+
}
|
|
589
|
+
},
|
|
590
|
+
children: [
|
|
591
|
+
renderThreads && threadsInline && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__threads-rail", children: renderThreads({ overlay: false, close: closeThreads }) }),
|
|
592
|
+
chatColumn,
|
|
593
|
+
actionsFloat && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__rail-actions", children: headerActions }),
|
|
594
|
+
renderAside && asideWide && /* @__PURE__ */ jsx(
|
|
595
|
+
"div",
|
|
596
|
+
{
|
|
597
|
+
ref: asideRailRef,
|
|
598
|
+
className: `ds-chat-widget__aside-rail ${asideShown ? "ds-chat-widget__aside-rail--open" : ""}`,
|
|
599
|
+
id: asideId,
|
|
600
|
+
inert: !asideShown || void 0,
|
|
601
|
+
children: /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-rail-inner", children: renderAside({ overlay: false, close: closeAside }) })
|
|
602
|
+
}
|
|
603
|
+
),
|
|
604
|
+
artifactInline && !artifactExpanded && /* @__PURE__ */ jsx(
|
|
605
|
+
"div",
|
|
606
|
+
{
|
|
607
|
+
className: `ds-chat-widget__artifact-grip ${artifactResizing ? "ds-chat-widget__artifact-grip--resizing" : ""}`,
|
|
608
|
+
"aria-hidden": "true",
|
|
609
|
+
onPointerDown: beginArtifactResize,
|
|
610
|
+
onPointerMove: moveArtifactResize,
|
|
611
|
+
onPointerUp: endArtifactResize,
|
|
612
|
+
onPointerCancel: endArtifactResize
|
|
613
|
+
}
|
|
614
|
+
),
|
|
615
|
+
artifacts && asideWide && /* @__PURE__ */ jsx(
|
|
616
|
+
"div",
|
|
617
|
+
{
|
|
618
|
+
ref: artifactRailRef,
|
|
619
|
+
className: `ds-chat-widget__artifact-rail ${artifactShown ? "ds-chat-widget__artifact-rail--open" : ""}`,
|
|
620
|
+
id: artifactId,
|
|
621
|
+
inert: !artifactShown || void 0,
|
|
622
|
+
children: /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__artifact-rail-inner", children: /* @__PURE__ */ jsx(
|
|
623
|
+
ChatArtifactPane,
|
|
624
|
+
{
|
|
625
|
+
artifact: heldArtifact,
|
|
626
|
+
expanded: artifactExpanded,
|
|
627
|
+
onExpandedChange: setArtifactExpanded,
|
|
628
|
+
onClose: closeArtifactPanel
|
|
629
|
+
}
|
|
630
|
+
) })
|
|
631
|
+
}
|
|
632
|
+
),
|
|
633
|
+
artifacts && !asideWide && /* @__PURE__ */ jsxs(
|
|
634
|
+
"div",
|
|
635
|
+
{
|
|
636
|
+
className: `ds-chat-widget__aside-overlay ${artifactShown ? "ds-chat-widget__aside-overlay--open" : ""}`,
|
|
637
|
+
children: [
|
|
638
|
+
/* @__PURE__ */ jsx(
|
|
639
|
+
"button",
|
|
640
|
+
{
|
|
641
|
+
type: "button",
|
|
642
|
+
className: "ds-chat-widget__rail-scrim",
|
|
643
|
+
"aria-label": "Close the artifact panel",
|
|
644
|
+
tabIndex: artifactShown ? 0 : -1,
|
|
645
|
+
onClick: closeArtifactPanel
|
|
646
|
+
}
|
|
647
|
+
),
|
|
648
|
+
/* @__PURE__ */ jsxs(
|
|
649
|
+
"div",
|
|
650
|
+
{
|
|
651
|
+
className: "ds-chat-widget__aside-sheet ds-chat-widget__aside-sheet--artifact",
|
|
652
|
+
id: artifactId,
|
|
653
|
+
children: [
|
|
654
|
+
/* @__PURE__ */ jsx("span", { className: "ds-chat-widget__aside-grabber", "aria-hidden": "true" }),
|
|
655
|
+
/* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-sheet-body", children: /* @__PURE__ */ jsx(ChatArtifactPane, { artifact: heldArtifact, onClose: closeArtifactPanel }) })
|
|
656
|
+
]
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
]
|
|
660
|
+
}
|
|
661
|
+
),
|
|
662
|
+
renderThreads && !threadsInline && /* @__PURE__ */ jsxs(
|
|
663
|
+
"div",
|
|
664
|
+
{
|
|
665
|
+
className: `ds-chat-widget__threads-overlay ${threadsOpen ? "ds-chat-widget__threads-overlay--open" : ""}`,
|
|
666
|
+
children: [
|
|
667
|
+
/* @__PURE__ */ jsx(
|
|
668
|
+
"button",
|
|
669
|
+
{
|
|
670
|
+
type: "button",
|
|
671
|
+
className: "ds-chat-widget__rail-scrim",
|
|
672
|
+
"aria-label": "Close the chat history",
|
|
673
|
+
tabIndex: threadsOpen ? 0 : -1,
|
|
674
|
+
onClick: closeThreads
|
|
675
|
+
}
|
|
676
|
+
),
|
|
677
|
+
/* @__PURE__ */ jsx("div", { className: "ds-chat-widget__threads-sheet", children: renderThreads({ overlay: true, close: closeThreads }) })
|
|
678
|
+
]
|
|
679
|
+
}
|
|
680
|
+
),
|
|
681
|
+
renderAside && !asideWide && /* @__PURE__ */ jsxs(
|
|
682
|
+
"div",
|
|
683
|
+
{
|
|
684
|
+
className: `ds-chat-widget__aside-overlay ${asideShown ? "ds-chat-widget__aside-overlay--open" : ""}`,
|
|
685
|
+
children: [
|
|
686
|
+
/* @__PURE__ */ jsx(
|
|
687
|
+
"button",
|
|
688
|
+
{
|
|
689
|
+
type: "button",
|
|
690
|
+
className: "ds-chat-widget__rail-scrim",
|
|
691
|
+
"aria-label": "Close the agent panel",
|
|
692
|
+
tabIndex: asideShown ? 0 : -1,
|
|
693
|
+
onClick: closeAside
|
|
694
|
+
}
|
|
695
|
+
),
|
|
696
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__aside-sheet", id: asideId, children: [
|
|
697
|
+
/* @__PURE__ */ jsx("span", { className: "ds-chat-widget__aside-grabber", "aria-hidden": "true" }),
|
|
698
|
+
/* @__PURE__ */ jsx("div", { className: "ds-chat-widget__aside-sheet-body", children: renderAside({ overlay: true, close: closeAside }) })
|
|
699
|
+
] })
|
|
700
|
+
]
|
|
701
|
+
}
|
|
702
|
+
)
|
|
703
|
+
]
|
|
704
|
+
}
|
|
705
|
+
);
|
|
492
706
|
}
|
|
493
707
|
export {
|
|
494
708
|
ChatWidget
|
package/chat/TurnContent.d.ts
CHANGED
|
@@ -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;
|
package/chat/TurnContent.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, Fragment } from "react/jsx-runtime";
|
|
3
|
-
import {
|
|
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)
|
|
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 {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { ChatArtifact, ChatCard } from './types';
|
|
2
|
+
/** The artifact a card carries, or null when it is some other kind of card. */
|
|
3
|
+
export declare const cardArtifact: (card: ChatCard | undefined) => ChatArtifact | null;
|
|
4
|
+
/** Wraps an artifact as the card a transport sends. */
|
|
5
|
+
export declare const artifactCard: (artifact: ChatArtifact) => ChatCard;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ARTIFACT_CARD_KIND } from "./types.js";
|
|
2
|
+
const isArtifact = (data) => {
|
|
3
|
+
const candidate = data;
|
|
4
|
+
return typeof candidate === "object" && candidate !== null && typeof candidate.id === "string" && typeof candidate.title === "string" && typeof candidate.kind === "string";
|
|
5
|
+
};
|
|
6
|
+
const cardArtifact = (card) => card?.kind === ARTIFACT_CARD_KIND && isArtifact(card.data) ? card.data : null;
|
|
7
|
+
const artifactCard = (artifact) => ({
|
|
8
|
+
kind: ARTIFACT_CARD_KIND,
|
|
9
|
+
data: artifact
|
|
10
|
+
});
|
|
11
|
+
export {
|
|
12
|
+
artifactCard,
|
|
13
|
+
cardArtifact
|
|
14
|
+
};
|
package/chat/context.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { ChatPlacement, ChatSide } from './placement';
|
|
3
3
|
import { ThreadsState } from './threads';
|
|
4
|
-
import { ChatFeedbackVerdict } from './types';
|
|
4
|
+
import { ChatArtifact, ChatFeedbackVerdict } from './types';
|
|
5
5
|
import { ChatState } from './useChat';
|
|
6
6
|
/** Panel is the docked rail; full is the viewport takeover. */
|
|
7
7
|
export type ChatView = 'panel' | 'full';
|
|
@@ -10,6 +10,17 @@ export type ChatView = 'panel' | 'full';
|
|
|
10
10
|
* a kind with no renderer draws nothing.
|
|
11
11
|
*/
|
|
12
12
|
export type ChatCardRenderers = Record<string, (data: unknown) => ReactNode>;
|
|
13
|
+
/**
|
|
14
|
+
* Draws an artifact's preview as a React node, keyed by the artifact's
|
|
15
|
+
* `kind`. A kind with no renderer falls back to the built-in frames.
|
|
16
|
+
*/
|
|
17
|
+
export type ChatArtifactRenderers = Record<string, (artifact: ChatArtifact) => ReactNode>;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the widget rendering this subtree seats the artifact panel (its
|
|
20
|
+
* `artifacts` prop). With no seat an artifact has nowhere to open, so the
|
|
21
|
+
* thread draws no tile for it: the feature is whole or it is absent.
|
|
22
|
+
*/
|
|
23
|
+
export declare const ArtifactSeatContext: import('react').Context<boolean>;
|
|
13
24
|
/** How the widget renders a link the model wrote. */
|
|
14
25
|
export type ChatLinkRenderer = (props: {
|
|
15
26
|
href: string;
|
|
@@ -52,6 +63,12 @@ export interface ChatWidgetContextValue extends ChatState {
|
|
|
52
63
|
/** The thread lifecycle behind the built-in threads and tabs. */
|
|
53
64
|
threads: ThreadsState;
|
|
54
65
|
renderers: ChatCardRenderers;
|
|
66
|
+
/** The artifact on stage in the artifact panel, or null while it is closed. */
|
|
67
|
+
artifact: ChatArtifact | null;
|
|
68
|
+
/** Puts an artifact on stage. The widget seats the panel; this only says which. */
|
|
69
|
+
openArtifact: (artifact: ChatArtifact) => void;
|
|
70
|
+
closeArtifact: () => void;
|
|
71
|
+
artifactRenderers: ChatArtifactRenderers;
|
|
55
72
|
renderLink: ChatLinkRenderer;
|
|
56
73
|
isInternalHref: (href: string) => boolean;
|
|
57
74
|
/** Record and send a thumbs verdict; null when feedback is off. */
|
package/chat/context.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useContext, createContext } from "react";
|
|
3
|
+
const ArtifactSeatContext = createContext(false);
|
|
3
4
|
const ChatWidgetContext = createContext(null);
|
|
4
5
|
function useChatWidget() {
|
|
5
6
|
const context = useContext(ChatWidgetContext);
|
|
@@ -20,6 +21,7 @@ function createFeedbackPoster(endpoint = "/api/chat/feedback") {
|
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
export {
|
|
24
|
+
ArtifactSeatContext,
|
|
23
25
|
ChatWidgetContext,
|
|
24
26
|
createFeedbackPoster,
|
|
25
27
|
isInternalPath,
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { ChatArtifactRenderers } from './context';
|
|
2
|
+
import { ChatArtifact } from './types';
|
|
3
|
+
/** Draws the demo's live artifact kinds. Pass to `ChatProvider`'s `artifactRenderers`. */
|
|
4
|
+
export declare const DEMO_ARTIFACT_RENDERERS: ChatArtifactRenderers;
|
|
5
|
+
export declare const DEMO_DASHBOARD_ARTIFACT: ChatArtifact;
|
|
6
|
+
export declare const DEMO_PAGE_ARTIFACT: ChatArtifact;
|