rift-ds 1.9.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.
- package/README.md +3 -3
- package/bin/templates/chat/app/api/chat/route.ts +3 -0
- package/chat/AssistantTurn.js +5 -2
- package/chat/ChatWidget.css +5 -4
- package/chat/ChatWidget.d.ts +10 -1
- package/chat/ChatWidget.js +17 -4
- package/chat/DemoWelcome.js +17 -5
- package/chat/FollowupSuggestions.d.ts +7 -4
- package/chat/FollowupSuggestions.js +5 -3
- package/chat/TurnQuestion.d.ts +19 -0
- package/chat/TurnQuestion.js +38 -0
- package/chat/demo-script.js +256 -50
- package/chat/followups.d.ts +2 -2
- package/chat/followups.js +1 -0
- package/chat/index.d.ts +2 -1
- package/chat/index.js +11 -0
- package/chat/queries.d.ts +2 -2
- package/chat/queries.js +3 -2
- package/chat/questions.d.ts +97 -0
- package/chat/questions.js +140 -0
- package/chat/registry.json.d.ts +3 -2
- package/chat/registry.json.js +1 -1
- package/chat/server.d.ts +19 -0
- package/chat/server.js +45 -1
- package/chat/sim.d.ts +12 -1
- package/chat/sim.js +19 -3
- package/chat/types.d.ts +58 -1
- package/chat/types.js +1 -0
- package/chat/useChat.d.ts +16 -1
- package/chat/useChat.js +39 -2
- package/components/AlertDialog/AlertDialog.css +1 -1
- package/components/AnchorNav/AnchorNav.css +1 -1
- package/components/AppSidebar/AppSidebar.css +5 -5
- package/components/AppSidebar/AppSidebar.js +3 -2
- package/components/Attachment/Attachment.css +3 -1
- package/components/BottomNav/BottomNav.css +1 -1
- package/components/Chart/Chart.css +1 -0
- package/components/ChatThread/ChatThread.css +21 -0
- package/components/ChatThread/ChatThread.d.ts +12 -1
- package/components/ChatThread/ChatThread.js +40 -1
- package/components/ColorPicker/ColorPicker.css +1 -1
- package/components/Combobox/Combobox.css +1 -1
- package/components/CommandPalette/CommandPalette.css +1 -1
- package/components/Composer/Composer.css +1 -1
- package/components/ContextMenu/ContextMenu.css +1 -1
- package/components/Dialog/Dialog.css +1 -1
- package/components/Drawer/Drawer.css +1 -1
- package/components/Dropdown/Dropdown.css +1 -1
- package/components/DropdownMenu/DropdownMenu.css +1 -1
- package/components/FilterBar/FilterBar.css +1 -1
- package/components/HoverCard/HoverCard.css +1 -1
- package/components/Lightbox/Lightbox.css +1 -1
- package/components/ModelPicker/ModelPicker.css +1 -1
- package/components/Popover/Popover.css +1 -1
- package/components/PromptSuggestions/PromptSuggestions.css +67 -0
- package/components/PromptSuggestions/PromptSuggestions.d.ts +8 -0
- package/components/PromptSuggestions/PromptSuggestions.js +34 -26
- package/components/QuestionCard/QuestionCard.css +358 -0
- package/components/QuestionCard/QuestionCard.d.ts +136 -0
- package/components/QuestionCard/QuestionCard.js +371 -0
- package/components/RichDropdown/RichDropdown.css +1 -1
- package/components/SourceChip/SourceChip.css +1 -1
- package/components/TimePicker/TimePicker.css +1 -1
- package/components/Toast/Toast.css +1 -1
- package/components/Tooltip/Tooltip.css +1 -1
- package/components/TopAppBar/TopAppBar.css +1 -1
- package/components/registry.json +8 -0
- package/components/registry.json.d.ts +8 -0
- package/components/registry.json.js +1 -1
- package/index.d.ts +1 -0
- package/index.js +3 -0
- package/package.json +5 -1
- package/tokens/breakpoints.d.ts +60 -0
- package/tokens/breakpoints.js +92 -0
- package/tokens/registry.d.ts +1 -1
- package/tokens/registry.json +14 -0
- package/tokens/registry.json.d.ts +14 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-light.css +22 -0
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ An open source React design system built for AI products and coding agents: comp
|
|
|
13
13
|
|
|
14
14
|
## Why Rift DS
|
|
15
15
|
|
|
16
|
-
- **<!-- component-count -->
|
|
16
|
+
- **<!-- component-count -->153<!-- /component-count --> components.** Buttons, forms and tables through to charts, navigation and overlays, each typed and documented with live examples. React is the only required peer dependency.
|
|
17
17
|
- **AI patterns, the whole chat and templates, ready to use.** Composer, streaming chat thread, tool calls, reasoning and thread panel are in the box, the complete AI chat ships assembled as one widget that one command scaffolds, and the docs site has complete template screens built from them.
|
|
18
18
|
- **Fully themeable.** The shipped themes are complete looks to start from, each in light and dark. One `data-brand` attribute applies a theme, and a few CSS overrides make your own.
|
|
19
19
|
- **Ready for coding agents.** One command installs the agent skills, and a public MCP endpoint serves every component's props, so your agent builds with the library as it actually is. No key, no account.
|
|
@@ -94,13 +94,13 @@ import 'rift-ds/tokens/presets/presets.css';
|
|
|
94
94
|
|
|
95
95
|
## Components
|
|
96
96
|
|
|
97
|
-
<!-- component-count -->
|
|
97
|
+
<!-- component-count -->153<!-- /component-count --> components, including a chat set for AI products (Chat thread, Composer, Tool call, Reasoning, Thread panel) and Shader field, a WebGL2 ambient background whose light sources read your colour tokens at runtime, so it re-themes with everything else. Details and live examples are on the [docs site](https://rift-ds.com/components).
|
|
98
98
|
|
|
99
99
|
<details>
|
|
100
100
|
<summary>The full list</summary>
|
|
101
101
|
|
|
102
102
|
<!-- component-list:start -->
|
|
103
|
-
Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Prompt suggestions · Prose · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Tabs · Tag input · Textarea · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
|
|
103
|
+
Accordion · Agent plan · Agent rail · Agent status · AI button · Alert · Alert dialog · Anchor nav · Animated number · App layout · App sidebar · Area chart · Artifact card · Artifact panel · Attachment drop zone · Attachment group · Attachment tile · Attachment viewer · Avatar · Avatar group · Badge · Banner · Bar chart · Bottom nav · Breadcrumb · Button · Button group · Card · Card stack · Carousel · Chat header · Chat marker · Chat message · Chat thread · Checkbox · Chip · Circular button · Code block · Code diff · Colour picker · Combo chart · Combobox · Command palette · Composer · Contact card · Context menu · Contribution graph · Data table · Date input · Date picker · Dialog · Divider · Document chip · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Event calendar · Field · Figure · File input · Filter bar · Funnel chart · Gantt chart · Gauge · Globe · Hover card · Image compare · Input · Inspector colour picker · Inspector dropdown · Inspector input · Inspector section · Inspector segments · Inspector slider · Inspector toggle switch · Instructions · Interrupt card · Kbd · Legend tile · Lightbox · Line chart · Link list · Map callout · Map legend · Mention · Message actions · Message card · Meter · Model picker · Nav · Nav list · Notification centre · Number input · Pagination · Panel · Pie chart · Pin input · Pixel avatar · Popover · Progress bar · Prompt suggestions · Prose · Question card · Quote · Radar chart · Radial chart · Radio button · Rating · Reasoning · Rich dropdown · Scatter chart · Section title · Segmented control · Selection card · Shader field · Sidebar panel · Skeleton · Slider · Source chip · Source trail · Sparkline · Spinner · Split button · Split pane · Stacked bar chart · Stat · Status dot · Stepper · Streaming text · Swatch · Table · Tabs · Tag input · Textarea · Thread panel · Thread tabs · Time picker · Timeline · Toast · Toggle group · Toggle switch · Tool call · Toolbar · Tooltip · Top app bar · Tree view · Treemap · Usage card · Waveform · Widget · World map
|
|
104
104
|
<!-- component-list:end -->
|
|
105
105
|
|
|
106
106
|
</details>
|
|
@@ -23,6 +23,9 @@ const handler = createChatHandler({
|
|
|
23
23
|
models: CHAT_MODELS,
|
|
24
24
|
defaultModel: DEFAULT_CHAT_MODEL,
|
|
25
25
|
// tools: [], // Deterministic lookups the model may call; a tool can answer with a card.
|
|
26
|
+
// A reply can end in a question with answers to pick, drawn as a card the
|
|
27
|
+
// visitor taps. Asking ends the exchange; the answer arrives as the next message.
|
|
28
|
+
askUser: true,
|
|
26
29
|
guardrails,
|
|
27
30
|
});
|
|
28
31
|
|
package/chat/AssistantTurn.js
CHANGED
|
@@ -12,6 +12,7 @@ import { DOCK_QUERY } from "./queries.js";
|
|
|
12
12
|
import { FollowupSuggestions } from "./FollowupSuggestions.js";
|
|
13
13
|
import { ResponseActions } from "./ResponseActions.js";
|
|
14
14
|
import { TurnCard } from "./TurnContent.js";
|
|
15
|
+
import { TurnQuestion } from "./TurnQuestion.js";
|
|
15
16
|
function MarkdownLink({ href, children }) {
|
|
16
17
|
const { view, setOpen, renderLink, isInternalHref } = useChatWidget();
|
|
17
18
|
if (!href || !isInternalHref(href)) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
@@ -44,7 +45,7 @@ function AssistantTurn({ turn, live }) {
|
|
|
44
45
|
const durationSeconds = live ? live.durationSeconds : turn?.durationSeconds;
|
|
45
46
|
const text = live ? live.text : turn?.text ?? "";
|
|
46
47
|
const { turns, live: liveTurn, freshFollowupsId } = useChatWidget();
|
|
47
|
-
const followupsPending = !!turn && turn.text !== "" && !!turn.exchangeId && !turn.followups && !liveTurn && turns[turns.length - 1]?.id === turn.id;
|
|
48
|
+
const followupsPending = !!turn && turn.text !== "" && !!turn.exchangeId && !turn.followups && !turn.question && !liveTurn && turns[turns.length - 1]?.id === turn.id;
|
|
48
49
|
return /* @__PURE__ */ jsx(
|
|
49
50
|
ChatMessage,
|
|
50
51
|
{
|
|
@@ -56,6 +57,7 @@ function AssistantTurn({ turn, live }) {
|
|
|
56
57
|
{
|
|
57
58
|
turnId: turn.id,
|
|
58
59
|
suggestions: turn.followups,
|
|
60
|
+
chosen: turn.followupChosen,
|
|
59
61
|
entrance: turn.id === freshFollowupsId
|
|
60
62
|
}
|
|
61
63
|
) : followupsPending && turn ? /* @__PURE__ */ jsx(FollowupSuggestions, { turnId: turn.id, suggestions: [], pending: true }) : void 0,
|
|
@@ -72,7 +74,8 @@ function AssistantTurn({ turn, live }) {
|
|
|
72
74
|
),
|
|
73
75
|
turn?.content,
|
|
74
76
|
turn?.card && /* @__PURE__ */ jsx(TurnCard, { card: turn.card }),
|
|
75
|
-
text !== "" && /* @__PURE__ */ jsx(Prose, { children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents, children: text }) })
|
|
77
|
+
text !== "" && /* @__PURE__ */ jsx(Prose, { children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components: markdownComponents, children: text }) }),
|
|
78
|
+
turn?.question && /* @__PURE__ */ jsx(TurnQuestion, { turn })
|
|
76
79
|
] })
|
|
77
80
|
}
|
|
78
81
|
);
|
package/chat/ChatWidget.css
CHANGED
|
@@ -976,7 +976,7 @@
|
|
|
976
976
|
box-shadow: var(--shadow-floating);
|
|
977
977
|
overflow: hidden;
|
|
978
978
|
backdrop-filter: blur(24px);
|
|
979
|
-
z-index:
|
|
979
|
+
z-index: var(--layer-chat-panel);
|
|
980
980
|
/* The panel-to-takeover glide: the same card grows to fill the viewport,
|
|
981
981
|
so every property that differs between the two views animates together. */
|
|
982
982
|
transition:
|
|
@@ -1050,7 +1050,7 @@ html[data-chat-resizing] .ds-chat-launcher__panel {
|
|
|
1050
1050
|
position: fixed;
|
|
1051
1051
|
inset: 0;
|
|
1052
1052
|
background: var(--color-scrim);
|
|
1053
|
-
z-index:
|
|
1053
|
+
z-index: var(--layer-chat-scrim);
|
|
1054
1054
|
animation: ds-chat-launcher-scrim-in var(--motion-duration-slow) var(--motion-ease-standard);
|
|
1055
1055
|
}
|
|
1056
1056
|
|
|
@@ -1075,7 +1075,7 @@ html[data-chat-resizing] .ds-chat-launcher__panel {
|
|
|
1075
1075
|
justify-content: center;
|
|
1076
1076
|
cursor: ew-resize;
|
|
1077
1077
|
touch-action: none;
|
|
1078
|
-
z-index:
|
|
1078
|
+
z-index: calc(var(--layer-chat-panel) + 1);
|
|
1079
1079
|
}
|
|
1080
1080
|
|
|
1081
1081
|
.ds-chat-launcher__dock-handle--left {
|
|
@@ -1135,6 +1135,7 @@ html[data-chat-resizing] .ds-chat-launcher__panel {
|
|
|
1135
1135
|
corner, all inside the card. */
|
|
1136
1136
|
.ds-chat-launcher__float-grip {
|
|
1137
1137
|
position: absolute;
|
|
1138
|
+
/* ds-allow(stacking): local order inside the floating card, above its own content; the card itself sits on --layer-chat-panel. */
|
|
1138
1139
|
z-index: 10;
|
|
1139
1140
|
display: flex;
|
|
1140
1141
|
align-items: center;
|
|
@@ -1246,7 +1247,7 @@ html[data-chat-resizing] .ds-chat-launcher__panel--floating .ds-chat-header {
|
|
|
1246
1247
|
position: fixed;
|
|
1247
1248
|
right: var(--ds-chat-inset);
|
|
1248
1249
|
bottom: var(--ds-chat-inset);
|
|
1249
|
-
z-index:
|
|
1250
|
+
z-index: var(--layer-chat-launcher);
|
|
1250
1251
|
}
|
|
1251
1252
|
|
|
1252
1253
|
/* The button's own solid field keeps it legible over whatever content
|
package/chat/ChatWidget.d.ts
CHANGED
|
@@ -37,8 +37,17 @@ export interface ChatWidgetProps {
|
|
|
37
37
|
phone?: boolean;
|
|
38
38
|
/** The header brand name. */
|
|
39
39
|
title?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Which way the conversation reads. `top-down` (the default) floats each
|
|
42
|
+
* new turn to the top of the thread and streams the answer in below it.
|
|
43
|
+
* `bottom-up` is the classic message thread: turns stack from the bottom,
|
|
44
|
+
* the newest just above the composer, and the view follows the answer.
|
|
45
|
+
*/
|
|
46
|
+
flow?: 'top-down' | 'bottom-up';
|
|
40
47
|
/** The composer's placeholder text. */
|
|
41
48
|
placeholder?: string;
|
|
49
|
+
/** The composer's placeholder while a set of questions holds it: the answer goes in the card, or the card is skipped. */
|
|
50
|
+
questionsPlaceholder?: string;
|
|
42
51
|
/** Show the conversation starters on the welcome screen. */
|
|
43
52
|
showStarters?: boolean;
|
|
44
53
|
/** The header mark's image src; null (the default) shows the name alone. */
|
|
@@ -112,4 +121,4 @@ export interface ChatWidgetProps {
|
|
|
112
121
|
* a stage card, a whole page). It reads all of its state from ChatProvider,
|
|
113
122
|
* so the conversation is the same object wherever it shows.
|
|
114
123
|
*/
|
|
115
|
-
export declare function ChatWidget({ fullscreenEnabled, closeEnabled, placementEnabled, compact, phone, title, placeholder, showStarters, logo, tagline, starters: startersInput, startersKey, composerActions, models, defaultModel, lockedModelDescription, fileAttachments, maxAttachments, maxAttachmentBytes, mentions, threads, threadsProps, tabs, aside, agent, artifacts, contextLabel, widgets, widgetsLayout, widgetsWidth, disclaimer, }: ChatWidgetProps): import("react").JSX.Element;
|
|
124
|
+
export declare function ChatWidget({ fullscreenEnabled, closeEnabled, placementEnabled, compact, phone, title, flow, placeholder, questionsPlaceholder, showStarters, logo, tagline, starters: startersInput, startersKey, composerActions, models, defaultModel, lockedModelDescription, fileAttachments, maxAttachments, maxAttachmentBytes, mentions, threads, threadsProps, tabs, aside, agent, artifacts, contextLabel, widgets, widgetsLayout, widgetsWidth, disclaimer, }: ChatWidgetProps): import("react").JSX.Element;
|
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 {
|
|
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";
|
|
@@ -27,6 +27,8 @@ import { CHAT_PLACEMENTS, chatPlacement } from "./placement.js";
|
|
|
27
27
|
import { ASIDE_WITH_THREADS_MIN_WIDTH, ARTIFACT_MIN_COLUMN_WIDTH, THREADS_RAIL_MIN_WIDTH, ASIDE_RAIL_MIN_WIDTH } from "./queries.js";
|
|
28
28
|
import { fitsChip } from "./suggestions.js";
|
|
29
29
|
import { TurnCard } from "./TurnContent.js";
|
|
30
|
+
import { isBlockingQuestion } from "./questions.js";
|
|
31
|
+
import { ReplyBody } from "./TurnQuestion.js";
|
|
30
32
|
import "./ChatWidget.css";
|
|
31
33
|
const DEFAULT_MAX_ATTACHMENTS = 10;
|
|
32
34
|
const DEFAULT_MAX_ATTACHMENT_BYTES = 50 * 1024 * 1024;
|
|
@@ -40,7 +42,9 @@ function ChatWidget({
|
|
|
40
42
|
compact = false,
|
|
41
43
|
phone = false,
|
|
42
44
|
title = "Assistant",
|
|
45
|
+
flow = "top-down",
|
|
43
46
|
placeholder = "Ask anything",
|
|
47
|
+
questionsPlaceholder = "Answer the questions above, or skip them",
|
|
44
48
|
showStarters = true,
|
|
45
49
|
logo = null,
|
|
46
50
|
tagline = "How can I help you today?",
|
|
@@ -91,6 +95,14 @@ function ChatWidget({
|
|
|
91
95
|
openArtifact,
|
|
92
96
|
closeArtifact
|
|
93
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]);
|
|
94
106
|
const lockedValues = modelPolicy?.locked ?? [];
|
|
95
107
|
const pick = chosenModel && !lockedValues.includes(chosenModel) ? chosenModel : null;
|
|
96
108
|
const pickerValue = pick ?? modelPolicy?.default ?? defaultModel ?? models?.[0]?.value;
|
|
@@ -478,7 +490,7 @@ function ChatWidget({
|
|
|
478
490
|
),
|
|
479
491
|
tabsNode && /* @__PURE__ */ jsx("div", { className: "ds-chat-widget__tabs-row", children: tabsNode }),
|
|
480
492
|
/* @__PURE__ */ jsxs("div", { className: "ds-chat-widget__body", children: [
|
|
481
|
-
/* @__PURE__ */ jsx(ArtifactSeatContext.Provider, { value: artifacts, children: /* @__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: [
|
|
482
494
|
...turns.map(
|
|
483
495
|
(turn) => turn.bare ? (
|
|
484
496
|
/* Conversation furniture (a day marker): the injected
|
|
@@ -495,7 +507,7 @@ function ChatWidget({
|
|
|
495
507
|
turn.content,
|
|
496
508
|
turn.card && /* @__PURE__ */ jsx(TurnCard, { card: turn.card })
|
|
497
509
|
] }),
|
|
498
|
-
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
|
|
499
511
|
},
|
|
500
512
|
turn.id
|
|
501
513
|
)
|
|
@@ -515,7 +527,8 @@ function ChatWidget({
|
|
|
515
527
|
Composer,
|
|
516
528
|
{
|
|
517
529
|
ref: composerRef,
|
|
518
|
-
placeholder,
|
|
530
|
+
placeholder: questionsOpen ? questionsPlaceholder : placeholder,
|
|
531
|
+
disabled: questionsOpen,
|
|
519
532
|
sendLabel: "Send",
|
|
520
533
|
aiGlow: true,
|
|
521
534
|
value: draft,
|
package/chat/DemoWelcome.js
CHANGED
|
@@ -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:
|
|
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("
|
|
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("
|
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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 {
|
|
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)
|
|
29
|
+
if (item) chooseFollowup(turnId, item.label);
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { ChatReply, ChatTurn } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The questions an assistant turn asks, drawn under its prose. Live only
|
|
4
|
+
* while the turn is the newest and nothing is streaming: once the
|
|
5
|
+
* conversation moves on without an answer the card stays as it was offered,
|
|
6
|
+
* inert, so the transcript still shows what was asked.
|
|
7
|
+
*/
|
|
8
|
+
export declare function TurnQuestion({ turn }: {
|
|
9
|
+
turn: ChatTurn;
|
|
10
|
+
}): import("react").JSX.Element | null;
|
|
11
|
+
/**
|
|
12
|
+
* A user turn that answered a set of questions: each question over its
|
|
13
|
+
* answers. One question, with one pick or several, stays the ordinary
|
|
14
|
+
* bubble, its picks joined with commas.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ReplyBody({ reply, text }: {
|
|
17
|
+
reply?: ChatReply;
|
|
18
|
+
text: string;
|
|
19
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useRef, useEffect } from "react";
|
|
4
|
+
import { QuestionSummary, QuestionCard } from "../components/QuestionCard/QuestionCard.js";
|
|
5
|
+
import { useChatWidget } from "./context.js";
|
|
6
|
+
import { isBlockingQuestion } from "./questions.js";
|
|
7
|
+
function TurnQuestion({ turn }) {
|
|
8
|
+
const { turns, live, answerQuestion, skipQuestion } = useChatWidget();
|
|
9
|
+
const set = turn.question;
|
|
10
|
+
const newest = turns[turns.length - 1]?.id === turn.id;
|
|
11
|
+
const blocking = newest && isBlockingQuestion(turn, live !== null);
|
|
12
|
+
const ref = useRef(null);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!blocking) return;
|
|
15
|
+
ref.current?.querySelector("button:not(:disabled)")?.focus({ preventScroll: true });
|
|
16
|
+
}, []);
|
|
17
|
+
if (!set) return null;
|
|
18
|
+
return /* @__PURE__ */ jsx(
|
|
19
|
+
QuestionCard,
|
|
20
|
+
{
|
|
21
|
+
ref,
|
|
22
|
+
className: "ds-chat-widget__question",
|
|
23
|
+
questions: set.questions,
|
|
24
|
+
values: turn.answers,
|
|
25
|
+
disabled: turn.answers === void 0 && (!newest || live !== null),
|
|
26
|
+
onValuesChange: (answers) => answerQuestion(turn.id, answers),
|
|
27
|
+
onSkip: blocking ? () => skipQuestion(turn.id) : void 0
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function ReplyBody({ reply, text }) {
|
|
32
|
+
if (!reply || reply.items.length === 1) return /* @__PURE__ */ jsx(Fragment, { children: text });
|
|
33
|
+
return /* @__PURE__ */ jsx(QuestionSummary, { variant: "plain", items: reply.items });
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
ReplyBody,
|
|
37
|
+
TurnQuestion
|
|
38
|
+
};
|