@mcp-b/react-components 0.6.4 → 0.7.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/dist/PromptInput-D6-G24p7.d.ts +528 -0
- package/dist/ThinkChatToolParts-DnUiZgCC.d.ts +83 -0
- package/dist/components/AgentChat.d.ts +11 -14
- package/dist/components/AgentChat.js +76 -57
- package/dist/components/AgentConnection.js +21 -18
- package/dist/components/Context.d.ts +6 -10
- package/dist/components/Context.js +4 -2
- package/dist/components/Conversation.d.ts +28 -28
- package/dist/components/Conversation.js +35 -54
- package/dist/components/Marker.d.ts +57 -0
- package/dist/components/Marker.js +89 -0
- package/dist/components/McpServerPicker.js +8 -10
- package/dist/components/MessageScroller.d.ts +52 -0
- package/dist/components/MessageScroller.js +62 -0
- package/dist/components/PromptInput.d.ts +2 -534
- package/dist/components/PromptInput.js +17 -11
- package/dist/components/ThinkChat.d.ts +10 -77
- package/dist/components/ThinkChat.js +59 -856
- package/dist/components/ThinkChatActivity.d.ts +73 -0
- package/dist/components/ThinkChatActivity.js +429 -0
- package/dist/components/ThinkChatContext.d.ts +28 -0
- package/dist/components/ThinkChatContext.js +10 -0
- package/dist/components/ThinkChatToolPanels.d.ts +39 -0
- package/dist/components/ThinkChatToolPanels.js +355 -0
- package/dist/components/ThinkChatToolParts.d.ts +2 -0
- package/dist/components/ThinkChatToolParts.js +66 -0
- package/dist/components/WorkspaceExplorer.d.ts +1 -1
- package/dist/components/useStickToBottom.d.ts +5 -2
- package/dist/components/useStickToBottom.js +5 -2
- package/dist/styles/agent-chat.css +15 -3
- package/dist/styles/agent-connection.css +12 -0
- package/dist/styles/code-block.css +4 -4
- package/dist/styles/conversation.css +10 -0
- package/dist/styles/index.css +2 -0
- package/dist/styles/marker.css +95 -0
- package/dist/styles/message-scroller.css +90 -0
- package/dist/styles/message.css +2 -1
- package/dist/styles/scrollbars.css +10 -2
- package/dist/styles/streamdown.css +2 -0
- package/dist/utils/mcp-server-catalog.d.ts +392 -1
- package/dist/utils/mcp-server-catalog.js +137 -1
- package/package.json +3 -2
|
@@ -2,15 +2,17 @@ import { t as cx } from "../class-names-9UtZwb0J.js";
|
|
|
2
2
|
import { D as PlusIcon, O as RefreshIcon } from "../icons-BCNawx-T.js";
|
|
3
3
|
import { Activity, ActivityContent, ActivityPanel, ActivityTrigger } from "./Activity.js";
|
|
4
4
|
import { Context, ContextContent, ContextContentBody, ContextContentHeader, ContextTrigger, getContextUsage, getContextUsedTokens } from "./Context.js";
|
|
5
|
-
import {
|
|
5
|
+
import { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport } from "./MessageScroller.js";
|
|
6
|
+
import { ConversationEmptyState } from "./Conversation.js";
|
|
6
7
|
import { Message, MessageAction, MessageActions, MessageAttachment, MessageContent, MessageResponse } from "./Message.js";
|
|
7
8
|
import { MenuGroup, MenuGroupLabel } from "./Menu.js";
|
|
8
9
|
import { PromptInput, PromptInputActionAddAttachments, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuTrigger, PromptInputApproval, PromptInputAttachments, PromptInputBody, PromptInputButton, PromptInputFooter, PromptInputPermissionSelector, PromptInputSubmit, PromptInputTextarea, PromptInputTools, getApprovalRequestId, getApprovalStateKey, getPendingToolApprovals, usePromptInputAttachments } from "./PromptInput.js";
|
|
9
10
|
import { Reasoning, ReasoningContent, ReasoningTrigger } from "./Reasoning.js";
|
|
10
11
|
import * as React from "react";
|
|
11
12
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
12
|
-
import { getToolName, isFileUIPart, isReasoningUIPart, isTextUIPart,
|
|
13
|
+
import { getToolName, isFileUIPart, isReasoningUIPart, isTextUIPart, isToolUIPart } from "ai";
|
|
13
14
|
//#region src/components/AgentChat.tsx
|
|
15
|
+
const EMPTY_PENDING_APPROVALS = [];
|
|
14
16
|
function AgentChatAttachButton() {
|
|
15
17
|
const attachments = usePromptInputAttachments();
|
|
16
18
|
return /* @__PURE__ */ jsx(PromptInputButton, {
|
|
@@ -33,11 +35,11 @@ function AgentChatAddMenu({ children }) {
|
|
|
33
35
|
function isCompactedPart(message, part, partIndex) {
|
|
34
36
|
const metadata = message.metadata;
|
|
35
37
|
if (isTextUIPart(part)) return Array.isArray(metadata?.compactedTextParts) && metadata.compactedTextParts.includes(partIndex);
|
|
36
|
-
if (
|
|
38
|
+
if (isToolUIPart(part)) return Array.isArray(metadata?.compactedToolOutputs) && metadata.compactedToolOutputs.includes(part.toolCallId);
|
|
37
39
|
return false;
|
|
38
40
|
}
|
|
39
41
|
function getPartKey(part, partIndex) {
|
|
40
|
-
if (
|
|
42
|
+
if (isToolUIPart(part)) return part.toolCallId;
|
|
41
43
|
if ("id" in part && typeof part.id === "string") return `${part.type}-${part.id}`;
|
|
42
44
|
return `${part.type}-${partIndex}`;
|
|
43
45
|
}
|
|
@@ -88,39 +90,35 @@ function renderDefaultPart(part) {
|
|
|
88
90
|
isStreaming: part.state === "streaming",
|
|
89
91
|
children: [/* @__PURE__ */ jsx(ReasoningTrigger, {}), /* @__PURE__ */ jsx(ReasoningContent, { children: part.text })]
|
|
90
92
|
});
|
|
91
|
-
if (
|
|
93
|
+
if (isToolUIPart(part)) return renderToolActivity(part);
|
|
92
94
|
return null;
|
|
93
95
|
}
|
|
94
96
|
/**
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* This is the presentational layer under `ThinkChat` — props in, no
|
|
100
|
-
* transport. Use it directly with plain AI SDK `useChat` or a read-only
|
|
101
|
-
* transcript; if you have a Cloudflare Think agent, use `ThinkChat` instead.
|
|
97
|
+
* Low-level transcript/composer surface used by `ThinkChat`: props in, no
|
|
98
|
+
* transport. Product code that talks to a Cloudflare Think agent should render
|
|
99
|
+
* `ThinkChat`; use this directly only for plain AI SDK or read-only messages.
|
|
102
100
|
*/
|
|
103
|
-
function AgentChat({ className, messages, status = "ready", onSubmit, onStop, placeholder = "Ask anything...", disabled, emptyState, composerTools, composerAddMenuItems, notice, onRegenerate, onApprovalResponse, executionApprovals, contextWindow, renderPart, renderMessageAfter, ref }) {
|
|
101
|
+
function AgentChat({ className, messages, status = "ready", onSubmit, onStop, placeholder = "Ask anything...", disabled, emptyState, composerTools, composerAddMenuItems, notice, onRegenerate, onApprovalResponse, executionApprovals, pendingApprovals: durableApprovals = EMPTY_PENDING_APPROVALS, contextWindow, renderPart, renderMessageAfter, ref }) {
|
|
104
102
|
const handleSubmit = React.useCallback((message) => {
|
|
105
103
|
onSubmit?.(message);
|
|
106
104
|
}, [onSubmit]);
|
|
107
105
|
const [skippedApprovalIds, setSkippedApprovalIds] = React.useState([]);
|
|
108
|
-
const
|
|
106
|
+
const pendingApprovalItems = [...getPendingToolApprovals(messages, { pausedExecutions: Boolean(executionApprovals) }).filter(({ part }) => {
|
|
109
107
|
if (part.state === "approval-requested" && !onApprovalResponse) return false;
|
|
110
108
|
const id = getApprovalStateKey(part);
|
|
111
109
|
return Boolean(id) && !skippedApprovalIds.includes(id);
|
|
112
|
-
});
|
|
110
|
+
}).map((approval) => ({ part: approval.part })), ...executionApprovals ? durableApprovals.filter((approval) => approval.source === "action" && !skippedApprovalIds.includes(approval.executionId)).map((approval) => ({ approval })) : []];
|
|
113
111
|
const approvalsWired = Boolean(onApprovalResponse || executionApprovals);
|
|
114
112
|
const [permissionMode, setPermissionMode] = React.useState("ask");
|
|
115
113
|
const autoApprovedIds = React.useRef(/* @__PURE__ */ new Set());
|
|
116
114
|
React.useEffect(() => {
|
|
117
115
|
if (permissionMode !== "bypass") return;
|
|
118
|
-
for (const
|
|
119
|
-
const id = getApprovalRequestId(part);
|
|
120
|
-
const key = getApprovalStateKey(part);
|
|
116
|
+
for (const item of pendingApprovalItems) {
|
|
117
|
+
const id = "part" in item ? getApprovalRequestId(item.part) : item.approval.executionId;
|
|
118
|
+
const key = "part" in item ? getApprovalStateKey(item.part) : item.approval.executionId;
|
|
121
119
|
if (!id || !key || autoApprovedIds.current.has(key)) continue;
|
|
122
120
|
autoApprovedIds.current.add(key);
|
|
123
|
-
if (part.state === "approval-requested") onApprovalResponse?.({
|
|
121
|
+
if ("part" in item && item.part.state === "approval-requested") onApprovalResponse?.({
|
|
124
122
|
id,
|
|
125
123
|
approved: true
|
|
126
124
|
});
|
|
@@ -129,10 +127,11 @@ function AgentChat({ className, messages, status = "ready", onSubmit, onStop, pl
|
|
|
129
127
|
}, [
|
|
130
128
|
permissionMode,
|
|
131
129
|
messages,
|
|
130
|
+
durableApprovals,
|
|
132
131
|
onApprovalResponse,
|
|
133
132
|
executionApprovals
|
|
134
133
|
]);
|
|
135
|
-
const pendingApproval = permissionMode === "bypass" ? void 0 :
|
|
134
|
+
const pendingApproval = permissionMode === "bypass" ? void 0 : pendingApprovalItems[0];
|
|
136
135
|
const contextUsage = contextWindow ? getContextUsage(messages) : void 0;
|
|
137
136
|
const lastAssistantIndex = onRegenerate && status !== "streaming" && status !== "submitted" ? messages.findLastIndex((message) => message.role === "assistant") : -1;
|
|
138
137
|
const composerRef = (el) => {
|
|
@@ -147,40 +146,59 @@ function AgentChat({ className, messages, status = "ready", onSubmit, onStop, pl
|
|
|
147
146
|
return /* @__PURE__ */ jsxs("div", {
|
|
148
147
|
ref,
|
|
149
148
|
className: cx("agent-chat", className),
|
|
150
|
-
children: [/* @__PURE__ */
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
children:
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
149
|
+
children: [/* @__PURE__ */ jsx(MessageScrollerProvider, {
|
|
150
|
+
autoScroll: true,
|
|
151
|
+
defaultScrollPosition: "last-anchor",
|
|
152
|
+
children: /* @__PURE__ */ jsxs(MessageScroller, {
|
|
153
|
+
className: "agent-chat__conversation",
|
|
154
|
+
children: [/* @__PURE__ */ jsx(MessageScrollerViewport, { children: /* @__PURE__ */ jsxs(MessageScrollerContent, {
|
|
155
|
+
"aria-busy": status === "streaming" || status === "submitted",
|
|
156
|
+
className: "agent-chat__transcript-content",
|
|
157
|
+
children: [messages.length === 0 ? /* @__PURE__ */ jsx(MessageScrollerItem, {
|
|
158
|
+
className: "agent-chat__empty-row",
|
|
159
|
+
children: emptyState ?? /* @__PURE__ */ jsx(ConversationEmptyState, {
|
|
160
|
+
title: "Start a conversation",
|
|
161
|
+
description: "Ask the agent to inspect, plan, or build."
|
|
162
|
+
})
|
|
163
|
+
}) : null, messages.map((message, messageIndex) => /* @__PURE__ */ jsx(MessageScrollerItem, {
|
|
164
|
+
messageId: message.id,
|
|
165
|
+
scrollAnchor: message.role === "user",
|
|
166
|
+
children: /* @__PURE__ */ jsxs(Message, {
|
|
167
|
+
from: message.role,
|
|
168
|
+
children: [
|
|
169
|
+
message.parts.map((part, partIndex) => {
|
|
170
|
+
const defaultRender = () => renderDefaultPart(part);
|
|
171
|
+
const custom = renderPart?.({
|
|
172
|
+
message,
|
|
173
|
+
part,
|
|
174
|
+
partIndex,
|
|
175
|
+
defaultRender
|
|
176
|
+
});
|
|
177
|
+
const rendered = custom === void 0 ? defaultRender() : custom;
|
|
178
|
+
return rendered ? /* @__PURE__ */ jsxs(MessageContent, { children: [rendered, isCompactedPart(message, part, partIndex) ? /* @__PURE__ */ jsx("div", {
|
|
179
|
+
className: "message__compacted",
|
|
180
|
+
children: "Output truncated for storage"
|
|
181
|
+
}) : null] }, getPartKey(part, partIndex)) : null;
|
|
182
|
+
}),
|
|
183
|
+
renderMessageAfter?.({
|
|
184
|
+
message,
|
|
185
|
+
messageIndex
|
|
186
|
+
}),
|
|
187
|
+
onRegenerate && messageIndex === lastAssistantIndex ? /* @__PURE__ */ jsx(MessageActions, { children: /* @__PURE__ */ jsx(MessageAction, {
|
|
188
|
+
label: "Retry",
|
|
189
|
+
tooltip: "Regenerate response",
|
|
190
|
+
onClick: () => onRegenerate(message.id),
|
|
191
|
+
children: /* @__PURE__ */ jsx(RefreshIcon, { size: 14 })
|
|
192
|
+
}) }) : null
|
|
193
|
+
]
|
|
194
|
+
})
|
|
195
|
+
}, message.id ?? messageIndex))]
|
|
196
|
+
}) }), /* @__PURE__ */ jsx(MessageScrollerButton, {
|
|
197
|
+
"aria-label": "Scroll to latest",
|
|
198
|
+
className: "agent-chat__scroll-button",
|
|
199
|
+
size: "icon"
|
|
200
|
+
})]
|
|
201
|
+
})
|
|
184
202
|
}), /* @__PURE__ */ jsxs("div", {
|
|
185
203
|
ref: composerRef,
|
|
186
204
|
className: "agent-chat__composer",
|
|
@@ -191,12 +209,13 @@ function AgentChat({ className, messages, status = "ready", onSubmit, onStop, pl
|
|
|
191
209
|
multiple: true,
|
|
192
210
|
onSubmit: handleSubmit,
|
|
193
211
|
children: pendingApproval ? /* @__PURE__ */ jsx(PromptInputApproval, {
|
|
194
|
-
part: pendingApproval.part,
|
|
195
|
-
|
|
212
|
+
part: "part" in pendingApproval ? pendingApproval.part : void 0,
|
|
213
|
+
approval: "approval" in pendingApproval ? pendingApproval.approval : void 0,
|
|
214
|
+
count: pendingApprovalItems.length,
|
|
196
215
|
onRespond: onApprovalResponse,
|
|
197
216
|
executionApprovals,
|
|
198
217
|
onSkip: () => {
|
|
199
|
-
const id = getApprovalStateKey(pendingApproval.part);
|
|
218
|
+
const id = "part" in pendingApproval ? getApprovalStateKey(pendingApproval.part) : pendingApproval.approval.executionId;
|
|
200
219
|
if (id) setSkippedApprovalIds((previous) => [...previous, id]);
|
|
201
220
|
}
|
|
202
221
|
}) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -3,7 +3,7 @@ import { y as LinkIcon } from "../icons-BCNawx-T.js";
|
|
|
3
3
|
import { Popover } from "./Popover.js";
|
|
4
4
|
import { Button } from "./Button.js";
|
|
5
5
|
import { a as CodeBlockCopyButton, i as CodeBlockContent, n as CodeBlockActions, p as CodeBlockTitle, r as CodeBlockContainer, s as CodeBlockHeader, t as CodeBlock } from "../CodeBlock-BK0sOAbV.js";
|
|
6
|
-
import { DCR_MCP_SERVER_CARDS, MANUAL_MCP_SERVER_CARDS } from "../utils/mcp-server-catalog.js";
|
|
6
|
+
import { DCR_MCP_SERVER_CARDS, MANUAL_MCP_SERVER_CARDS, getMcpServerIconSrc } from "../utils/mcp-server-catalog.js";
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
//#region src/components/AgentConnection.tsx
|
|
@@ -192,26 +192,29 @@ function McpServerList({ heading, servers }) {
|
|
|
192
192
|
function McpServerSetup({ setup }) {
|
|
193
193
|
return /* @__PURE__ */ jsxs("div", {
|
|
194
194
|
className: "agent-connect__mcp-setup",
|
|
195
|
-
children: [
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsx("p", { children: setup.description }),
|
|
197
|
+
/* @__PURE__ */ jsxs("p", {
|
|
198
|
+
className: "agent-connect__mcp-setup-auth",
|
|
199
|
+
children: [/* @__PURE__ */ jsx("span", { children: setup.auth.label }), setup.auth.fields.length > 0 ? /* @__PURE__ */ jsx("span", { children: setup.auth.fields.map((field) => field.label).join(", ") }) : null]
|
|
200
|
+
}),
|
|
201
|
+
setup.links.length > 0 ? /* @__PURE__ */ jsx("ul", {
|
|
202
|
+
className: "agent-connect__mcp-setup-links",
|
|
203
|
+
role: "list",
|
|
204
|
+
children: setup.links.map((link) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("a", {
|
|
205
|
+
href: link.url,
|
|
206
|
+
target: "_blank",
|
|
207
|
+
rel: "noreferrer",
|
|
208
|
+
children: link.label
|
|
209
|
+
}) }, `${link.label}:${link.url}`))
|
|
210
|
+
}) : null
|
|
211
|
+
]
|
|
205
212
|
});
|
|
206
213
|
}
|
|
207
214
|
function renderMcpServerIcon(server) {
|
|
208
|
-
const
|
|
209
|
-
if (
|
|
210
|
-
src
|
|
211
|
-
name: server.name
|
|
212
|
-
});
|
|
213
|
-
if (icon) return /* @__PURE__ */ jsx(McpServerIconImage, {
|
|
214
|
-
src: icon.src,
|
|
215
|
+
const src = getMcpServerIconSrc(server);
|
|
216
|
+
if (src) return /* @__PURE__ */ jsx(McpServerIconImage, {
|
|
217
|
+
src,
|
|
215
218
|
name: server.name
|
|
216
219
|
});
|
|
217
220
|
return server.name.slice(0, 1);
|
|
@@ -3,19 +3,13 @@ import * as React from "react";
|
|
|
3
3
|
import { LanguageModelUsage, UIMessage } from "ai";
|
|
4
4
|
|
|
5
5
|
//#region src/components/Context.d.ts
|
|
6
|
-
/**
|
|
7
|
-
* The AI SDK's `LanguageModelUsage`, taken whole. `Partial` only relaxes the
|
|
8
|
-
* required-but-`undefined`able keys for fixtures/JSX literals — key names and
|
|
9
|
-
* meanings are upstream's.
|
|
10
|
-
*/
|
|
11
|
-
type ContextTokenUsage = Partial<LanguageModelUsage>;
|
|
12
6
|
interface ContextProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
13
7
|
/** Tokens currently used. */
|
|
14
8
|
usedTokens: number;
|
|
15
9
|
/** Maximum context window size. */
|
|
16
10
|
maxTokens: number;
|
|
17
11
|
/** Optional usage breakdown. */
|
|
18
|
-
usage?:
|
|
12
|
+
usage?: LanguageModelUsage;
|
|
19
13
|
/** Optional model identifier shown in the header. */
|
|
20
14
|
modelId?: string;
|
|
21
15
|
/** Optional total cost in USD. */
|
|
@@ -31,11 +25,13 @@ interface ContextProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
|
31
25
|
*
|
|
32
26
|
* @example
|
|
33
27
|
* ```tsx
|
|
28
|
+
* const usage = getContextUsage(messages);
|
|
29
|
+
*
|
|
34
30
|
* <Context
|
|
35
|
-
* usedTokens={
|
|
31
|
+
* usedTokens={getContextUsedTokens(usage)}
|
|
36
32
|
* maxTokens={200_000}
|
|
37
33
|
* modelId="claude-opus-4-6"
|
|
38
|
-
* usage={
|
|
34
|
+
* usage={usage}
|
|
39
35
|
* totalCostUsd={0.42}
|
|
40
36
|
* >
|
|
41
37
|
* <ContextTrigger />
|
|
@@ -127,4 +123,4 @@ declare function getContextUsage(messages: readonly UIMessage[]): LanguageModelU
|
|
|
127
123
|
/** Tokens occupying the context window: `totalTokens`, or input + output. */
|
|
128
124
|
declare function getContextUsedTokens(usage: LanguageModelUsage | undefined): number;
|
|
129
125
|
//#endregion
|
|
130
|
-
export { Context, ContextCacheUsage, ContextCacheUsageProps, ContextContent, ContextContentBody, ContextContentBodyProps, ContextContentFooter, ContextContentFooterProps, ContextContentHeader, ContextContentHeaderProps, ContextContentProps, ContextInputUsage, ContextInputUsageProps, ContextOutputUsage, ContextOutputUsageProps, ContextProps, ContextReasoningUsage, ContextReasoningUsageProps,
|
|
126
|
+
export { Context, ContextCacheUsage, ContextCacheUsageProps, ContextContent, ContextContentBody, ContextContentBodyProps, ContextContentFooter, ContextContentFooterProps, ContextContentHeader, ContextContentHeaderProps, ContextContentProps, ContextInputUsage, ContextInputUsageProps, ContextOutputUsage, ContextOutputUsageProps, ContextProps, ContextReasoningUsage, ContextReasoningUsageProps, ContextTrigger, ContextTriggerProps, ContextUsageRowProps, getContextUsage, getContextUsedTokens };
|
|
@@ -17,11 +17,13 @@ function useContextContext() {
|
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
19
|
* ```tsx
|
|
20
|
+
* const usage = getContextUsage(messages);
|
|
21
|
+
*
|
|
20
22
|
* <Context
|
|
21
|
-
* usedTokens={
|
|
23
|
+
* usedTokens={getContextUsedTokens(usage)}
|
|
22
24
|
* maxTokens={200_000}
|
|
23
25
|
* modelId="claude-opus-4-6"
|
|
24
|
-
* usage={
|
|
26
|
+
* usage={usage}
|
|
25
27
|
* totalCostUsd={0.42}
|
|
26
28
|
* >
|
|
27
29
|
* <ContextTrigger />
|
|
@@ -1,23 +1,15 @@
|
|
|
1
1
|
import { t as Button } from "../Button-Ct5I6aTu.js";
|
|
2
|
+
import { MessageScrollerButtonProps, MessageScrollerItemProps } from "./MessageScroller.js";
|
|
2
3
|
import * as React from "react";
|
|
4
|
+
import { UIMessage } from "ai";
|
|
3
5
|
|
|
4
6
|
//#region src/components/Conversation.d.ts
|
|
5
|
-
/**
|
|
6
|
-
|
|
7
|
-
* {@link messagesToMarkdown}. Structurally compatible with the AI SDK
|
|
8
|
-
* `UIMessage` type so consumers can pass those directly.
|
|
9
|
-
*/
|
|
10
|
-
interface ConversationMessage {
|
|
7
|
+
/** AI SDK `UIMessage`, or a plain text fallback for non-chat exports. */
|
|
8
|
+
type ConversationMessage = UIMessage | {
|
|
11
9
|
id?: string;
|
|
12
10
|
role: string;
|
|
13
|
-
/** AI SDK-style parts array. The text types are concatenated for export. */
|
|
14
|
-
parts?: Array<{
|
|
15
|
-
type: string;
|
|
16
|
-
text?: string;
|
|
17
|
-
}>;
|
|
18
|
-
/** Plain-text content; used as a fallback when `parts` is not provided. */
|
|
19
11
|
content?: string;
|
|
20
|
-
}
|
|
12
|
+
};
|
|
21
13
|
interface ConversationProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
|
|
22
14
|
/** Additional class names for layout only; do not override scroll behavior from apps. */
|
|
23
15
|
className?: string;
|
|
@@ -25,16 +17,17 @@ interface ConversationProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
25
17
|
children?: React.ReactNode;
|
|
26
18
|
}
|
|
27
19
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
20
|
+
* Compatibility wrapper around {@link MessageScroller} for existing
|
|
21
|
+
* conversation imports. Prefer MessageScroller directly for new chat
|
|
22
|
+
* transcript surfaces.
|
|
31
23
|
*
|
|
32
24
|
* Use for assistant/user message streams. Do not use for static lists or
|
|
33
25
|
* logs that should not auto-stick to the latest item.
|
|
34
26
|
*
|
|
35
27
|
* Sigvelo changes: removed Tailwind classes, uses a native scroll region with
|
|
36
|
-
* the
|
|
37
|
-
* keeps transcript export in a separate ConversationDownload
|
|
28
|
+
* the shared MessageScroller primitive, emits `role="log"` with polite
|
|
29
|
+
* updates, and keeps transcript export in a separate ConversationDownload
|
|
30
|
+
* action.
|
|
38
31
|
*
|
|
39
32
|
* Styling consumes Sigvelo canvas, surface, border, spacing, radius, shadow,
|
|
40
33
|
* and motion tokens. Add new behavior in this package when multiple apps need
|
|
@@ -48,7 +41,11 @@ interface ConversationProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
48
41
|
* ```tsx
|
|
49
42
|
* <Conversation>
|
|
50
43
|
* <ConversationContent>
|
|
51
|
-
* {messages.map((m) =>
|
|
44
|
+
* {messages.map((m) => (
|
|
45
|
+
* <ConversationItem key={m.id} messageId={m.id} scrollAnchor={m.role === "user"}>
|
|
46
|
+
* <Message {...m} />
|
|
47
|
+
* </ConversationItem>
|
|
48
|
+
* ))}
|
|
52
49
|
* </ConversationContent>
|
|
53
50
|
* <ConversationScrollButton />
|
|
54
51
|
* </Conversation>
|
|
@@ -72,11 +69,17 @@ interface ConversationContentProps extends React.HTMLAttributes<HTMLDivElement>
|
|
|
72
69
|
declare function ConversationContent({
|
|
73
70
|
className,
|
|
74
71
|
children,
|
|
75
|
-
ref,
|
|
76
72
|
...props
|
|
77
73
|
}: ConversationContentProps & {
|
|
78
74
|
ref?: React.Ref<HTMLDivElement>;
|
|
79
75
|
}): React.JSX.Element;
|
|
76
|
+
type ConversationItemProps = MessageScrollerItemProps;
|
|
77
|
+
/** Optional wrapper for one turn/message. Enables upstream scroll anchoring and visibility tracking. */
|
|
78
|
+
declare function ConversationItem({
|
|
79
|
+
className,
|
|
80
|
+
scrollAnchor,
|
|
81
|
+
...props
|
|
82
|
+
}: ConversationItemProps): React.JSX.Element;
|
|
80
83
|
interface ConversationEmptyStateProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "title"> {
|
|
81
84
|
title?: React.ReactNode;
|
|
82
85
|
description?: React.ReactNode;
|
|
@@ -98,30 +101,27 @@ declare function ConversationEmptyState({
|
|
|
98
101
|
}: ConversationEmptyStateProps & {
|
|
99
102
|
ref?: React.Ref<HTMLDivElement>;
|
|
100
103
|
}): React.JSX.Element;
|
|
101
|
-
interface ConversationScrollButtonProps extends Omit<
|
|
104
|
+
interface ConversationScrollButtonProps extends Omit<MessageScrollerButtonProps, "children" | "className"> {
|
|
102
105
|
className?: string;
|
|
103
|
-
/** Optional click handler; runs before the default scroll-to-bottom action. */
|
|
104
|
-
onClick?: React.MouseEventHandler<HTMLButtonElement>;
|
|
105
106
|
/** Accessible label for the button. */
|
|
106
107
|
label?: string;
|
|
108
|
+
children?: React.ReactNode;
|
|
107
109
|
}
|
|
108
110
|
/**
|
|
109
111
|
* Floating button that scrolls the conversation to the latest message.
|
|
110
|
-
* Hidden while the viewport is already
|
|
112
|
+
* Hidden from view and tab order while the viewport is already at the end.
|
|
111
113
|
*/
|
|
112
114
|
declare function ConversationScrollButton({
|
|
113
115
|
className,
|
|
114
116
|
variant,
|
|
115
117
|
color,
|
|
116
118
|
size,
|
|
117
|
-
onClick,
|
|
118
119
|
label,
|
|
119
120
|
children,
|
|
120
|
-
ref,
|
|
121
121
|
...props
|
|
122
122
|
}: ConversationScrollButtonProps & {
|
|
123
123
|
ref?: React.Ref<HTMLButtonElement>;
|
|
124
|
-
}): React.JSX.Element
|
|
124
|
+
}): React.JSX.Element;
|
|
125
125
|
/**
|
|
126
126
|
* Converts a conversation to a Markdown document with a heading per message.
|
|
127
127
|
*
|
|
@@ -163,4 +163,4 @@ declare function ConversationDownload({
|
|
|
163
163
|
ref?: React.Ref<HTMLButtonElement>;
|
|
164
164
|
}): React.JSX.Element | null;
|
|
165
165
|
//#endregion
|
|
166
|
-
export { Conversation, ConversationContent, ConversationContentProps, ConversationDownload, ConversationDownloadProps, ConversationEmptyState, ConversationEmptyStateProps, ConversationMessage, ConversationProps, ConversationScrollButton, ConversationScrollButtonProps, messagesToMarkdown };
|
|
166
|
+
export { Conversation, ConversationContent, ConversationContentProps, ConversationDownload, ConversationDownloadProps, ConversationEmptyState, ConversationEmptyStateProps, ConversationItem, ConversationItemProps, ConversationMessage, ConversationProps, ConversationScrollButton, ConversationScrollButtonProps, messagesToMarkdown };
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
import { t as cx } from "../class-names-9UtZwb0J.js";
|
|
2
2
|
import { d as DownloadIcon, t as ArrowDownIcon } from "../icons-BCNawx-T.js";
|
|
3
3
|
import { Button } from "./Button.js";
|
|
4
|
-
import {
|
|
4
|
+
import { MessageScroller, MessageScrollerButton, MessageScrollerContent, MessageScrollerItem, MessageScrollerProvider, MessageScrollerViewport } from "./MessageScroller.js";
|
|
5
5
|
import * as React from "react";
|
|
6
6
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { isReasoningUIPart, isTextUIPart } from "ai";
|
|
7
8
|
//#region src/components/Conversation.tsx
|
|
8
|
-
const ConversationContext = React.createContext(null);
|
|
9
|
-
function useConversationContext(component) {
|
|
10
|
-
const ctx = React.useContext(ConversationContext);
|
|
11
|
-
if (!ctx) throw new Error(`${component} must be used inside a <Conversation>.`);
|
|
12
|
-
return ctx;
|
|
13
|
-
}
|
|
14
9
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
10
|
+
* Compatibility wrapper around {@link MessageScroller} for existing
|
|
11
|
+
* conversation imports. Prefer MessageScroller directly for new chat
|
|
12
|
+
* transcript surfaces.
|
|
18
13
|
*
|
|
19
14
|
* Use for assistant/user message streams. Do not use for static lists or
|
|
20
15
|
* logs that should not auto-stick to the latest item.
|
|
21
16
|
*
|
|
22
17
|
* Sigvelo changes: removed Tailwind classes, uses a native scroll region with
|
|
23
|
-
* the
|
|
24
|
-
* keeps transcript export in a separate ConversationDownload
|
|
18
|
+
* the shared MessageScroller primitive, emits `role="log"` with polite
|
|
19
|
+
* updates, and keeps transcript export in a separate ConversationDownload
|
|
20
|
+
* action.
|
|
25
21
|
*
|
|
26
22
|
* Styling consumes Sigvelo canvas, surface, border, spacing, radius, shadow,
|
|
27
23
|
* and motion tokens. Add new behavior in this package when multiple apps need
|
|
@@ -35,7 +31,11 @@ function useConversationContext(component) {
|
|
|
35
31
|
* ```tsx
|
|
36
32
|
* <Conversation>
|
|
37
33
|
* <ConversationContent>
|
|
38
|
-
* {messages.map((m) =>
|
|
34
|
+
* {messages.map((m) => (
|
|
35
|
+
* <ConversationItem key={m.id} messageId={m.id} scrollAnchor={m.role === "user"}>
|
|
36
|
+
* <Message {...m} />
|
|
37
|
+
* </ConversationItem>
|
|
38
|
+
* ))}
|
|
39
39
|
* </ConversationContent>
|
|
40
40
|
* <ConversationScrollButton />
|
|
41
41
|
* </Conversation>
|
|
@@ -44,29 +44,19 @@ function useConversationContext(component) {
|
|
|
44
44
|
* @see {@link https://elements.ai-sdk.dev/components/conversation | AI Elements Conversation}
|
|
45
45
|
*/
|
|
46
46
|
function Conversation({ className, children, ref, ...props }) {
|
|
47
|
-
const { viewportRef, contentRef, isAtBottom, scrollToBottom } = useStickToBottom();
|
|
48
47
|
const handleViewportRef = React.useCallback((element) => {
|
|
49
|
-
viewportRef(element);
|
|
50
48
|
if (typeof ref === "function") {
|
|
51
49
|
ref(element);
|
|
52
50
|
return;
|
|
53
51
|
}
|
|
54
52
|
if (ref) ref.current = element;
|
|
55
|
-
}, [ref
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}), [
|
|
61
|
-
contentRef,
|
|
62
|
-
isAtBottom,
|
|
63
|
-
scrollToBottom
|
|
64
|
-
]);
|
|
65
|
-
return /* @__PURE__ */ jsx(ConversationContext.Provider, {
|
|
66
|
-
value: ctx,
|
|
67
|
-
children: /* @__PURE__ */ jsx("div", {
|
|
53
|
+
}, [ref]);
|
|
54
|
+
return /* @__PURE__ */ jsx(MessageScrollerProvider, {
|
|
55
|
+
autoScroll: true,
|
|
56
|
+
defaultScrollPosition: "end",
|
|
57
|
+
children: /* @__PURE__ */ jsx(MessageScroller, {
|
|
68
58
|
className: cx("conversation", className),
|
|
69
|
-
children: /* @__PURE__ */ jsx(
|
|
59
|
+
children: /* @__PURE__ */ jsx(MessageScrollerViewport, {
|
|
70
60
|
ref: handleViewportRef,
|
|
71
61
|
tabIndex: 0,
|
|
72
62
|
className: "conversation__viewport",
|
|
@@ -80,25 +70,24 @@ function Conversation({ className, children, ref, ...props }) {
|
|
|
80
70
|
* Inner layout wrapper for conversation children. Applies vertical stacking
|
|
81
71
|
* and padding; does not scroll (the parent {@link Conversation} does).
|
|
82
72
|
*/
|
|
83
|
-
function ConversationContent({ className, children,
|
|
84
|
-
|
|
85
|
-
return /* @__PURE__ */ jsx("div", {
|
|
86
|
-
ref: React.useCallback((element) => {
|
|
87
|
-
registerContentElement(element);
|
|
88
|
-
if (typeof ref === "function") {
|
|
89
|
-
ref(element);
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
92
|
-
if (ref) ref.current = element;
|
|
93
|
-
}, [registerContentElement, ref]),
|
|
73
|
+
function ConversationContent({ className, children, ...props }) {
|
|
74
|
+
return /* @__PURE__ */ jsx(MessageScrollerContent, {
|
|
94
75
|
role: "log",
|
|
95
76
|
"aria-live": "polite",
|
|
96
|
-
"aria-relevant": "additions
|
|
77
|
+
"aria-relevant": "additions",
|
|
97
78
|
className: cx("conversation__content", className),
|
|
98
79
|
...props,
|
|
99
80
|
children
|
|
100
81
|
});
|
|
101
82
|
}
|
|
83
|
+
/** Optional wrapper for one turn/message. Enables upstream scroll anchoring and visibility tracking. */
|
|
84
|
+
function ConversationItem({ className, scrollAnchor = false, ...props }) {
|
|
85
|
+
return /* @__PURE__ */ jsx(MessageScrollerItem, {
|
|
86
|
+
className: cx("conversation__item", className),
|
|
87
|
+
scrollAnchor,
|
|
88
|
+
...props
|
|
89
|
+
});
|
|
90
|
+
}
|
|
102
91
|
/**
|
|
103
92
|
* Placeholder shown when there are no messages yet. Renders an icon, title,
|
|
104
93
|
* and description stacked vertically; pass `children` to override the
|
|
@@ -128,23 +117,15 @@ function ConversationEmptyState({ className, title = "No messages yet", descript
|
|
|
128
117
|
}
|
|
129
118
|
/**
|
|
130
119
|
* Floating button that scrolls the conversation to the latest message.
|
|
131
|
-
* Hidden while the viewport is already
|
|
120
|
+
* Hidden from view and tab order while the viewport is already at the end.
|
|
132
121
|
*/
|
|
133
|
-
function ConversationScrollButton({ className, variant = "outline", color = "neutral", size = "icon",
|
|
134
|
-
|
|
135
|
-
if (isAtBottom) return null;
|
|
136
|
-
return /* @__PURE__ */ jsx(Button, {
|
|
137
|
-
ref,
|
|
138
|
-
type: "button",
|
|
122
|
+
function ConversationScrollButton({ className, variant = "outline", color = "neutral", size = "icon", label = "Scroll to latest", children, ...props }) {
|
|
123
|
+
return /* @__PURE__ */ jsx(MessageScrollerButton, {
|
|
139
124
|
variant,
|
|
140
125
|
color,
|
|
141
126
|
size,
|
|
142
127
|
className: cx("conversation__scroll-button", className),
|
|
143
128
|
"aria-label": label,
|
|
144
|
-
onClick: (event) => {
|
|
145
|
-
onClick?.(event);
|
|
146
|
-
if (!event.defaultPrevented) scrollToBottom();
|
|
147
|
-
},
|
|
148
129
|
...props,
|
|
149
130
|
children: children ?? /* @__PURE__ */ jsx(ArrowDownIcon, {})
|
|
150
131
|
});
|
|
@@ -156,7 +137,7 @@ const ROLE_HEADING = {
|
|
|
156
137
|
tool: "### Tool"
|
|
157
138
|
};
|
|
158
139
|
function defaultFormatMessage(message) {
|
|
159
|
-
return `${ROLE_HEADING[message.role] ?? `### ${message.role}`}\n\n${(
|
|
140
|
+
return `${ROLE_HEADING[message.role] ?? `### ${message.role}`}\n\n${("parts" in message ? message.parts.filter((part) => isTextUIPart(part) || isReasoningUIPart(part)).map((part) => part.text).join("") : message.content ?? "").trim()}`;
|
|
160
141
|
}
|
|
161
142
|
/**
|
|
162
143
|
* Converts a conversation to a Markdown document with a heading per message.
|
|
@@ -202,4 +183,4 @@ function ConversationDownload({ className, messages, filename = "conversation.md
|
|
|
202
183
|
});
|
|
203
184
|
}
|
|
204
185
|
//#endregion
|
|
205
|
-
export { Conversation, ConversationContent, ConversationDownload, ConversationEmptyState, ConversationScrollButton, messagesToMarkdown };
|
|
186
|
+
export { Conversation, ConversationContent, ConversationDownload, ConversationEmptyState, ConversationItem, ConversationScrollButton, messagesToMarkdown };
|