@mcp-b/react-components 0.2.1 → 0.3.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/{CodeBlock-BrCoypnc.js → CodeBlock-C2Xjxu9M.js} +1 -1
- package/dist/FileTree-D0c9Yj9U.js +310 -0
- package/dist/PromptInput-Dzn-fOPc.d.ts +444 -0
- package/dist/components/Activity.js +1 -1
- package/dist/components/AgentChat.d.ts +19 -9
- package/dist/components/AgentChat.js +27 -24
- package/dist/components/AgentConnection.js +2 -2
- package/dist/components/Artifact.js +1 -1
- package/dist/components/BrowserLiveView.d.ts +47 -0
- package/dist/components/BrowserLiveView.js +135 -0
- package/dist/components/BrowserReplay.d.ts +40 -0
- package/dist/components/BrowserReplay.js +187 -0
- package/dist/components/ChainOfThought.d.ts +1 -1
- package/dist/components/ChainOfThought.js +1 -1
- package/dist/components/CodeBlock.js +1 -1
- package/dist/components/Commit.js +1 -1
- package/dist/components/ContextMenu.js +1 -1
- package/dist/components/Conversation.js +1 -1
- package/dist/components/Dialog.js +1 -1
- package/dist/components/EnvironmentVariables.js +1 -1
- package/dist/components/FileTree.js +1 -309
- package/dist/components/Menubar.js +1 -1
- package/dist/components/Message.d.ts +2 -1
- package/dist/components/Message.js +1 -1
- package/dist/components/ModelSelector.js +1 -1
- package/dist/components/NavigationMenu.js +1 -1
- package/dist/components/PricingPage.js +1 -1
- package/dist/components/PromptInput.d.ts +2 -399
- package/dist/components/PromptInput.js +177 -2
- package/dist/components/Queue.js +1 -1
- package/dist/components/Reasoning.js +1 -1
- package/dist/components/Select.js +1 -1
- package/dist/components/Sources.js +1 -1
- package/dist/components/Stepper.js +1 -1
- package/dist/components/Task.js +1 -1
- package/dist/components/Terminal.js +2 -2
- package/dist/components/TestResults.js +1 -1
- package/dist/components/ThinkChat.d.ts +186 -0
- package/dist/components/ThinkChat.js +730 -0
- package/dist/components/Tool.d.ts +32 -28
- package/dist/components/Tool.js +29 -16
- package/dist/components/WebPreview.js +1 -1
- package/dist/components/WorkspaceExplorer.d.ts +102 -0
- package/dist/components/WorkspaceExplorer.js +352 -0
- package/dist/components/icons.d.ts +2 -2
- package/dist/components/icons.js +2 -2
- package/dist/{copy-button-DAPlB8Z0.js → copy-button-Dd6DEhOm.js} +1 -1
- package/dist/{icons-Bf7JMKk-.js → icons-DQC4Zw5R.js} +5 -1
- package/dist/{icons-BklFyZbx.d.ts → icons-ctdVxng0.d.ts} +2 -1
- package/dist/styles/activity.css +6 -5
- package/dist/styles/conversation.css +3 -0
- package/dist/styles/prompt-input.css +149 -0
- package/package.json +26 -3
|
@@ -0,0 +1,730 @@
|
|
|
1
|
+
import { Activity, ActivityContent, ActivityItem, ActivityItemGroup, ActivityOutput, ActivityPanel, ActivityText, ActivityTrigger } from "./Activity.js";
|
|
2
|
+
import { Tab, TabPanel, Tabs, TabsList } from "./Tabs.js";
|
|
3
|
+
import { t as CodeBlock } from "../CodeBlock-C2Xjxu9M.js";
|
|
4
|
+
import { AgentChat } from "./AgentChat.js";
|
|
5
|
+
import { BrowserLiveView } from "./BrowserLiveView.js";
|
|
6
|
+
import { BrowserReplay } from "./BrowserReplay.js";
|
|
7
|
+
import { WorkspaceExplorer, inferWorkspaceLanguage, useWorkspace } from "./WorkspaceExplorer.js";
|
|
8
|
+
import * as React from "react";
|
|
9
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
import { getToolName, isReasoningUIPart, isToolUIPart } from "ai";
|
|
11
|
+
import { getToolPartState, useAgentChat } from "@cloudflare/think/react";
|
|
12
|
+
import { useAgent } from "agents/react";
|
|
13
|
+
//#region src/components/ThinkChat.tsx
|
|
14
|
+
/**
|
|
15
|
+
* Think-specific chat rendering: interprets a Think agent's UIMessage stream
|
|
16
|
+
* (reasoning + workspace/skill/fetch/browser tool parts) into Activity blocks.
|
|
17
|
+
*
|
|
18
|
+
* Every shape here mirrors an upstream contract — see docs/think-chat.md
|
|
19
|
+
* ("Upstream source map" and the re-alignment checklist) for where each comes from,
|
|
20
|
+
* which are compile-time typed vs. hand-rolled mirrors of untyped upstream
|
|
21
|
+
* ToolSets, and how to re-align when @cloudflare/think (experimental) changes.
|
|
22
|
+
*/
|
|
23
|
+
const WORKSPACE_TOOL_NAMES = new Set([
|
|
24
|
+
"read",
|
|
25
|
+
"write",
|
|
26
|
+
"edit",
|
|
27
|
+
"list",
|
|
28
|
+
"find",
|
|
29
|
+
"grep",
|
|
30
|
+
"delete",
|
|
31
|
+
"bash"
|
|
32
|
+
]);
|
|
33
|
+
function isWorkspaceToolPart(part) {
|
|
34
|
+
return isToolUIPart(part) && WORKSPACE_TOOL_NAMES.has(getToolName(part));
|
|
35
|
+
}
|
|
36
|
+
const SKILL_TOOL_NAMES = new Set([
|
|
37
|
+
"activate_skill",
|
|
38
|
+
"read_skill_resource",
|
|
39
|
+
"run_skill_script"
|
|
40
|
+
]);
|
|
41
|
+
function isSkillToolPart(part) {
|
|
42
|
+
return isToolUIPart(part) && SKILL_TOOL_NAMES.has(getToolName(part));
|
|
43
|
+
}
|
|
44
|
+
function isFetchToolPart(part) {
|
|
45
|
+
return isToolUIPart(part) && getToolName(part).startsWith("fetch_");
|
|
46
|
+
}
|
|
47
|
+
const BROWSER_TOOL_NAMES = new Set([
|
|
48
|
+
"browser_execute",
|
|
49
|
+
"browser_markdown",
|
|
50
|
+
"browser_extract",
|
|
51
|
+
"browser_links",
|
|
52
|
+
"browser_scrape",
|
|
53
|
+
"browser_content"
|
|
54
|
+
]);
|
|
55
|
+
function isBrowserToolPart(part) {
|
|
56
|
+
return isToolUIPart(part) && BROWSER_TOOL_NAMES.has(getToolName(part));
|
|
57
|
+
}
|
|
58
|
+
const ThinkChatContext = React.createContext(null);
|
|
59
|
+
function useThinkChatContext(component) {
|
|
60
|
+
const context = React.useContext(ThinkChatContext);
|
|
61
|
+
if (!context) throw new Error(`${component} must be used inside <ThinkChat.Provider> or <ThinkChat.Root>.`);
|
|
62
|
+
return context;
|
|
63
|
+
}
|
|
64
|
+
function toAgentChatStatus(chat) {
|
|
65
|
+
if (chat.status === "submitted") return "submitted";
|
|
66
|
+
if (chat.isStreaming || chat.isRecovering) return "streaming";
|
|
67
|
+
return chat.status ?? "ready";
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Chat UI for Cloudflare Think agents. Provide a `useAgentChat` value (or let
|
|
71
|
+
* `ThinkChat.Root` create one) and `ThinkChat.Messages` renders the message
|
|
72
|
+
* stream with agent work folded into collapsed Activity blocks.
|
|
73
|
+
*/
|
|
74
|
+
function ThinkChatProvider({ chat, children }) {
|
|
75
|
+
return /* @__PURE__ */ jsx(ThinkChatContext.Provider, {
|
|
76
|
+
value: chat,
|
|
77
|
+
children
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Batteries-included root: connects to the named Think agent (upstream's
|
|
82
|
+
* `useAgent` + `useAgentChat`) and provides the live chat to everything
|
|
83
|
+
* inside. If the app already owns the chat hook, use `ThinkChat.Provider`.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```tsx
|
|
87
|
+
* <ThinkChat.Root agent="my-agent" name="session-1">
|
|
88
|
+
* <ThinkChat.Messages />
|
|
89
|
+
* </ThinkChat.Root>
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
function ThinkChatRoot({ agent: agentOption, name, children }) {
|
|
93
|
+
return /* @__PURE__ */ jsx(ThinkChatProvider, {
|
|
94
|
+
chat: useAgentChat({ agent: useAgent(typeof agentOption === "string" ? {
|
|
95
|
+
agent: agentOption,
|
|
96
|
+
name
|
|
97
|
+
} : {
|
|
98
|
+
...agentOption,
|
|
99
|
+
name: name ?? agentOption.name
|
|
100
|
+
}) }),
|
|
101
|
+
children
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The full conversation surface — message list plus composer — with the
|
|
106
|
+
* activity renderer as the default `renderPart`: runs of reasoning/tool parts
|
|
107
|
+
* fold into one collapsed Activity block with derived labels and per-tool
|
|
108
|
+
* panels. Extend for app-specific tools with `toolRenderers` (label/panel
|
|
109
|
+
* inside the log) or `excludeTools` + your own `renderPart` (standalone
|
|
110
|
+
* card) — see README "App-specific tools" and the Custom Tool Panel story.
|
|
111
|
+
*/
|
|
112
|
+
function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, ...props }) {
|
|
113
|
+
const chat = useThinkChatContext("ThinkChat.Messages");
|
|
114
|
+
const status = toAgentChatStatus(chat);
|
|
115
|
+
const defaultRenderPart = React.useMemo(() => createThinkChatActivityRenderer({
|
|
116
|
+
excludeTools,
|
|
117
|
+
toolRenderers
|
|
118
|
+
}), [excludeTools, toolRenderers]);
|
|
119
|
+
return /* @__PURE__ */ jsx(AgentChat, {
|
|
120
|
+
...props,
|
|
121
|
+
renderPart: renderPart ?? defaultRenderPart,
|
|
122
|
+
messages: chat.messages,
|
|
123
|
+
status,
|
|
124
|
+
onStop: chat.stop,
|
|
125
|
+
onApprovalResponse: chat.addToolApprovalResponse,
|
|
126
|
+
onSubmit: ({ text }) => {
|
|
127
|
+
const trimmed = text.trim();
|
|
128
|
+
if (trimmed) chat.sendMessage({ text: trimmed });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/** Agent work (reasoning, tool calls, step boundaries) vs. content shown to the user. */
|
|
133
|
+
function isThinkChatActivityPart(part, excludeTools) {
|
|
134
|
+
if (part === void 0) return false;
|
|
135
|
+
if (isToolUIPart(part) && excludeTools?.has(getToolName(part))) return false;
|
|
136
|
+
return part.type === "step-start" || isReasoningUIPart(part) || isToolUIPart(part);
|
|
137
|
+
}
|
|
138
|
+
/** In-flight: the stream opened this part but hasn't delivered its result yet. */
|
|
139
|
+
function isPartWorking(part) {
|
|
140
|
+
if (isReasoningUIPart(part)) return part.state === "streaming";
|
|
141
|
+
if (isToolUIPart(part)) {
|
|
142
|
+
const state = getToolPartState(part);
|
|
143
|
+
return state === "loading" || state === "streaming" || state === "waiting-approval";
|
|
144
|
+
}
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
/** Errored or denied — upstream's simplified tool-part state vocabulary. */
|
|
148
|
+
function isPartFailed(part) {
|
|
149
|
+
const state = getToolPartState(part);
|
|
150
|
+
return state === "error" || state === "denied";
|
|
151
|
+
}
|
|
152
|
+
function summarizeToolInput(input) {
|
|
153
|
+
if (!input || typeof input !== "object") return null;
|
|
154
|
+
for (const value of Object.values(input)) if (typeof value === "string" && value.trim()) {
|
|
155
|
+
const line = value.trim().split("\n", 1)[0] ?? "";
|
|
156
|
+
return line.length > 64 ? `${line.slice(0, 64)}…` : line;
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* One-line natural-language label for an activity part ("Ran vp test",
|
|
162
|
+
* "Fetched developers.cloudflare.com/agents/"); `null` for boundaries that
|
|
163
|
+
* aren't steps (step-start). Exported so a custom `renderPart` can reuse the
|
|
164
|
+
* built-in labels for tools it doesn't override.
|
|
165
|
+
*/
|
|
166
|
+
function describeThinkChatActivityPart(part, toolRenderers) {
|
|
167
|
+
if (isToolUIPart(part)) {
|
|
168
|
+
const custom = toolRenderers?.[getToolName(part)]?.describe?.(part);
|
|
169
|
+
if (custom !== void 0) return custom;
|
|
170
|
+
}
|
|
171
|
+
if (isReasoningUIPart(part)) {
|
|
172
|
+
const snippet = summarizeToolInput({ text: part.text });
|
|
173
|
+
const label = part.state === "streaming" ? "Thinking…" : "Thought";
|
|
174
|
+
return snippet ? `${label} · ${snippet}` : label;
|
|
175
|
+
}
|
|
176
|
+
if (isWorkspaceToolPart(part)) return describeWorkspaceToolPart(part);
|
|
177
|
+
if (isSkillToolPart(part)) return describeSkillToolPart(part);
|
|
178
|
+
if (isFetchToolPart(part)) return describeFetchToolPart(part);
|
|
179
|
+
if (isBrowserToolPart(part)) return describeBrowserToolPart(part);
|
|
180
|
+
if (isToolUIPart(part)) {
|
|
181
|
+
const name = getToolName(part);
|
|
182
|
+
const verb = isPartFailed(part) ? `${name} failed` : isPartWorking(part) ? `Running ${name}` : `Ran ${name}`;
|
|
183
|
+
const detail = summarizeToolInput(part.input);
|
|
184
|
+
return detail ? `${verb} · ${detail}` : verb;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
function basename(path) {
|
|
189
|
+
return path?.split("/").filter(Boolean).at(-1) ?? null;
|
|
190
|
+
}
|
|
191
|
+
function describeWorkspaceToolPart(part) {
|
|
192
|
+
const working = isPartWorking(part);
|
|
193
|
+
const failed = isPartFailed(part);
|
|
194
|
+
if (part.type === "tool-bash") {
|
|
195
|
+
const command = part.input?.script?.trim() ?? "";
|
|
196
|
+
if (failed) return command ? `Command failed · ${command}` : "Command failed";
|
|
197
|
+
const verb = working ? "Running" : "Ran";
|
|
198
|
+
return command ? `${verb} ${command}` : `${verb} a command`;
|
|
199
|
+
}
|
|
200
|
+
const label = (() => {
|
|
201
|
+
switch (part.type) {
|
|
202
|
+
case "tool-read": return `${working ? "Reading" : "Read"} ${basename(part.input?.path) ?? "a file"}`;
|
|
203
|
+
case "tool-write": return `${working ? "Writing" : "Wrote"} ${basename(part.input?.path) ?? "a file"}`;
|
|
204
|
+
case "tool-edit": return `${working ? "Editing" : "Edited"} ${basename(part.input?.path) ?? "a file"}`;
|
|
205
|
+
case "tool-delete": return `${working ? "Deleting" : "Deleted"} ${part.input?.path || "a file"}`;
|
|
206
|
+
case "tool-list": {
|
|
207
|
+
const path = part.input?.path;
|
|
208
|
+
return `${working ? "Listing" : "Listed"} ${path && path !== "/" ? path : "files"}`;
|
|
209
|
+
}
|
|
210
|
+
case "tool-find": return `${working ? "Searching" : "Searched"} for ${part.input?.pattern || "files"}`;
|
|
211
|
+
case "tool-grep": return `${working ? "Searching" : "Searched"} for ${part.input?.query || "text"}`;
|
|
212
|
+
}
|
|
213
|
+
})();
|
|
214
|
+
return failed ? `${label} · failed` : label;
|
|
215
|
+
}
|
|
216
|
+
function describeSkillToolPart(part) {
|
|
217
|
+
const working = isPartWorking(part);
|
|
218
|
+
const failed = isPartFailed(part);
|
|
219
|
+
const label = (() => {
|
|
220
|
+
switch (part.type) {
|
|
221
|
+
case "tool-activate_skill": {
|
|
222
|
+
const name = part.input?.name;
|
|
223
|
+
return `${working ? "Using" : "Used"} ${name ? `the ${name} skill` : "a skill"}`;
|
|
224
|
+
}
|
|
225
|
+
case "tool-read_skill_resource": return `${working ? "Reading" : "Read"} ${part.input?.path ?? "a skill resource"}`;
|
|
226
|
+
case "tool-run_skill_script": return `${working ? "Running" : "Ran"} ${part.input?.path ?? "a skill script"}`;
|
|
227
|
+
}
|
|
228
|
+
})();
|
|
229
|
+
return failed ? `${label} · failed` : label;
|
|
230
|
+
}
|
|
231
|
+
function displayUrl(url) {
|
|
232
|
+
return url ? url.replace(/^https?:\/\//, "") : "";
|
|
233
|
+
}
|
|
234
|
+
function describeBrowserToolPart(part) {
|
|
235
|
+
const working = isPartWorking(part);
|
|
236
|
+
const failed = isPartFailed(part);
|
|
237
|
+
if (part.type === "tool-browser_execute") {
|
|
238
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
239
|
+
if (failed || output?.status === "error") return "Browser automation failed";
|
|
240
|
+
if (output?.status === "paused") return "Browser waiting for approval";
|
|
241
|
+
return working ? "Using the browser" : "Used the browser";
|
|
242
|
+
}
|
|
243
|
+
const page = displayUrl(part.input?.url) || "a page";
|
|
244
|
+
const label = (() => {
|
|
245
|
+
switch (part.type) {
|
|
246
|
+
case "tool-browser_extract": return `${working ? "Extracting" : "Extracted"} data from ${page}`;
|
|
247
|
+
case "tool-browser_links": return `${working ? "Listing" : "Listed"} links on ${page}`;
|
|
248
|
+
case "tool-browser_scrape": return `${working ? "Scraping" : "Scraped"} ${page}`;
|
|
249
|
+
default: return `${working ? "Browsing" : "Browsed"} ${page}`;
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
return failed ? `${label} · failed` : label;
|
|
253
|
+
}
|
|
254
|
+
function describeFetchToolPart(part) {
|
|
255
|
+
const target = part.input?.url ?? part.input?.path ?? "";
|
|
256
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
257
|
+
if (isPartFailed(part) || output?.ok === false) return target ? `Fetch failed · ${target}` : "Fetch failed";
|
|
258
|
+
const verb = isPartWorking(part) ? "Fetching" : "Fetched";
|
|
259
|
+
return target ? `${verb} ${target}` : `${verb} a URL`;
|
|
260
|
+
}
|
|
261
|
+
function summarizeActivitySteps(parts) {
|
|
262
|
+
const byTool = /* @__PURE__ */ new Map();
|
|
263
|
+
const otherTools = /* @__PURE__ */ new Set();
|
|
264
|
+
const skillNames = /* @__PURE__ */ new Set();
|
|
265
|
+
let thoughts = 0;
|
|
266
|
+
let skillParts = 0;
|
|
267
|
+
let fetches = 0;
|
|
268
|
+
let browses = 0;
|
|
269
|
+
for (const part of parts) if (isReasoningUIPart(part)) thoughts++;
|
|
270
|
+
else if (isWorkspaceToolPart(part)) {
|
|
271
|
+
const name = getToolName(part);
|
|
272
|
+
byTool.set(name, (byTool.get(name) ?? 0) + 1);
|
|
273
|
+
} else if (isSkillToolPart(part)) {
|
|
274
|
+
skillParts++;
|
|
275
|
+
if (typeof part.input?.name === "string") skillNames.add(part.input.name);
|
|
276
|
+
} else if (isFetchToolPart(part)) fetches++;
|
|
277
|
+
else if (isBrowserToolPart(part)) browses++;
|
|
278
|
+
else if (isToolUIPart(part)) otherTools.add(getToolName(part));
|
|
279
|
+
const count = (name) => byTool.get(name) ?? 0;
|
|
280
|
+
const files = (n) => n === 1 ? "a file" : `${n} files`;
|
|
281
|
+
const reads = count("read");
|
|
282
|
+
const edits = count("edit") + count("write");
|
|
283
|
+
const deletes = count("delete");
|
|
284
|
+
const commands = count("bash");
|
|
285
|
+
const segments = [];
|
|
286
|
+
if (thoughts > 0) segments.push("thought");
|
|
287
|
+
if (skillParts > 0) segments.push(skillNames.size === 1 ? `used the ${[...skillNames][0]} skill` : skillNames.size > 1 ? `used ${skillNames.size} skills` : "used a skill");
|
|
288
|
+
if (reads > 0) segments.push(`read ${files(reads)}`);
|
|
289
|
+
if (edits > 0) segments.push(`edited ${files(edits)}`);
|
|
290
|
+
if (deletes > 0) segments.push(`deleted ${files(deletes)}`);
|
|
291
|
+
if (count("find") + count("grep") > 0) segments.push("searched code");
|
|
292
|
+
if (count("list") > 0) segments.push("listed files");
|
|
293
|
+
if (fetches > 0) segments.push(fetches === 1 ? "fetched a URL" : `fetched ${fetches} URLs`);
|
|
294
|
+
if (browses > 0) segments.push("used the browser");
|
|
295
|
+
if (commands > 0) segments.push(commands === 1 ? "ran a command" : `ran ${commands} commands`);
|
|
296
|
+
for (const tool of otherTools) segments.push(`used ${tool}`);
|
|
297
|
+
if (segments.length === 0) return "Worked";
|
|
298
|
+
const top = segments.slice(0, 3);
|
|
299
|
+
const joined = top.length === 1 ? top[0] : `${top.slice(0, -1).join(", ")} and ${top.at(-1)}`;
|
|
300
|
+
return joined.charAt(0).toUpperCase() + joined.slice(1);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* One run of agent work as a collapsed Activity block: while streaming the
|
|
304
|
+
* label shimmers with the live step; settled, it becomes a short summary
|
|
305
|
+
* ("Thought, read 2 files and ran a command") over an expandable step log
|
|
306
|
+
* with per-tool panels. Normally reached through `ThinkChat.Messages`;
|
|
307
|
+
* exported for apps that lay out messages themselves.
|
|
308
|
+
*/
|
|
309
|
+
function ThinkChatActivity({ parts, defaultOpen, isStreaming, toolRenderers }) {
|
|
310
|
+
const steps = parts.flatMap((part, index) => {
|
|
311
|
+
const text = describeThinkChatActivityPart(part, toolRenderers);
|
|
312
|
+
if (!text) return [];
|
|
313
|
+
return [{
|
|
314
|
+
key: isToolUIPart(part) ? part.toolCallId : `${part.type}-${index}`,
|
|
315
|
+
text,
|
|
316
|
+
part
|
|
317
|
+
}];
|
|
318
|
+
});
|
|
319
|
+
if (steps.length === 0) return null;
|
|
320
|
+
const streaming = isStreaming ?? parts.some(isPartWorking);
|
|
321
|
+
const settledLabel = summarizeActivitySteps(parts);
|
|
322
|
+
const latest = steps.at(-1);
|
|
323
|
+
return /* @__PURE__ */ jsxs(Activity, {
|
|
324
|
+
isStreaming: streaming,
|
|
325
|
+
defaultOpen,
|
|
326
|
+
children: [/* @__PURE__ */ jsx(ActivityTrigger, { label: streaming && latest ? latest.text : settledLabel }), /* @__PURE__ */ jsx(ActivityContent, { children: steps.map((step) => /* @__PURE__ */ jsx(ThinkChatActivityStep, {
|
|
327
|
+
text: step.text,
|
|
328
|
+
part: step.part,
|
|
329
|
+
toolRenderers
|
|
330
|
+
}, step.key)) })]
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
function ThinkChatActivityStep({ text, part, toolRenderers }) {
|
|
334
|
+
if (isToolUIPart(part)) {
|
|
335
|
+
const customPanel = toolRenderers?.[getToolName(part)]?.panel?.(part);
|
|
336
|
+
if (customPanel === null) return /* @__PURE__ */ jsx(ActivityItem, { children: text });
|
|
337
|
+
if (customPanel !== void 0) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
338
|
+
label: text,
|
|
339
|
+
children: customPanel
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
if (isReasoningUIPart(part) && part.text.trim()) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
343
|
+
label: text,
|
|
344
|
+
children: /* @__PURE__ */ jsx(ActivityText, { children: part.text })
|
|
345
|
+
});
|
|
346
|
+
if (isWorkspaceToolPart(part) && part.type !== "tool-delete") return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
347
|
+
label: text,
|
|
348
|
+
children: /* @__PURE__ */ jsx(ThinkChatWorkspacePanel, { part })
|
|
349
|
+
});
|
|
350
|
+
if (isSkillToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
351
|
+
label: text,
|
|
352
|
+
children: /* @__PURE__ */ jsx(ThinkChatSkillPanel, { part })
|
|
353
|
+
});
|
|
354
|
+
if (isFetchToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
355
|
+
label: text,
|
|
356
|
+
children: /* @__PURE__ */ jsx(ThinkChatFetchPanel, { part })
|
|
357
|
+
});
|
|
358
|
+
if (isBrowserToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
359
|
+
label: text,
|
|
360
|
+
children: /* @__PURE__ */ jsx(ThinkChatBrowserPanel, { part })
|
|
361
|
+
});
|
|
362
|
+
return /* @__PURE__ */ jsx(ActivityItem, { children: text });
|
|
363
|
+
}
|
|
364
|
+
function renderDeniedPanel(label, reason) {
|
|
365
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
366
|
+
label,
|
|
367
|
+
text: reason,
|
|
368
|
+
status: "denied",
|
|
369
|
+
error: true
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
function numberLines(content, from = 1) {
|
|
373
|
+
const lines = content.split("\n");
|
|
374
|
+
const width = String(from + lines.length - 1).length;
|
|
375
|
+
return lines.map((line, i) => `${String(from + i).padStart(width)} ${line}`).join("\n");
|
|
376
|
+
}
|
|
377
|
+
function prefixLines(text, prefix) {
|
|
378
|
+
return text.split("\n").map((line) => `${prefix}${line}`).join("\n");
|
|
379
|
+
}
|
|
380
|
+
/** Default panels for the Think workspace tools, Claude Code-style: read/write
|
|
381
|
+
show the file with line numbers, edit shows a mini diff, list/find/grep
|
|
382
|
+
show their result lists, bash gets the shell panel. Everything is typed by
|
|
383
|
+
the upstream tool contract. */
|
|
384
|
+
function ThinkChatWorkspacePanel({ part }) {
|
|
385
|
+
if (part.type === "tool-bash") return /* @__PURE__ */ jsx(ThinkChatShellPanel, { part });
|
|
386
|
+
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
387
|
+
label: getToolName(part),
|
|
388
|
+
text: part.errorText,
|
|
389
|
+
status: "error",
|
|
390
|
+
error: true
|
|
391
|
+
});
|
|
392
|
+
if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
|
|
393
|
+
switch (part.type) {
|
|
394
|
+
case "tool-read": {
|
|
395
|
+
const label = part.input?.path ?? "file";
|
|
396
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
397
|
+
if (output && "error" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
398
|
+
label,
|
|
399
|
+
text: output.error,
|
|
400
|
+
status: "error",
|
|
401
|
+
error: true
|
|
402
|
+
});
|
|
403
|
+
if (output && "content" in output) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
404
|
+
label,
|
|
405
|
+
text: output.content,
|
|
406
|
+
status: `${output.totalLines} lines`
|
|
407
|
+
});
|
|
408
|
+
if (output) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
409
|
+
label,
|
|
410
|
+
text: `${output.name} · ${output.mediaType} · ${output.sizeBytes} bytes`
|
|
411
|
+
});
|
|
412
|
+
return /* @__PURE__ */ jsx(ActivityPanel, { label });
|
|
413
|
+
}
|
|
414
|
+
case "tool-write": {
|
|
415
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
416
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
417
|
+
label: part.input?.path ?? "file",
|
|
418
|
+
text: part.input?.content ? numberLines(part.input.content) : void 0,
|
|
419
|
+
status: output ? `${output.lines} lines written` : void 0
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
case "tool-edit": {
|
|
423
|
+
const input = part.input;
|
|
424
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
425
|
+
const status = !output ? void 0 : output.error ? output.error : output.created ? "created" : output.fuzzyMatch ? "replaced (fuzzy match)" : "replaced";
|
|
426
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
427
|
+
label: input?.path ?? "file",
|
|
428
|
+
status,
|
|
429
|
+
error: Boolean(output?.error),
|
|
430
|
+
children: input?.old_string !== void 0 && input.new_string !== void 0 ? /* @__PURE__ */ jsxs(ActivityOutput, { children: [
|
|
431
|
+
/* @__PURE__ */ jsx("span", {
|
|
432
|
+
className: "activity__diff-del",
|
|
433
|
+
children: prefixLines(input.old_string, "- ")
|
|
434
|
+
}),
|
|
435
|
+
"\n",
|
|
436
|
+
/* @__PURE__ */ jsx("span", {
|
|
437
|
+
className: "activity__diff-add",
|
|
438
|
+
children: prefixLines(input.new_string, "+ ")
|
|
439
|
+
})
|
|
440
|
+
] }) : null
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
case "tool-list": {
|
|
444
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
445
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
446
|
+
label: part.input?.path ?? "/",
|
|
447
|
+
text: output?.entries.join("\n"),
|
|
448
|
+
status: output ? `${output.count} entries` : void 0
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
case "tool-find": {
|
|
452
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
453
|
+
const files = Array.isArray(output?.files) ? output.files.map(String) : [];
|
|
454
|
+
const count = typeof output?.count === "number" ? output.count : files.length;
|
|
455
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
456
|
+
label: part.input?.pattern ?? "files",
|
|
457
|
+
text: files.join("\n"),
|
|
458
|
+
status: output ? `${count} files${output.truncated ? " (truncated)" : ""}` : void 0
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
case "tool-grep": {
|
|
462
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
463
|
+
const matches = Array.isArray(output?.matches) ? output.matches.map((m) => typeof m === "string" ? m : m.context ?? JSON.stringify(m)) : [];
|
|
464
|
+
const status = output && typeof output.totalMatches === "number" ? `${output.totalMatches} matches in ${String(output.filesWithMatches)} files` : void 0;
|
|
465
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
466
|
+
label: part.input?.query ?? "search",
|
|
467
|
+
text: matches.join("\n"),
|
|
468
|
+
status
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
default: return null;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/** Skill panels show what the model actually received: the activated skill's
|
|
475
|
+
body, the resource text, or the script result. */
|
|
476
|
+
function ThinkChatSkillPanel({ part }) {
|
|
477
|
+
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
478
|
+
label: getToolName(part),
|
|
479
|
+
text: part.errorText,
|
|
480
|
+
status: "error",
|
|
481
|
+
error: true
|
|
482
|
+
});
|
|
483
|
+
if (part.state === "output-denied") return renderDeniedPanel(getToolName(part), part.approval.reason);
|
|
484
|
+
switch (part.type) {
|
|
485
|
+
case "tool-activate_skill": {
|
|
486
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
487
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
488
|
+
label: part.input?.name ?? "skill",
|
|
489
|
+
text: output
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
case "tool-read_skill_resource": {
|
|
493
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
494
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
495
|
+
label: part.input?.path ?? "resource",
|
|
496
|
+
text: output
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
case "tool-run_skill_script": {
|
|
500
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
501
|
+
const text = output === void 0 ? void 0 : typeof output === "string" ? output : JSON.stringify(output, null, 2);
|
|
502
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
503
|
+
label: part.input?.path ?? "script",
|
|
504
|
+
text
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function ThinkChatFetchPanel({ part }) {
|
|
510
|
+
const label = part.input?.url ?? part.input?.path ?? "fetch";
|
|
511
|
+
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
512
|
+
label,
|
|
513
|
+
text: part.errorText,
|
|
514
|
+
status: "error",
|
|
515
|
+
error: true
|
|
516
|
+
});
|
|
517
|
+
if (part.state === "output-denied") return renderDeniedPanel(label, part.approval.reason);
|
|
518
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
519
|
+
if (!output) return /* @__PURE__ */ jsx(ActivityPanel, { label });
|
|
520
|
+
if (!output.ok) return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
521
|
+
label: output.finalUrl ?? label,
|
|
522
|
+
text: output.message,
|
|
523
|
+
status: output.code,
|
|
524
|
+
error: true
|
|
525
|
+
});
|
|
526
|
+
const body = output.body ?? (output.json !== void 0 ? JSON.stringify(output.json, null, 2) : output.path ? `saved to ${output.path}` : "");
|
|
527
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
528
|
+
label: output.finalUrl,
|
|
529
|
+
text: body || void 0,
|
|
530
|
+
status: `${output.status} · ${output.bytes} bytes${output.truncated ? " (truncated)" : ""}`
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
function findBrowserResultMedia(value, depth = 0) {
|
|
534
|
+
const media = {};
|
|
535
|
+
const visit = (node, level) => {
|
|
536
|
+
if (level > 4 || node === null) return;
|
|
537
|
+
if (typeof node === "string") {
|
|
538
|
+
if (!media.liveViewUrl && node.includes("live.browser.run")) media.liveViewUrl = node;
|
|
539
|
+
else if (!media.screenshot && node.startsWith("data:image/")) media.screenshot = node;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
if (Array.isArray(node)) {
|
|
543
|
+
for (const item of node) visit(item, level + 1);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
if (typeof node === "object") {
|
|
547
|
+
const record = node;
|
|
548
|
+
if (media.expiresInMs === void 0 && typeof record.url === "string" && typeof record.expiresInMs === "number") media.expiresInMs = record.expiresInMs;
|
|
549
|
+
for (const item of Object.values(record)) visit(item, level + 1);
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
visit(value, depth);
|
|
553
|
+
return media;
|
|
554
|
+
}
|
|
555
|
+
function ThinkChatBrowserPanel({ part }) {
|
|
556
|
+
const [seenAt] = React.useState(() => Date.now());
|
|
557
|
+
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
558
|
+
label: "Browser",
|
|
559
|
+
text: part.errorText,
|
|
560
|
+
status: "error",
|
|
561
|
+
error: true
|
|
562
|
+
});
|
|
563
|
+
if (part.state === "output-denied") return renderDeniedPanel("Browser", part.approval.reason);
|
|
564
|
+
if (part.type === "tool-browser_execute") {
|
|
565
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
566
|
+
const code = part.input?.code ?? "";
|
|
567
|
+
const media = output?.status === "completed" ? findBrowserResultMedia(output.result) : {};
|
|
568
|
+
const hasMedia = Boolean(media.liveViewUrl || media.screenshot);
|
|
569
|
+
const sections = [
|
|
570
|
+
output && "logs" in output && output.logs?.length ? output.logs.join("\n") : "",
|
|
571
|
+
output?.status === "error" ? output.error : "",
|
|
572
|
+
output?.status === "completed" && output.result !== void 0 && !hasMedia ? typeof output.result === "string" ? output.result : JSON.stringify(output.result, null, 2) : ""
|
|
573
|
+
].filter(Boolean);
|
|
574
|
+
const status = !output ? void 0 : output.status === "paused" ? "paused · needs approval" : output.status === "error" ? "error" : "completed";
|
|
575
|
+
const browserView = media.liveViewUrl ? /* @__PURE__ */ jsx(BrowserLiveView, {
|
|
576
|
+
url: media.liveViewUrl,
|
|
577
|
+
expiresAt: seenAt + (media.expiresInMs ?? 300 * 1e3),
|
|
578
|
+
fallback: media.screenshot ? /* @__PURE__ */ jsx("img", {
|
|
579
|
+
src: media.screenshot,
|
|
580
|
+
alt: "Last browser state"
|
|
581
|
+
}) : void 0
|
|
582
|
+
}) : media.screenshot ? /* @__PURE__ */ jsx("img", {
|
|
583
|
+
className: "activity__browser-screenshot",
|
|
584
|
+
src: media.screenshot,
|
|
585
|
+
alt: "Browser screenshot"
|
|
586
|
+
}) : null;
|
|
587
|
+
const codeView = /* @__PURE__ */ jsxs(Fragment, { children: [code ? /* @__PURE__ */ jsx(CodeBlock, {
|
|
588
|
+
className: "activity__code-block",
|
|
589
|
+
code,
|
|
590
|
+
language: "js"
|
|
591
|
+
}) : null, sections.length ? /* @__PURE__ */ jsx(ActivityOutput, { children: sections.join("\n\n") }) : null] });
|
|
592
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
593
|
+
label: "Browser",
|
|
594
|
+
status,
|
|
595
|
+
error: output?.status === "error",
|
|
596
|
+
children: browserView && (code || sections.length) ? /* @__PURE__ */ jsxs(Tabs, {
|
|
597
|
+
className: "activity__browser-tabs",
|
|
598
|
+
defaultValue: "browser",
|
|
599
|
+
children: [
|
|
600
|
+
/* @__PURE__ */ jsxs(TabsList, {
|
|
601
|
+
variant: "line",
|
|
602
|
+
children: [/* @__PURE__ */ jsx(Tab, {
|
|
603
|
+
value: "browser",
|
|
604
|
+
children: "Preview"
|
|
605
|
+
}), /* @__PURE__ */ jsx(Tab, {
|
|
606
|
+
value: "code",
|
|
607
|
+
children: "Code"
|
|
608
|
+
})]
|
|
609
|
+
}),
|
|
610
|
+
/* @__PURE__ */ jsx(TabPanel, {
|
|
611
|
+
value: "browser",
|
|
612
|
+
children: browserView
|
|
613
|
+
}),
|
|
614
|
+
/* @__PURE__ */ jsx(TabPanel, {
|
|
615
|
+
value: "code",
|
|
616
|
+
children: codeView
|
|
617
|
+
})
|
|
618
|
+
]
|
|
619
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [browserView, code || sections.length ? codeView : null] })
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
623
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
624
|
+
label: displayUrl(part.input?.url) || "page",
|
|
625
|
+
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),
|
|
626
|
+
status: part.type === "tool-browser_links" && Array.isArray(output) ? `${output.length} links` : void 0
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
function ThinkChatShellPanel({ part }) {
|
|
630
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
631
|
+
const errorText = part.state === "output-error" ? part.errorText : "";
|
|
632
|
+
const deniedReason = part.state === "output-denied" ? part.approval.reason ?? "denied" : "";
|
|
633
|
+
const failed = Boolean(errorText) || Boolean(deniedReason) || output !== void 0 && (output.exitCode !== 0 || Boolean(output.errors?.length));
|
|
634
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
635
|
+
label: "Shell",
|
|
636
|
+
text: formatBashPanelText(part.input, output, errorText || deniedReason),
|
|
637
|
+
status: output !== void 0 || errorText || deniedReason ? getBashStatusText(output, errorText, deniedReason) : void 0,
|
|
638
|
+
error: failed
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
function ThinkChatActivityGroup({ message, parts, isTrailing, toolRenderers }) {
|
|
642
|
+
const chat = React.useContext(ThinkChatContext);
|
|
643
|
+
const chatStillWorking = isTrailing && Boolean(chat?.isStreaming) && chat?.messages.at(-1)?.id === message.id;
|
|
644
|
+
return /* @__PURE__ */ jsx(ThinkChatActivity, {
|
|
645
|
+
parts,
|
|
646
|
+
isStreaming: parts.some(isPartWorking) || chatStillWorking,
|
|
647
|
+
toolRenderers
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Builds a renderPart handler that folds every run of consecutive activity
|
|
652
|
+
* parts (reasoning + tool calls) into one collapsed Activity box. Everything
|
|
653
|
+
* is derived from the UIMessage stream: part order, tool states, reasoning
|
|
654
|
+
* state. Excluded tools split the run and return undefined so your own
|
|
655
|
+
* renderPart (or the generic Tool card) takes over for them.
|
|
656
|
+
*/
|
|
657
|
+
function createThinkChatActivityRenderer({ excludeTools, toolRenderers } = {}) {
|
|
658
|
+
const excluded = excludeTools?.length ? new Set(excludeTools) : void 0;
|
|
659
|
+
const isActivity = (part) => isThinkChatActivityPart(part, excluded);
|
|
660
|
+
return (context) => {
|
|
661
|
+
const { message, part, partIndex } = context;
|
|
662
|
+
if (!isActivity(part)) return void 0;
|
|
663
|
+
let start = partIndex;
|
|
664
|
+
while (start > 0 && isActivity(message.parts[start - 1])) start--;
|
|
665
|
+
if (partIndex !== start) return null;
|
|
666
|
+
let end = partIndex;
|
|
667
|
+
while (end + 1 < message.parts.length && isActivity(message.parts[end + 1])) end++;
|
|
668
|
+
return /* @__PURE__ */ jsx(ThinkChatActivityGroup, {
|
|
669
|
+
message,
|
|
670
|
+
parts: message.parts.slice(start, end + 1),
|
|
671
|
+
isTrailing: end === message.parts.length - 1,
|
|
672
|
+
toolRenderers
|
|
673
|
+
});
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
/** Default activity renderer — `createThinkChatActivityRenderer()` with no overrides. */
|
|
677
|
+
const renderThinkChatActivityPart = createThinkChatActivityRenderer();
|
|
678
|
+
/**
|
|
679
|
+
* The ambient chat value from `ThinkChat.Root`/`Provider` — for custom tool
|
|
680
|
+
* panels, cards, or composer actions that need `messages`, `status`, or
|
|
681
|
+
* `sendMessage`. Throws outside a provider.
|
|
682
|
+
*/
|
|
683
|
+
function useThinkChat() {
|
|
684
|
+
return useThinkChatContext("useThinkChat");
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Namespace entry point. `Root` + `Messages` is the standard surface;
|
|
688
|
+
* `Provider` + `useThinkChat` when the app owns the chat hook; `Activity`,
|
|
689
|
+
* `renderActivityPart`, and `createActivityRenderer` for custom layouts and
|
|
690
|
+
* app-specific tools.
|
|
691
|
+
*/
|
|
692
|
+
const ThinkChat = {
|
|
693
|
+
Provider: ThinkChatProvider,
|
|
694
|
+
Root: ThinkChatRoot,
|
|
695
|
+
Messages: ThinkChatMessages,
|
|
696
|
+
Activity: ThinkChatActivity,
|
|
697
|
+
renderActivityPart: renderThinkChatActivityPart,
|
|
698
|
+
createActivityRenderer: createThinkChatActivityRenderer
|
|
699
|
+
};
|
|
700
|
+
function formatBashPanelText(input, output, errorText) {
|
|
701
|
+
return [
|
|
702
|
+
input?.script?.trim() ? `$ bash\n${input.script}` : "",
|
|
703
|
+
output?.stdout,
|
|
704
|
+
output?.stderr,
|
|
705
|
+
errorText,
|
|
706
|
+
output?.skippedFiles?.length ? ["skipped files", ...output.skippedFiles].join("\n") : "",
|
|
707
|
+
output?.errors?.length ? ["sync errors", ...output.errors].join("\n") : "",
|
|
708
|
+
output ? formatChangedFiles(output.changedFiles) : ""
|
|
709
|
+
].filter(Boolean).join("\n\n");
|
|
710
|
+
}
|
|
711
|
+
function formatChangedFiles(changedFiles) {
|
|
712
|
+
if (!changedFiles) return "";
|
|
713
|
+
const lines = [
|
|
714
|
+
...changedFiles.created.map((path) => `created ${path}`),
|
|
715
|
+
...changedFiles.updated.map((path) => `updated ${path}`),
|
|
716
|
+
...changedFiles.deleted.map((path) => `deleted ${path}`),
|
|
717
|
+
...changedFiles.directoriesCreated.map((path) => `created directory ${path}`),
|
|
718
|
+
...changedFiles.directoriesDeleted.map((path) => `deleted directory ${path}`)
|
|
719
|
+
];
|
|
720
|
+
return lines.length ? ["workspace changes", ...lines].join("\n") : "";
|
|
721
|
+
}
|
|
722
|
+
function getBashStatusText(output, errorText, deniedReason) {
|
|
723
|
+
if (errorText) return "error";
|
|
724
|
+
if (deniedReason) return "denied";
|
|
725
|
+
if (output?.errors?.length) return "sync errors";
|
|
726
|
+
if (output && output.exitCode !== 0) return `exit ${output.exitCode}`;
|
|
727
|
+
return "success";
|
|
728
|
+
}
|
|
729
|
+
//#endregion
|
|
730
|
+
export { BrowserLiveView, BrowserReplay, ThinkChat, ThinkChatActivity, ThinkChatMessages, ThinkChatProvider, ThinkChatRoot, WorkspaceExplorer, createThinkChatActivityRenderer, describeThinkChatActivityPart, inferWorkspaceLanguage, isThinkChatActivityPart, renderThinkChatActivityPart, useThinkChat, useWorkspace };
|