rift-ds 1.6.0 → 1.8.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 +34 -13
- package/bin/rift-ds.mjs +231 -44
- package/bin/templates/chat/.env.chat.example +21 -0
- package/bin/templates/chat/app/api/chat/feedback/route.ts +12 -0
- package/bin/templates/chat/app/api/chat/followups/route.ts +23 -0
- package/bin/templates/chat/app/api/chat/guardrails.ts +18 -0
- package/bin/templates/chat/app/api/chat/route.ts +31 -0
- package/bin/templates/chat/components/chat/ChatMount.tsx +52 -0
- package/bin/templates/chat/components/chat/models.ts +29 -0
- package/chat/AssistantTurn.d.ts +5 -0
- package/chat/AssistantTurn.js +82 -0
- package/chat/ChatAgentPane.css +56 -0
- package/chat/ChatAgentPane.d.ts +21 -0
- package/chat/ChatAgentPane.js +25 -0
- package/chat/ChatLauncher.d.ts +64 -0
- package/chat/ChatLauncher.js +347 -0
- package/chat/ChatProvider.d.ts +53 -0
- package/chat/ChatProvider.js +181 -0
- package/chat/ChatTabs.d.ts +13 -0
- package/chat/ChatTabs.js +24 -0
- package/chat/ChatThreads.d.ts +26 -0
- package/chat/ChatThreads.js +55 -0
- package/chat/ChatWidget.css +1072 -0
- package/chat/ChatWidget.d.ts +113 -0
- package/chat/ChatWidget.js +495 -0
- package/chat/DemoWelcome.d.ts +13 -0
- package/chat/DemoWelcome.js +131 -0
- package/chat/FollowupSuggestions.d.ts +23 -0
- package/chat/FollowupSuggestions.js +34 -0
- package/chat/ResponseActions.d.ts +18 -0
- package/chat/ResponseActions.js +79 -0
- package/chat/TurnContent.d.ts +9 -0
- package/chat/TurnContent.js +12 -0
- package/chat/context.d.ts +65 -0
- package/chat/context.js +27 -0
- package/chat/demo-agent-panes.d.ts +5 -0
- package/chat/demo-agent-panes.js +49 -0
- package/chat/demo-agent.d.ts +3 -0
- package/chat/demo-agent.js +28 -0
- package/chat/demo-card-components.d.ts +46 -0
- package/chat/demo-card-components.js +59 -0
- package/chat/demo-cards.d.ts +32 -0
- package/chat/demo-cards.js +59 -0
- package/chat/demo-fixtures.d.ts +20 -0
- package/chat/demo-fixtures.js +113 -0
- package/chat/demo-script.d.ts +6 -0
- package/chat/demo-script.js +215 -0
- package/chat/demo.css +46 -0
- package/chat/demo.d.ts +9 -0
- package/chat/demo.js +24 -0
- package/chat/followups.d.ts +41 -0
- package/chat/followups.js +55 -0
- package/chat/greeting.d.ts +11 -0
- package/chat/greeting.js +21 -0
- package/chat/index.d.ts +34 -0
- package/chat/index.js +80 -0
- package/chat/placement.d.ts +48 -0
- package/chat/placement.js +39 -0
- package/chat/queries.d.ts +9 -0
- package/chat/queries.js +22 -0
- package/chat/registry.d.ts +25 -0
- package/chat/registry.js +9 -0
- package/chat/registry.json.d.ts +69 -0
- package/chat/registry.json.js +11 -0
- package/chat/server.d.ts +217 -0
- package/chat/server.js +488 -0
- package/chat/sim.d.ts +71 -0
- package/chat/sim.js +125 -0
- package/chat/starters.d.ts +15 -0
- package/chat/starters.js +38 -0
- package/chat/suggestions.d.ts +22 -0
- package/chat/suggestions.js +9 -0
- package/chat/threads.d.ts +73 -0
- package/chat/threads.js +270 -0
- package/chat/transport.d.ts +49 -0
- package/chat/transport.js +105 -0
- package/chat/types.d.ts +210 -0
- package/chat/types.js +16 -0
- package/chat/upstash.d.ts +63 -0
- package/chat/upstash.js +147 -0
- package/chat/useChat.d.ts +64 -0
- package/chat/useChat.js +241 -0
- package/cli/templates/chat/app/api/chat/feedback/route.d.ts +3 -0
- package/cli/templates/chat/app/api/chat/followups/route.d.ts +3 -0
- package/cli/templates/chat/app/api/chat/guardrails.d.ts +10 -0
- package/cli/templates/chat/app/api/chat/route.d.ts +4 -0
- package/cli/templates/chat/components/chat/ChatMount.d.ts +1 -0
- package/cli/templates/chat/components/chat/models.d.ts +11 -0
- package/components/AgentRail/AgentRail.css +2 -2
- package/components/Attachment/Attachment.css +14 -0
- package/components/Attachment/AttachmentGroup.d.ts +2 -2
- package/components/Attachment/AttachmentGroup.js +7 -2
- package/components/Composer/Composer.css +88 -4
- package/components/Composer/Composer.d.ts +21 -0
- package/components/Composer/Composer.js +199 -14
- package/components/Mention/Mention.css +195 -0
- package/components/Mention/Mention.d.ts +68 -0
- package/components/Mention/Mention.js +145 -0
- package/components/Mention/mentions.d.ts +81 -0
- package/components/Mention/mentions.js +83 -0
- package/components/ShaderField/ShaderField.d.ts +6 -5
- package/components/ShaderField/useShaderField.d.ts +2 -1
- package/components/ShaderField/useShaderField.js +6 -6
- package/components/StatusDot/StatusDot.css +36 -13
- package/components/StatusDot/StatusDot.d.ts +16 -4
- package/components/StatusDot/StatusDot.js +25 -5
- package/components/ThreadPanel/ThreadPanel.css +20 -11
- package/components/ThreadPanel/ThreadPanel.d.ts +15 -3
- package/components/ThreadPanel/ThreadPanel.js +30 -6
- package/components/ThreadTabs/ThreadTabs.css +18 -8
- package/components/ThreadTabs/ThreadTabs.d.ts +8 -2
- package/components/ThreadTabs/ThreadTabs.js +24 -5
- package/components/registry.json +9 -1
- package/components/registry.json.d.ts +9 -1
- package/components/registry.json.js +1 -1
- package/fonts/material-symbols.css +2 -1
- package/index.d.ts +1 -0
- package/index.js +11 -0
- package/package.json +43 -2
- package/tokens/presets/coral.css +18 -2
- package/tokens/presets/forest.css +18 -2
- package/tokens/presets/gold.css +18 -2
- package/tokens/presets/mono.css +18 -2
- package/tokens/presets/pink.css +18 -2
- package/tokens/presets/terminal.css +18 -2
- package/tokens/presets/violet.css +18 -2
- package/tokens/presets/warm.css +18 -2
- package/tokens/presets/zest.css +18 -2
- package/tokens/registry.json +6 -0
- package/tokens/registry.json.d.ts +6 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +10 -0
- package/tokens/tokens-light.css +10 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { BarChart } from "../components/Chart/BarChart.js";
|
|
4
|
+
import { PixelAvatar } from "../components/PixelAvatar/PixelAvatar.js";
|
|
5
|
+
import { PromptSuggestions } from "../components/PromptSuggestions/PromptSuggestions.js";
|
|
6
|
+
import { Spinner } from "../components/Spinner/Spinner.js";
|
|
7
|
+
import { StatusDot } from "../components/StatusDot/StatusDot.js";
|
|
8
|
+
import { WidgetGrid, Widget, WidgetRow, WidgetGroup } from "../components/Widget/Widget.js";
|
|
9
|
+
const LINKS = [
|
|
10
|
+
{ id: "How do I get started?", icon: "rocket_launch", label: "Get started", description: "Set up a workspace" },
|
|
11
|
+
{ id: "What do the plans include?", icon: "sell", label: "Plans", description: "What each one includes" },
|
|
12
|
+
{ id: "Invite my team to a workspace", icon: "group_add", label: "Team", description: "Invite the others" },
|
|
13
|
+
{ id: "How is the trial going?", icon: "monitoring", label: "Trial", description: "Signups so far" }
|
|
14
|
+
];
|
|
15
|
+
const CARDS = [
|
|
16
|
+
{
|
|
17
|
+
id: "Set it all up for me",
|
|
18
|
+
icon: "auto_awesome",
|
|
19
|
+
label: "Set up the workspace",
|
|
20
|
+
description: "Roles, channels and the first project, done for you."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "Which plan fits a team of five?",
|
|
24
|
+
icon: "sell",
|
|
25
|
+
label: "Pick a plan",
|
|
26
|
+
description: "Which plan fits a team of five?"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "Find a time we can all meet",
|
|
30
|
+
icon: "event",
|
|
31
|
+
label: "Find a time",
|
|
32
|
+
description: "A slot the whole team has free this week."
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
const BOARD_SIGNUPS = [
|
|
36
|
+
{ label: "W1", value: 320 },
|
|
37
|
+
{ label: "W2", value: 410 },
|
|
38
|
+
{ label: "W3", value: 380 },
|
|
39
|
+
{ label: "W4", value: 520 },
|
|
40
|
+
{ label: "W5", value: 560 },
|
|
41
|
+
{ label: "W6", value: 640 }
|
|
42
|
+
];
|
|
43
|
+
const BOARD_PROJECTS = [
|
|
44
|
+
["Workspace revamp", "4 open tasks", "1m"],
|
|
45
|
+
["Billing cleanup", "Waiting on finance", "3d"],
|
|
46
|
+
["Launch week", "Plan attached", "5d"]
|
|
47
|
+
];
|
|
48
|
+
function DemoBoard({ onAsk }) {
|
|
49
|
+
return /* @__PURE__ */ jsxs(WidgetGrid, { children: [
|
|
50
|
+
/* @__PURE__ */ jsx(Widget, { title: "Working now", count: 1, children: /* @__PURE__ */ jsx(
|
|
51
|
+
WidgetRow,
|
|
52
|
+
{
|
|
53
|
+
leading: /* @__PURE__ */ jsx(Spinner, { size: "sm" }),
|
|
54
|
+
title: "Importing last year’s invoices",
|
|
55
|
+
description: "212 of 340 matched",
|
|
56
|
+
meta: "2m 13s"
|
|
57
|
+
}
|
|
58
|
+
) }),
|
|
59
|
+
/* @__PURE__ */ jsxs(Widget, { title: "Trial signups", children: [
|
|
60
|
+
/* @__PURE__ */ jsx(BarChart, { data: BOARD_SIGNUPS, dataLabel: "Signups", height: 140, bare: true }),
|
|
61
|
+
/* @__PURE__ */ jsx(WidgetRow, { title: "How is the trial going?", meta: "Ask", onClick: () => onAsk("How is the trial going?") })
|
|
62
|
+
] }),
|
|
63
|
+
/* @__PURE__ */ jsx(Widget, { title: "Projects", count: BOARD_PROJECTS.length, children: BOARD_PROJECTS.map(([name, state, when]) => /* @__PURE__ */ jsx(WidgetRow, { leading: /* @__PURE__ */ jsx(PixelAvatar, { name }), title: name, description: state, meta: when }, name)) }),
|
|
64
|
+
/* @__PURE__ */ jsxs(Widget, { title: "To do", count: 4, children: [
|
|
65
|
+
/* @__PURE__ */ jsxs(WidgetGroup, { label: "Waiting on you", children: [
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
WidgetRow,
|
|
68
|
+
{
|
|
69
|
+
leading: /* @__PURE__ */ jsx(StatusDot, { variant: "warning", size: "sm", "aria-hidden": "true" }),
|
|
70
|
+
title: "Invite the other three",
|
|
71
|
+
description: "2 of 5 seats filled",
|
|
72
|
+
meta: "Today",
|
|
73
|
+
onClick: () => onAsk("Go ahead and invite the other three")
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
/* @__PURE__ */ jsx(
|
|
77
|
+
WidgetRow,
|
|
78
|
+
{
|
|
79
|
+
leading: /* @__PURE__ */ jsx(StatusDot, { variant: "info", size: "sm", "aria-hidden": "true" }),
|
|
80
|
+
title: "Choose a plan",
|
|
81
|
+
description: "Trial ends in 9 days",
|
|
82
|
+
meta: "Oct 19",
|
|
83
|
+
onClick: () => onAsk("Which plan fits a team of five?")
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
] }),
|
|
87
|
+
/* @__PURE__ */ jsxs(WidgetGroup, { label: "In progress", children: [
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
WidgetRow,
|
|
90
|
+
{
|
|
91
|
+
leading: /* @__PURE__ */ jsx(StatusDot, { variant: "positive", size: "sm", "aria-hidden": "true" }),
|
|
92
|
+
title: "Shared calendar",
|
|
93
|
+
description: "Synced an hour ago",
|
|
94
|
+
meta: "On"
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
/* @__PURE__ */ jsx(
|
|
98
|
+
WidgetRow,
|
|
99
|
+
{
|
|
100
|
+
leading: /* @__PURE__ */ jsx(StatusDot, { variant: "neutral", size: "sm", "aria-hidden": "true" }),
|
|
101
|
+
title: "Mention notifications",
|
|
102
|
+
description: "Quiet hours, 6pm to 8am",
|
|
103
|
+
meta: "Paused"
|
|
104
|
+
}
|
|
105
|
+
)
|
|
106
|
+
] })
|
|
107
|
+
] }),
|
|
108
|
+
/* @__PURE__ */ jsxs(Widget, { title: "This week", children: [
|
|
109
|
+
/* @__PURE__ */ jsx(WidgetRow, { title: "Signups", meta: "640" }),
|
|
110
|
+
/* @__PURE__ */ jsx(WidgetRow, { title: "Seats in use", meta: "5 of 12" }),
|
|
111
|
+
/* @__PURE__ */ jsx(WidgetRow, { title: "Messages sent", meta: "1,284" })
|
|
112
|
+
] })
|
|
113
|
+
] });
|
|
114
|
+
}
|
|
115
|
+
function DemoWelcome({
|
|
116
|
+
variant,
|
|
117
|
+
onAsk
|
|
118
|
+
}) {
|
|
119
|
+
if (variant === "links") {
|
|
120
|
+
return /* @__PURE__ */ jsx(PromptSuggestions, { layout: "tiles", ariaLabel: "Places to start", suggestions: LINKS, onValueChange: onAsk });
|
|
121
|
+
}
|
|
122
|
+
if (variant === "cards") {
|
|
123
|
+
return /* @__PURE__ */ jsx(PromptSuggestions, { layout: "cards", ariaLabel: "Things to ask", suggestions: CARDS, onValueChange: onAsk });
|
|
124
|
+
}
|
|
125
|
+
if (variant === "dashboard") return /* @__PURE__ */ jsx(DemoBoard, { onAsk });
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
export {
|
|
129
|
+
DemoBoard,
|
|
130
|
+
DemoWelcome
|
|
131
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The follow-up questions under a finished answer. Deliberately the same
|
|
3
|
+
* component, layout and scale as the welcome screen's conversation starters:
|
|
4
|
+
* one pattern for "here is something you could ask", whether the conversation
|
|
5
|
+
* has started or not. The only difference is where the questions come from.
|
|
6
|
+
*
|
|
7
|
+
* Tapping one sends it as the next message. A tap while a response is already
|
|
8
|
+
* streaming is ignored by `send`, the same as pressing enter in the composer
|
|
9
|
+
* would be.
|
|
10
|
+
*
|
|
11
|
+
* While the questions are still being generated, `pending` shows the row's
|
|
12
|
+
* shimmer placeholders in their place: a real loading state, unlike the
|
|
13
|
+
* welcome starters' staged one.
|
|
14
|
+
*/
|
|
15
|
+
export declare function FollowupSuggestions({ turnId, suggestions, pending, entrance, }: {
|
|
16
|
+
/** The turn these belong to; makes each suggestion's id unique in the thread. */
|
|
17
|
+
turnId: string;
|
|
18
|
+
suggestions: string[];
|
|
19
|
+
/** The follow-ups request is still in flight; show placeholders. */
|
|
20
|
+
pending?: boolean;
|
|
21
|
+
/** Play the chips' staggered pop-in. Off for rows remounted by a reopen, whose suggestions already arrived once. */
|
|
22
|
+
entrance?: boolean;
|
|
23
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { PromptSuggestions } from "../components/PromptSuggestions/PromptSuggestions.js";
|
|
4
|
+
import { useChatWidget } from "./context.js";
|
|
5
|
+
function FollowupSuggestions({
|
|
6
|
+
turnId,
|
|
7
|
+
suggestions,
|
|
8
|
+
pending = false,
|
|
9
|
+
entrance = true
|
|
10
|
+
}) {
|
|
11
|
+
const { send } = useChatWidget();
|
|
12
|
+
const items = suggestions.map((label, index) => ({
|
|
13
|
+
id: `${turnId}-followup-${index}`,
|
|
14
|
+
label
|
|
15
|
+
}));
|
|
16
|
+
return /* @__PURE__ */ jsx(
|
|
17
|
+
PromptSuggestions,
|
|
18
|
+
{
|
|
19
|
+
className: "ds-chat-widget__followups",
|
|
20
|
+
layout: "stack",
|
|
21
|
+
ariaLabel: "Follow-up questions",
|
|
22
|
+
pending,
|
|
23
|
+
entrance,
|
|
24
|
+
suggestions: items,
|
|
25
|
+
onValueChange: (id) => {
|
|
26
|
+
const item = items.find((candidate) => candidate.id === id);
|
|
27
|
+
if (item) send(item.label);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
FollowupSuggestions
|
|
34
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ChatTurn } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The row under a committed assistant turn: copy, and, when the server
|
|
4
|
+
* logged the exchange and the provider has somewhere to send a verdict,
|
|
5
|
+
* thumbs up and down. A scripted transport never sends an exchange id, so
|
|
6
|
+
* it shows copy alone: the thumbs would have nothing to rate.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately not the library's MessageActions component: its items API
|
|
9
|
+
* swaps an icon by name, which cannot crossfade, and this row's copied-tick
|
|
10
|
+
* (status colour, scale-and-fade swap) and filled chosen thumb both need
|
|
11
|
+
* styling per element that the items shape does not carry.
|
|
12
|
+
*
|
|
13
|
+
* Copy takes the turn's raw markdown, not the rendered text: it pastes back
|
|
14
|
+
* into anything that reads markdown, and plain readers still get the words.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ResponseActions({ turn }: {
|
|
17
|
+
turn: ChatTurn;
|
|
18
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { MOTION_FEEDBACK_RESET_MS } from "../tokens/motion.js";
|
|
5
|
+
import { useChatWidget } from "./context.js";
|
|
6
|
+
function ResponseActions({ turn }) {
|
|
7
|
+
const { submitFeedback } = useChatWidget();
|
|
8
|
+
const [copied, setCopied] = useState(false);
|
|
9
|
+
const revertTimer = useRef(null);
|
|
10
|
+
useEffect(
|
|
11
|
+
() => () => {
|
|
12
|
+
if (revertTimer.current !== null) clearTimeout(revertTimer.current);
|
|
13
|
+
},
|
|
14
|
+
[]
|
|
15
|
+
);
|
|
16
|
+
const copy = async () => {
|
|
17
|
+
try {
|
|
18
|
+
await navigator.clipboard.writeText(turn.text);
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
setCopied(true);
|
|
23
|
+
if (revertTimer.current !== null) clearTimeout(revertTimer.current);
|
|
24
|
+
revertTimer.current = setTimeout(() => setCopied(false), MOTION_FEEDBACK_RESET_MS);
|
|
25
|
+
};
|
|
26
|
+
const vote = (verdict) => {
|
|
27
|
+
if (!turn.exchangeId || !submitFeedback || turn.feedback === verdict) return;
|
|
28
|
+
submitFeedback(turn.id, turn.exchangeId, verdict);
|
|
29
|
+
};
|
|
30
|
+
const iconClass = (hidden, tick = false) => [
|
|
31
|
+
"ds-chat-widget__action-icon",
|
|
32
|
+
tick ? "ds-chat-widget__action-icon--tick" : "",
|
|
33
|
+
hidden ? "ds-chat-widget__action-icon--hidden" : "",
|
|
34
|
+
"material-symbols-rounded"
|
|
35
|
+
].filter(Boolean).join(" ");
|
|
36
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
37
|
+
/* @__PURE__ */ jsxs(
|
|
38
|
+
"button",
|
|
39
|
+
{
|
|
40
|
+
type: "button",
|
|
41
|
+
className: "ds-chat-widget__action",
|
|
42
|
+
onClick: copy,
|
|
43
|
+
"aria-label": copied ? "Copied" : "Copy response",
|
|
44
|
+
children: [
|
|
45
|
+
/* @__PURE__ */ jsx("span", { className: iconClass(copied), "aria-hidden": "true", children: "content_copy" }),
|
|
46
|
+
/* @__PURE__ */ jsx("span", { className: iconClass(!copied, true), "aria-hidden": "true", children: "check" })
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
),
|
|
50
|
+
turn.exchangeId && submitFeedback && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
51
|
+
/* @__PURE__ */ jsx(
|
|
52
|
+
"button",
|
|
53
|
+
{
|
|
54
|
+
type: "button",
|
|
55
|
+
className: "ds-chat-widget__action",
|
|
56
|
+
onClick: () => vote("up"),
|
|
57
|
+
"aria-label": "Good response",
|
|
58
|
+
"aria-pressed": turn.feedback === "up",
|
|
59
|
+
children: /* @__PURE__ */ jsx("span", { className: iconClass(false), "aria-hidden": "true", children: "thumb_up" })
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
/* @__PURE__ */ jsx(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
type: "button",
|
|
66
|
+
className: "ds-chat-widget__action",
|
|
67
|
+
onClick: () => vote("down"),
|
|
68
|
+
"aria-label": "Bad response",
|
|
69
|
+
"aria-pressed": turn.feedback === "down",
|
|
70
|
+
children: /* @__PURE__ */ jsx("span", { className: iconClass(false), "aria-hidden": "true", children: "thumb_down" })
|
|
71
|
+
}
|
|
72
|
+
)
|
|
73
|
+
] }),
|
|
74
|
+
/* @__PURE__ */ jsx("span", { className: "ds-chat-widget__sr-only", "aria-live": "polite", children: copied ? "Copied to clipboard" : "" })
|
|
75
|
+
] });
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
ResponseActions
|
|
79
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ChatCard } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* A turn's `card`, drawn through the provider's renderers. A kind with no
|
|
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.
|
|
6
|
+
*/
|
|
7
|
+
export declare function TurnCard({ card }: {
|
|
8
|
+
card: ChatCard;
|
|
9
|
+
}): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useChatWidget } from "./context.js";
|
|
4
|
+
function TurnCard({ card }) {
|
|
5
|
+
const { renderers } = useChatWidget();
|
|
6
|
+
const render = renderers[card.kind];
|
|
7
|
+
if (!render) return null;
|
|
8
|
+
return /* @__PURE__ */ jsx(Fragment, { children: render(card.data) });
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
TurnCard
|
|
12
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { ChatPlacement, ChatSide } from './placement';
|
|
3
|
+
import { ThreadsState } from './threads';
|
|
4
|
+
import { ChatFeedbackVerdict } from './types';
|
|
5
|
+
import { ChatState } from './useChat';
|
|
6
|
+
/** Panel is the docked rail; full is the viewport takeover. */
|
|
7
|
+
export type ChatView = 'panel' | 'full';
|
|
8
|
+
/**
|
|
9
|
+
* Draws a `card` event's data as a React node. Keyed by the card's `kind`;
|
|
10
|
+
* a kind with no renderer draws nothing.
|
|
11
|
+
*/
|
|
12
|
+
export type ChatCardRenderers = Record<string, (data: unknown) => ReactNode>;
|
|
13
|
+
/** How the widget renders a link the model wrote. */
|
|
14
|
+
export type ChatLinkRenderer = (props: {
|
|
15
|
+
href: string;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
/** Call on navigation: closes the panel when it covers the page. */
|
|
18
|
+
onNavigate: () => void;
|
|
19
|
+
}) => ReactNode;
|
|
20
|
+
/** Sends a thumbs verdict somewhere. */
|
|
21
|
+
export type FeedbackSink = (exchangeId: string, verdict: ChatFeedbackVerdict) => Promise<void>;
|
|
22
|
+
export interface ChatWidgetContextValue extends ChatState {
|
|
23
|
+
/** The visitor's explicit model pick (a wire value), null while following the server's default. */
|
|
24
|
+
chosenModel: string | null;
|
|
25
|
+
/** Records an explicit pick; it rides on every send until changed or cleared by a lock. */
|
|
26
|
+
setChosenModel: (value: string | null) => void;
|
|
27
|
+
open: boolean;
|
|
28
|
+
setOpen: (open: boolean) => void;
|
|
29
|
+
toggleOpen: () => void;
|
|
30
|
+
/** Where the panel is in its lifecycle: `closing` is the exit beat after a close, during which the launcher keeps the panel rendered so its exit animation is seen. Everything behavioural keys on `open`; only presence reads this. */
|
|
31
|
+
panelPhase: 'open' | 'closing' | 'closed';
|
|
32
|
+
/** Which starters key has played its staged reveal, so a reopen shows the set standing rather than generating again. */
|
|
33
|
+
starterRevealKey: string | null;
|
|
34
|
+
setStarterRevealKey: (key: string | null) => void;
|
|
35
|
+
/** The draw for the starter pools: rolled once per load and again by reset(). */
|
|
36
|
+
starterSeed: number;
|
|
37
|
+
/** The turn whose follow-ups landed most recently in this open session, or null. Its row plays the entrance. */
|
|
38
|
+
freshFollowupsId: string | null;
|
|
39
|
+
view: ChatView;
|
|
40
|
+
setView: (view: ChatView) => void;
|
|
41
|
+
/** The panel's seat: a dock edge or floating. Provider-lifetime, so a reload returns to the host's default. */
|
|
42
|
+
placement: ChatPlacement;
|
|
43
|
+
setPlacement: (placement: ChatPlacement) => void;
|
|
44
|
+
/** The dock edge, remembered while floating, so the card has an edge to return to and to overlay from. */
|
|
45
|
+
side: ChatSide;
|
|
46
|
+
/** Popped out of the dock: a non-modal card the visitor moves and sizes. */
|
|
47
|
+
floating: boolean;
|
|
48
|
+
draft: string;
|
|
49
|
+
setDraft: (draft: string) => void;
|
|
50
|
+
/** The element to restore focus to when the panel closes. */
|
|
51
|
+
returnFocusRef: React.MutableRefObject<HTMLElement | null>;
|
|
52
|
+
/** The thread lifecycle behind the built-in threads and tabs. */
|
|
53
|
+
threads: ThreadsState;
|
|
54
|
+
renderers: ChatCardRenderers;
|
|
55
|
+
renderLink: ChatLinkRenderer;
|
|
56
|
+
isInternalHref: (href: string) => boolean;
|
|
57
|
+
/** Record and send a thumbs verdict; null when feedback is off. */
|
|
58
|
+
submitFeedback: ((turnId: string, exchangeId: string, verdict: ChatFeedbackVerdict) => void) | null;
|
|
59
|
+
}
|
|
60
|
+
export declare const ChatWidgetContext: import('react').Context<ChatWidgetContextValue | null>;
|
|
61
|
+
/** The chat's state and actions, for anything rendered inside `ChatProvider`. */
|
|
62
|
+
export declare function useChatWidget(): ChatWidgetContextValue;
|
|
63
|
+
export declare const isInternalPath: (href: string) => boolean;
|
|
64
|
+
/** A sink that posts a verdict to an endpoint speaking the feedback protocol (`createFeedbackHandler`). */
|
|
65
|
+
export declare function createFeedbackPoster(endpoint?: string): FeedbackSink;
|
package/chat/context.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useContext, createContext } from "react";
|
|
3
|
+
const ChatWidgetContext = createContext(null);
|
|
4
|
+
function useChatWidget() {
|
|
5
|
+
const context = useContext(ChatWidgetContext);
|
|
6
|
+
if (!context) {
|
|
7
|
+
throw new Error("useChatWidget must be used inside a ChatProvider");
|
|
8
|
+
}
|
|
9
|
+
return context;
|
|
10
|
+
}
|
|
11
|
+
const INTERNAL_PATH = /^\/(?:[a-z0-9-]+(?:\/[a-z0-9-]+){0,3})?$/;
|
|
12
|
+
const isInternalPath = (href) => INTERNAL_PATH.test(href);
|
|
13
|
+
function createFeedbackPoster(endpoint = "/api/chat/feedback") {
|
|
14
|
+
return async (id, verdict) => {
|
|
15
|
+
await fetch(endpoint, {
|
|
16
|
+
method: "POST",
|
|
17
|
+
headers: { "Content-Type": "application/json" },
|
|
18
|
+
body: JSON.stringify({ id, verdict })
|
|
19
|
+
});
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export {
|
|
23
|
+
ChatWidgetContext,
|
|
24
|
+
createFeedbackPoster,
|
|
25
|
+
isInternalPath,
|
|
26
|
+
useChatWidget
|
|
27
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, Fragment, jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "../components/Button/Button.js";
|
|
4
|
+
import { NotificationItem } from "../components/NotificationCenter/NotificationCenter.js";
|
|
5
|
+
import "./ChatAgentPane.css";
|
|
6
|
+
const NOOP = () => {
|
|
7
|
+
};
|
|
8
|
+
function DemoActivityPane() {
|
|
9
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
10
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-chat-agent-pane__group", children: [
|
|
11
|
+
/* @__PURE__ */ jsx("p", { className: "ds-chat-agent-pane__heading", children: "Today" }),
|
|
12
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Watch the Q3 invoices", media: "bolt", time: "9:13 am", children: "Started the hourly check on the billing inbox" }),
|
|
13
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Draft the renewal note", media: "check_circle", time: "9:04 am", children: "Asked for your choice on the tone" }),
|
|
14
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Send the approval request", media: "check_circle", time: "9:02 am", children: "Prepared the request and paused for a yes" }),
|
|
15
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Tidy the workspace roles", media: "check_circle", time: "8:58 am", children: "Interrupted" })
|
|
16
|
+
] }),
|
|
17
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-chat-agent-pane__group", children: [
|
|
18
|
+
/* @__PURE__ */ jsx("p", { className: "ds-chat-agent-pane__heading", children: "Yesterday" }),
|
|
19
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Summarize the standup", media: "check_circle", time: "4:40 pm", children: "Posted the recap to the team channel" }),
|
|
20
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Retire the stale flags", media: "check_circle", time: "11:12 am", children: "Opened a pull request for review" })
|
|
21
|
+
] })
|
|
22
|
+
] });
|
|
23
|
+
}
|
|
24
|
+
function DemoScheduledPane() {
|
|
25
|
+
return /* @__PURE__ */ jsxs("div", { className: "ds-chat-agent-pane__group", children: [
|
|
26
|
+
/* @__PURE__ */ jsx("p", { className: "ds-chat-agent-pane__heading", children: "Daily" }),
|
|
27
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Morning digest", media: "schedule", time: "8:00 am", children: "Every weekday" }),
|
|
28
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Inbox sweep", media: "mail", time: "Every 30m", children: "Flags anything still waiting on a reply" }),
|
|
29
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Invoice watch", media: "receipt_long", time: "Hourly", children: "Asks before it pays anything" })
|
|
30
|
+
] });
|
|
31
|
+
}
|
|
32
|
+
function DemoPersonalizationPane({ name }) {
|
|
33
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
34
|
+
/* @__PURE__ */ jsx("div", { className: "ds-chat-agent-pane__card", children: /* @__PURE__ */ jsxs("div", { className: "ds-chat-agent-pane__card-row", children: [
|
|
35
|
+
/* @__PURE__ */ jsx("span", { className: "ds-chat-agent-pane__card-label", children: name }),
|
|
36
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", size: "compact", iconLeft: "edit", onClick: NOOP, label: "Edit" })
|
|
37
|
+
] }) }),
|
|
38
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-chat-agent-pane__group", children: [
|
|
39
|
+
/* @__PURE__ */ jsx("p", { className: "ds-chat-agent-pane__heading", children: "What it remembers" }),
|
|
40
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Tone", media: "favorite", time: "18 Sep", children: "Plain and short, no exclamation marks" }),
|
|
41
|
+
/* @__PURE__ */ jsx(NotificationItem, { title: "Working hours", media: "psychology", time: "22 Sep", children: "Nothing scheduled before 9am" })
|
|
42
|
+
] })
|
|
43
|
+
] });
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
DemoActivityPane,
|
|
47
|
+
DemoPersonalizationPane,
|
|
48
|
+
DemoScheduledPane
|
|
49
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { EmptyState } from "../components/EmptyState/EmptyState.js";
|
|
4
|
+
import { DemoActivityPane, DemoScheduledPane, DemoPersonalizationPane } from "./demo-agent-panes.js";
|
|
5
|
+
function demoAgentTabs(name) {
|
|
6
|
+
return [
|
|
7
|
+
{ id: "activity", label: "Activity", icon: "list", content: /* @__PURE__ */ jsx(DemoActivityPane, {}) },
|
|
8
|
+
{
|
|
9
|
+
id: "approvals",
|
|
10
|
+
label: "Approvals",
|
|
11
|
+
icon: "verified_user",
|
|
12
|
+
content: /* @__PURE__ */ jsx(
|
|
13
|
+
EmptyState,
|
|
14
|
+
{
|
|
15
|
+
size: "compact",
|
|
16
|
+
icon: "verified_user",
|
|
17
|
+
title: "No approvals yet",
|
|
18
|
+
description: "Anything the agent wants to spend or send lands here first."
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
},
|
|
22
|
+
{ id: "scheduled", label: "Scheduled", icon: "history_toggle_off", content: /* @__PURE__ */ jsx(DemoScheduledPane, {}) },
|
|
23
|
+
{ id: "profile", label: "You", icon: "fingerprint", content: /* @__PURE__ */ jsx(DemoPersonalizationPane, { name }) }
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
demoAgentTabs
|
|
28
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ChatCardRenderers } from './context';
|
|
2
|
+
import { ChatCard } from './types';
|
|
3
|
+
export interface DemoChartCard {
|
|
4
|
+
type: 'area' | 'bar';
|
|
5
|
+
title: string;
|
|
6
|
+
meta?: string;
|
|
7
|
+
data: Record<string, string | number>[];
|
|
8
|
+
xKey: string;
|
|
9
|
+
yKey: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
export interface DemoToolCallCard {
|
|
13
|
+
name: string;
|
|
14
|
+
summary: string;
|
|
15
|
+
status: 'success' | 'error' | 'running';
|
|
16
|
+
duration?: string;
|
|
17
|
+
args?: string;
|
|
18
|
+
/** The error can be retried in place; the retry runs for a beat and lands as a success. */
|
|
19
|
+
retryable?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface DemoInterruptCard {
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
options: {
|
|
25
|
+
value: string;
|
|
26
|
+
label: string;
|
|
27
|
+
variant: 'primary' | 'secondary';
|
|
28
|
+
}[];
|
|
29
|
+
}
|
|
30
|
+
/** Several cards landing as one turn's stack. */
|
|
31
|
+
export interface DemoStackCard {
|
|
32
|
+
items: ChatCard[];
|
|
33
|
+
}
|
|
34
|
+
export declare function DemoToolCall({ card }: {
|
|
35
|
+
card: DemoToolCallCard;
|
|
36
|
+
}): import("react").JSX.Element;
|
|
37
|
+
export declare function DemoInterrupt({ card }: {
|
|
38
|
+
card: DemoInterruptCard;
|
|
39
|
+
}): import("react").JSX.Element;
|
|
40
|
+
export declare function DemoChart({ card }: {
|
|
41
|
+
card: DemoChartCard;
|
|
42
|
+
}): import("react").JSX.Element;
|
|
43
|
+
export declare function DemoStack({ card, renderers }: {
|
|
44
|
+
card: DemoStackCard;
|
|
45
|
+
renderers: ChatCardRenderers;
|
|
46
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { Button } from "../components/Button/Button.js";
|
|
5
|
+
import { AreaChart } from "../components/Chart/AreaChart.js";
|
|
6
|
+
import { BarChart } from "../components/Chart/BarChart.js";
|
|
7
|
+
import { CodeBlock } from "../components/CodeBlock/CodeBlock.js";
|
|
8
|
+
import { InterruptCard } from "../components/InterruptCard/InterruptCard.js";
|
|
9
|
+
import { MessageCard } from "../components/MessageCard/MessageCard.js";
|
|
10
|
+
import { ToolCall } from "../components/ToolCall/ToolCall.js";
|
|
11
|
+
const RETRY_RUN_MS = 1400;
|
|
12
|
+
function DemoToolCall({ card }) {
|
|
13
|
+
const [status, setStatus] = useState(card.status);
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (status !== "running" || !card.retryable) return;
|
|
16
|
+
const timer = setTimeout(() => setStatus("success"), RETRY_RUN_MS);
|
|
17
|
+
return () => clearTimeout(timer);
|
|
18
|
+
}, [status, card.retryable]);
|
|
19
|
+
return /* @__PURE__ */ jsx(
|
|
20
|
+
ToolCall,
|
|
21
|
+
{
|
|
22
|
+
name: card.name,
|
|
23
|
+
summary: card.summary,
|
|
24
|
+
status,
|
|
25
|
+
duration: status === "running" ? void 0 : card.duration,
|
|
26
|
+
actions: card.retryable && status === "error" ? /* @__PURE__ */ jsx(Button, { label: "Retry", variant: "secondary", size: "compact", onClick: () => setStatus("running") }) : void 0,
|
|
27
|
+
children: card.args ? /* @__PURE__ */ jsx(CodeBlock, { code: card.args, language: "json" }) : void 0
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
function DemoInterrupt({ card }) {
|
|
32
|
+
const [value, setValue] = useState(void 0);
|
|
33
|
+
return /* @__PURE__ */ jsx(
|
|
34
|
+
InterruptCard,
|
|
35
|
+
{
|
|
36
|
+
title: card.title,
|
|
37
|
+
description: card.description,
|
|
38
|
+
options: card.options,
|
|
39
|
+
value,
|
|
40
|
+
onValueChange: setValue
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
function DemoChart({ card }) {
|
|
45
|
+
const chart = card.type === "area" ? /* @__PURE__ */ jsx(AreaChart, { data: card.data, xKey: card.xKey, series: [{ dataKey: card.yKey, label: card.label }], height: 150 }) : /* @__PURE__ */ jsx(BarChart, { data: card.data, xKey: card.xKey, yKey: card.yKey, dataLabel: card.label, height: 150 });
|
|
46
|
+
return /* @__PURE__ */ jsx(MessageCard, { media: chart, title: card.title, meta: card.meta });
|
|
47
|
+
}
|
|
48
|
+
function DemoStack({ card, renderers }) {
|
|
49
|
+
return /* @__PURE__ */ jsx("div", { className: "ds-chat-demo__stack", children: card.items.map((item, index) => {
|
|
50
|
+
const render = renderers[item.kind];
|
|
51
|
+
return render ? /* @__PURE__ */ jsx("div", { children: render(item.data) }, index) : null;
|
|
52
|
+
}) });
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
DemoChart,
|
|
56
|
+
DemoInterrupt,
|
|
57
|
+
DemoStack,
|
|
58
|
+
DemoToolCall
|
|
59
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AgentPlanStep } from '../components/AgentPlan/AgentPlan';
|
|
2
|
+
import { ChatCardRenderers } from './context';
|
|
3
|
+
import { DemoChartCard, DemoInterruptCard, DemoStackCard, DemoToolCallCard } from './demo-card-components';
|
|
4
|
+
export type { DemoChartCard, DemoInterruptCard, DemoStackCard, DemoToolCallCard };
|
|
5
|
+
export interface DemoProductCard {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
meta?: string;
|
|
9
|
+
actions?: {
|
|
10
|
+
label: string;
|
|
11
|
+
variant: 'secondary' | 'tertiary';
|
|
12
|
+
}[];
|
|
13
|
+
}
|
|
14
|
+
export interface DemoSourcesCard {
|
|
15
|
+
items: {
|
|
16
|
+
title: string;
|
|
17
|
+
source: string;
|
|
18
|
+
meta?: string;
|
|
19
|
+
excerpt: string;
|
|
20
|
+
letter?: string;
|
|
21
|
+
index?: number;
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
export interface DemoDiffCard {
|
|
25
|
+
diff: string;
|
|
26
|
+
filename: string;
|
|
27
|
+
}
|
|
28
|
+
export interface DemoPlanCard {
|
|
29
|
+
steps: AgentPlanStep[];
|
|
30
|
+
}
|
|
31
|
+
/** Draws every demo card kind. Pass to `ChatProvider`'s `renderers`. */
|
|
32
|
+
export declare const DEMO_RENDERERS: ChatCardRenderers;
|