@mcp-b/react-components 0.13.0 → 0.14.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.
@@ -1,8 +1,9 @@
1
- import { tt as PromptInputMessage } from "./PromptInput-p5KmOmt1.js";
1
+ import { W as PromptInputDictationProps, tt as PromptInputMessage } from "./PromptInput-ClNqBiIw.js";
2
2
  import * as React from "react";
3
3
  import { ChatAddToolApproveResponseFunction, ChatStatus, UIMessage } from "ai";
4
4
  import { PendingApproval, Think } from "@cloudflare/think";
5
5
  import { AgentStub } from "agents/client";
6
+ import { UseVoiceInputReturn } from "@cloudflare/voice/react";
6
7
 
7
8
  //#region src/components/AgentChat.d.ts
8
9
  type AgentChatPart<MessageT extends UIMessage = UIMessage> = MessageT["parts"][number];
@@ -85,6 +86,10 @@ interface AgentChatProps<MessageT extends UIMessage = UIMessage> {
85
86
  emptyState?: React.ReactNode;
86
87
  /** Extra controls rendered in the composer toolbar. */
87
88
  composerTools?: React.ReactNode;
89
+ /** Whole upstream voice-input hook result used by the built-in dictation recorder. */
90
+ voiceInput?: UseVoiceInputReturn;
91
+ /** Host prerequisite run before the built-in recorder calls `voiceInput.start()`. */
92
+ beforeVoiceStart?: PromptInputDictationProps["beforeStart"];
88
93
  /**
89
94
  * Rendered in the prompt-composer header before attachments and the text
90
95
  * field. Use it to compose a compact Activity, task state, or app-specific
@@ -175,6 +180,8 @@ declare function AgentChat<MessageT extends UIMessage = UIMessage>({
175
180
  emptyState,
176
181
  composerHeader,
177
182
  composerTools,
183
+ voiceInput,
184
+ beforeVoiceStart,
178
185
  composerAddMenuItems,
179
186
  notice,
180
187
  onRegenerate,
@@ -235,6 +235,16 @@ declare function PromptInputButton({
235
235
  interface PromptInputDictationProps extends Omit<PromptInputButtonProps, "children" | "onClick" | "tooltip"> {
236
236
  /** The whole upstream `useVoiceInput` result; authentication and transport stay in the host. */
237
237
  voice: UseVoiceInputReturn;
238
+ /**
239
+ * Runs immediately before the unchanged upstream `voice.start()` call.
240
+ * Use this for host-specific prerequisites, such as opening an extension tab
241
+ * that can present Chrome's microphone permission prompt. Reject to prevent
242
+ * `voice.start()` and surface the error in the composer.
243
+ *
244
+ * @see https://github.com/cloudflare/agents/blob/main/examples/voice-input/src/client.tsx
245
+ * @see https://developer.chrome.com/docs/extensions/reference/api/offscreen#type-Reason
246
+ */
247
+ beforeStart?: (voice: UseVoiceInputReturn) => void | Promise<void>;
238
248
  }
239
249
  /**
240
250
  * Voice-dictation control for PromptInput. Final upstream transcripts are
@@ -242,6 +252,7 @@ interface PromptInputDictationProps extends Omit<PromptInputButtonProps, "childr
242
252
  */
243
253
  declare function PromptInputDictation({
244
254
  voice,
255
+ beforeStart,
245
256
  className,
246
257
  disabled,
247
258
  ref,
@@ -1,4 +1,4 @@
1
- import { a as AgentChatRenderPartContext, r as AgentChatProps } from "./AgentChat-Br6tKEl8.js";
1
+ import { a as AgentChatRenderPartContext, r as AgentChatProps } from "./AgentChat-Cgb1xuT7.js";
2
2
  import { s as ThinkChatPart } from "./ThinkChatToolParts-Cv4o7j6F.js";
3
3
  import * as React from "react";
4
4
  import { DynamicToolUIPart, ToolUIPart, UIMessage } from "ai";
@@ -1,2 +1,2 @@
1
- import { a as AgentChatRenderPartContext, i as AgentChatRenderMessageContext, n as AgentChatPart, r as AgentChatProps, t as AgentChat } from "../AgentChat-Br6tKEl8.js";
1
+ import { a as AgentChatRenderPartContext, i as AgentChatRenderMessageContext, n as AgentChatPart, r as AgentChatProps, t as AgentChat } from "../AgentChat-Cgb1xuT7.js";
2
2
  export { AgentChat, AgentChatPart, AgentChatProps, AgentChatRenderMessageContext, AgentChatRenderPartContext };
@@ -7,7 +7,7 @@ import { MessageScroller, MessageScrollerButton, MessageScrollerContent, Message
7
7
  import { ConversationEmptyState } from "./Conversation.js";
8
8
  import { Message, MessageAction, MessageActions, MessageAttachment, MessageContent, MessageResponse } from "./Message.js";
9
9
  import { Marker, MarkerContent } from "./Marker.js";
10
- import { PromptInput, PromptInputActionAddAttachments, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuTrigger, PromptInputApproval, PromptInputAttachments, PromptInputBody, PromptInputButton, PromptInputFooter, PromptInputHeader, PromptInputPermissionSelector, PromptInputSubmit, PromptInputTextarea, PromptInputTools, getApprovalRequestId, getApprovalStateKey, getPendingToolApprovals, usePromptInputAttachments } from "./PromptInput.js";
10
+ import { PromptInput, PromptInputActionAddAttachments, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuTrigger, PromptInputApproval, PromptInputAttachments, PromptInputBody, PromptInputButton, PromptInputDictation, PromptInputFooter, PromptInputHeader, PromptInputPermissionSelector, PromptInputSubmit, PromptInputTextarea, PromptInputTools, getApprovalRequestId, getApprovalStateKey, getPendingToolApprovals, usePromptInputAttachments } from "./PromptInput.js";
11
11
  import { Reasoning, ReasoningContent, ReasoningTrigger } from "./Reasoning.js";
12
12
  import { Source, Sources, SourcesContent, SourcesTrigger } from "./Sources.js";
13
13
  import { t as CopyButton } from "../copy-button-DUMLYdsk.js";
@@ -310,7 +310,7 @@ function renderDefaultPart(part) {
310
310
  * transport. Product code that talks to a Cloudflare Think agent should render
311
311
  * `ThinkChat`; use this directly only for plain AI SDK or read-only messages.
312
312
  */
313
- function AgentChat({ className, messages, status = "ready", onSubmit, onStop, placeholder = "Ask anything...", disabled, emptyState, composerHeader, composerTools, composerAddMenuItems, notice, onRegenerate, onEditAndResend, onApprovalResponse, thinkStub, onExecutionSettled, pendingApprovals: durableApprovals = EMPTY_PENDING_APPROVALS, contextWindow, renderPart, renderMessageAfter, ref }) {
313
+ function AgentChat({ className, messages, status = "ready", onSubmit, onStop, placeholder = "Ask anything...", disabled, emptyState, composerHeader, composerTools, voiceInput, beforeVoiceStart, composerAddMenuItems, notice, onRegenerate, onEditAndResend, onApprovalResponse, thinkStub, onExecutionSettled, pendingApprovals: durableApprovals = EMPTY_PENDING_APPROVALS, contextWindow, renderPart, renderMessageAfter, ref }) {
314
314
  const [copyAnnouncement, setCopyAnnouncement] = React.useState("");
315
315
  const copyAnnouncementTimer = React.useRef(null);
316
316
  const announceCopyStatus = React.useCallback((message) => {
@@ -551,7 +551,12 @@ function AgentChat({ className, messages, status = "ready", onSubmit, onStop, pl
551
551
  value: permissionMode,
552
552
  onValueChange: setPermissionMode
553
553
  }) : null,
554
- composerTools
554
+ composerTools,
555
+ voiceInput ? /* @__PURE__ */ jsx(PromptInputDictation, {
556
+ voice: voiceInput,
557
+ beforeStart: beforeVoiceStart,
558
+ disabled
559
+ }) : null
555
560
  ] }), /* @__PURE__ */ jsxs("div", {
556
561
  className: "prompt-input__footer-end",
557
562
  children: [contextWindow ? /* @__PURE__ */ jsxs(Context, {
@@ -1,2 +1,2 @@
1
- import { $ as PromptInputHoverCardTrigger, A as PromptInputCommandEmptyProps, At as getPendingToolApprovals, B as PromptInputCommandSeparator, C as PromptInputBody, Ct as PromptInputTools, D as PromptInputButtonTooltip, Dt as TooltipSide, E as PromptInputButtonProps, Et as TextInputContext, F as PromptInputCommandItem, G as PromptInputFooter, H as PromptInputControllerProps, I as PromptInputCommandItemProps, J as PromptInputHeaderProps, K as PromptInputFooterProps, L as PromptInputCommandList, M as PromptInputCommandGroupProps, Mt as usePromptInputController, N as PromptInputCommandInput, Nt as useProviderAttachments, O as PromptInputCommand, Ot as getApprovalRequestId, P as PromptInputCommandInputProps, Q as PromptInputHoverCardProps, R as PromptInputCommandListProps, S as PromptInputAttachmentsProps, St as PromptInputTextareaProps, T as PromptInputButton, Tt as PromptInputTooltipConfig, U as PromptInputDictation, V as PromptInputCommandSeparatorProps, W as PromptInputDictationProps, X as PromptInputHoverCardContent, Y as PromptInputHoverCard, Z as PromptInputHoverCardContentProps, _ as PromptInputApproval, _t as PromptInputTabLabelProps, a as PromptInputActionAddAttachments, at as PromptInputProvider, b as PromptInputAttachmentProps, bt as PromptInputTabsListProps, c as PromptInputActionAddScreenshotProps, ct as PromptInputSelectProps, d as PromptInputActionMenuContentProps, dt as PromptInputTab, et as PromptInputHoverCardTriggerProps, f as PromptInputActionMenuItem, ft as PromptInputTabBody, g as PromptInputActionMenuTriggerProps, gt as PromptInputTabLabel, h as PromptInputActionMenuTrigger, ht as PromptInputTabItemProps, i as PromptInput, it as PromptInputProps, j as PromptInputCommandGroup, jt as usePromptInputAttachments, k as PromptInputCommandEmpty, kt as getApprovalStateKey, l as PromptInputActionMenu, lt as PromptInputSubmit, m as PromptInputActionMenuProps, mt as PromptInputTabItem, n as PendingToolApproval, nt as PromptInputPermissionSelector, o as PromptInputActionAddAttachmentsProps, ot as PromptInputProviderProps, p as PromptInputActionMenuItemProps, pt as PromptInputTabBodyProps, q as PromptInputHeader, r as PermissionMode, rt as PromptInputPermissionSelectorProps, s as PromptInputActionAddScreenshot, st as PromptInputSelect, t as AttachmentsContext, tt as PromptInputMessage, u as PromptInputActionMenuContent, ut as PromptInputSubmitProps, v as PromptInputApprovalProps, vt as PromptInputTabProps, w as PromptInputBodyProps, wt as PromptInputToolsProps, x as PromptInputAttachments, xt as PromptInputTextarea, y as PromptInputAttachment, yt as PromptInputTabsList, z as PromptInputCommandProps } from "../PromptInput-p5KmOmt1.js";
1
+ import { $ as PromptInputHoverCardTrigger, A as PromptInputCommandEmptyProps, At as getPendingToolApprovals, B as PromptInputCommandSeparator, C as PromptInputBody, Ct as PromptInputTools, D as PromptInputButtonTooltip, Dt as TooltipSide, E as PromptInputButtonProps, Et as TextInputContext, F as PromptInputCommandItem, G as PromptInputFooter, H as PromptInputControllerProps, I as PromptInputCommandItemProps, J as PromptInputHeaderProps, K as PromptInputFooterProps, L as PromptInputCommandList, M as PromptInputCommandGroupProps, Mt as usePromptInputController, N as PromptInputCommandInput, Nt as useProviderAttachments, O as PromptInputCommand, Ot as getApprovalRequestId, P as PromptInputCommandInputProps, Q as PromptInputHoverCardProps, R as PromptInputCommandListProps, S as PromptInputAttachmentsProps, St as PromptInputTextareaProps, T as PromptInputButton, Tt as PromptInputTooltipConfig, U as PromptInputDictation, V as PromptInputCommandSeparatorProps, W as PromptInputDictationProps, X as PromptInputHoverCardContent, Y as PromptInputHoverCard, Z as PromptInputHoverCardContentProps, _ as PromptInputApproval, _t as PromptInputTabLabelProps, a as PromptInputActionAddAttachments, at as PromptInputProvider, b as PromptInputAttachmentProps, bt as PromptInputTabsListProps, c as PromptInputActionAddScreenshotProps, ct as PromptInputSelectProps, d as PromptInputActionMenuContentProps, dt as PromptInputTab, et as PromptInputHoverCardTriggerProps, f as PromptInputActionMenuItem, ft as PromptInputTabBody, g as PromptInputActionMenuTriggerProps, gt as PromptInputTabLabel, h as PromptInputActionMenuTrigger, ht as PromptInputTabItemProps, i as PromptInput, it as PromptInputProps, j as PromptInputCommandGroup, jt as usePromptInputAttachments, k as PromptInputCommandEmpty, kt as getApprovalStateKey, l as PromptInputActionMenu, lt as PromptInputSubmit, m as PromptInputActionMenuProps, mt as PromptInputTabItem, n as PendingToolApproval, nt as PromptInputPermissionSelector, o as PromptInputActionAddAttachmentsProps, ot as PromptInputProviderProps, p as PromptInputActionMenuItemProps, pt as PromptInputTabBodyProps, q as PromptInputHeader, r as PermissionMode, rt as PromptInputPermissionSelectorProps, s as PromptInputActionAddScreenshot, st as PromptInputSelect, t as AttachmentsContext, tt as PromptInputMessage, u as PromptInputActionMenuContent, ut as PromptInputSubmitProps, v as PromptInputApprovalProps, vt as PromptInputTabProps, w as PromptInputBodyProps, wt as PromptInputToolsProps, x as PromptInputAttachments, xt as PromptInputTextarea, y as PromptInputAttachment, yt as PromptInputTabsList, z as PromptInputCommandProps } from "../PromptInput-ClNqBiIw.js";
2
2
  export { AttachmentsContext, PendingToolApproval, PermissionMode, PromptInput, PromptInputActionAddAttachments, PromptInputActionAddAttachmentsProps, PromptInputActionAddScreenshot, PromptInputActionAddScreenshotProps, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuContentProps, PromptInputActionMenuItem, PromptInputActionMenuItemProps, PromptInputActionMenuProps, PromptInputActionMenuTrigger, PromptInputActionMenuTriggerProps, PromptInputApproval, PromptInputApprovalProps, PromptInputAttachment, PromptInputAttachmentProps, PromptInputAttachments, PromptInputAttachmentsProps, PromptInputBody, PromptInputBodyProps, PromptInputButton, PromptInputButtonProps, PromptInputButtonTooltip, PromptInputCommand, PromptInputCommandEmpty, PromptInputCommandEmptyProps, PromptInputCommandGroup, PromptInputCommandGroupProps, PromptInputCommandInput, PromptInputCommandInputProps, PromptInputCommandItem, PromptInputCommandItemProps, PromptInputCommandList, PromptInputCommandListProps, PromptInputCommandProps, PromptInputCommandSeparator, PromptInputCommandSeparatorProps, PromptInputControllerProps, PromptInputDictation, PromptInputDictationProps, PromptInputFooter, PromptInputFooterProps, PromptInputHeader, PromptInputHeaderProps, PromptInputHoverCard, PromptInputHoverCardContent, PromptInputHoverCardContentProps, PromptInputHoverCardProps, PromptInputHoverCardTrigger, PromptInputHoverCardTriggerProps, PromptInputMessage, PromptInputPermissionSelector, PromptInputPermissionSelectorProps, PromptInputProps, PromptInputProvider, PromptInputProviderProps, PromptInputSelect, PromptInputSelectProps, PromptInputSubmit, PromptInputSubmitProps, PromptInputTab, PromptInputTabBody, PromptInputTabBodyProps, PromptInputTabItem, PromptInputTabItemProps, PromptInputTabLabel, PromptInputTabLabelProps, PromptInputTabProps, PromptInputTabsList, PromptInputTabsListProps, PromptInputTextarea, PromptInputTextareaProps, PromptInputTools, PromptInputToolsProps, PromptInputTooltipConfig, TextInputContext, TooltipSide, getApprovalRequestId, getApprovalStateKey, getPendingToolApprovals, usePromptInputAttachments, usePromptInputController, useProviderAttachments };
@@ -1,5 +1,5 @@
1
1
  import { t as cx } from "../class-names-9UtZwb0J.js";
2
- import { D as PencilIcon, F as StopIcon, L as WarningIcon, N as SpinnerIcon, P as SquareIcon, S as MicrophoneIcon, T as PaperclipIcon, h as FileIcon, i as ArrowUpIcon, k as PlusIcon, o as ChevronDownIcon, w as MonitorIcon, z as XIcon } from "../icons-Ba9THQKz.js";
2
+ import { D as PencilIcon, F as StopIcon, L as WarningIcon, N as SpinnerIcon, P as SquareIcon, R as WrenchIcon, S as MicrophoneIcon, T as PaperclipIcon, h as FileIcon, i as ArrowUpIcon, k as PlusIcon, o as ChevronDownIcon, w as MonitorIcon, z as XIcon } from "../icons-Ba9THQKz.js";
3
3
  import { Button } from "./Button.js";
4
4
  import { Tooltip } from "./Tooltip.js";
5
5
  import { t as AttachmentPreview } from "../attachment-preview-DIs50Q0h.js";
@@ -450,6 +450,7 @@ function PromptInputButton({ className, tooltip, variant = "ghost", color = "neu
450
450
  const resolvedSize = size ?? (React.Children.count(children) > 1 ? "xs" : "icon-xs");
451
451
  const tooltipConfig = typeof tooltip === "string" ? { content: tooltip } : tooltip;
452
452
  const fallbackLabel = ariaLabel ?? (typeof tooltipConfig?.content === "string" ? tooltipConfig.content : void 0);
453
+ const hasResponsiveLabel = Boolean(tooltipConfig && React.Children.count(children) > 1);
453
454
  const button = /* @__PURE__ */ jsx(Button, {
454
455
  ref,
455
456
  type: "button",
@@ -458,6 +459,7 @@ function PromptInputButton({ className, tooltip, variant = "ghost", color = "neu
458
459
  size: resolvedSize,
459
460
  className: cx("prompt-input__button", className),
460
461
  "aria-label": fallbackLabel,
462
+ "data-responsive-label": hasResponsiveLabel || void 0,
461
463
  ...props,
462
464
  children
463
465
  });
@@ -478,10 +480,14 @@ function PromptInputButton({ className, tooltip, variant = "ghost", color = "neu
478
480
  * Voice-dictation control for PromptInput. Final upstream transcripts are
479
481
  * appended to the composer while interim text remains ephemeral.
480
482
  */
481
- function PromptInputDictation({ voice, className, disabled, ref, ...props }) {
483
+ function PromptInputDictation({ voice, beforeStart, className, disabled, ref, ...props }) {
482
484
  const controller = useOptionalPromptInputController();
485
+ const attachments = usePromptInputAttachments();
483
486
  const buttonRef = React.useRef(null);
484
487
  const committedTranscriptRef = React.useRef("");
488
+ const submitAfterStopRef = React.useRef(false);
489
+ const recordingStartedAtRef = React.useRef(null);
490
+ const [elapsedSeconds, setElapsedSeconds] = React.useState(0);
485
491
  const [isStarting, setIsStarting] = React.useState(false);
486
492
  const [startError, setStartError] = React.useState(null);
487
493
  React.useImperativeHandle(ref, () => buttonRef.current, []);
@@ -501,49 +507,195 @@ function PromptInputDictation({ voice, className, disabled, ref, ...props }) {
501
507
  textarea.value = appendDictationTranscript(textarea.value, addition);
502
508
  textarea.dispatchEvent(new Event("input", { bubbles: true }));
503
509
  }, [controller, voice.transcript]);
510
+ React.useEffect(() => {
511
+ if (!voice.isListening) {
512
+ recordingStartedAtRef.current = null;
513
+ setElapsedSeconds(0);
514
+ if (submitAfterStopRef.current) {
515
+ submitAfterStopRef.current = false;
516
+ window.requestAnimationFrame(() => buttonRef.current?.form?.requestSubmit());
517
+ }
518
+ return;
519
+ }
520
+ recordingStartedAtRef.current ??= Date.now();
521
+ const updateElapsed = () => {
522
+ setElapsedSeconds(Math.floor((Date.now() - (recordingStartedAtRef.current ?? Date.now())) / 1e3));
523
+ };
524
+ updateElapsed();
525
+ const timer = window.setInterval(updateElapsed, 250);
526
+ return () => window.clearInterval(timer);
527
+ }, [voice.isListening]);
504
528
  const error = startError ?? voice.error;
505
529
  const listening = voice.isListening;
506
530
  const label = listening ? "Stop dictation" : error ? "Retry dictation" : "Start dictation";
507
531
  const status = error ? `Dictation failed: ${error}` : listening ? voice.interimTranscript ? "Listening. Speech detected." : "Listening for speech." : isStarting ? "Starting dictation." : "";
508
532
  const level = Math.min(Math.max(voice.audioLevel, 0), 1);
509
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PromptInputButton, {
510
- ref: buttonRef,
511
- className: cx("prompt-input__dictation", className),
512
- tooltip: {
513
- content: label,
514
- side: "top"
515
- },
516
- "aria-label": label,
517
- "aria-pressed": listening,
518
- "data-error": error ? "true" : void 0,
519
- "data-listening": listening ? "true" : void 0,
520
- "data-starting": isStarting ? "true" : void 0,
521
- disabled: disabled || isStarting,
522
- onClick: () => {
523
- setStartError(null);
524
- if (listening) {
525
- voice.stop();
533
+ const elapsed = formatDictationElapsed(elapsedSeconds);
534
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
535
+ /* @__PURE__ */ jsx(PromptInputButton, {
536
+ ref: buttonRef,
537
+ className: cx("prompt-input__dictation", className),
538
+ tooltip: {
539
+ content: label,
540
+ side: "top"
541
+ },
542
+ "aria-label": label,
543
+ "aria-hidden": listening || void 0,
544
+ "aria-pressed": listening,
545
+ "data-error": error ? "true" : void 0,
546
+ "data-listening": listening ? "true" : void 0,
547
+ "data-starting": isStarting ? "true" : void 0,
548
+ disabled: disabled || isStarting || listening,
549
+ tabIndex: listening ? -1 : void 0,
550
+ onClick: () => {
551
+ setStartError(null);
552
+ if (listening) {
553
+ voice.stop();
554
+ return;
555
+ }
556
+ setIsStarting(true);
557
+ Promise.resolve().then(() => beforeStart?.(voice)).then(() => voice.start()).catch((reason) => {
558
+ setStartError(reason instanceof Error ? reason.message : String(reason));
559
+ }).finally(() => setIsStarting(false));
560
+ },
561
+ ...props,
562
+ children: /* @__PURE__ */ jsx("span", {
563
+ className: "prompt-input__dictation-icon",
564
+ style: { "--prompt-input-dictation-level": level },
565
+ "aria-hidden": "true",
566
+ children: isStarting ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : listening ? /* @__PURE__ */ jsx(StopIcon, {}) : /* @__PURE__ */ jsx(MicrophoneIcon, {})
567
+ })
568
+ }),
569
+ listening ? /* @__PURE__ */ jsxs("div", {
570
+ className: "prompt-input__recording",
571
+ role: "group",
572
+ "aria-label": "Voice recording controls",
573
+ children: [
574
+ /* @__PURE__ */ jsx(PromptInputButton, {
575
+ className: "prompt-input__recording-add",
576
+ tooltip: "Add photos or files",
577
+ onClick: () => attachments.openFileDialog(),
578
+ children: /* @__PURE__ */ jsx(PlusIcon, {})
579
+ }),
580
+ /* @__PURE__ */ jsx(PromptInputRecordingWaveform, {
581
+ active: true,
582
+ level
583
+ }),
584
+ /* @__PURE__ */ jsx("time", {
585
+ className: "prompt-input__recording-time",
586
+ dateTime: `PT${elapsedSeconds}S`,
587
+ children: elapsed
588
+ }),
589
+ /* @__PURE__ */ jsx(Button, {
590
+ className: "prompt-input__recording-button",
591
+ type: "button",
592
+ variant: "normal",
593
+ color: "neutral",
594
+ size: "icon",
595
+ "aria-label": "Stop dictation",
596
+ onClick: () => voice.stop(),
597
+ children: /* @__PURE__ */ jsx(SquareIcon, { size: 12 })
598
+ }),
599
+ /* @__PURE__ */ jsx(Button, {
600
+ className: "prompt-input__recording-button",
601
+ type: "button",
602
+ color: "primary",
603
+ size: "icon",
604
+ "aria-label": "Stop dictation and send",
605
+ onClick: () => {
606
+ submitAfterStopRef.current = true;
607
+ voice.stop();
608
+ },
609
+ children: /* @__PURE__ */ jsx(ArrowUpIcon, { size: 18 })
610
+ })
611
+ ]
612
+ }) : null,
613
+ /* @__PURE__ */ jsx("span", {
614
+ className: "prompt-input__dictation-status",
615
+ role: "status",
616
+ "aria-live": "polite",
617
+ "aria-atomic": "true",
618
+ children: status
619
+ })
620
+ ] });
621
+ }
622
+ /**
623
+ * Scrolling canvas waveform adapted from ElevenLabs UI's Waveform component.
624
+ * Cloudflare Voice already owns the microphone stream, so this renderer
625
+ * consumes its upstream `audioLevel` instead of requesting a second stream.
626
+ */
627
+ function PromptInputRecordingWaveform({ active, level }) {
628
+ const canvasRef = React.useRef(null);
629
+ const containerRef = React.useRef(null);
630
+ const historyRef = React.useRef([]);
631
+ const levelRef = React.useRef(level);
632
+ levelRef.current = level;
633
+ React.useEffect(() => {
634
+ const canvas = canvasRef.current;
635
+ const container = containerRef.current;
636
+ if (!canvas || !container) return;
637
+ const resize = () => {
638
+ const rect = container.getBoundingClientRect();
639
+ const dpr = window.devicePixelRatio || 1;
640
+ canvas.width = Math.max(1, Math.round(rect.width * dpr));
641
+ canvas.height = Math.max(1, Math.round(rect.height * dpr));
642
+ canvas.style.width = `${rect.width}px`;
643
+ canvas.style.height = `${rect.height}px`;
644
+ canvas.getContext("2d")?.setTransform(dpr, 0, 0, dpr, 0, 0);
645
+ };
646
+ const observer = new ResizeObserver(resize);
647
+ observer.observe(container);
648
+ resize();
649
+ const barWidth = 2;
650
+ const step = 4;
651
+ let animationFrame = 0;
652
+ let lastUpdate = 0;
653
+ let smoothedLevel = levelRef.current;
654
+ const draw = (time) => {
655
+ const context = canvas.getContext("2d");
656
+ const rect = canvas.getBoundingClientRect();
657
+ if (!context || rect.width === 0 || rect.height === 0) {
658
+ animationFrame = window.requestAnimationFrame(draw);
526
659
  return;
527
660
  }
528
- setIsStarting(true);
529
- voice.start().catch((reason) => {
530
- setStartError(reason instanceof Error ? reason.message : String(reason));
531
- }).finally(() => setIsStarting(false));
532
- },
533
- ...props,
534
- children: /* @__PURE__ */ jsx("span", {
535
- className: "prompt-input__dictation-icon",
536
- style: { "--prompt-input-dictation-level": level },
537
- "aria-hidden": "true",
538
- children: isStarting ? /* @__PURE__ */ jsx(SpinnerIcon, {}) : listening ? /* @__PURE__ */ jsx(StopIcon, {}) : /* @__PURE__ */ jsx(MicrophoneIcon, {})
539
- })
540
- }), /* @__PURE__ */ jsx("span", {
541
- className: "prompt-input__dictation-status",
542
- role: "status",
543
- "aria-live": "polite",
544
- "aria-atomic": "true",
545
- children: status
546
- })] });
661
+ const barCount = Math.max(1, Math.floor(rect.width / step));
662
+ if (active && time - lastUpdate >= 50) {
663
+ smoothedLevel += (levelRef.current - smoothedLevel) * .45;
664
+ historyRef.current.push(Math.max(.08, Math.min(1, smoothedLevel)));
665
+ if (historyRef.current.length > barCount) historyRef.current.shift();
666
+ lastUpdate = time;
667
+ }
668
+ context.clearRect(0, 0, rect.width, rect.height);
669
+ context.fillStyle = getComputedStyle(canvas).color;
670
+ const centerY = rect.height / 2;
671
+ for (let index = 0; index < barCount; index += 1) {
672
+ const historyIndex = historyRef.current.length - barCount + index;
673
+ const value = historyIndex >= 0 ? historyRef.current[historyIndex] ?? .08 : .08;
674
+ const barHeight = Math.max(2, value * rect.height * .9);
675
+ const x = index * step;
676
+ context.globalAlpha = .45 + value * .55;
677
+ context.beginPath();
678
+ context.roundRect(x, centerY - barHeight / 2, barWidth, barHeight, 1);
679
+ context.fill();
680
+ }
681
+ context.globalAlpha = 1;
682
+ animationFrame = window.requestAnimationFrame(draw);
683
+ };
684
+ animationFrame = window.requestAnimationFrame(draw);
685
+ return () => {
686
+ observer.disconnect();
687
+ window.cancelAnimationFrame(animationFrame);
688
+ };
689
+ }, [active]);
690
+ return /* @__PURE__ */ jsx("div", {
691
+ ref: containerRef,
692
+ className: "prompt-input__recording-waveform",
693
+ "aria-hidden": "true",
694
+ children: /* @__PURE__ */ jsx("canvas", { ref: canvasRef })
695
+ });
696
+ }
697
+ function formatDictationElapsed(totalSeconds) {
698
+ return `${Math.floor(totalSeconds / 60)}:${(totalSeconds % 60).toString().padStart(2, "0")}`;
547
699
  }
548
700
  function appendDictationTranscript(current, transcript) {
549
701
  if (!current) return transcript;
@@ -635,23 +787,38 @@ const PERMISSION_MODES = [{
635
787
  */
636
788
  function PromptInputPermissionSelector({ value, onValueChange, question = "How should agent actions be approved?" }) {
637
789
  const selected = PERMISSION_MODES.find((mode) => mode.value === value) ?? PERMISSION_MODES[0];
638
- return /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsx(Menu.Trigger, {
790
+ const trigger = /* @__PURE__ */ jsx(Menu.Trigger, {
639
791
  unstyled: true,
640
792
  render: /* @__PURE__ */ jsxs(PromptInputButton, {
641
793
  size: "xs",
642
794
  className: "prompt-input__permissions-trigger",
643
795
  "data-mode": value,
644
796
  "aria-label": selected.label,
645
- children: [/* @__PURE__ */ jsx("span", {
646
- className: "prompt-input__permissions-current",
647
- children: PERMISSION_MODES.map((mode) => /* @__PURE__ */ jsxs("span", {
648
- className: "prompt-input__permissions-current-option",
649
- "data-active": mode.value === value || void 0,
650
- children: [mode.value === "bypass" ? /* @__PURE__ */ jsx(WarningIcon, { size: 14 }) : null, mode.label]
651
- }, mode.value))
652
- }), /* @__PURE__ */ jsx(ChevronDownIcon, { size: 12 })]
797
+ children: [
798
+ /* @__PURE__ */ jsx("span", {
799
+ className: "prompt-input__permissions-icon",
800
+ children: value === "bypass" ? /* @__PURE__ */ jsx(WarningIcon, { size: 14 }) : /* @__PURE__ */ jsx(WrenchIcon, { size: 14 })
801
+ }),
802
+ /* @__PURE__ */ jsx("span", {
803
+ className: "prompt-input__permissions-current",
804
+ children: PERMISSION_MODES.map((mode) => /* @__PURE__ */ jsx("span", {
805
+ className: "prompt-input__permissions-current-option",
806
+ "data-active": mode.value === value || void 0,
807
+ children: mode.label
808
+ }, mode.value))
809
+ }),
810
+ /* @__PURE__ */ jsx(ChevronDownIcon, {
811
+ className: "prompt-input__permissions-chevron",
812
+ size: 12
813
+ })
814
+ ]
653
815
  })
654
- }), /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { children: /* @__PURE__ */ jsxs(Menu.Popup, {
816
+ });
817
+ return /* @__PURE__ */ jsxs(Menu.Root, { children: [/* @__PURE__ */ jsxs(Tooltip.Root, { children: [/* @__PURE__ */ jsx(Tooltip.Trigger, { render: trigger }), /* @__PURE__ */ jsx(Tooltip.Portal, { children: /* @__PURE__ */ jsx(Tooltip.Positioner, {
818
+ side: "top",
819
+ sideOffset: 6,
820
+ children: /* @__PURE__ */ jsx(Tooltip.Popup, { children: selected.label })
821
+ }) })] }), /* @__PURE__ */ jsx(Menu.Portal, { children: /* @__PURE__ */ jsx(Menu.Positioner, { children: /* @__PURE__ */ jsxs(Menu.Popup, {
655
822
  className: "prompt-input__permissions-menu",
656
823
  children: [/* @__PURE__ */ jsx("div", {
657
824
  className: "prompt-input__permissions-question",
@@ -1,8 +1,8 @@
1
- import { a as AgentChatRenderPartContext, r as AgentChatProps } from "../AgentChat-Br6tKEl8.js";
1
+ import { a as AgentChatRenderPartContext, r as AgentChatProps } from "../AgentChat-Cgb1xuT7.js";
2
2
  import { n as BrowserLiveViewProps, t as BrowserLiveView } from "../BrowserLiveView-BV5qgFgI.js";
3
3
  import { n as BrowserReplayProps, t as BrowserReplay } from "../BrowserReplay-xWvRjRAC.js";
4
4
  import { _ as isThinkChatActivityPart } from "../ThinkChatToolParts-Cv4o7j6F.js";
5
- import { a as ThinkChatToolRenderers, c as renderThinkChatActivityPart, i as ThinkChatToolRenderer, n as ThinkChatActivityProps, o as createThinkChatActivityRenderer, r as ThinkChatActivityRendererOptions, s as describeThinkChatActivityPart, t as ThinkChatActivity } from "../ThinkChatActivity-Cuk28Js_.js";
5
+ import { a as ThinkChatToolRenderers, c as renderThinkChatActivityPart, i as ThinkChatToolRenderer, n as ThinkChatActivityProps, o as createThinkChatActivityRenderer, r as ThinkChatActivityRendererOptions, s as describeThinkChatActivityPart, t as ThinkChatActivity } from "../ThinkChatActivity-m9orbdeI.js";
6
6
  import { n as ThinkChatValue } from "../ThinkChatContext-C3Eb1Xpi.js";
7
7
  import { FileInfo, UseWorkspaceOptions, WorkspaceExplorer, WorkspaceExplorerProps, WorkspaceFileState, WorkspaceInfo, WorkspaceSource, WorkspaceState, inferWorkspaceLanguage, useWorkspace } from "./WorkspaceExplorer.js";
8
8
  import * as React from "react";
@@ -10,6 +10,7 @@ import { UIMessage } from "ai";
10
10
  import { UseAgentOptions } from "agents/react";
11
11
  import { PendingApproval, Think } from "@cloudflare/think";
12
12
  import { AgentStub } from "agents/client";
13
+ import { UseVoiceInputReturn } from "@cloudflare/voice/react";
13
14
 
14
15
  //#region src/components/ThinkChat.d.ts
15
16
  interface ThinkChatProviderProps<State = unknown, MessageT extends UIMessage = UIMessage> {
@@ -32,6 +33,10 @@ interface ThinkChatMessagesProps<MessageT extends UIMessage = UIMessage> extends
32
33
  emptyState?: React.ReactNode;
33
34
  /** Extra controls rendered inside the composer, next to the send button. */
34
35
  composerTools?: React.ReactNode;
36
+ /** Whole upstream `useVoiceInput` result for the built-in microphone recorder. */
37
+ voiceInput?: UseVoiceInputReturn;
38
+ /** Host prerequisite run before the built-in recorder calls `voiceInput.start()`. */
39
+ beforeVoiceStart?: AgentChatProps<MessageT>["beforeVoiceStart"];
35
40
  /** Optional product state rendered in the PromptInput header, such as an Activity. */
36
41
  composerHeader?: AgentChatProps<MessageT>["composerHeader"];
37
42
  /** Extra rows rendered in the built-in plus menu after files. */
@@ -1,3 +1,3 @@
1
1
  import { _ as isThinkChatActivityPart } from "../ThinkChatToolParts-Cv4o7j6F.js";
2
- import { a as ThinkChatToolRenderers, c as renderThinkChatActivityPart, i as ThinkChatToolRenderer, n as ThinkChatActivityProps, o as createThinkChatActivityRenderer, r as ThinkChatActivityRendererOptions, s as describeThinkChatActivityPart, t as ThinkChatActivity } from "../ThinkChatActivity-Cuk28Js_.js";
2
+ import { a as ThinkChatToolRenderers, c as renderThinkChatActivityPart, i as ThinkChatToolRenderer, n as ThinkChatActivityProps, o as createThinkChatActivityRenderer, r as ThinkChatActivityRendererOptions, s as describeThinkChatActivityPart, t as ThinkChatActivity } from "../ThinkChatActivity-m9orbdeI.js";
3
3
  export { ThinkChatActivity, ThinkChatActivityProps, ThinkChatActivityRendererOptions, ThinkChatToolRenderer, ThinkChatToolRenderers, createThinkChatActivityRenderer, describeThinkChatActivityPart, isThinkChatActivityPart, renderThinkChatActivityPart };
@@ -1,4 +1,6 @@
1
1
  .prompt-input {
2
+ container: prompt-input / inline-size;
3
+ position: relative;
2
4
  display: flex;
3
5
  flex-direction: column;
4
6
  gap: var(--sigvelo-spacing-1-5);
@@ -17,6 +19,55 @@
17
19
  box-shadow var(--sigvelo-duration-fast) var(--sigvelo-ease-standard);
18
20
  }
19
21
 
22
+ .prompt-input__recording {
23
+ display: flex;
24
+ align-items: center;
25
+ gap: var(--sigvelo-spacing-2);
26
+ flex: 1;
27
+ min-width: 0;
28
+ }
29
+
30
+ .prompt-input__recording-add {
31
+ flex: 0 0 auto;
32
+ }
33
+
34
+ .prompt-input__recording-waveform {
35
+ flex: 1 1 auto;
36
+ min-inline-size: 0;
37
+ block-size: var(--sigvelo-spacing-4);
38
+ color: var(--sigvelo-color-text);
39
+ }
40
+
41
+ .prompt-input__recording-waveform canvas {
42
+ display: block;
43
+ inline-size: 100%;
44
+ block-size: 100%;
45
+ }
46
+
47
+ .prompt-input__recording-time {
48
+ min-inline-size: 2.5rem;
49
+ color: var(--sigvelo-color-text-muted);
50
+ font-variant-numeric: tabular-nums;
51
+ font-size: var(--sigvelo-text-sm);
52
+ text-align: end;
53
+ }
54
+
55
+ .prompt-input__recording-button {
56
+ flex: 0 0 auto;
57
+ border-radius: var(--sigvelo-radius-full);
58
+ }
59
+
60
+ .prompt-input__tools:has(.prompt-input__recording) {
61
+ flex: 1;
62
+ flex-wrap: nowrap;
63
+ width: 100%;
64
+ }
65
+
66
+ .prompt-input__tools:has(.prompt-input__recording) > :not(.prompt-input__recording),
67
+ .prompt-input__footer:has(.prompt-input__recording) > :not(.prompt-input__tools) {
68
+ display: none;
69
+ }
70
+
20
71
  .prompt-input__file-input {
21
72
  position: fixed;
22
73
  inline-size: 1px;
@@ -288,6 +339,13 @@
288
339
  gap: 0.25rem;
289
340
  }
290
341
 
342
+ .prompt-input__permissions-icon {
343
+ display: inline-flex;
344
+ align-items: center;
345
+ justify-content: center;
346
+ flex: 0 0 auto;
347
+ }
348
+
291
349
  .prompt-input__permissions-trigger:hover,
292
350
  .prompt-input__permissions-trigger[data-popup-open] {
293
351
  background-color: color-mix(in oklab, transparent, var(--sigvelo-color-text) 5%);
@@ -314,6 +372,38 @@
314
372
  white-space: nowrap;
315
373
  }
316
374
 
375
+ @container prompt-input (max-width: 36rem) {
376
+ .prompt-input__footer {
377
+ gap: var(--sigvelo-spacing-1);
378
+ }
379
+
380
+ .prompt-input__tools {
381
+ min-width: 0;
382
+ flex-wrap: nowrap;
383
+ }
384
+
385
+ .prompt-input__button[data-responsive-label] {
386
+ inline-size: 2.75rem;
387
+ min-block-size: 2.75rem;
388
+ padding-inline: 0;
389
+ border-radius: var(--sigvelo-radius-full);
390
+ }
391
+
392
+ .prompt-input__button[data-responsive-label] > span,
393
+ .prompt-input__permissions-current,
394
+ .prompt-input__permissions-chevron {
395
+ display: none;
396
+ }
397
+
398
+ .prompt-input__permissions-trigger {
399
+ inline-size: 2.75rem;
400
+ min-block-size: 2.75rem;
401
+ padding-inline: 0;
402
+ justify-content: center;
403
+ border-radius: var(--sigvelo-radius-full);
404
+ }
405
+ }
406
+
317
407
  .prompt-input__permissions-current-option:not([data-active]) {
318
408
  visibility: hidden;
319
409
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcp-b/react-components",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
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": {
@@ -53,7 +53,7 @@
53
53
  "lucide-react": "^1.23.0",
54
54
  "shiki": "^3.23.0",
55
55
  "streamdown": "^2.5.0",
56
- "@mcp-b/design-tokens": "0.13.0"
56
+ "@mcp-b/design-tokens": "0.14.0"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@ai-sdk/react": "^3.0.222",