@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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { AgentChatProps, AgentChatRenderPartContext } from "./AgentChat.js";
|
|
2
|
+
import { _ as isThinkChatActivityPart, s as ThinkChatPart } from "../ThinkChatToolParts-DnUiZgCC.js";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { DynamicToolUIPart, ToolUIPart, UIMessage } from "ai";
|
|
5
|
+
|
|
6
|
+
//#region src/components/ThinkChatActivity.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Bespoke rendering for one tool inside the activity log. Both hooks fall
|
|
9
|
+
* through to the built-in families when they return `undefined`, so an
|
|
10
|
+
* override can adjust just the label or just the panel.
|
|
11
|
+
*/
|
|
12
|
+
interface ThinkChatToolRenderer {
|
|
13
|
+
/** One-line step label. `null` hides the step from the log. */
|
|
14
|
+
describe?: (part: ToolUIPart | DynamicToolUIPart) => string | null | undefined;
|
|
15
|
+
/** Expanded detail under the step. `null` keeps the step a plain row. */
|
|
16
|
+
panel?: (part: ToolUIPart | DynamicToolUIPart) => React.ReactNode;
|
|
17
|
+
}
|
|
18
|
+
type ThinkChatToolRenderers = Record<string, ThinkChatToolRenderer>;
|
|
19
|
+
interface ThinkChatActivityRendererOptions {
|
|
20
|
+
/**
|
|
21
|
+
* Tool names to keep out of activity folding entirely — the run splits
|
|
22
|
+
* around them and they fall through to your own renderPart / AgentChat's
|
|
23
|
+
* generic standalone activity row. For tools that should stay in the log
|
|
24
|
+
* with custom UI, use `toolRenderers` instead.
|
|
25
|
+
*/
|
|
26
|
+
excludeTools?: readonly string[];
|
|
27
|
+
/** Per-tool label/panel overrides inside the activity log, keyed by tool name. */
|
|
28
|
+
toolRenderers?: ThinkChatToolRenderers;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One-line natural-language label for an activity part ("Ran vp test",
|
|
32
|
+
* "Fetched developers.cloudflare.com/agents/"); `null` for boundaries that
|
|
33
|
+
* aren't steps (step-start). Exported so a custom `renderPart` can reuse the
|
|
34
|
+
* built-in labels for tools it doesn't override.
|
|
35
|
+
*/
|
|
36
|
+
declare function describeThinkChatActivityPart(part: ThinkChatPart, toolRenderers?: ThinkChatToolRenderers): string | null;
|
|
37
|
+
interface ThinkChatActivityProps {
|
|
38
|
+
/** Render with the log expanded. */
|
|
39
|
+
defaultOpen?: boolean;
|
|
40
|
+
parts: readonly ThinkChatPart[];
|
|
41
|
+
/** Defaults to deriving from part states (reasoning streaming, tool awaiting output). */
|
|
42
|
+
isStreaming?: boolean;
|
|
43
|
+
/** Per-tool label/panel overrides, keyed by tool name. */
|
|
44
|
+
toolRenderers?: ThinkChatToolRenderers;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One run of agent work as a collapsed Activity block: while streaming the
|
|
48
|
+
* label shimmers with the live step; settled, it becomes a short summary
|
|
49
|
+
* ("Thought, read 2 files and ran a command") over an expandable step log
|
|
50
|
+
* with per-tool panels. Normally reached through `ThinkChat.Messages`;
|
|
51
|
+
* exported for apps that lay out messages themselves.
|
|
52
|
+
*/
|
|
53
|
+
declare function ThinkChatActivity({
|
|
54
|
+
parts,
|
|
55
|
+
defaultOpen,
|
|
56
|
+
isStreaming,
|
|
57
|
+
toolRenderers
|
|
58
|
+
}: ThinkChatActivityProps): React.JSX.Element | null;
|
|
59
|
+
/**
|
|
60
|
+
* Builds a renderPart handler that folds every run of consecutive activity
|
|
61
|
+
* parts (reasoning + tool calls) into one collapsed Activity box. Everything
|
|
62
|
+
* is derived from the UIMessage stream: part order, tool states, reasoning
|
|
63
|
+
* state. Excluded tools split the run and return undefined so your own
|
|
64
|
+
* renderPart (or AgentChat's generic activity row) takes over for them.
|
|
65
|
+
*/
|
|
66
|
+
declare function createThinkChatActivityRenderer({
|
|
67
|
+
excludeTools,
|
|
68
|
+
toolRenderers
|
|
69
|
+
}?: ThinkChatActivityRendererOptions): NonNullable<AgentChatProps["renderPart"]>;
|
|
70
|
+
/** Default activity renderer — `createThinkChatActivityRenderer()` with no overrides. */
|
|
71
|
+
declare const renderThinkChatActivityPart: (context: AgentChatRenderPartContext<UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>>) => React.ReactNode | undefined;
|
|
72
|
+
//#endregion
|
|
73
|
+
export { ThinkChatActivity, ThinkChatActivityProps, ThinkChatActivityRendererOptions, ThinkChatToolRenderer, ThinkChatToolRenderers, createThinkChatActivityRenderer, describeThinkChatActivityPart, isThinkChatActivityPart, renderThinkChatActivityPart };
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import { M as TerminalIcon, N as WarningIcon, P as WrenchIcon, S as MonitorIcon, T as PencilIcon, k as SearchIcon, m as FileIcon, v as InfoIcon, y as LinkIcon } from "../icons-BCNawx-T.js";
|
|
2
|
+
import { Activity, ActivityContent, ActivityItem, ActivityItemGroup, ActivityText, ActivityTrigger } from "./Activity.js";
|
|
3
|
+
import { ThinkChatContext } from "./ThinkChatContext.js";
|
|
4
|
+
import { displayUrl, isBrowserToolPart, isExecuteToolPart, isFetchToolPart, isPartFailed, isPartWorking, isSkillToolPart, isThinkChatActivityPart, isWorkspaceToolPart } from "./ThinkChatToolParts.js";
|
|
5
|
+
import { ThinkChatBrowserPanel, ThinkChatExecutePanel, ThinkChatFetchPanel, ThinkChatSkillPanel, ThinkChatWorkspacePanel } from "./ThinkChatToolPanels.js";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { getToolName, isReasoningUIPart, isToolUIPart } from "ai";
|
|
9
|
+
//#region src/components/ThinkChatActivity.tsx
|
|
10
|
+
function summarizeToolInput(input) {
|
|
11
|
+
if (!input || typeof input !== "object") return null;
|
|
12
|
+
for (const value of Object.values(input)) if (typeof value === "string" && value.trim()) {
|
|
13
|
+
const line = value.trim().split("\n", 1)[0] ?? "";
|
|
14
|
+
return line.length > 64 ? `${line.slice(0, 64)}…` : line;
|
|
15
|
+
}
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const HTTP_URL_PATTERN = /https?:\/\/[^\s"'<>)}\]]+/i;
|
|
19
|
+
function firstHttpUrl(value) {
|
|
20
|
+
const text = typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
21
|
+
const raw = HTTP_URL_PATTERN.exec(text)?.[0]?.replace(/[.,;:!?]+$/, "");
|
|
22
|
+
if (!raw) return null;
|
|
23
|
+
try {
|
|
24
|
+
return new URL(raw).href;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function ActivityFavicon({ url }) {
|
|
30
|
+
const src = faviconHref(url);
|
|
31
|
+
const [failed, setFailed] = React.useState(false);
|
|
32
|
+
React.useEffect(() => setFailed(false), [src]);
|
|
33
|
+
if (!src || failed) return /* @__PURE__ */ jsx(LinkIcon, {});
|
|
34
|
+
return /* @__PURE__ */ jsx("img", {
|
|
35
|
+
className: "activity__favicon",
|
|
36
|
+
src,
|
|
37
|
+
alt: "",
|
|
38
|
+
loading: "lazy",
|
|
39
|
+
decoding: "async",
|
|
40
|
+
onError: () => setFailed(true)
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function faviconHref(url) {
|
|
44
|
+
try {
|
|
45
|
+
return new URL("/favicon.ico", url).href;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function ActivityKindIcon({ kind }) {
|
|
51
|
+
switch (kind) {
|
|
52
|
+
case "thought": return /* @__PURE__ */ jsx(InfoIcon, {});
|
|
53
|
+
case "read": return /* @__PURE__ */ jsx(FileIcon, {});
|
|
54
|
+
case "edit": return /* @__PURE__ */ jsx(PencilIcon, {});
|
|
55
|
+
case "search": return /* @__PURE__ */ jsx(SearchIcon, {});
|
|
56
|
+
case "web": return /* @__PURE__ */ jsx(LinkIcon, {});
|
|
57
|
+
case "command": return /* @__PURE__ */ jsx(TerminalIcon, {});
|
|
58
|
+
case "browser": return /* @__PURE__ */ jsx(MonitorIcon, {});
|
|
59
|
+
case "tool": return /* @__PURE__ */ jsx(WrenchIcon, {});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function genericToolLabels(name, hasUrl) {
|
|
63
|
+
const normalized = name.toLowerCase();
|
|
64
|
+
if (normalized.includes("grep") || normalized.includes("find") || normalized.includes("code")) return {
|
|
65
|
+
working: "Searching code",
|
|
66
|
+
done: "Searched code",
|
|
67
|
+
failed: "Code search failed",
|
|
68
|
+
summary: "searched code",
|
|
69
|
+
icon: "search"
|
|
70
|
+
};
|
|
71
|
+
if (normalized.includes("search") || normalized.includes("web") || normalized.includes("browser") || normalized.includes("google")) return {
|
|
72
|
+
working: "Searching the web",
|
|
73
|
+
done: "Searched the web",
|
|
74
|
+
failed: "Web search failed",
|
|
75
|
+
summary: "searched the web",
|
|
76
|
+
icon: "web"
|
|
77
|
+
};
|
|
78
|
+
if (hasUrl || normalized.includes("fetch") || normalized.includes("http") || normalized.includes("url")) return {
|
|
79
|
+
working: "Reading a URL",
|
|
80
|
+
done: "Read a URL",
|
|
81
|
+
failed: "URL read failed",
|
|
82
|
+
summary: "read a URL",
|
|
83
|
+
icon: "web"
|
|
84
|
+
};
|
|
85
|
+
if (normalized.includes("edit") || normalized.includes("write") || normalized.includes("delete") || normalized.includes("patch") || normalized.includes("apply") || normalized.includes("create") || normalized.includes("update")) return {
|
|
86
|
+
working: "Editing files",
|
|
87
|
+
done: "Edited files",
|
|
88
|
+
failed: "Edit failed",
|
|
89
|
+
summary: "edited files",
|
|
90
|
+
icon: "edit"
|
|
91
|
+
};
|
|
92
|
+
if (normalized.includes("exec") || normalized.includes("bash") || normalized.includes("shell") || normalized.includes("terminal") || normalized.includes("command")) return {
|
|
93
|
+
working: "Running a command",
|
|
94
|
+
done: "Ran a command",
|
|
95
|
+
failed: "Command failed",
|
|
96
|
+
summary: "ran a command",
|
|
97
|
+
icon: "command"
|
|
98
|
+
};
|
|
99
|
+
if (normalized.includes("read") || normalized.includes("open") || normalized.includes("list") || normalized.includes("file")) return {
|
|
100
|
+
working: "Reading files",
|
|
101
|
+
done: "Read files",
|
|
102
|
+
failed: "Read failed",
|
|
103
|
+
summary: "read files",
|
|
104
|
+
icon: "read"
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
working: "Using a tool",
|
|
108
|
+
done: "Used a tool",
|
|
109
|
+
failed: "Tool failed",
|
|
110
|
+
summary: "used a tool",
|
|
111
|
+
icon: "tool"
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function describeGenericToolPart(part) {
|
|
115
|
+
const labels = genericToolLabels(getToolName(part), activityPartUrl(part) !== null);
|
|
116
|
+
const label = isPartFailed(part) ? labels.failed : isPartWorking(part) ? labels.working : labels.done;
|
|
117
|
+
const detail = summarizeToolInput(part.input);
|
|
118
|
+
return detail ? `${label} · ${detail}` : label;
|
|
119
|
+
}
|
|
120
|
+
function activityPartUrl(part) {
|
|
121
|
+
if (isFetchToolPart(part)) return firstHttpUrl(part.input?.url ?? part.input?.path);
|
|
122
|
+
if (isBrowserToolPart(part) && part.type !== "tool-browser_execute") return firstHttpUrl(part.input?.url);
|
|
123
|
+
if (isToolUIPart(part) && !isExecuteToolPart(part)) return firstHttpUrl(part.input) ?? (part.state === "output-available" ? firstHttpUrl(part.output) : null);
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
function activityPartIconKind(part) {
|
|
127
|
+
if (isReasoningUIPart(part)) return "thought";
|
|
128
|
+
if (isWorkspaceToolPart(part)) switch (part.type) {
|
|
129
|
+
case "tool-read":
|
|
130
|
+
case "tool-list": return "read";
|
|
131
|
+
case "tool-write":
|
|
132
|
+
case "tool-edit":
|
|
133
|
+
case "tool-delete": return "edit";
|
|
134
|
+
case "tool-find":
|
|
135
|
+
case "tool-grep": return "search";
|
|
136
|
+
case "tool-bash": return "command";
|
|
137
|
+
}
|
|
138
|
+
if (isSkillToolPart(part)) return "tool";
|
|
139
|
+
if (isFetchToolPart(part)) return "web";
|
|
140
|
+
if (isBrowserToolPart(part)) return part.type === "tool-browser_execute" ? "browser" : "web";
|
|
141
|
+
if (isExecuteToolPart(part)) return "command";
|
|
142
|
+
if (isToolUIPart(part)) return genericToolLabels(getToolName(part), activityPartUrl(part) !== null).icon;
|
|
143
|
+
return "tool";
|
|
144
|
+
}
|
|
145
|
+
function activityPartIcon(part) {
|
|
146
|
+
const url = activityPartUrl(part);
|
|
147
|
+
return url ? /* @__PURE__ */ jsx(ActivityFavicon, { url }) : /* @__PURE__ */ jsx(ActivityKindIcon, { kind: activityPartIconKind(part) });
|
|
148
|
+
}
|
|
149
|
+
function activitySummaryIcon(parts) {
|
|
150
|
+
if (parts.find((part) => isToolUIPart(part) && isPartFailed(part))) return /* @__PURE__ */ jsx(WarningIcon, {});
|
|
151
|
+
const representative = parts.find((part) => activityPartIconKind(part) === "edit") ?? parts.find((part) => activityPartUrl(part)) ?? parts.find((part) => describeThinkChatActivityPart(part));
|
|
152
|
+
return representative ? activityPartIcon(representative) : /* @__PURE__ */ jsx(ActivityKindIcon, { kind: "tool" });
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* One-line natural-language label for an activity part ("Ran vp test",
|
|
156
|
+
* "Fetched developers.cloudflare.com/agents/"); `null` for boundaries that
|
|
157
|
+
* aren't steps (step-start). Exported so a custom `renderPart` can reuse the
|
|
158
|
+
* built-in labels for tools it doesn't override.
|
|
159
|
+
*/
|
|
160
|
+
function describeThinkChatActivityPart(part, toolRenderers) {
|
|
161
|
+
if (isToolUIPart(part)) {
|
|
162
|
+
const custom = toolRenderers?.[getToolName(part)]?.describe?.(part);
|
|
163
|
+
if (custom !== void 0) return custom;
|
|
164
|
+
}
|
|
165
|
+
if (isReasoningUIPart(part)) {
|
|
166
|
+
const snippet = summarizeToolInput({ text: part.text });
|
|
167
|
+
const label = part.state === "streaming" ? "Thinking…" : "Thought";
|
|
168
|
+
return snippet ? `${label} · ${snippet}` : label;
|
|
169
|
+
}
|
|
170
|
+
if (isWorkspaceToolPart(part)) return describeWorkspaceToolPart(part);
|
|
171
|
+
if (isSkillToolPart(part)) return describeSkillToolPart(part);
|
|
172
|
+
if (isFetchToolPart(part)) return describeFetchToolPart(part);
|
|
173
|
+
if (isBrowserToolPart(part)) return describeBrowserToolPart(part);
|
|
174
|
+
if (isExecuteToolPart(part)) return describeExecuteToolPart(part);
|
|
175
|
+
if (isToolUIPart(part)) return describeGenericToolPart(part);
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
function basename(path) {
|
|
179
|
+
return path?.split("/").filter(Boolean).at(-1) ?? null;
|
|
180
|
+
}
|
|
181
|
+
function describeWorkspaceToolPart(part) {
|
|
182
|
+
const working = isPartWorking(part);
|
|
183
|
+
const failed = isPartFailed(part);
|
|
184
|
+
if (part.type === "tool-bash") {
|
|
185
|
+
const command = part.input?.script?.trim() ?? "";
|
|
186
|
+
if (failed) return command ? `Command failed · ${command}` : "Command failed";
|
|
187
|
+
const verb = working ? "Running" : "Ran";
|
|
188
|
+
return command ? `${verb} ${command}` : `${verb} a command`;
|
|
189
|
+
}
|
|
190
|
+
const label = (() => {
|
|
191
|
+
switch (part.type) {
|
|
192
|
+
case "tool-read": return `${working ? "Reading" : "Read"} ${basename(part.input?.path) ?? "a file"}`;
|
|
193
|
+
case "tool-write": return `${working ? "Writing" : "Wrote"} ${basename(part.input?.path) ?? "a file"}`;
|
|
194
|
+
case "tool-edit": return `${working ? "Editing" : "Edited"} ${basename(part.input?.path) ?? "a file"}`;
|
|
195
|
+
case "tool-delete": return `${working ? "Deleting" : "Deleted"} ${part.input?.path || "a file"}`;
|
|
196
|
+
case "tool-list": {
|
|
197
|
+
const path = part.input?.path;
|
|
198
|
+
return `${working ? "Listing" : "Listed"} ${path && path !== "/" ? path : "files"}`;
|
|
199
|
+
}
|
|
200
|
+
case "tool-find": return `${working ? "Searching" : "Searched"} for ${part.input?.pattern || "files"}`;
|
|
201
|
+
case "tool-grep": return `${working ? "Searching" : "Searched"} for ${part.input?.query || "text"}`;
|
|
202
|
+
}
|
|
203
|
+
})();
|
|
204
|
+
return failed ? `${label} · failed` : label;
|
|
205
|
+
}
|
|
206
|
+
function describeSkillToolPart(part) {
|
|
207
|
+
const working = isPartWorking(part);
|
|
208
|
+
const failed = isPartFailed(part);
|
|
209
|
+
const label = (() => {
|
|
210
|
+
switch (part.type) {
|
|
211
|
+
case "tool-activate_skill": {
|
|
212
|
+
const name = part.input?.name;
|
|
213
|
+
return `${working ? "Using" : "Used"} ${name ? `the ${name} skill` : "a skill"}`;
|
|
214
|
+
}
|
|
215
|
+
case "tool-read_skill_resource": return `${working ? "Reading" : "Read"} ${part.input?.path ?? "a skill resource"}`;
|
|
216
|
+
case "tool-run_skill_script": return `${working ? "Running" : "Ran"} ${part.input?.path ?? "a skill script"}`;
|
|
217
|
+
}
|
|
218
|
+
})();
|
|
219
|
+
return failed ? `${label} · failed` : label;
|
|
220
|
+
}
|
|
221
|
+
function describeBrowserToolPart(part) {
|
|
222
|
+
const working = isPartWorking(part);
|
|
223
|
+
const failed = isPartFailed(part);
|
|
224
|
+
if (part.type === "tool-browser_execute") {
|
|
225
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
226
|
+
if (failed || output?.status === "error") return "Browser automation failed";
|
|
227
|
+
if (output?.status === "paused") return "Browser waiting for approval";
|
|
228
|
+
return working ? "Using the browser" : "Used the browser";
|
|
229
|
+
}
|
|
230
|
+
const page = displayUrl(part.input?.url) || "a page";
|
|
231
|
+
const label = (() => {
|
|
232
|
+
switch (part.type) {
|
|
233
|
+
case "tool-browser_extract": return `${working ? "Extracting" : "Extracted"} data from ${page}`;
|
|
234
|
+
case "tool-browser_links": return `${working ? "Listing" : "Listed"} links on ${page}`;
|
|
235
|
+
case "tool-browser_scrape": return `${working ? "Scraping" : "Scraped"} ${page}`;
|
|
236
|
+
default: return `${working ? "Browsing" : "Browsed"} ${page}`;
|
|
237
|
+
}
|
|
238
|
+
})();
|
|
239
|
+
return failed ? `${label} · failed` : label;
|
|
240
|
+
}
|
|
241
|
+
function describeExecuteToolPart(part) {
|
|
242
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
243
|
+
if (isPartFailed(part) || output?.status === "error") return "Code failed";
|
|
244
|
+
if (output?.status === "paused") return "Code waiting for approval";
|
|
245
|
+
const label = isPartWorking(part) ? "Running code" : "Ran code";
|
|
246
|
+
const snippet = summarizeToolInput(part.input);
|
|
247
|
+
return snippet ? `${label} · ${snippet}` : label;
|
|
248
|
+
}
|
|
249
|
+
function describeFetchToolPart(part) {
|
|
250
|
+
const target = part.input?.url ?? part.input?.path ?? "";
|
|
251
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
252
|
+
if (isPartFailed(part) || output?.ok === false) return target ? `Fetch failed · ${target}` : "Fetch failed";
|
|
253
|
+
const verb = isPartWorking(part) ? "Fetching" : "Fetched";
|
|
254
|
+
return target ? `${verb} ${target}` : `${verb} a URL`;
|
|
255
|
+
}
|
|
256
|
+
function summarizeActivitySteps(parts) {
|
|
257
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
258
|
+
const genericSegments = /* @__PURE__ */ new Set();
|
|
259
|
+
const skillNames = /* @__PURE__ */ new Set();
|
|
260
|
+
let thoughts = 0;
|
|
261
|
+
let skillParts = 0;
|
|
262
|
+
let fetches = 0;
|
|
263
|
+
let browses = 0;
|
|
264
|
+
let codeRuns = 0;
|
|
265
|
+
for (const part of parts) if (isReasoningUIPart(part)) thoughts++;
|
|
266
|
+
else if (isWorkspaceToolPart(part)) {
|
|
267
|
+
const name = getToolName(part);
|
|
268
|
+
byTool.set(name, (byTool.get(name) ?? 0) + 1);
|
|
269
|
+
} else if (isSkillToolPart(part)) {
|
|
270
|
+
skillParts++;
|
|
271
|
+
if (typeof part.input?.name === "string") skillNames.add(part.input.name);
|
|
272
|
+
} else if (isFetchToolPart(part)) fetches++;
|
|
273
|
+
else if (isBrowserToolPart(part)) browses++;
|
|
274
|
+
else if (isExecuteToolPart(part)) codeRuns++;
|
|
275
|
+
else if (isToolUIPart(part)) genericSegments.add(genericToolLabels(getToolName(part), activityPartUrl(part) !== null).summary);
|
|
276
|
+
const count = (name) => byTool.get(name) ?? 0;
|
|
277
|
+
const files = (n) => n === 1 ? "a file" : `${n} files`;
|
|
278
|
+
const reads = count("read");
|
|
279
|
+
const edits = count("edit") + count("write");
|
|
280
|
+
const deletes = count("delete");
|
|
281
|
+
const commands = count("bash");
|
|
282
|
+
const segments = [];
|
|
283
|
+
if (thoughts > 0) segments.push("thought");
|
|
284
|
+
if (skillParts > 0) segments.push(skillNames.size === 1 ? `used the ${[...skillNames][0]} skill` : skillNames.size > 1 ? `used ${skillNames.size} skills` : "used a skill");
|
|
285
|
+
if (reads > 0) segments.push(`read ${files(reads)}`);
|
|
286
|
+
if (edits > 0) segments.push(`edited ${files(edits)}`);
|
|
287
|
+
if (deletes > 0) segments.push(`deleted ${files(deletes)}`);
|
|
288
|
+
if (count("find") + count("grep") > 0) segments.push("searched code");
|
|
289
|
+
if (count("list") > 0) segments.push("listed files");
|
|
290
|
+
if (fetches > 0) segments.push(fetches === 1 ? "fetched a URL" : `fetched ${fetches} URLs`);
|
|
291
|
+
if (browses > 0) segments.push("used the browser");
|
|
292
|
+
if (codeRuns > 0) segments.push("ran code");
|
|
293
|
+
if (commands > 0) segments.push(commands === 1 ? "ran a command" : `ran ${commands} commands`);
|
|
294
|
+
for (const segment of [
|
|
295
|
+
"edited files",
|
|
296
|
+
"searched code",
|
|
297
|
+
"searched the web",
|
|
298
|
+
"read a URL",
|
|
299
|
+
"ran a command",
|
|
300
|
+
"read files",
|
|
301
|
+
"used a tool"
|
|
302
|
+
]) if (genericSegments.has(segment)) segments.push(segment);
|
|
303
|
+
if (segments.length === 0) return "Worked";
|
|
304
|
+
const top = segments.slice(0, 3);
|
|
305
|
+
const joined = top.length === 1 ? top[0] : `${top.slice(0, -1).join(", ")} and ${top.at(-1)}`;
|
|
306
|
+
return joined.charAt(0).toUpperCase() + joined.slice(1);
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* One run of agent work as a collapsed Activity block: while streaming the
|
|
310
|
+
* label shimmers with the live step; settled, it becomes a short summary
|
|
311
|
+
* ("Thought, read 2 files and ran a command") over an expandable step log
|
|
312
|
+
* with per-tool panels. Normally reached through `ThinkChat.Messages`;
|
|
313
|
+
* exported for apps that lay out messages themselves.
|
|
314
|
+
*/
|
|
315
|
+
function ThinkChatActivity({ parts, defaultOpen, isStreaming, toolRenderers }) {
|
|
316
|
+
const steps = parts.flatMap((part, index) => {
|
|
317
|
+
const text = describeThinkChatActivityPart(part, toolRenderers);
|
|
318
|
+
if (!text) return [];
|
|
319
|
+
return [{
|
|
320
|
+
key: isToolUIPart(part) ? part.toolCallId : `${part.type}-${index}`,
|
|
321
|
+
text,
|
|
322
|
+
part
|
|
323
|
+
}];
|
|
324
|
+
});
|
|
325
|
+
if (steps.length === 0) return null;
|
|
326
|
+
const streaming = isStreaming ?? parts.some(isPartWorking);
|
|
327
|
+
const settledLabel = summarizeActivitySteps(parts);
|
|
328
|
+
const latest = steps.at(-1);
|
|
329
|
+
return /* @__PURE__ */ jsxs(Activity, {
|
|
330
|
+
isStreaming: streaming,
|
|
331
|
+
defaultOpen,
|
|
332
|
+
children: [/* @__PURE__ */ jsx(ActivityTrigger, {
|
|
333
|
+
icon: streaming && latest ? activityPartIcon(latest.part) : activitySummaryIcon(parts),
|
|
334
|
+
label: streaming && latest ? latest.text : settledLabel
|
|
335
|
+
}), /* @__PURE__ */ jsx(ActivityContent, { children: steps.map((step) => /* @__PURE__ */ jsx(ThinkChatActivityStep, {
|
|
336
|
+
text: step.text,
|
|
337
|
+
part: step.part,
|
|
338
|
+
toolRenderers
|
|
339
|
+
}, step.key)) })]
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
function ThinkChatActivityStep({ text, part, toolRenderers }) {
|
|
343
|
+
const icon = activityPartIcon(part);
|
|
344
|
+
if (isToolUIPart(part)) {
|
|
345
|
+
const customPanel = toolRenderers?.[getToolName(part)]?.panel?.(part);
|
|
346
|
+
if (customPanel === null) return /* @__PURE__ */ jsx(ActivityItem, {
|
|
347
|
+
icon,
|
|
348
|
+
children: text
|
|
349
|
+
});
|
|
350
|
+
if (customPanel !== void 0) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
351
|
+
label: text,
|
|
352
|
+
icon,
|
|
353
|
+
children: customPanel
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
if (isReasoningUIPart(part) && part.text.trim()) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
357
|
+
label: text,
|
|
358
|
+
icon,
|
|
359
|
+
children: /* @__PURE__ */ jsx(ActivityText, { children: part.text })
|
|
360
|
+
});
|
|
361
|
+
if (isWorkspaceToolPart(part) && part.type !== "tool-delete") return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
362
|
+
label: text,
|
|
363
|
+
icon,
|
|
364
|
+
children: /* @__PURE__ */ jsx(ThinkChatWorkspacePanel, { part })
|
|
365
|
+
});
|
|
366
|
+
if (isSkillToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
367
|
+
label: text,
|
|
368
|
+
icon,
|
|
369
|
+
children: /* @__PURE__ */ jsx(ThinkChatSkillPanel, { part })
|
|
370
|
+
});
|
|
371
|
+
if (isFetchToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
372
|
+
label: text,
|
|
373
|
+
icon,
|
|
374
|
+
children: /* @__PURE__ */ jsx(ThinkChatFetchPanel, { part })
|
|
375
|
+
});
|
|
376
|
+
if (isBrowserToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
377
|
+
label: text,
|
|
378
|
+
icon,
|
|
379
|
+
children: /* @__PURE__ */ jsx(ThinkChatBrowserPanel, { part })
|
|
380
|
+
});
|
|
381
|
+
if (isExecuteToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
382
|
+
label: text,
|
|
383
|
+
icon,
|
|
384
|
+
children: /* @__PURE__ */ jsx(ThinkChatExecutePanel, { part })
|
|
385
|
+
});
|
|
386
|
+
return /* @__PURE__ */ jsx(ActivityItem, {
|
|
387
|
+
icon,
|
|
388
|
+
children: text
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
function ThinkChatActivityGroup({ message, parts, isTrailing, toolRenderers }) {
|
|
392
|
+
const chat = React.useContext(ThinkChatContext);
|
|
393
|
+
const chatStillWorking = isTrailing && Boolean(chat?.isStreaming) && chat?.messages.at(-1)?.id === message.id;
|
|
394
|
+
return /* @__PURE__ */ jsx(ThinkChatActivity, {
|
|
395
|
+
parts,
|
|
396
|
+
isStreaming: parts.some(isPartWorking) || chatStillWorking,
|
|
397
|
+
toolRenderers
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Builds a renderPart handler that folds every run of consecutive activity
|
|
402
|
+
* parts (reasoning + tool calls) into one collapsed Activity box. Everything
|
|
403
|
+
* is derived from the UIMessage stream: part order, tool states, reasoning
|
|
404
|
+
* state. Excluded tools split the run and return undefined so your own
|
|
405
|
+
* renderPart (or AgentChat's generic activity row) takes over for them.
|
|
406
|
+
*/
|
|
407
|
+
function createThinkChatActivityRenderer({ excludeTools, toolRenderers } = {}) {
|
|
408
|
+
const excluded = excludeTools?.length ? new Set(excludeTools) : void 0;
|
|
409
|
+
const isActivity = (part) => isThinkChatActivityPart(part, excluded);
|
|
410
|
+
return (context) => {
|
|
411
|
+
const { message, part, partIndex } = context;
|
|
412
|
+
if (!isActivity(part)) return void 0;
|
|
413
|
+
let start = partIndex;
|
|
414
|
+
while (start > 0 && isActivity(message.parts[start - 1])) start--;
|
|
415
|
+
if (partIndex !== start) return null;
|
|
416
|
+
let end = partIndex;
|
|
417
|
+
while (end + 1 < message.parts.length && isActivity(message.parts[end + 1])) end++;
|
|
418
|
+
return /* @__PURE__ */ jsx(ThinkChatActivityGroup, {
|
|
419
|
+
message,
|
|
420
|
+
parts: message.parts.slice(start, end + 1),
|
|
421
|
+
isTrailing: end === message.parts.length - 1,
|
|
422
|
+
toolRenderers
|
|
423
|
+
});
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
/** Default activity renderer — `createThinkChatActivityRenderer()` with no overrides. */
|
|
427
|
+
const renderThinkChatActivityPart = createThinkChatActivityRenderer();
|
|
428
|
+
//#endregion
|
|
429
|
+
export { ThinkChatActivity, createThinkChatActivityRenderer, describeThinkChatActivityPart, isThinkChatActivityPart, renderThinkChatActivityPart };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { UIMessage } from "ai";
|
|
3
|
+
import { useAgentChat } from "@cloudflare/think/react";
|
|
4
|
+
|
|
5
|
+
//#region src/components/ThinkChatContext.d.ts
|
|
6
|
+
type ThinkChatValue = ReturnType<typeof useAgentChat<unknown, UIMessage>>;
|
|
7
|
+
declare const ThinkChatContext: React.Context<(Omit<import("@ai-sdk/react").UseChatHelpers<UIMessage<unknown, import("ai").UIDataTypes, import("ai").UITools>>, "addToolOutput"> & {
|
|
8
|
+
clearHistory: () => void;
|
|
9
|
+
addToolOutput: (opts: {
|
|
10
|
+
toolCallId: string;
|
|
11
|
+
toolName?: string;
|
|
12
|
+
output?: unknown;
|
|
13
|
+
state?: "output-available" | "output-error";
|
|
14
|
+
errorText?: string;
|
|
15
|
+
}) => void;
|
|
16
|
+
isServerStreaming: boolean;
|
|
17
|
+
isStreaming: boolean;
|
|
18
|
+
isRecovering: boolean;
|
|
19
|
+
isToolContinuation: boolean;
|
|
20
|
+
connectionError: (Error & {
|
|
21
|
+
code: number;
|
|
22
|
+
reason: string;
|
|
23
|
+
wasClean: boolean;
|
|
24
|
+
}) | null;
|
|
25
|
+
}) | null>;
|
|
26
|
+
declare function useThinkChatContext(component: string): ThinkChatValue;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { ThinkChatContext, ThinkChatValue, useThinkChatContext };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
//#region src/components/ThinkChatContext.ts
|
|
3
|
+
const ThinkChatContext = React.createContext(null);
|
|
4
|
+
function useThinkChatContext(component) {
|
|
5
|
+
const context = React.useContext(ThinkChatContext);
|
|
6
|
+
if (!context) throw new Error(`${component} must be used inside <ThinkChat.Provider> or <ThinkChat.Root>.`);
|
|
7
|
+
return context;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { ThinkChatContext, useThinkChatContext };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { i as SkillToolPart, l as WorkspaceToolPart, n as ExecuteToolPart, r as FetchToolPart, t as BrowserToolPart } from "../ThinkChatToolParts-DnUiZgCC.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/components/ThinkChatToolPanels.d.ts
|
|
5
|
+
/** Default panels for the Think workspace tools, Claude Code-style: read/write
|
|
6
|
+
show the file with line numbers, edit shows a mini diff, list/find/grep
|
|
7
|
+
show their result lists, bash gets the shell panel. Everything is typed by
|
|
8
|
+
the upstream tool contract. */
|
|
9
|
+
declare function ThinkChatWorkspacePanel({
|
|
10
|
+
part
|
|
11
|
+
}: {
|
|
12
|
+
part: WorkspaceToolPart;
|
|
13
|
+
}): React.JSX.Element | null;
|
|
14
|
+
/** Skill panels show what the model actually received: the activated skill's
|
|
15
|
+
body, the resource text, or the script result. */
|
|
16
|
+
declare function ThinkChatSkillPanel({
|
|
17
|
+
part
|
|
18
|
+
}: {
|
|
19
|
+
part: SkillToolPart;
|
|
20
|
+
}): React.JSX.Element;
|
|
21
|
+
declare function ThinkChatFetchPanel({
|
|
22
|
+
part
|
|
23
|
+
}: {
|
|
24
|
+
part: FetchToolPart;
|
|
25
|
+
}): React.JSX.Element;
|
|
26
|
+
/** The codemode execute tool as an activity panel: highlighted TypeScript
|
|
27
|
+
plus logs, result, and the paused pending-approval list. */
|
|
28
|
+
declare function ThinkChatExecutePanel({
|
|
29
|
+
part
|
|
30
|
+
}: {
|
|
31
|
+
part: ExecuteToolPart;
|
|
32
|
+
}): React.JSX.Element;
|
|
33
|
+
declare function ThinkChatBrowserPanel({
|
|
34
|
+
part
|
|
35
|
+
}: {
|
|
36
|
+
part: BrowserToolPart;
|
|
37
|
+
}): React.JSX.Element;
|
|
38
|
+
//#endregion
|
|
39
|
+
export { ThinkChatBrowserPanel, ThinkChatExecutePanel, ThinkChatFetchPanel, ThinkChatSkillPanel, ThinkChatWorkspacePanel };
|