@mcp-b/react-components 0.5.6 → 0.5.7

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.
@@ -40,6 +40,8 @@ interface ActivityTriggerProps extends Omit<CollapsibleTriggerProps, "className"
40
40
  className?: string;
41
41
  /** Header text. Defaults to "Working…". */
42
42
  label?: React.ReactNode;
43
+ /** Optional leading icon for the header. */
44
+ icon?: React.ReactNode;
43
45
  }
44
46
  /**
45
47
  * Clickable header line that toggles the activity log. The label shimmers
@@ -48,6 +50,7 @@ interface ActivityTriggerProps extends Omit<CollapsibleTriggerProps, "className"
48
50
  declare function ActivityTrigger({
49
51
  className,
50
52
  label,
53
+ icon,
51
54
  children,
52
55
  ref,
53
56
  ...props
@@ -38,7 +38,7 @@ function Activity({ className, isStreaming = false, defaultOpen = false, ref, ..
38
38
  * Clickable header line that toggles the activity log. The label shimmers
39
39
  * while the parent Activity is streaming.
40
40
  */
41
- function ActivityTrigger({ className, label = "Working…", children, ref, ...props }) {
41
+ function ActivityTrigger({ className, label = "Working…", icon, children, ref, ...props }) {
42
42
  if (children !== void 0 && children !== null) return /* @__PURE__ */ jsx(CollapsibleTrigger, {
43
43
  ref,
44
44
  className: cx("activity__trigger", className),
@@ -51,14 +51,22 @@ function ActivityTrigger({ className, label = "Working…", children, ref, ...pr
51
51
  className: cx("activity__trigger", className),
52
52
  ...props,
53
53
  unstyled: true,
54
- children: [/* @__PURE__ */ jsx("span", {
55
- className: "activity__trigger-label",
56
- children: label
57
- }), /* @__PURE__ */ jsx("span", {
58
- className: "activity__trigger-icon",
59
- "aria-hidden": "true",
60
- children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
61
- })]
54
+ children: [
55
+ icon ? /* @__PURE__ */ jsx("span", {
56
+ className: "activity__item-icon",
57
+ "aria-hidden": "true",
58
+ children: icon
59
+ }) : null,
60
+ /* @__PURE__ */ jsx("span", {
61
+ className: "activity__trigger-label",
62
+ children: label
63
+ }),
64
+ /* @__PURE__ */ jsx("span", {
65
+ className: "activity__trigger-icon",
66
+ "aria-hidden": "true",
67
+ children: /* @__PURE__ */ jsx(ChevronRightIcon, {})
68
+ })
69
+ ]
62
70
  });
63
71
  }
64
72
  /** The collapsible log of ActivityItem children. */
@@ -1,3 +1,4 @@
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";
1
2
  import { Activity, ActivityContent, ActivityItem, ActivityItemGroup, ActivityOutput, ActivityPanel, ActivityText, ActivityTrigger } from "./Activity.js";
2
3
  import { Tab, TabPanel, Tabs, TabsList } from "./Tabs.js";
3
4
  import { AgentChat } from "./AgentChat.js";
@@ -171,6 +172,142 @@ function summarizeToolInput(input) {
171
172
  }
172
173
  return null;
173
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
+ }
174
311
  /**
175
312
  * One-line natural-language label for an activity part ("Ran vp test",
176
313
  * "Fetched developers.cloudflare.com/agents/"); `null` for boundaries that
@@ -192,12 +329,7 @@ function describeThinkChatActivityPart(part, toolRenderers) {
192
329
  if (isFetchToolPart(part)) return describeFetchToolPart(part);
193
330
  if (isBrowserToolPart(part)) return describeBrowserToolPart(part);
194
331
  if (isExecuteToolPart(part)) return describeExecuteToolPart(part);
195
- if (isToolUIPart(part)) {
196
- const name = getToolName(part);
197
- const verb = isPartFailed(part) ? `${name} failed` : isPartWorking(part) ? `Running ${name}` : `Ran ${name}`;
198
- const detail = summarizeToolInput(part.input);
199
- return detail ? `${verb} · ${detail}` : verb;
200
- }
332
+ if (isToolUIPart(part)) return describeGenericToolPart(part);
201
333
  return null;
202
334
  }
203
335
  function basename(path) {
@@ -283,7 +415,7 @@ function describeFetchToolPart(part) {
283
415
  }
284
416
  function summarizeActivitySteps(parts) {
285
417
  const byTool = /* @__PURE__ */ new Map();
286
- const otherTools = /* @__PURE__ */ new Set();
418
+ const genericSegments = /* @__PURE__ */ new Set();
287
419
  const skillNames = /* @__PURE__ */ new Set();
288
420
  let thoughts = 0;
289
421
  let skillParts = 0;
@@ -300,7 +432,7 @@ function summarizeActivitySteps(parts) {
300
432
  } else if (isFetchToolPart(part)) fetches++;
301
433
  else if (isBrowserToolPart(part)) browses++;
302
434
  else if (isExecuteToolPart(part)) codeRuns++;
303
- else if (isToolUIPart(part)) otherTools.add(getToolName(part));
435
+ else if (isToolUIPart(part)) genericSegments.add(genericToolLabels(getToolName(part), activityPartUrl(part) !== null).summary);
304
436
  const count = (name) => byTool.get(name) ?? 0;
305
437
  const files = (n) => n === 1 ? "a file" : `${n} files`;
306
438
  const reads = count("read");
@@ -319,7 +451,15 @@ function summarizeActivitySteps(parts) {
319
451
  if (browses > 0) segments.push("used the browser");
320
452
  if (codeRuns > 0) segments.push("ran code");
321
453
  if (commands > 0) segments.push(commands === 1 ? "ran a command" : `ran ${commands} commands`);
322
- for (const tool of otherTools) segments.push(`used ${tool}`);
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);
323
463
  if (segments.length === 0) return "Worked";
324
464
  const top = segments.slice(0, 3);
325
465
  const joined = top.length === 1 ? top[0] : `${top.slice(0, -1).join(", ")} and ${top.at(-1)}`;
@@ -349,7 +489,10 @@ function ThinkChatActivity({ parts, defaultOpen, isStreaming, toolRenderers }) {
349
489
  return /* @__PURE__ */ jsxs(Activity, {
350
490
  isStreaming: streaming,
351
491
  defaultOpen,
352
- children: [/* @__PURE__ */ jsx(ActivityTrigger, { label: streaming && latest ? latest.text : settledLabel }), /* @__PURE__ */ jsx(ActivityContent, { children: steps.map((step) => /* @__PURE__ */ jsx(ThinkChatActivityStep, {
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, {
353
496
  text: step.text,
354
497
  part: step.part,
355
498
  toolRenderers
@@ -357,39 +500,53 @@ function ThinkChatActivity({ parts, defaultOpen, isStreaming, toolRenderers }) {
357
500
  });
358
501
  }
359
502
  function ThinkChatActivityStep({ text, part, toolRenderers }) {
503
+ const icon = activityPartIcon(part);
360
504
  if (isToolUIPart(part)) {
361
505
  const customPanel = toolRenderers?.[getToolName(part)]?.panel?.(part);
362
- if (customPanel === null) return /* @__PURE__ */ jsx(ActivityItem, { children: text });
506
+ if (customPanel === null) return /* @__PURE__ */ jsx(ActivityItem, {
507
+ icon,
508
+ children: text
509
+ });
363
510
  if (customPanel !== void 0) return /* @__PURE__ */ jsx(ActivityItemGroup, {
364
511
  label: text,
512
+ icon,
365
513
  children: customPanel
366
514
  });
367
515
  }
368
516
  if (isReasoningUIPart(part) && part.text.trim()) return /* @__PURE__ */ jsx(ActivityItemGroup, {
369
517
  label: text,
518
+ icon,
370
519
  children: /* @__PURE__ */ jsx(ActivityText, { children: part.text })
371
520
  });
372
521
  if (isWorkspaceToolPart(part) && part.type !== "tool-delete") return /* @__PURE__ */ jsx(ActivityItemGroup, {
373
522
  label: text,
523
+ icon,
374
524
  children: /* @__PURE__ */ jsx(ThinkChatWorkspacePanel, { part })
375
525
  });
376
526
  if (isSkillToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
377
527
  label: text,
528
+ icon,
378
529
  children: /* @__PURE__ */ jsx(ThinkChatSkillPanel, { part })
379
530
  });
380
531
  if (isFetchToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
381
532
  label: text,
533
+ icon,
382
534
  children: /* @__PURE__ */ jsx(ThinkChatFetchPanel, { part })
383
535
  });
384
536
  if (isBrowserToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
385
537
  label: text,
538
+ icon,
386
539
  children: /* @__PURE__ */ jsx(ThinkChatBrowserPanel, { part })
387
540
  });
388
541
  if (isExecuteToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
389
542
  label: text,
543
+ icon,
390
544
  children: /* @__PURE__ */ jsx(ThinkChatExecutePanel, { part })
391
545
  });
392
- return /* @__PURE__ */ jsx(ActivityItem, { children: text });
546
+ return /* @__PURE__ */ jsx(ActivityItem, {
547
+ icon,
548
+ children: text
549
+ });
393
550
  }
394
551
  function renderDeniedPanel(label, reason) {
395
552
  return /* @__PURE__ */ jsx(ActivityPanel, {
@@ -342,6 +342,15 @@
342
342
  color: var(--sigvelo-color-text-muted);
343
343
  }
344
344
 
345
+ .activity__favicon {
346
+ display: block;
347
+ width: 1rem;
348
+ height: 1rem;
349
+ border-radius: var(--sigvelo-radius-xs);
350
+ background-color: var(--sigvelo-color-surface);
351
+ object-fit: contain;
352
+ }
353
+
345
354
  .activity__item-text {
346
355
  min-width: 0;
347
356
  overflow-wrap: anywhere;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/react-components",
3
- "version": "0.5.6",
3
+ "version": "0.5.7",
4
4
  "description": "MCP-B React components built on Base UI and shared design tokens, including Cloudflare Think chat primitives.",
5
5
  "homepage": "https://design-system.sigvelo.com",
6
6
  "bugs": {
@@ -49,7 +49,7 @@
49
49
  "anser": "^2.3.5",
50
50
  "shiki": "^3.23.0",
51
51
  "streamdown": "^2.5.0",
52
- "@mcp-b/design-tokens": "0.5.6"
52
+ "@mcp-b/design-tokens": "0.5.7"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@ai-sdk/react": "^3.0.194",