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
@@ -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
+ };
@@ -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;
@@ -0,0 +1,80 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import { DemoDashboardArtifact } from "./demo-card-components.js";
4
+ const DEMO_ARTIFACT_RENDERERS = {
5
+ "demo-dashboard": () => /* @__PURE__ */ jsx(DemoDashboardArtifact, {})
6
+ };
7
+ const DEMO_DASHBOARD_ARTIFACT = {
8
+ id: "launch-dashboard",
9
+ title: "Launch dashboard",
10
+ kind: "demo-dashboard",
11
+ meta: "Dashboard",
12
+ icon: "space_dashboard"
13
+ };
14
+ const LAUNCH_PAGE_HTML = `<!doctype html>
15
+ <html lang="en">
16
+ <head>
17
+ <meta charset="utf-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1">
19
+ <meta name="color-scheme" content="light dark">
20
+ <title>Acme Workspaces</title>
21
+ <style>
22
+ * { box-sizing: border-box; }
23
+ body { margin: 0; font-family: ui-sans-serif, system-ui, sans-serif; color: #14181f; background: #f6f4ef; line-height: 1.5; }
24
+ header { display: flex; align-items: center; justify-content: space-between; padding: 20px 32px; }
25
+ .brand { font-weight: 700; letter-spacing: -0.01em; }
26
+ nav { display: flex; gap: 20px; font-size: 14px; color: #55606e; }
27
+ .hero { padding: 56px 32px 40px; max-width: 720px; }
28
+ .eyebrow { font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: #7a5cff; }
29
+ h1 { margin: 12px 0 16px; font-size: clamp(32px, 6vw, 52px); line-height: 1.05; letter-spacing: -0.03em; }
30
+ .lede { margin: 0 0 28px; font-size: 18px; color: #414b58; max-width: 52ch; }
31
+ .cta { display: inline-block; padding: 12px 22px; border-radius: 999px; background: #14181f; color: #fff; font-weight: 600; font-size: 15px; }
32
+ .ghost { margin-left: 16px; font-size: 15px; font-weight: 600; color: #14181f; }
33
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 16px; padding: 8px 32px 48px; }
34
+ .card { padding: 20px; border-radius: 16px; background: #fff; border: 1px solid #e7e3da; }
35
+ .card h2 { margin: 0 0 6px; font-size: 16px; }
36
+ .card p { margin: 0; font-size: 14px; color: #55606e; }
37
+ footer { padding: 20px 32px 32px; font-size: 13px; color: #7b8592; }
38
+ @media (prefers-color-scheme: dark) {
39
+ body { color: #eef0f3; background: #101216; }
40
+ nav, .card p, footer { color: #98a1ad; }
41
+ .eyebrow { color: #a999ff; }
42
+ .lede { color: #c2c8d0; }
43
+ .cta { background: #eef0f3; color: #101216; }
44
+ .ghost { color: #eef0f3; }
45
+ .card { background: #181b21; border-color: #2a2e36; }
46
+ }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <header>
51
+ <span class="brand">Acme</span>
52
+ <nav><span>Product</span><span>Plans</span><span>Sign in</span></nav>
53
+ </header>
54
+ <section class="hero">
55
+ <span class="eyebrow">Launching this week</span>
56
+ <h1>One workspace for the whole team.</h1>
57
+ <p class="lede">Shared projects, guest seats and ninety days of version history, ready the moment your team joins.</p>
58
+ <span class="cta">Start the free trial</span><span class="ghost">See the plans</span>
59
+ </section>
60
+ <section class="grid">
61
+ <div class="card"><h2>Shared workspaces</h2><p>Every project in one place, open to the people who need it.</p></div>
62
+ <div class="card"><h2>Guest seats</h2><p>Bring in a client or a contractor without buying a full seat.</p></div>
63
+ <div class="card"><h2>Version history</h2><p>Ninety days of every change, one click from restored.</p></div>
64
+ </section>
65
+ <footer>Acme is a made-up company. This page is a demo artifact.</footer>
66
+ </body>
67
+ </html>`;
68
+ const DEMO_PAGE_ARTIFACT = {
69
+ id: "launch-page",
70
+ title: "Launch page",
71
+ kind: "html",
72
+ meta: "Web page",
73
+ icon: "web",
74
+ html: LAUNCH_PAGE_HTML
75
+ };
76
+ export {
77
+ DEMO_ARTIFACT_RENDERERS,
78
+ DEMO_DASHBOARD_ARTIFACT,
79
+ DEMO_PAGE_ARTIFACT
80
+ };
@@ -44,3 +44,5 @@ export declare function DemoStack({ card, renderers }: {
44
44
  card: DemoStackCard;
45
45
  renderers: ChatCardRenderers;
46
46
  }): import("react").JSX.Element;
47
+ /** The dashboard's rows ask the chat, like the welcome board's. */
48
+ export declare function DemoDashboardArtifact(): import("react").JSX.Element;
@@ -8,6 +8,8 @@ import { CodeBlock } from "../components/CodeBlock/CodeBlock.js";
8
8
  import { InterruptCard } from "../components/InterruptCard/InterruptCard.js";
9
9
  import { MessageCard } from "../components/MessageCard/MessageCard.js";
10
10
  import { ToolCall } from "../components/ToolCall/ToolCall.js";
11
+ import { useChatWidget } from "./context.js";
12
+ import { DemoBoard } from "./DemoWelcome.js";
11
13
  const RETRY_RUN_MS = 1400;
12
14
  function DemoToolCall({ card }) {
13
15
  const [status, setStatus] = useState(card.status);
@@ -51,8 +53,13 @@ function DemoStack({ card, renderers }) {
51
53
  return render ? /* @__PURE__ */ jsx("div", { children: render(item.data) }, index) : null;
52
54
  }) });
53
55
  }
56
+ function DemoDashboardArtifact() {
57
+ const { send } = useChatWidget();
58
+ return /* @__PURE__ */ jsx("div", { className: "ds-chat-demo__artifact-board", children: /* @__PURE__ */ jsx(DemoBoard, { onAsk: (prompt) => send(prompt) }) });
59
+ }
54
60
  export {
55
61
  DemoChart,
62
+ DemoDashboardArtifact,
56
63
  DemoInterrupt,
57
64
  DemoStack,
58
65
  DemoToolCall
@@ -1,3 +1,5 @@
1
+ import { artifactCard } from "./artifacts.js";
2
+ import { DEMO_DASHBOARD_ARTIFACT, DEMO_PAGE_ARTIFACT } from "./demo-artifacts.js";
1
3
  import { scriptChat } from "./sim.js";
2
4
  const TRIAL_SIGNUPS = [
3
5
  { week: "W1", signups: 320 },
@@ -18,7 +20,44 @@ const PLAN_STEPS = [
18
20
  { label: "Invite the three teammates", status: "active" },
19
21
  { label: "Post the welcome note", status: "pending" }
20
22
  ];
23
+ const KICKOFF_STEPS = [
24
+ { label: "Hold the slot on every calendar", status: "completed" },
25
+ { label: "Draft the agenda from the launch plan", status: "active" },
26
+ { label: "Send the invites", status: "pending" }
27
+ ];
21
28
  const card = (kind, data) => ({ kind, data });
29
+ function planFor(answer) {
30
+ const size = answer.toLowerCase();
31
+ const plan = size.includes("just me") ? "the Starter plan covers you: unlimited projects, no seats to manage" : size.includes("more than 50") ? "the Business plan fits: single sign-on, audit logs and pooled seats" : "the Team plan fits: shared workspaces for everyone, and guest seats stay free";
32
+ return {
33
+ steps: [{ status: "Matching the plan", point: "Team size decides the plan; nothing else differs." }],
34
+ text: `For ${answer.trim().replace(/[.!?]$/, "")}, ${plan}.`,
35
+ followups: ["What do the plans include?", "Invite my team to a workspace", "Set up notifications for me"]
36
+ };
37
+ }
38
+ const INVITES_SENT = {
39
+ steps: [
40
+ { status: "Sending the invites", point: "Three teammates to invite, all editors." },
41
+ { status: "Confirming delivery", point: "One call covers the batch." }
42
+ ],
43
+ text: "All three invites are out. Everyone lands in the workspace with editor access.",
44
+ card: card("tool-call", {
45
+ name: "invite_teammates",
46
+ summary: "3 editors to acme-design",
47
+ status: "success",
48
+ duration: "0.6s",
49
+ args: '{\n "workspace": "acme-design",\n "role": "editor",\n "emails": 3\n}'
50
+ }),
51
+ followups: ["Set up notifications for me", "Take us up to twelve seats", "Plan our launch kickoff"]
52
+ };
53
+ const PLANS_COMPARED = {
54
+ steps: [
55
+ { status: "Comparing the plans", point: "Three plans differ on collaboration, not on limits." },
56
+ { status: "Drafting the answer", point: "Name what the Team plan adds over the base." }
57
+ ],
58
+ text: "Every plan includes unlimited projects. The Team plan adds shared workspaces, guest seats, and a 90 day version history.",
59
+ followups: ["Which plan fits a team of five?", "Help me pick a plan", "Where is that written down?"]
60
+ };
22
61
  const DEMO_STARTERS = [
23
62
  { id: "start", label: "How do I get started?" },
24
63
  { id: "plans", label: "What do the plans include?" },
@@ -40,16 +79,7 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
40
79
  ],
41
80
  card: card("plan", { steps: PLAN_STEPS }),
42
81
  followups: ["Find a time we can all meet", "Take us up to twelve seats", "How is the trial going?"]
43
- }).user("What do the plans include?").assistant(
44
- "Every plan includes unlimited projects. The Team plan adds shared workspaces, guest seats, and a 90 day version history.",
45
- {
46
- steps: [
47
- { status: "Comparing the plans", point: "Three plans differ on collaboration, not on limits." },
48
- { status: "Drafting the answer", point: "Name what the Team plan adds over the base." }
49
- ],
50
- followups: ["Which plan fits a team of five?", "Where is that written down?", "How is the trial going?"]
51
- }
52
- ).user("Which plan fits a team of five?").assistant("The Team plan fits what you described: shared workspaces for the whole team, and guest seats stay free.", {
82
+ }).user("What do the plans include?").assistant(PLANS_COMPARED.text, PLANS_COMPARED).user("Which plan fits a team of five?").assistant("The Team plan fits what you described: shared workspaces for the whole team, and guest seats stay free. Shall we go with it?", {
53
83
  steps: [
54
84
  { status: "Matching plans to the team", point: "The visitor needs shared workspaces and guest seats." },
55
85
  { status: "Drafting the answer", point: "The Team plan covers both; lead with the card." }
@@ -57,13 +87,26 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
57
87
  card: card("product-card", {
58
88
  title: "Team plan",
59
89
  description: "Shared workspaces, guest seats, and version history for the whole team.",
60
- meta: "Billed monthly, cancel any time",
61
- actions: [
62
- { label: "Choose plan", variant: "secondary" },
63
- { label: "Compare plans", variant: "tertiary" }
64
- ]
90
+ meta: "Billed monthly, cancel any time"
65
91
  }),
66
- followups: ["Where is that written down?", "Invite my team to a workspace", "How is the trial going?"]
92
+ question: {
93
+ id: "team-plan",
94
+ questions: [
95
+ {
96
+ id: "plan",
97
+ prompt: "Go with the Team plan?",
98
+ options: [
99
+ { value: "choose", label: "Choose the Team plan" },
100
+ { value: "compare", label: "Compare the plans first" }
101
+ ]
102
+ }
103
+ ]
104
+ },
105
+ answered: (answer) => answer.toLowerCase().includes("compare") ? PLANS_COMPARED : {
106
+ steps: [{ status: "Switching the plan", point: "Move the workspace to Team from today." }],
107
+ text: "Done. The workspace is on the Team plan from today, billed monthly, and you can step back down any time.",
108
+ followups: ["Invite my team to a workspace", "Where is that written down?", "How is the trial going?"]
109
+ }
67
110
  }).user("Where is that written down?").assistant("Each chip opens the page the answer came from, so nothing here needs taking on trust.", {
68
111
  steps: [
69
112
  { status: "Collecting the sources", point: "Three product pages back the answer." },
@@ -110,26 +153,30 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
110
153
  label: "Signups"
111
154
  }),
112
155
  followups: ["Which plan fits a team of five?", "Invite my team to a workspace", "Where is that written down?"]
113
- }).user("Invite my team to a workspace").assistant("Happy to. Give me the word and I will send invites to the three teammates on your account, all as editors.", {
156
+ }).user("Invite my team to a workspace").assistant("Happy to. Shall I send invites to the three teammates on your account, all as editors?", {
114
157
  steps: [
115
158
  { status: "Checking the account", point: "Three teammates sit on the account, none invited yet." },
116
159
  { status: "Drafting the answer", point: "Confirm before sending anything." }
117
160
  ],
118
- followups: ["Go ahead and invite the other three", "Take us up to twelve seats", "Find a time we can all meet"]
119
- }).user("Go ahead and invite the other three").assistant("All three invites are out. Everyone lands in the workspace with editor access.", {
120
- steps: [
121
- { status: "Sending the invites", point: "Three teammates to invite, all editors." },
122
- { status: "Confirming delivery", point: "One call covers the batch." }
123
- ],
124
- card: card("tool-call", {
125
- name: "invite_teammates",
126
- summary: "3 editors to acme-design",
127
- status: "success",
128
- duration: "0.6s",
129
- args: '{\n "workspace": "acme-design",\n "role": "editor",\n "emails": 3\n}'
130
- }),
131
- followups: ["Find a time we can all meet", "Take us up to twelve seats", "How is the trial going?"]
132
- }).user("Take us up to twelve seats").assistant("Only the seat count moves, from five to twelve. Everything else in the config stays as it was.", {
161
+ question: {
162
+ id: "invites",
163
+ questions: [
164
+ {
165
+ id: "send",
166
+ prompt: "Send the invites?",
167
+ options: [
168
+ { value: "send", label: "Invite all three" },
169
+ { value: "wait", label: "Not yet" }
170
+ ]
171
+ }
172
+ ]
173
+ },
174
+ answered: (answer) => answer.toLowerCase().startsWith("not yet") ? {
175
+ steps: [{ status: "Holding the invites", point: "Nothing sent; keep the list ready." }],
176
+ text: "Nothing sent. The three are ready to go whenever you are.",
177
+ followups: ["Take us up to twelve seats", "Find a time we can all meet", "How is the trial going?"]
178
+ } : INVITES_SENT
179
+ }).user("Go ahead and invite the other three").assistant(INVITES_SENT.text, INVITES_SENT).user("Take us up to twelve seats").assistant("Only the seat count moves, from five to twelve. Everything else in the config stays as it was. Apply it?", {
133
180
  steps: [
134
181
  { status: "Locating the seat limit", point: "The seat limit lives in workspace.config.ts." },
135
182
  { status: "Preparing the change", point: "One line changes; show the diff." }
@@ -138,7 +185,34 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
138
185
  filename: "workspace.config.ts",
139
186
  diff: '@@ -12,7 +12,7 @@\n export const workspace = {\n name: "acme-design",\n- seats: 5,\n+ seats: 12,\n region: "eu-west",\n };'
140
187
  }),
141
- followups: ["Go ahead and invite the other three", "What do the plans include?", "Find a time we can all meet"]
188
+ question: {
189
+ id: "seats",
190
+ questions: [
191
+ {
192
+ id: "apply",
193
+ prompt: "Apply the change?",
194
+ options: [
195
+ { value: "apply", label: "Apply it" },
196
+ { value: "wait", label: "Not yet" }
197
+ ]
198
+ }
199
+ ]
200
+ },
201
+ answered: (answer) => answer.toLowerCase().startsWith("not yet") ? {
202
+ steps: [{ status: "Leaving the config", point: "The diff stays a proposal." }],
203
+ text: "Left as it is. The workspace stays at five seats until you say otherwise.",
204
+ followups: ["What do the plans include?", "Invite my team to a workspace", "How is the trial going?"]
205
+ } : {
206
+ steps: [{ status: "Applying the change", point: "One line in workspace.config.ts." }],
207
+ text: "Applied. The workspace takes twelve seats from now, and the next invoice picks up the difference.",
208
+ card: card("tool-call", {
209
+ name: "update_config",
210
+ summary: "workspace.config.ts, seats 5 to 12",
211
+ status: "success",
212
+ duration: "0.4s"
213
+ }),
214
+ followups: ["Invite my team to a workspace", "Find a time we can all meet", "How is the trial going?"]
215
+ }
142
216
  }).user("Here is the launch plan we are working from.").assistant(
143
217
  "Three phases, and most of the budget sits behind launch week itself. The dates line up with the workspace you already set up, so nothing needs to move.",
144
218
  {
@@ -167,31 +241,185 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
167
241
  }),
168
242
  followups: ["Set it all up for me", "Take us up to twelve seats", "Find a time we can all meet"]
169
243
  }
170
- ).user("Sync the shared calendar").assistant("The sync timed out on the provider's side. Retry below and it should land.", {
244
+ ).user("Sync the shared calendar").assistant("The sync timed out on the provider's side. It usually lands on a second try. Run it again?", {
171
245
  steps: [{ status: "Starting the calendar sync", point: "The calendar sync runs after the invites." }],
172
246
  card: card("tool-call", {
173
247
  name: "sync_calendar",
174
248
  summary: "the shared team calendar",
175
249
  status: "error",
176
- duration: "2.1s",
177
- retryable: true
250
+ duration: "2.1s"
178
251
  }),
179
- followups: ["Find a time we can all meet", "Go ahead and invite the other three", "How is the trial going?"]
180
- }).user("Find a time we can all meet").assistant("I can check everyone's calendar for a slot, once you allow it below.", {
252
+ question: {
253
+ id: "sync-retry",
254
+ questions: [
255
+ {
256
+ id: "retry",
257
+ prompt: "Try the sync again?",
258
+ options: [
259
+ { value: "retry", label: "Retry the sync" },
260
+ { value: "leave", label: "Leave it for now" }
261
+ ]
262
+ }
263
+ ]
264
+ },
265
+ answered: (answer) => answer.toLowerCase().startsWith("leave") ? {
266
+ steps: [{ status: "Leaving the sync", point: "Nothing to retry until asked." }],
267
+ text: "Left for now. Nothing else depends on it, and it can run again whenever you like.",
268
+ followups: ["Find a time we can all meet", "Invite my team to a workspace", "How is the trial going?"]
269
+ } : {
270
+ steps: [{ status: "Retrying the sync", point: "Second attempt against the provider." }],
271
+ text: "Second time lucky. The shared calendar is in sync.",
272
+ card: card("tool-call", {
273
+ name: "sync_calendar",
274
+ summary: "the shared team calendar",
275
+ status: "success",
276
+ duration: "1.2s"
277
+ }),
278
+ followups: ["Find a time we can all meet", "Plan our launch kickoff", "How is the trial going?"]
279
+ }
280
+ }).user("Find a time we can all meet").assistant("I can check everyone's calendar for a slot. Can I read the shared team calendar?", {
181
281
  steps: [
182
282
  { status: "Checking what this needs", point: "Suggesting times needs calendar access." },
183
283
  { status: "Preparing the request", point: "Ask before connecting anything." }
184
284
  ],
185
- card: card("interrupt", {
186
- title: "Connect the shared calendar?",
187
- description: "The assistant wants read access to the team calendar to suggest meeting times.",
188
- options: [
189
- { value: "allow", label: "Allow once", variant: "primary" },
190
- { value: "always", label: "Always allow", variant: "secondary" },
191
- { value: "deny", label: "Not now", variant: "secondary" }
285
+ question: {
286
+ id: "calendar-access",
287
+ questions: [
288
+ {
289
+ id: "access",
290
+ prompt: "Read the shared calendar?",
291
+ options: [
292
+ { value: "once", label: "Yes, just this once" },
293
+ { value: "always", label: "Yes, always" },
294
+ { value: "no", label: "Not now" }
295
+ ]
296
+ }
192
297
  ]
193
- }),
194
- followups: ["Go ahead and invite the other three", "How is the trial going?", "How do I get started?"]
298
+ },
299
+ answered: (answer) => answer.toLowerCase().startsWith("not now") ? {
300
+ steps: [{ status: "Leaving the calendar alone", point: "No access; offer the manual route." }],
301
+ text: "No problem, nothing is connected. You can share a few times that suit you and I will send them round.",
302
+ followups: ["Go ahead and invite the other three", "How is the trial going?", "How do I get started?"]
303
+ } : {
304
+ steps: [
305
+ { status: "Reading the shared calendar", point: "Four calendars, the next ten working days." },
306
+ { status: "Finding a free slot", point: "Thursday afternoon is clear for everyone." }
307
+ ],
308
+ text: answer.toLowerCase().includes("always") ? "Connected for good. Thursday at 2pm is the first slot everyone has free, and I will keep an eye out when plans change." : "Read once and disconnected. Thursday at 2pm is the first slot everyone has free.",
309
+ followups: ["Plan our launch kickoff", "Go ahead and invite the other three", "How is the trial going?"]
310
+ }
311
+ }).user("Build me a launch dashboard").assistant(
312
+ "Here is the launch on one board: what is running now, the trial signups, the projects and what is waiting on you. The rows that ask a question will ask it here.",
313
+ {
314
+ steps: [
315
+ { status: "Gathering the launch numbers", point: "Signups, seats and open work all belong on one board." },
316
+ { status: "Laying out the dashboard", point: "Too much for a card; build it as an artifact." }
317
+ ],
318
+ card: artifactCard(DEMO_DASHBOARD_ARTIFACT),
319
+ followups: ["Draft the launch page", "How is the trial going?", "Which plan fits a team of five?"]
320
+ }
321
+ ).user("Draft the launch page").assistant(
322
+ "A first draft of the launch page is up beside the chat: the headline, the three things the Team plan adds, and one call to action. The source is behind the Code view.",
323
+ {
324
+ steps: [
325
+ { status: "Reading the launch plan", point: "The page should lead with the workspace, then the plan." },
326
+ { status: "Writing the page", point: "One document, its own styles, no scripts." }
327
+ ],
328
+ card: artifactCard(DEMO_PAGE_ARTIFACT),
329
+ followups: ["Build me a launch dashboard", "What do the plans include?", "How is the trial going?"]
330
+ }
331
+ ).user("Help me pick a plan").assistant("Happy to. How big is the team that will use the workspace?", {
332
+ steps: [{ status: "Reading the plans", point: "Plans differ by team size; ask before recommending." }],
333
+ question: {
334
+ id: "team-size",
335
+ questions: [
336
+ {
337
+ id: "size",
338
+ prompt: "How big is the team?",
339
+ options: [
340
+ { value: "solo", label: "Just me" },
341
+ { value: "small", label: "2 to 10 people" },
342
+ { value: "medium", label: "11 to 50 people" },
343
+ { value: "large", label: "More than 50" }
344
+ ],
345
+ other: true
346
+ }
347
+ ]
348
+ },
349
+ answered: planFor
350
+ }).user("Set up notifications for me").assistant("Sure. Which of these should reach you? Pick as many as you like.", {
351
+ steps: [{ status: "Reading your settings", point: "Four kinds of notification; ask which matter." }],
352
+ question: {
353
+ id: "notifications",
354
+ questions: [
355
+ {
356
+ id: "kinds",
357
+ prompt: "Notify me about",
358
+ mode: "multi",
359
+ options: [
360
+ { value: "mentions", label: "Mentions", description: "When someone writes your name" },
361
+ { value: "comments", label: "Comments on my files" },
362
+ { value: "invites", label: "Invites accepted" },
363
+ { value: "summary", label: "A weekly summary", description: "Mondays, one email" }
364
+ ],
365
+ other: true
366
+ }
367
+ ]
368
+ },
369
+ answered: (answer) => ({
370
+ steps: [{ status: "Saving your settings", point: "Turn on what was picked, everything else off." }],
371
+ text: `Done. You will hear about ${answer.trim().toLowerCase()}, and nothing else. Change it any time in Settings.`,
372
+ followups: ["Plan our launch kickoff", "Help me pick a plan", "How is the trial going?"]
373
+ })
374
+ }).user("Plan our launch kickoff").assistant("Three quick questions first, so the kickoff fits the team.", {
375
+ steps: [
376
+ { status: "Reading the launch plan", point: "The kickoff needs a date, a guest list and a format." },
377
+ { status: "Preparing the questions", point: "Ask all three before booking anything." }
378
+ ],
379
+ question: {
380
+ id: "kickoff",
381
+ questions: [
382
+ {
383
+ id: "when",
384
+ prompt: "When should it happen?",
385
+ options: [
386
+ { value: "this-week", label: "This week" },
387
+ { value: "next-week", label: "Next week" },
388
+ { value: "after-beta", label: "After the beta" }
389
+ ]
390
+ },
391
+ {
392
+ id: "who",
393
+ prompt: "Who should be there?",
394
+ mode: "multi",
395
+ options: [
396
+ { value: "design", label: "Design" },
397
+ { value: "engineering", label: "Engineering" },
398
+ { value: "marketing", label: "Marketing" },
399
+ { value: "sales", label: "Sales" }
400
+ ]
401
+ },
402
+ {
403
+ id: "format",
404
+ prompt: "What format?",
405
+ options: [
406
+ { value: "room", label: "In person" },
407
+ { value: "video", label: "A video call" },
408
+ { value: "async", label: "An async update" }
409
+ ],
410
+ other: true
411
+ }
412
+ ]
413
+ },
414
+ answered: {
415
+ steps: [
416
+ { status: "Booking the kickoff", point: "Find the first slot everyone named is free." },
417
+ { status: "Drafting the agenda", point: "Lead with the launch dates, then owners." }
418
+ ],
419
+ text: "Kickoff is on the calendar. The slot is held for everyone you named, and the agenda is being drafted from the launch plan.",
420
+ card: card("plan", { steps: KICKOFF_STEPS }),
421
+ followups: ["Find a time we can all meet", "Build me a launch dashboard", "Set up notifications for me"]
422
+ }
195
423
  }).fallback(
196
424
  "The launch is on track overall. Three of the four readiness tracks are green, and the one item I would watch is the **legal review**, since it gates the pricing rollout.",
197
425
  {
@@ -203,10 +431,10 @@ const DEMO_SCRIPT = scriptChat().user("How do I get started?").assistant(
203
431
  followups: ["Which track is behind?", "Who owns the legal review?", "What happens if pricing slips?"]
204
432
  }
205
433
  ).fallback(
206
- "This chat is a scripted preview of the widget, so free text gets one of a few written replies. The suggestion chips lead through the parts of the story that show off the rich content: a plan, a chart, a diff, a tool call and an approval.",
434
+ "This chat is a scripted preview of the widget, so free text gets one of a few written replies. The suggestion chips lead through the parts of the story that show off the rich content: a plan, a chart, a diff, a tool call, an artifact and questions to answer.",
207
435
  {
208
436
  steps: [{ status: "Reading the question", point: "Nothing scripted matches this one." }],
209
- followups: ["How do I get started?", "What do the plans include?", "How is the trial going?"]
437
+ followups: ["How do I get started?", "Help me pick a plan", "Plan our launch kickoff"]
210
438
  }
211
439
  ).build();
212
440
  export {