@mcp-b/react-components 0.6.5 → 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/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/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 +1 -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/package.json +3 -2
|
@@ -1,76 +1,53 @@
|
|
|
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, ActivityOutput, ActivityPanel, ActivityText, ActivityTrigger } from "./Activity.js";
|
|
3
|
-
import { Tab, TabPanel, Tabs, TabsList } from "./Tabs.js";
|
|
4
1
|
import { AgentChat } from "./AgentChat.js";
|
|
5
|
-
import { t as CodeBlock } from "../CodeBlock-BK0sOAbV.js";
|
|
6
2
|
import { BrowserLiveView } from "./BrowserLiveView.js";
|
|
7
3
|
import { BrowserReplay } from "./BrowserReplay.js";
|
|
4
|
+
import { ThinkChatContext, useThinkChatContext } from "./ThinkChatContext.js";
|
|
5
|
+
import { isThinkChatActivityPart } from "./ThinkChatToolParts.js";
|
|
6
|
+
import { ThinkChatActivity, createThinkChatActivityRenderer, describeThinkChatActivityPart, renderThinkChatActivityPart } from "./ThinkChatActivity.js";
|
|
8
7
|
import { WorkspaceExplorer, inferWorkspaceLanguage, useWorkspace } from "./WorkspaceExplorer.js";
|
|
9
8
|
import * as React from "react";
|
|
10
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
-
import {
|
|
12
|
-
import { getToolPartState, useAgentChat } from "@cloudflare/think/react";
|
|
10
|
+
import { useAgentChat } from "@cloudflare/think/react";
|
|
13
11
|
import { useAgent } from "agents/react";
|
|
14
12
|
//#region src/components/ThinkChat.tsx
|
|
15
|
-
/**
|
|
16
|
-
* Think-specific chat rendering: interprets a Think agent's UIMessage stream
|
|
17
|
-
* (reasoning + workspace/skill/fetch/browser/execute tool parts) into
|
|
18
|
-
* Activity blocks.
|
|
19
|
-
*
|
|
20
|
-
* Every shape here mirrors an upstream contract — see docs/think-chat.md
|
|
21
|
-
* ("Upstream source map" and the re-alignment checklist) for where each comes from,
|
|
22
|
-
* which are compile-time typed vs. hand-rolled mirrors of untyped upstream
|
|
23
|
-
* ToolSets, and how to re-align when @cloudflare/think (experimental) changes.
|
|
24
|
-
*/
|
|
25
|
-
const WORKSPACE_TOOL_NAMES = new Set([
|
|
26
|
-
"read",
|
|
27
|
-
"write",
|
|
28
|
-
"edit",
|
|
29
|
-
"list",
|
|
30
|
-
"find",
|
|
31
|
-
"grep",
|
|
32
|
-
"delete",
|
|
33
|
-
"bash"
|
|
34
|
-
]);
|
|
35
|
-
function isWorkspaceToolPart(part) {
|
|
36
|
-
return isToolUIPart(part) && WORKSPACE_TOOL_NAMES.has(getToolName(part));
|
|
37
|
-
}
|
|
38
|
-
const SKILL_TOOL_NAMES = new Set([
|
|
39
|
-
"activate_skill",
|
|
40
|
-
"read_skill_resource",
|
|
41
|
-
"run_skill_script"
|
|
42
|
-
]);
|
|
43
|
-
function isSkillToolPart(part) {
|
|
44
|
-
return isToolUIPart(part) && SKILL_TOOL_NAMES.has(getToolName(part));
|
|
45
|
-
}
|
|
46
|
-
function isFetchToolPart(part) {
|
|
47
|
-
return isToolUIPart(part) && getToolName(part).startsWith("fetch_");
|
|
48
|
-
}
|
|
49
|
-
const BROWSER_TOOL_NAMES = new Set([
|
|
50
|
-
"browser_execute",
|
|
51
|
-
"browser_markdown",
|
|
52
|
-
"browser_extract",
|
|
53
|
-
"browser_links",
|
|
54
|
-
"browser_scrape",
|
|
55
|
-
"browser_content"
|
|
56
|
-
]);
|
|
57
|
-
function isBrowserToolPart(part) {
|
|
58
|
-
return isToolUIPart(part) && BROWSER_TOOL_NAMES.has(getToolName(part));
|
|
59
|
-
}
|
|
60
|
-
function isExecuteToolPart(part) {
|
|
61
|
-
return isToolUIPart(part) && getToolName(part) === "execute";
|
|
62
|
-
}
|
|
63
|
-
const ThinkChatContext = React.createContext(null);
|
|
64
|
-
function useThinkChatContext(component) {
|
|
65
|
-
const context = React.useContext(ThinkChatContext);
|
|
66
|
-
if (!context) throw new Error(`${component} must be used inside <ThinkChat.Provider> or <ThinkChat.Root>.`);
|
|
67
|
-
return context;
|
|
68
|
-
}
|
|
69
13
|
function toAgentChatStatus(chat) {
|
|
70
14
|
if (chat.status === "submitted") return "submitted";
|
|
71
15
|
if (chat.isStreaming || chat.isRecovering) return "streaming";
|
|
72
16
|
return chat.status ?? "ready";
|
|
73
17
|
}
|
|
18
|
+
function useThinkPendingApprovals(stub, refreshKey) {
|
|
19
|
+
const [pendingApprovals, setPendingApprovals] = React.useState([]);
|
|
20
|
+
const refresh = React.useCallback(async () => {
|
|
21
|
+
try {
|
|
22
|
+
setPendingApprovals(await stub.pendingApprovals());
|
|
23
|
+
} catch {
|
|
24
|
+
setPendingApprovals([]);
|
|
25
|
+
}
|
|
26
|
+
}, [stub]);
|
|
27
|
+
React.useEffect(() => {
|
|
28
|
+
refresh();
|
|
29
|
+
}, [refresh, refreshKey]);
|
|
30
|
+
const executionApprovals = React.useMemo(() => ({
|
|
31
|
+
approve: async (id) => {
|
|
32
|
+
try {
|
|
33
|
+
return await stub.approveExecution(id);
|
|
34
|
+
} finally {
|
|
35
|
+
refresh();
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
reject: async (id, reason) => {
|
|
39
|
+
try {
|
|
40
|
+
return await stub.rejectExecution(id, reason);
|
|
41
|
+
} finally {
|
|
42
|
+
refresh();
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}), [stub, refresh]);
|
|
46
|
+
return React.useMemo(() => ({
|
|
47
|
+
pendingApprovals,
|
|
48
|
+
executionApprovals
|
|
49
|
+
}), [pendingApprovals, executionApprovals]);
|
|
50
|
+
}
|
|
74
51
|
/**
|
|
75
52
|
* Chat UI for Cloudflare Think agents. Provide a `useAgentChat` value (or let
|
|
76
53
|
* `ThinkChat.Root` create one) and `ThinkChat.Messages` renders the message
|
|
@@ -95,17 +72,24 @@ function ThinkChatProvider({ chat, children }) {
|
|
|
95
72
|
* ```
|
|
96
73
|
*/
|
|
97
74
|
function ThinkChatRoot({ agent: agentOption, name, children }) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
75
|
+
const agent = useAgent(typeof agentOption === "string" ? {
|
|
76
|
+
agent: agentOption,
|
|
77
|
+
name
|
|
78
|
+
} : {
|
|
79
|
+
...agentOption,
|
|
80
|
+
name: name ?? agentOption.name
|
|
81
|
+
});
|
|
82
|
+
const chat = useAgentChat({ agent });
|
|
83
|
+
const approvals = useThinkPendingApprovals(agent.stub, `${chat.messages.length}:${chat.status}`);
|
|
84
|
+
return /* @__PURE__ */ jsx(ThinkChatContext.Provider, {
|
|
85
|
+
value: chat,
|
|
86
|
+
children: /* @__PURE__ */ jsx(ThinkChatApprovalsContext.Provider, {
|
|
87
|
+
value: approvals,
|
|
88
|
+
children
|
|
89
|
+
})
|
|
107
90
|
});
|
|
108
91
|
}
|
|
92
|
+
const ThinkChatApprovalsContext = React.createContext(null);
|
|
109
93
|
/**
|
|
110
94
|
* The full conversation surface — message list plus composer — with the
|
|
111
95
|
* activity renderer as the default `renderPart`: runs of reasoning/tool parts
|
|
@@ -114,9 +98,12 @@ function ThinkChatRoot({ agent: agentOption, name, children }) {
|
|
|
114
98
|
* inside the log) or `excludeTools` + your own `renderPart` (standalone
|
|
115
99
|
* card) — see README "App-specific tools" and the Custom Tool Panel story.
|
|
116
100
|
*/
|
|
117
|
-
function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, executionApprovals, notice, ...props }) {
|
|
101
|
+
function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, executionApprovals, pendingApprovals, notice, ...props }) {
|
|
118
102
|
const chat = useThinkChatContext("ThinkChat.Messages");
|
|
103
|
+
const approvals = React.useContext(ThinkChatApprovalsContext);
|
|
119
104
|
const status = toAgentChatStatus(chat);
|
|
105
|
+
const resolvedExecutionApprovals = executionApprovals ?? approvals?.executionApprovals;
|
|
106
|
+
const resolvedPendingApprovals = pendingApprovals ?? approvals?.pendingApprovals;
|
|
120
107
|
const defaultRenderPart = React.useMemo(() => createThinkChatActivityRenderer({
|
|
121
108
|
excludeTools,
|
|
122
109
|
toolRenderers
|
|
@@ -132,7 +119,8 @@ function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, executionA
|
|
|
132
119
|
status,
|
|
133
120
|
onStop: chat.stop,
|
|
134
121
|
onApprovalResponse: chat.addToolApprovalResponse,
|
|
135
|
-
executionApprovals,
|
|
122
|
+
executionApprovals: resolvedExecutionApprovals,
|
|
123
|
+
pendingApprovals: resolvedPendingApprovals,
|
|
136
124
|
notice: notice ?? connectionNotice,
|
|
137
125
|
onRegenerate: (messageId) => void chat.regenerate({ messageId }),
|
|
138
126
|
onSubmit: ({ text, files }) => {
|
|
@@ -144,762 +132,6 @@ function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, executionA
|
|
|
144
132
|
}
|
|
145
133
|
});
|
|
146
134
|
}
|
|
147
|
-
/** Agent work (reasoning, tool calls, step boundaries) vs. content shown to the user. */
|
|
148
|
-
function isThinkChatActivityPart(part, excludeTools) {
|
|
149
|
-
if (part === void 0) return false;
|
|
150
|
-
if (isToolUIPart(part) && excludeTools?.has(getToolName(part))) return false;
|
|
151
|
-
return part.type === "step-start" || isReasoningUIPart(part) || isToolUIPart(part);
|
|
152
|
-
}
|
|
153
|
-
/** In-flight: the stream opened this part but hasn't delivered its result yet. */
|
|
154
|
-
function isPartWorking(part) {
|
|
155
|
-
if (isReasoningUIPart(part)) return part.state === "streaming";
|
|
156
|
-
if (isToolUIPart(part)) {
|
|
157
|
-
const state = getToolPartState(part);
|
|
158
|
-
return state === "loading" || state === "streaming" || state === "waiting-approval";
|
|
159
|
-
}
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
/** Errored or denied — upstream's simplified tool-part state vocabulary. */
|
|
163
|
-
function isPartFailed(part) {
|
|
164
|
-
const state = getToolPartState(part);
|
|
165
|
-
return state === "error" || state === "denied";
|
|
166
|
-
}
|
|
167
|
-
function summarizeToolInput(input) {
|
|
168
|
-
if (!input || typeof input !== "object") return null;
|
|
169
|
-
for (const value of Object.values(input)) if (typeof value === "string" && value.trim()) {
|
|
170
|
-
const line = value.trim().split("\n", 1)[0] ?? "";
|
|
171
|
-
return line.length > 64 ? `${line.slice(0, 64)}…` : line;
|
|
172
|
-
}
|
|
173
|
-
return null;
|
|
174
|
-
}
|
|
175
|
-
const HTTP_URL_PATTERN = /https?:\/\/[^\s"'<>)}\]]+/i;
|
|
176
|
-
function firstHttpUrl(value) {
|
|
177
|
-
const text = typeof value === "string" ? value : JSON.stringify(value) ?? "";
|
|
178
|
-
const raw = HTTP_URL_PATTERN.exec(text)?.[0]?.replace(/[.,;:!?]+$/, "");
|
|
179
|
-
if (!raw) return null;
|
|
180
|
-
try {
|
|
181
|
-
return new URL(raw).href;
|
|
182
|
-
} catch {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function ActivityFavicon({ url }) {
|
|
187
|
-
const src = faviconHref(url);
|
|
188
|
-
const [failed, setFailed] = React.useState(false);
|
|
189
|
-
React.useEffect(() => setFailed(false), [src]);
|
|
190
|
-
if (!src || failed) return /* @__PURE__ */ jsx(LinkIcon, {});
|
|
191
|
-
return /* @__PURE__ */ jsx("img", {
|
|
192
|
-
className: "activity__favicon",
|
|
193
|
-
src,
|
|
194
|
-
alt: "",
|
|
195
|
-
loading: "lazy",
|
|
196
|
-
decoding: "async",
|
|
197
|
-
onError: () => setFailed(true)
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
function faviconHref(url) {
|
|
201
|
-
try {
|
|
202
|
-
return new URL("/favicon.ico", url).href;
|
|
203
|
-
} catch {
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
function ActivityKindIcon({ kind }) {
|
|
208
|
-
switch (kind) {
|
|
209
|
-
case "thought": return /* @__PURE__ */ jsx(InfoIcon, {});
|
|
210
|
-
case "read": return /* @__PURE__ */ jsx(FileIcon, {});
|
|
211
|
-
case "edit": return /* @__PURE__ */ jsx(PencilIcon, {});
|
|
212
|
-
case "search": return /* @__PURE__ */ jsx(SearchIcon, {});
|
|
213
|
-
case "web": return /* @__PURE__ */ jsx(LinkIcon, {});
|
|
214
|
-
case "command": return /* @__PURE__ */ jsx(TerminalIcon, {});
|
|
215
|
-
case "browser": return /* @__PURE__ */ jsx(MonitorIcon, {});
|
|
216
|
-
case "tool": return /* @__PURE__ */ jsx(WrenchIcon, {});
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
function genericToolLabels(name, hasUrl) {
|
|
220
|
-
const normalized = name.toLowerCase();
|
|
221
|
-
if (normalized.includes("grep") || normalized.includes("find") || normalized.includes("code")) return {
|
|
222
|
-
working: "Searching code",
|
|
223
|
-
done: "Searched code",
|
|
224
|
-
failed: "Code search failed",
|
|
225
|
-
summary: "searched code",
|
|
226
|
-
icon: "search"
|
|
227
|
-
};
|
|
228
|
-
if (normalized.includes("search") || normalized.includes("web") || normalized.includes("browser") || normalized.includes("google")) return {
|
|
229
|
-
working: "Searching the web",
|
|
230
|
-
done: "Searched the web",
|
|
231
|
-
failed: "Web search failed",
|
|
232
|
-
summary: "searched the web",
|
|
233
|
-
icon: "web"
|
|
234
|
-
};
|
|
235
|
-
if (hasUrl || normalized.includes("fetch") || normalized.includes("http") || normalized.includes("url")) return {
|
|
236
|
-
working: "Reading a URL",
|
|
237
|
-
done: "Read a URL",
|
|
238
|
-
failed: "URL read failed",
|
|
239
|
-
summary: "read a URL",
|
|
240
|
-
icon: "web"
|
|
241
|
-
};
|
|
242
|
-
if (normalized.includes("edit") || normalized.includes("write") || normalized.includes("delete") || normalized.includes("patch") || normalized.includes("apply") || normalized.includes("create") || normalized.includes("update")) return {
|
|
243
|
-
working: "Editing files",
|
|
244
|
-
done: "Edited files",
|
|
245
|
-
failed: "Edit failed",
|
|
246
|
-
summary: "edited files",
|
|
247
|
-
icon: "edit"
|
|
248
|
-
};
|
|
249
|
-
if (normalized.includes("exec") || normalized.includes("bash") || normalized.includes("shell") || normalized.includes("terminal") || normalized.includes("command")) return {
|
|
250
|
-
working: "Running a command",
|
|
251
|
-
done: "Ran a command",
|
|
252
|
-
failed: "Command failed",
|
|
253
|
-
summary: "ran a command",
|
|
254
|
-
icon: "command"
|
|
255
|
-
};
|
|
256
|
-
if (normalized.includes("read") || normalized.includes("open") || normalized.includes("list") || normalized.includes("file")) return {
|
|
257
|
-
working: "Reading files",
|
|
258
|
-
done: "Read files",
|
|
259
|
-
failed: "Read failed",
|
|
260
|
-
summary: "read files",
|
|
261
|
-
icon: "read"
|
|
262
|
-
};
|
|
263
|
-
return {
|
|
264
|
-
working: "Using a tool",
|
|
265
|
-
done: "Used a tool",
|
|
266
|
-
failed: "Tool failed",
|
|
267
|
-
summary: "used a tool",
|
|
268
|
-
icon: "tool"
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
function describeGenericToolPart(part) {
|
|
272
|
-
const labels = genericToolLabels(getToolName(part), activityPartUrl(part) !== null);
|
|
273
|
-
const label = isPartFailed(part) ? labels.failed : isPartWorking(part) ? labels.working : labels.done;
|
|
274
|
-
const detail = summarizeToolInput(part.input);
|
|
275
|
-
return detail ? `${label} · ${detail}` : label;
|
|
276
|
-
}
|
|
277
|
-
function activityPartUrl(part) {
|
|
278
|
-
if (isFetchToolPart(part)) return firstHttpUrl(part.input?.url ?? part.input?.path);
|
|
279
|
-
if (isBrowserToolPart(part) && part.type !== "tool-browser_execute") return firstHttpUrl(part.input?.url);
|
|
280
|
-
if (isToolUIPart(part) && !isExecuteToolPart(part)) return firstHttpUrl(part.input) ?? (part.state === "output-available" ? firstHttpUrl(part.output) : null);
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
function activityPartIconKind(part) {
|
|
284
|
-
if (isReasoningUIPart(part)) return "thought";
|
|
285
|
-
if (isWorkspaceToolPart(part)) switch (part.type) {
|
|
286
|
-
case "tool-read":
|
|
287
|
-
case "tool-list": return "read";
|
|
288
|
-
case "tool-write":
|
|
289
|
-
case "tool-edit":
|
|
290
|
-
case "tool-delete": return "edit";
|
|
291
|
-
case "tool-find":
|
|
292
|
-
case "tool-grep": return "search";
|
|
293
|
-
case "tool-bash": return "command";
|
|
294
|
-
}
|
|
295
|
-
if (isSkillToolPart(part)) return "tool";
|
|
296
|
-
if (isFetchToolPart(part)) return "web";
|
|
297
|
-
if (isBrowserToolPart(part)) return part.type === "tool-browser_execute" ? "browser" : "web";
|
|
298
|
-
if (isExecuteToolPart(part)) return "command";
|
|
299
|
-
if (isToolUIPart(part)) return genericToolLabels(getToolName(part), activityPartUrl(part) !== null).icon;
|
|
300
|
-
return "tool";
|
|
301
|
-
}
|
|
302
|
-
function activityPartIcon(part) {
|
|
303
|
-
const url = activityPartUrl(part);
|
|
304
|
-
return url ? /* @__PURE__ */ jsx(ActivityFavicon, { url }) : /* @__PURE__ */ jsx(ActivityKindIcon, { kind: activityPartIconKind(part) });
|
|
305
|
-
}
|
|
306
|
-
function activitySummaryIcon(parts) {
|
|
307
|
-
if (parts.find((part) => isToolUIPart(part) && isPartFailed(part))) return /* @__PURE__ */ jsx(WarningIcon, {});
|
|
308
|
-
const representative = parts.find((part) => activityPartIconKind(part) === "edit") ?? parts.find((part) => activityPartUrl(part)) ?? parts.find((part) => describeThinkChatActivityPart(part));
|
|
309
|
-
return representative ? activityPartIcon(representative) : /* @__PURE__ */ jsx(ActivityKindIcon, { kind: "tool" });
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* One-line natural-language label for an activity part ("Ran vp test",
|
|
313
|
-
* "Fetched developers.cloudflare.com/agents/"); `null` for boundaries that
|
|
314
|
-
* aren't steps (step-start). Exported so a custom `renderPart` can reuse the
|
|
315
|
-
* built-in labels for tools it doesn't override.
|
|
316
|
-
*/
|
|
317
|
-
function describeThinkChatActivityPart(part, toolRenderers) {
|
|
318
|
-
if (isToolUIPart(part)) {
|
|
319
|
-
const custom = toolRenderers?.[getToolName(part)]?.describe?.(part);
|
|
320
|
-
if (custom !== void 0) return custom;
|
|
321
|
-
}
|
|
322
|
-
if (isReasoningUIPart(part)) {
|
|
323
|
-
const snippet = summarizeToolInput({ text: part.text });
|
|
324
|
-
const label = part.state === "streaming" ? "Thinking…" : "Thought";
|
|
325
|
-
return snippet ? `${label} · ${snippet}` : label;
|
|
326
|
-
}
|
|
327
|
-
if (isWorkspaceToolPart(part)) return describeWorkspaceToolPart(part);
|
|
328
|
-
if (isSkillToolPart(part)) return describeSkillToolPart(part);
|
|
329
|
-
if (isFetchToolPart(part)) return describeFetchToolPart(part);
|
|
330
|
-
if (isBrowserToolPart(part)) return describeBrowserToolPart(part);
|
|
331
|
-
if (isExecuteToolPart(part)) return describeExecuteToolPart(part);
|
|
332
|
-
if (isToolUIPart(part)) return describeGenericToolPart(part);
|
|
333
|
-
return null;
|
|
334
|
-
}
|
|
335
|
-
function basename(path) {
|
|
336
|
-
return path?.split("/").filter(Boolean).at(-1) ?? null;
|
|
337
|
-
}
|
|
338
|
-
function describeWorkspaceToolPart(part) {
|
|
339
|
-
const working = isPartWorking(part);
|
|
340
|
-
const failed = isPartFailed(part);
|
|
341
|
-
if (part.type === "tool-bash") {
|
|
342
|
-
const command = part.input?.script?.trim() ?? "";
|
|
343
|
-
if (failed) return command ? `Command failed · ${command}` : "Command failed";
|
|
344
|
-
const verb = working ? "Running" : "Ran";
|
|
345
|
-
return command ? `${verb} ${command}` : `${verb} a command`;
|
|
346
|
-
}
|
|
347
|
-
const label = (() => {
|
|
348
|
-
switch (part.type) {
|
|
349
|
-
case "tool-read": return `${working ? "Reading" : "Read"} ${basename(part.input?.path) ?? "a file"}`;
|
|
350
|
-
case "tool-write": return `${working ? "Writing" : "Wrote"} ${basename(part.input?.path) ?? "a file"}`;
|
|
351
|
-
case "tool-edit": return `${working ? "Editing" : "Edited"} ${basename(part.input?.path) ?? "a file"}`;
|
|
352
|
-
case "tool-delete": return `${working ? "Deleting" : "Deleted"} ${part.input?.path || "a file"}`;
|
|
353
|
-
case "tool-list": {
|
|
354
|
-
const path = part.input?.path;
|
|
355
|
-
return `${working ? "Listing" : "Listed"} ${path && path !== "/" ? path : "files"}`;
|
|
356
|
-
}
|
|
357
|
-
case "tool-find": return `${working ? "Searching" : "Searched"} for ${part.input?.pattern || "files"}`;
|
|
358
|
-
case "tool-grep": return `${working ? "Searching" : "Searched"} for ${part.input?.query || "text"}`;
|
|
359
|
-
}
|
|
360
|
-
})();
|
|
361
|
-
return failed ? `${label} · failed` : label;
|
|
362
|
-
}
|
|
363
|
-
function describeSkillToolPart(part) {
|
|
364
|
-
const working = isPartWorking(part);
|
|
365
|
-
const failed = isPartFailed(part);
|
|
366
|
-
const label = (() => {
|
|
367
|
-
switch (part.type) {
|
|
368
|
-
case "tool-activate_skill": {
|
|
369
|
-
const name = part.input?.name;
|
|
370
|
-
return `${working ? "Using" : "Used"} ${name ? `the ${name} skill` : "a skill"}`;
|
|
371
|
-
}
|
|
372
|
-
case "tool-read_skill_resource": return `${working ? "Reading" : "Read"} ${part.input?.path ?? "a skill resource"}`;
|
|
373
|
-
case "tool-run_skill_script": return `${working ? "Running" : "Ran"} ${part.input?.path ?? "a skill script"}`;
|
|
374
|
-
}
|
|
375
|
-
})();
|
|
376
|
-
return failed ? `${label} · failed` : label;
|
|
377
|
-
}
|
|
378
|
-
function displayUrl(url) {
|
|
379
|
-
return url ? url.replace(/^https?:\/\//, "") : "";
|
|
380
|
-
}
|
|
381
|
-
function describeBrowserToolPart(part) {
|
|
382
|
-
const working = isPartWorking(part);
|
|
383
|
-
const failed = isPartFailed(part);
|
|
384
|
-
if (part.type === "tool-browser_execute") {
|
|
385
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
386
|
-
if (failed || output?.status === "error") return "Browser automation failed";
|
|
387
|
-
if (output?.status === "paused") return "Browser waiting for approval";
|
|
388
|
-
return working ? "Using the browser" : "Used the browser";
|
|
389
|
-
}
|
|
390
|
-
const page = displayUrl(part.input?.url) || "a page";
|
|
391
|
-
const label = (() => {
|
|
392
|
-
switch (part.type) {
|
|
393
|
-
case "tool-browser_extract": return `${working ? "Extracting" : "Extracted"} data from ${page}`;
|
|
394
|
-
case "tool-browser_links": return `${working ? "Listing" : "Listed"} links on ${page}`;
|
|
395
|
-
case "tool-browser_scrape": return `${working ? "Scraping" : "Scraped"} ${page}`;
|
|
396
|
-
default: return `${working ? "Browsing" : "Browsed"} ${page}`;
|
|
397
|
-
}
|
|
398
|
-
})();
|
|
399
|
-
return failed ? `${label} · failed` : label;
|
|
400
|
-
}
|
|
401
|
-
function describeExecuteToolPart(part) {
|
|
402
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
403
|
-
if (isPartFailed(part) || output?.status === "error") return "Code failed";
|
|
404
|
-
if (output?.status === "paused") return "Code waiting for approval";
|
|
405
|
-
const label = isPartWorking(part) ? "Running code" : "Ran code";
|
|
406
|
-
const snippet = summarizeToolInput(part.input);
|
|
407
|
-
return snippet ? `${label} · ${snippet}` : label;
|
|
408
|
-
}
|
|
409
|
-
function describeFetchToolPart(part) {
|
|
410
|
-
const target = part.input?.url ?? part.input?.path ?? "";
|
|
411
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
412
|
-
if (isPartFailed(part) || output?.ok === false) return target ? `Fetch failed · ${target}` : "Fetch failed";
|
|
413
|
-
const verb = isPartWorking(part) ? "Fetching" : "Fetched";
|
|
414
|
-
return target ? `${verb} ${target}` : `${verb} a URL`;
|
|
415
|
-
}
|
|
416
|
-
function summarizeActivitySteps(parts) {
|
|
417
|
-
const byTool = /* @__PURE__ */ new Map();
|
|
418
|
-
const genericSegments = /* @__PURE__ */ new Set();
|
|
419
|
-
const skillNames = /* @__PURE__ */ new Set();
|
|
420
|
-
let thoughts = 0;
|
|
421
|
-
let skillParts = 0;
|
|
422
|
-
let fetches = 0;
|
|
423
|
-
let browses = 0;
|
|
424
|
-
let codeRuns = 0;
|
|
425
|
-
for (const part of parts) if (isReasoningUIPart(part)) thoughts++;
|
|
426
|
-
else if (isWorkspaceToolPart(part)) {
|
|
427
|
-
const name = getToolName(part);
|
|
428
|
-
byTool.set(name, (byTool.get(name) ?? 0) + 1);
|
|
429
|
-
} else if (isSkillToolPart(part)) {
|
|
430
|
-
skillParts++;
|
|
431
|
-
if (typeof part.input?.name === "string") skillNames.add(part.input.name);
|
|
432
|
-
} else if (isFetchToolPart(part)) fetches++;
|
|
433
|
-
else if (isBrowserToolPart(part)) browses++;
|
|
434
|
-
else if (isExecuteToolPart(part)) codeRuns++;
|
|
435
|
-
else if (isToolUIPart(part)) genericSegments.add(genericToolLabels(getToolName(part), activityPartUrl(part) !== null).summary);
|
|
436
|
-
const count = (name) => byTool.get(name) ?? 0;
|
|
437
|
-
const files = (n) => n === 1 ? "a file" : `${n} files`;
|
|
438
|
-
const reads = count("read");
|
|
439
|
-
const edits = count("edit") + count("write");
|
|
440
|
-
const deletes = count("delete");
|
|
441
|
-
const commands = count("bash");
|
|
442
|
-
const segments = [];
|
|
443
|
-
if (thoughts > 0) segments.push("thought");
|
|
444
|
-
if (skillParts > 0) segments.push(skillNames.size === 1 ? `used the ${[...skillNames][0]} skill` : skillNames.size > 1 ? `used ${skillNames.size} skills` : "used a skill");
|
|
445
|
-
if (reads > 0) segments.push(`read ${files(reads)}`);
|
|
446
|
-
if (edits > 0) segments.push(`edited ${files(edits)}`);
|
|
447
|
-
if (deletes > 0) segments.push(`deleted ${files(deletes)}`);
|
|
448
|
-
if (count("find") + count("grep") > 0) segments.push("searched code");
|
|
449
|
-
if (count("list") > 0) segments.push("listed files");
|
|
450
|
-
if (fetches > 0) segments.push(fetches === 1 ? "fetched a URL" : `fetched ${fetches} URLs`);
|
|
451
|
-
if (browses > 0) segments.push("used the browser");
|
|
452
|
-
if (codeRuns > 0) segments.push("ran code");
|
|
453
|
-
if (commands > 0) segments.push(commands === 1 ? "ran a command" : `ran ${commands} commands`);
|
|
454
|
-
for (const segment of [
|
|
455
|
-
"edited files",
|
|
456
|
-
"searched code",
|
|
457
|
-
"searched the web",
|
|
458
|
-
"read a URL",
|
|
459
|
-
"ran a command",
|
|
460
|
-
"read files",
|
|
461
|
-
"used a tool"
|
|
462
|
-
]) if (genericSegments.has(segment)) segments.push(segment);
|
|
463
|
-
if (segments.length === 0) return "Worked";
|
|
464
|
-
const top = segments.slice(0, 3);
|
|
465
|
-
const joined = top.length === 1 ? top[0] : `${top.slice(0, -1).join(", ")} and ${top.at(-1)}`;
|
|
466
|
-
return joined.charAt(0).toUpperCase() + joined.slice(1);
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* One run of agent work as a collapsed Activity block: while streaming the
|
|
470
|
-
* label shimmers with the live step; settled, it becomes a short summary
|
|
471
|
-
* ("Thought, read 2 files and ran a command") over an expandable step log
|
|
472
|
-
* with per-tool panels. Normally reached through `ThinkChat.Messages`;
|
|
473
|
-
* exported for apps that lay out messages themselves.
|
|
474
|
-
*/
|
|
475
|
-
function ThinkChatActivity({ parts, defaultOpen, isStreaming, toolRenderers }) {
|
|
476
|
-
const steps = parts.flatMap((part, index) => {
|
|
477
|
-
const text = describeThinkChatActivityPart(part, toolRenderers);
|
|
478
|
-
if (!text) return [];
|
|
479
|
-
return [{
|
|
480
|
-
key: isToolUIPart(part) ? part.toolCallId : `${part.type}-${index}`,
|
|
481
|
-
text,
|
|
482
|
-
part
|
|
483
|
-
}];
|
|
484
|
-
});
|
|
485
|
-
if (steps.length === 0) return null;
|
|
486
|
-
const streaming = isStreaming ?? parts.some(isPartWorking);
|
|
487
|
-
const settledLabel = summarizeActivitySteps(parts);
|
|
488
|
-
const latest = steps.at(-1);
|
|
489
|
-
return /* @__PURE__ */ jsxs(Activity, {
|
|
490
|
-
isStreaming: streaming,
|
|
491
|
-
defaultOpen,
|
|
492
|
-
children: [/* @__PURE__ */ jsx(ActivityTrigger, {
|
|
493
|
-
icon: streaming && latest ? activityPartIcon(latest.part) : activitySummaryIcon(parts),
|
|
494
|
-
label: streaming && latest ? latest.text : settledLabel
|
|
495
|
-
}), /* @__PURE__ */ jsx(ActivityContent, { children: steps.map((step) => /* @__PURE__ */ jsx(ThinkChatActivityStep, {
|
|
496
|
-
text: step.text,
|
|
497
|
-
part: step.part,
|
|
498
|
-
toolRenderers
|
|
499
|
-
}, step.key)) })]
|
|
500
|
-
});
|
|
501
|
-
}
|
|
502
|
-
function ThinkChatActivityStep({ text, part, toolRenderers }) {
|
|
503
|
-
const icon = activityPartIcon(part);
|
|
504
|
-
if (isToolUIPart(part)) {
|
|
505
|
-
const customPanel = toolRenderers?.[getToolName(part)]?.panel?.(part);
|
|
506
|
-
if (customPanel === null) return /* @__PURE__ */ jsx(ActivityItem, {
|
|
507
|
-
icon,
|
|
508
|
-
children: text
|
|
509
|
-
});
|
|
510
|
-
if (customPanel !== void 0) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
511
|
-
label: text,
|
|
512
|
-
icon,
|
|
513
|
-
children: customPanel
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
if (isReasoningUIPart(part) && part.text.trim()) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
517
|
-
label: text,
|
|
518
|
-
icon,
|
|
519
|
-
children: /* @__PURE__ */ jsx(ActivityText, { children: part.text })
|
|
520
|
-
});
|
|
521
|
-
if (isWorkspaceToolPart(part) && part.type !== "tool-delete") return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
522
|
-
label: text,
|
|
523
|
-
icon,
|
|
524
|
-
children: /* @__PURE__ */ jsx(ThinkChatWorkspacePanel, { part })
|
|
525
|
-
});
|
|
526
|
-
if (isSkillToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
527
|
-
label: text,
|
|
528
|
-
icon,
|
|
529
|
-
children: /* @__PURE__ */ jsx(ThinkChatSkillPanel, { part })
|
|
530
|
-
});
|
|
531
|
-
if (isFetchToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
532
|
-
label: text,
|
|
533
|
-
icon,
|
|
534
|
-
children: /* @__PURE__ */ jsx(ThinkChatFetchPanel, { part })
|
|
535
|
-
});
|
|
536
|
-
if (isBrowserToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
537
|
-
label: text,
|
|
538
|
-
icon,
|
|
539
|
-
children: /* @__PURE__ */ jsx(ThinkChatBrowserPanel, { part })
|
|
540
|
-
});
|
|
541
|
-
if (isExecuteToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
542
|
-
label: text,
|
|
543
|
-
icon,
|
|
544
|
-
children: /* @__PURE__ */ jsx(ThinkChatExecutePanel, { part })
|
|
545
|
-
});
|
|
546
|
-
return /* @__PURE__ */ jsx(ActivityItem, {
|
|
547
|
-
icon,
|
|
548
|
-
children: text
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
function renderDeniedPanel(label, reason) {
|
|
552
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
553
|
-
label,
|
|
554
|
-
text: reason,
|
|
555
|
-
status: "denied",
|
|
556
|
-
error: true
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
function numberLines(content, from = 1) {
|
|
560
|
-
const lines = content.split("\n");
|
|
561
|
-
const width = String(from + lines.length - 1).length;
|
|
562
|
-
return lines.map((line, i) => `${String(from + i).padStart(width)} ${line}`).join("\n");
|
|
563
|
-
}
|
|
564
|
-
function prefixLines(text, prefix) {
|
|
565
|
-
return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
|
|
566
|
-
}
|
|
567
|
-
/** Default panels for the Think workspace tools, Claude Code-style: read/write
|
|
568
|
-
show the file with line numbers, edit shows a mini diff, list/find/grep
|
|
569
|
-
show their result lists, bash gets the shell panel. Everything is typed by
|
|
570
|
-
the upstream tool contract. */
|
|
571
|
-
function ThinkChatWorkspacePanel({ part }) {
|
|
572
|
-
if (part.type === "tool-bash") return /* @__PURE__ */ jsx(ThinkChatShellPanel, { part });
|
|
573
|
-
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
574
|
-
label: getToolName(part),
|
|
575
|
-
text: part.errorText,
|
|
576
|
-
status: "error",
|
|
577
|
-
error: true
|
|
578
|
-
});
|
|
579
|
-
if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
|
|
580
|
-
switch (part.type) {
|
|
581
|
-
case "tool-read": {
|
|
582
|
-
const label = part.input?.path ?? "file";
|
|
583
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
584
|
-
if (output && "error" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
585
|
-
label,
|
|
586
|
-
text: output.error,
|
|
587
|
-
status: "error",
|
|
588
|
-
error: true
|
|
589
|
-
});
|
|
590
|
-
if (output && "content" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
591
|
-
label,
|
|
592
|
-
text: output.content,
|
|
593
|
-
status: `${output.totalLines} lines`
|
|
594
|
-
});
|
|
595
|
-
if (output) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
596
|
-
label,
|
|
597
|
-
text: `${output.name} · ${output.mediaType} · ${output.sizeBytes} bytes`
|
|
598
|
-
});
|
|
599
|
-
return /* @__PURE__ */ jsx(ActivityPanel, { label });
|
|
600
|
-
}
|
|
601
|
-
case "tool-write": {
|
|
602
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
603
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
604
|
-
label: part.input?.path ?? "file",
|
|
605
|
-
text: part.input?.content ? numberLines(part.input.content) : void 0,
|
|
606
|
-
status: output ? `${output.lines} lines written` : void 0
|
|
607
|
-
});
|
|
608
|
-
}
|
|
609
|
-
case "tool-edit": {
|
|
610
|
-
const input = part.input;
|
|
611
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
612
|
-
const status = !output ? void 0 : output.error ? output.error : output.created ? "created" : output.fuzzyMatch ? "replaced (fuzzy match)" : "replaced";
|
|
613
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
614
|
-
label: input?.path ?? "file",
|
|
615
|
-
status,
|
|
616
|
-
error: Boolean(output?.error),
|
|
617
|
-
children: input?.old_string !== void 0 && input.new_string !== void 0 ? /* @__PURE__ */ jsxs(ActivityOutput, { children: [
|
|
618
|
-
/* @__PURE__ */ jsx("span", {
|
|
619
|
-
className: "activity__diff-del",
|
|
620
|
-
children: prefixLines(input.old_string, "- ")
|
|
621
|
-
}),
|
|
622
|
-
"\n",
|
|
623
|
-
/* @__PURE__ */ jsx("span", {
|
|
624
|
-
className: "activity__diff-add",
|
|
625
|
-
children: prefixLines(input.new_string, "+ ")
|
|
626
|
-
})
|
|
627
|
-
] }) : null
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
case "tool-list": {
|
|
631
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
632
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
633
|
-
label: part.input?.path ?? "/",
|
|
634
|
-
text: output?.entries.join("\n"),
|
|
635
|
-
status: output ? `${output.count} entries` : void 0
|
|
636
|
-
});
|
|
637
|
-
}
|
|
638
|
-
case "tool-find": {
|
|
639
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
640
|
-
const files = Array.isArray(output?.files) ? output.files.map(String) : [];
|
|
641
|
-
const count = typeof output?.count === "number" ? output.count : files.length;
|
|
642
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
643
|
-
label: part.input?.pattern ?? "files",
|
|
644
|
-
text: files.join("\n"),
|
|
645
|
-
status: output ? `${count} files${output.truncated ? " (truncated)" : ""}` : void 0
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
case "tool-grep": {
|
|
649
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
650
|
-
const matches = Array.isArray(output?.matches) ? output.matches.map((m) => typeof m === "string" ? m : m.context ?? JSON.stringify(m)) : [];
|
|
651
|
-
const status = output && typeof output.totalMatches === "number" ? `${output.totalMatches} matches in ${String(output.filesWithMatches)} files` : void 0;
|
|
652
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
653
|
-
label: part.input?.query ?? "search",
|
|
654
|
-
text: matches.join("\n"),
|
|
655
|
-
status
|
|
656
|
-
});
|
|
657
|
-
}
|
|
658
|
-
default: return null;
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
/** Skill panels show what the model actually received: the activated skill's
|
|
662
|
-
body, the resource text, or the script result. */
|
|
663
|
-
function ThinkChatSkillPanel({ part }) {
|
|
664
|
-
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
665
|
-
label: getToolName(part),
|
|
666
|
-
text: part.errorText,
|
|
667
|
-
status: "error",
|
|
668
|
-
error: true
|
|
669
|
-
});
|
|
670
|
-
if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
|
|
671
|
-
switch (part.type) {
|
|
672
|
-
case "tool-activate_skill": {
|
|
673
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
674
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
675
|
-
label: part.input?.name ?? "skill",
|
|
676
|
-
text: output
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
case "tool-read_skill_resource": {
|
|
680
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
681
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
682
|
-
label: part.input?.path ?? "resource",
|
|
683
|
-
text: output
|
|
684
|
-
});
|
|
685
|
-
}
|
|
686
|
-
case "tool-run_skill_script": {
|
|
687
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
688
|
-
const text = output === void 0 ? void 0 : typeof output === "string" ? output : JSON.stringify(output, null, 2);
|
|
689
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
690
|
-
label: part.input?.path ?? "script",
|
|
691
|
-
text
|
|
692
|
-
});
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
function ThinkChatFetchPanel({ part }) {
|
|
697
|
-
const label = part.input?.url ?? part.input?.path ?? "fetch";
|
|
698
|
-
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
699
|
-
label,
|
|
700
|
-
text: part.errorText,
|
|
701
|
-
status: "error",
|
|
702
|
-
error: true
|
|
703
|
-
});
|
|
704
|
-
if (part.state === "output-denied") return renderDeniedPanel(label, part.approval.reason);
|
|
705
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
706
|
-
if (!output) return /* @__PURE__ */ jsx(ActivityPanel, { label });
|
|
707
|
-
if (!output.ok) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
708
|
-
label: output.finalUrl ?? label,
|
|
709
|
-
text: output.message,
|
|
710
|
-
status: output.code,
|
|
711
|
-
error: true
|
|
712
|
-
});
|
|
713
|
-
const body = output.body ?? (output.json !== void 0 ? JSON.stringify(output.json, null, 2) : output.path ? `saved to ${output.path}` : "");
|
|
714
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
715
|
-
label: output.finalUrl,
|
|
716
|
-
text: body || void 0,
|
|
717
|
-
status: `${output.status} · ${output.bytes} bytes${output.truncated ? " (truncated)" : ""}`
|
|
718
|
-
});
|
|
719
|
-
}
|
|
720
|
-
function findBrowserResultMedia(value, depth = 0) {
|
|
721
|
-
const media = {};
|
|
722
|
-
const visit = (node, level) => {
|
|
723
|
-
if (level > 4 || node === null) return;
|
|
724
|
-
if (typeof node === "string") {
|
|
725
|
-
if (!media.liveViewUrl && node.includes("live.browser.run")) media.liveViewUrl = node;
|
|
726
|
-
else if (!media.screenshot && node.startsWith("data:image/")) media.screenshot = node;
|
|
727
|
-
return;
|
|
728
|
-
}
|
|
729
|
-
if (Array.isArray(node)) {
|
|
730
|
-
for (const item of node) visit(item, level + 1);
|
|
731
|
-
return;
|
|
732
|
-
}
|
|
733
|
-
if (typeof node === "object") {
|
|
734
|
-
const record = node;
|
|
735
|
-
if (media.expiresInMs === void 0 && typeof record.url === "string" && typeof record.expiresInMs === "number") media.expiresInMs = record.expiresInMs;
|
|
736
|
-
for (const item of Object.values(record)) visit(item, level + 1);
|
|
737
|
-
}
|
|
738
|
-
};
|
|
739
|
-
visit(value, depth);
|
|
740
|
-
return media;
|
|
741
|
-
}
|
|
742
|
-
function ExecuteCodeView({ code, output, hideResult }) {
|
|
743
|
-
const sections = [
|
|
744
|
-
output && "logs" in output && output.logs?.length ? output.logs.join("\n") : "",
|
|
745
|
-
output?.status === "error" ? output.error : "",
|
|
746
|
-
output?.status === "paused" ? formatPendingActions(output.pending) : "",
|
|
747
|
-
output?.status === "completed" && output.result !== void 0 && !hideResult ? typeof output.result === "string" ? output.result : JSON.stringify(output.result, null, 2) : ""
|
|
748
|
-
].filter(Boolean);
|
|
749
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [code ? /* @__PURE__ */ jsx(CodeBlock, {
|
|
750
|
-
className: "activity__code-block",
|
|
751
|
-
code,
|
|
752
|
-
language: "ts"
|
|
753
|
-
}) : null, sections.length ? /* @__PURE__ */ jsx(ActivityOutput, { children: sections.join("\n\n") }) : null] });
|
|
754
|
-
}
|
|
755
|
-
function formatPendingActions(pending) {
|
|
756
|
-
return pending.map((action) => {
|
|
757
|
-
const args = action.args === void 0 ? "" : JSON.stringify(action.args);
|
|
758
|
-
return `awaiting approval: ${action.connector}.${action.method}(${args})`;
|
|
759
|
-
}).join("\n");
|
|
760
|
-
}
|
|
761
|
-
function executeStatusText(output) {
|
|
762
|
-
if (!output) return void 0;
|
|
763
|
-
return output.status === "paused" ? "paused · needs approval" : output.status;
|
|
764
|
-
}
|
|
765
|
-
/** The codemode execute tool as an activity panel: highlighted TypeScript
|
|
766
|
-
plus logs, result, and the paused pending-approval list. */
|
|
767
|
-
function ThinkChatExecutePanel({ part }) {
|
|
768
|
-
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
769
|
-
label: "Code",
|
|
770
|
-
text: part.errorText,
|
|
771
|
-
status: "error",
|
|
772
|
-
error: true
|
|
773
|
-
});
|
|
774
|
-
if (part.state === "output-denied") return renderDeniedPanel("Code", part.approval.reason);
|
|
775
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
776
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
777
|
-
label: "Code",
|
|
778
|
-
status: executeStatusText(output),
|
|
779
|
-
error: output?.status === "error",
|
|
780
|
-
children: /* @__PURE__ */ jsx(ExecuteCodeView, {
|
|
781
|
-
code: part.input?.code ?? "",
|
|
782
|
-
output
|
|
783
|
-
})
|
|
784
|
-
});
|
|
785
|
-
}
|
|
786
|
-
function ThinkChatBrowserPanel({ part }) {
|
|
787
|
-
const [seenAt] = React.useState(() => Date.now());
|
|
788
|
-
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
789
|
-
label: "Browser",
|
|
790
|
-
text: part.errorText,
|
|
791
|
-
status: "error",
|
|
792
|
-
error: true
|
|
793
|
-
});
|
|
794
|
-
if (part.state === "output-denied") return renderDeniedPanel("Browser", part.approval.reason);
|
|
795
|
-
if (part.type === "tool-browser_execute") {
|
|
796
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
797
|
-
const code = part.input?.code ?? "";
|
|
798
|
-
const media = output?.status === "completed" ? findBrowserResultMedia(output.result) : {};
|
|
799
|
-
const hasMedia = Boolean(media.liveViewUrl || media.screenshot);
|
|
800
|
-
const browserView = media.liveViewUrl ? /* @__PURE__ */ jsx(BrowserLiveView, {
|
|
801
|
-
url: media.liveViewUrl,
|
|
802
|
-
expiresAt: seenAt + (media.expiresInMs ?? 300 * 1e3),
|
|
803
|
-
fallback: media.screenshot ? /* @__PURE__ */ jsx("img", {
|
|
804
|
-
src: media.screenshot,
|
|
805
|
-
alt: "Last browser state"
|
|
806
|
-
}) : void 0
|
|
807
|
-
}) : media.screenshot ? /* @__PURE__ */ jsx("img", {
|
|
808
|
-
className: "activity__browser-screenshot",
|
|
809
|
-
src: media.screenshot,
|
|
810
|
-
alt: "Browser screenshot"
|
|
811
|
-
}) : null;
|
|
812
|
-
const codeView = /* @__PURE__ */ jsx(ExecuteCodeView, {
|
|
813
|
-
code,
|
|
814
|
-
output,
|
|
815
|
-
hideResult: hasMedia
|
|
816
|
-
});
|
|
817
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
818
|
-
label: "Browser",
|
|
819
|
-
status: executeStatusText(output),
|
|
820
|
-
error: output?.status === "error",
|
|
821
|
-
children: browserView && code ? /* @__PURE__ */ jsxs(Tabs, {
|
|
822
|
-
className: "activity__browser-tabs",
|
|
823
|
-
defaultValue: "browser",
|
|
824
|
-
children: [
|
|
825
|
-
/* @__PURE__ */ jsxs(TabsList, {
|
|
826
|
-
variant: "line",
|
|
827
|
-
children: [/* @__PURE__ */ jsx(Tab, {
|
|
828
|
-
value: "browser",
|
|
829
|
-
children: "Preview"
|
|
830
|
-
}), /* @__PURE__ */ jsx(Tab, {
|
|
831
|
-
value: "code",
|
|
832
|
-
children: "Code"
|
|
833
|
-
})]
|
|
834
|
-
}),
|
|
835
|
-
/* @__PURE__ */ jsx(TabPanel, {
|
|
836
|
-
value: "browser",
|
|
837
|
-
children: browserView
|
|
838
|
-
}),
|
|
839
|
-
/* @__PURE__ */ jsx(TabPanel, {
|
|
840
|
-
value: "code",
|
|
841
|
-
children: codeView
|
|
842
|
-
})
|
|
843
|
-
]
|
|
844
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [browserView, codeView] })
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
848
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
849
|
-
label: displayUrl(part.input?.url) || "page",
|
|
850
|
-
text: output === void 0 ? void 0 : typeof output === "string" ? output : Array.isArray(output) ? output.map((item) => typeof item === "string" ? item : JSON.stringify(item)).join("\n") : JSON.stringify(output, null, 2),
|
|
851
|
-
status: part.type === "tool-browser_links" && Array.isArray(output) ? `${output.length} links` : void 0
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
function ThinkChatShellPanel({ part }) {
|
|
855
|
-
const output = part.state === "output-available" ? part.output : void 0;
|
|
856
|
-
const errorText = part.state === "output-error" ? part.errorText : "";
|
|
857
|
-
const deniedReason = part.state === "output-denied" ? part.approval.reason ?? "denied" : "";
|
|
858
|
-
const failed = Boolean(errorText) || Boolean(deniedReason) || output !== void 0 && (output.exitCode !== 0 || Boolean(output.errors?.length));
|
|
859
|
-
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
860
|
-
label: "Shell",
|
|
861
|
-
text: formatBashPanelText(part.input, output, errorText || deniedReason),
|
|
862
|
-
status: output !== void 0 || errorText || deniedReason ? getBashStatusText(output, errorText, deniedReason) : void 0,
|
|
863
|
-
error: failed
|
|
864
|
-
});
|
|
865
|
-
}
|
|
866
|
-
function ThinkChatActivityGroup({ message, parts, isTrailing, toolRenderers }) {
|
|
867
|
-
const chat = React.useContext(ThinkChatContext);
|
|
868
|
-
const chatStillWorking = isTrailing && Boolean(chat?.isStreaming) && chat?.messages.at(-1)?.id === message.id;
|
|
869
|
-
return /* @__PURE__ */ jsx(ThinkChatActivity, {
|
|
870
|
-
parts,
|
|
871
|
-
isStreaming: parts.some(isPartWorking) || chatStillWorking,
|
|
872
|
-
toolRenderers
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Builds a renderPart handler that folds every run of consecutive activity
|
|
877
|
-
* parts (reasoning + tool calls) into one collapsed Activity box. Everything
|
|
878
|
-
* is derived from the UIMessage stream: part order, tool states, reasoning
|
|
879
|
-
* state. Excluded tools split the run and return undefined so your own
|
|
880
|
-
* renderPart (or AgentChat's generic activity row) takes over for them.
|
|
881
|
-
*/
|
|
882
|
-
function createThinkChatActivityRenderer({ excludeTools, toolRenderers } = {}) {
|
|
883
|
-
const excluded = excludeTools?.length ? new Set(excludeTools) : void 0;
|
|
884
|
-
const isActivity = (part) => isThinkChatActivityPart(part, excluded);
|
|
885
|
-
return (context) => {
|
|
886
|
-
const { message, part, partIndex } = context;
|
|
887
|
-
if (!isActivity(part)) return void 0;
|
|
888
|
-
let start = partIndex;
|
|
889
|
-
while (start > 0 && isActivity(message.parts[start - 1])) start--;
|
|
890
|
-
if (partIndex !== start) return null;
|
|
891
|
-
let end = partIndex;
|
|
892
|
-
while (end + 1 < message.parts.length && isActivity(message.parts[end + 1])) end++;
|
|
893
|
-
return /* @__PURE__ */ jsx(ThinkChatActivityGroup, {
|
|
894
|
-
message,
|
|
895
|
-
parts: message.parts.slice(start, end + 1),
|
|
896
|
-
isTrailing: end === message.parts.length - 1,
|
|
897
|
-
toolRenderers
|
|
898
|
-
});
|
|
899
|
-
};
|
|
900
|
-
}
|
|
901
|
-
/** Default activity renderer — `createThinkChatActivityRenderer()` with no overrides. */
|
|
902
|
-
const renderThinkChatActivityPart = createThinkChatActivityRenderer();
|
|
903
135
|
/**
|
|
904
136
|
* The ambient chat value from `ThinkChat.Root`/`Provider` — for custom tool
|
|
905
137
|
* panels, cards, or composer actions that need `messages`, `status`, or
|
|
@@ -922,34 +154,5 @@ const ThinkChat = {
|
|
|
922
154
|
renderActivityPart: renderThinkChatActivityPart,
|
|
923
155
|
createActivityRenderer: createThinkChatActivityRenderer
|
|
924
156
|
};
|
|
925
|
-
function formatBashPanelText(input, output, errorText) {
|
|
926
|
-
return [
|
|
927
|
-
input?.script?.trim() ? `$ bash\n${input.script}` : "",
|
|
928
|
-
output?.stdout,
|
|
929
|
-
output?.stderr,
|
|
930
|
-
errorText,
|
|
931
|
-
output?.skippedFiles?.length ? ["skipped files", ...output.skippedFiles].join("\n") : "",
|
|
932
|
-
output?.errors?.length ? ["sync errors", ...output.errors].join("\n") : "",
|
|
933
|
-
output ? formatChangedFiles(output.changedFiles) : ""
|
|
934
|
-
].filter(Boolean).join("\n\n");
|
|
935
|
-
}
|
|
936
|
-
function formatChangedFiles(changedFiles) {
|
|
937
|
-
if (!changedFiles) return "";
|
|
938
|
-
const lines = [
|
|
939
|
-
...changedFiles.created.map((path) => `created ${path}`),
|
|
940
|
-
...changedFiles.updated.map((path) => `updated ${path}`),
|
|
941
|
-
...changedFiles.deleted.map((path) => `deleted ${path}`),
|
|
942
|
-
...changedFiles.directoriesCreated.map((path) => `created directory ${path}`),
|
|
943
|
-
...changedFiles.directoriesDeleted.map((path) => `deleted directory ${path}`)
|
|
944
|
-
];
|
|
945
|
-
return lines.length ? ["workspace changes", ...lines].join("\n") : "";
|
|
946
|
-
}
|
|
947
|
-
function getBashStatusText(output, errorText, deniedReason) {
|
|
948
|
-
if (errorText) return "error";
|
|
949
|
-
if (deniedReason) return "denied";
|
|
950
|
-
if (output?.errors?.length) return "sync errors";
|
|
951
|
-
if (output && output.exitCode !== 0) return `exit ${output.exitCode}`;
|
|
952
|
-
return "success";
|
|
953
|
-
}
|
|
954
157
|
//#endregion
|
|
955
158
|
export { BrowserLiveView, BrowserReplay, ThinkChat, ThinkChatActivity, ThinkChatMessages, ThinkChatProvider, ThinkChatRoot, WorkspaceExplorer, createThinkChatActivityRenderer, describeThinkChatActivityPart, inferWorkspaceLanguage, isThinkChatActivityPart, renderThinkChatActivityPart, useThinkChat, useWorkspace };
|