@mcp-b/react-components 0.3.0 → 0.4.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/attachment-preview-ChtC3HiU.js +25 -0
- package/dist/components/AgentChat.d.ts +38 -1
- package/dist/components/AgentChat.js +135 -39
- package/dist/components/AgentConnection.js +1 -1
- package/dist/components/BrowserLiveView.js +1 -1
- package/dist/components/Context.d.ts +28 -27
- package/dist/components/Context.js +48 -47
- package/dist/components/Message.d.ts +16 -2
- package/dist/components/Message.js +36 -2
- package/dist/components/PricingPage.js +1 -1
- package/dist/components/PromptInput.d.ts +532 -2
- package/dist/components/PromptInput.js +153 -15
- package/dist/components/Suggestion.d.ts +43 -0
- package/dist/components/Suggestion.js +39 -0
- package/dist/components/ThinkChat.d.ts +22 -1
- package/dist/components/ThinkChat.js +85 -17
- package/dist/styles/agent-chat.css +19 -0
- package/dist/styles/attachment-preview.css +21 -0
- package/dist/styles/context.css +9 -17
- package/dist/styles/index.css +2 -0
- package/dist/styles/message.css +48 -0
- package/dist/styles/prompt-input.css +172 -0
- package/dist/styles/suggestions.css +21 -0
- package/package.json +2 -2
- package/dist/PromptInput-Dzn-fOPc.d.ts +0 -444
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { t as cx } from "../class-names-9UtZwb0J.js";
|
|
2
|
-
import { A as SpinnerIcon, C as MonitorIcon, D as PlusIcon, N as WarningIcon, T as PencilIcon, i as ArrowUpIcon, j as SquareIcon, v as ImageIcon } from "../icons-DQC4Zw5R.js";
|
|
2
|
+
import { A as SpinnerIcon, C as MonitorIcon, D as PlusIcon, F as XIcon, N as WarningIcon, T as PencilIcon, h as FileIcon, i as ArrowUpIcon, j as SquareIcon, s as ChevronDownIcon, v as ImageIcon } from "../icons-DQC4Zw5R.js";
|
|
3
3
|
import { Button } from "./Button.js";
|
|
4
4
|
import { Tooltip, TooltipPopup, TooltipPortal, TooltipPositioner, TooltipTrigger } from "./Tooltip.js";
|
|
5
|
+
import { t as AttachmentPreview } from "../attachment-preview-ChtC3HiU.js";
|
|
5
6
|
import { Combobox, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxList, ComboboxSeparator } from "./Combobox.js";
|
|
6
|
-
import { MenuItem, MenuPopup, MenuPortal, MenuPositioner, MenuRoot, MenuTrigger } from "./Menu.js";
|
|
7
|
+
import { MenuItem, MenuPopup, MenuPortal, MenuPositioner, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuRoot, MenuTrigger } from "./Menu.js";
|
|
7
8
|
import { Select, SelectContent, SelectItem, SelectPortal, SelectPositioner, SelectTrigger, SelectValue } from "./Select.js";
|
|
8
9
|
import { Tab, TabPanel, TabsList } from "./Tabs.js";
|
|
9
10
|
import * as React from "react";
|
|
@@ -339,6 +340,59 @@ function PromptInputHeader({ className, children, ref, ...props }) {
|
|
|
339
340
|
children
|
|
340
341
|
});
|
|
341
342
|
}
|
|
343
|
+
/**
|
|
344
|
+
* One pending attachment chip: image files show a thumbnail, everything else
|
|
345
|
+
* a file icon and name. The corner button removes it from the pending list.
|
|
346
|
+
*/
|
|
347
|
+
function PromptInputAttachment({ file, className, ref, ...props }) {
|
|
348
|
+
const attachments = usePromptInputAttachments();
|
|
349
|
+
const isImage = file.mediaType.startsWith("image/");
|
|
350
|
+
const name = file.filename ?? "attachment";
|
|
351
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
352
|
+
ref,
|
|
353
|
+
className: cx("prompt-input__attachment", className),
|
|
354
|
+
"data-image": isImage || void 0,
|
|
355
|
+
...props,
|
|
356
|
+
children: [isImage ? /* @__PURE__ */ jsx(AttachmentPreview, {
|
|
357
|
+
url: file.url,
|
|
358
|
+
filename: file.filename,
|
|
359
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
360
|
+
type: "button",
|
|
361
|
+
className: "prompt-input__attachment-zoom",
|
|
362
|
+
"aria-label": `Preview ${name}`,
|
|
363
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
364
|
+
className: "prompt-input__attachment-thumb",
|
|
365
|
+
src: file.url,
|
|
366
|
+
alt: ""
|
|
367
|
+
})
|
|
368
|
+
})
|
|
369
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(FileIcon, { className: "prompt-input__attachment-icon" }), /* @__PURE__ */ jsx("span", {
|
|
370
|
+
className: "prompt-input__attachment-name",
|
|
371
|
+
children: name
|
|
372
|
+
})] }), /* @__PURE__ */ jsx("button", {
|
|
373
|
+
type: "button",
|
|
374
|
+
className: "prompt-input__attachment-remove",
|
|
375
|
+
"aria-label": `Remove ${name}`,
|
|
376
|
+
onClick: () => attachments.remove(file.id),
|
|
377
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: 12 })
|
|
378
|
+
})]
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* The pending-attachment chip row. Renders nothing while the list is empty,
|
|
383
|
+
* so it can sit in the composer unconditionally — files added via the file
|
|
384
|
+
* dialog, paste, or drop appear here until submit sends and clears them.
|
|
385
|
+
*/
|
|
386
|
+
function PromptInputAttachments({ className, children, ref, ...props }) {
|
|
387
|
+
const attachments = usePromptInputAttachments();
|
|
388
|
+
if (attachments.files.length === 0) return null;
|
|
389
|
+
return /* @__PURE__ */ jsx("div", {
|
|
390
|
+
ref,
|
|
391
|
+
className: cx("prompt-input__attachments", className),
|
|
392
|
+
...props,
|
|
393
|
+
children: attachments.files.map((file) => /* @__PURE__ */ jsx(React.Fragment, { children: children ? children(file) : /* @__PURE__ */ jsx(PromptInputAttachment, { file }) }, file.id))
|
|
394
|
+
});
|
|
395
|
+
}
|
|
342
396
|
function PromptInputBody({ className, children, ref, ...props }) {
|
|
343
397
|
return /* @__PURE__ */ jsx("div", {
|
|
344
398
|
ref,
|
|
@@ -525,6 +579,60 @@ function PromptInputActionAddScreenshot({ label = "Take screenshot", onClick, ch
|
|
|
525
579
|
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MonitorIcon, { className: "prompt-input__action-menu-icon" }), label] })
|
|
526
580
|
});
|
|
527
581
|
}
|
|
582
|
+
const PERMISSION_MODES = [{
|
|
583
|
+
value: "ask",
|
|
584
|
+
label: "Ask for approval",
|
|
585
|
+
description: "Pause and ask before sensitive actions run"
|
|
586
|
+
}, {
|
|
587
|
+
value: "bypass",
|
|
588
|
+
label: "Bypass permissions",
|
|
589
|
+
description: "Approve every request automatically"
|
|
590
|
+
}];
|
|
591
|
+
/**
|
|
592
|
+
* Footer control for choosing how approval requests are handled — ask each
|
|
593
|
+
* time, or bypass. Purely a picker: the auto-approve behavior lives with
|
|
594
|
+
* whoever owns the approvals (`AgentChat` wires both ends).
|
|
595
|
+
*/
|
|
596
|
+
function PromptInputPermissionSelector({ value, onValueChange, question = "How should agent actions be approved?" }) {
|
|
597
|
+
return /* @__PURE__ */ jsxs(MenuRoot, { children: [/* @__PURE__ */ jsx(MenuTrigger, { render: /* @__PURE__ */ jsxs(PromptInputButton, {
|
|
598
|
+
size: "sm",
|
|
599
|
+
className: "prompt-input__permissions-trigger",
|
|
600
|
+
"data-mode": value,
|
|
601
|
+
"aria-label": (PERMISSION_MODES.find((mode) => mode.value === value) ?? PERMISSION_MODES[0]).label,
|
|
602
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
603
|
+
className: "prompt-input__permissions-current",
|
|
604
|
+
children: PERMISSION_MODES.map((mode) => /* @__PURE__ */ jsxs("span", {
|
|
605
|
+
className: "prompt-input__permissions-current-option",
|
|
606
|
+
"data-active": mode.value === value || void 0,
|
|
607
|
+
children: [mode.value === "bypass" ? /* @__PURE__ */ jsx(WarningIcon, { size: 14 }) : null, mode.label]
|
|
608
|
+
}, mode.value))
|
|
609
|
+
}), /* @__PURE__ */ jsx(ChevronDownIcon, { size: 12 })]
|
|
610
|
+
}) }), /* @__PURE__ */ jsx(MenuPortal, { children: /* @__PURE__ */ jsx(MenuPositioner, { children: /* @__PURE__ */ jsxs(MenuPopup, {
|
|
611
|
+
className: "prompt-input__permissions-menu",
|
|
612
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
613
|
+
className: "prompt-input__permissions-question",
|
|
614
|
+
children: question
|
|
615
|
+
}), /* @__PURE__ */ jsx(MenuRadioGroup, {
|
|
616
|
+
value,
|
|
617
|
+
onValueChange: (mode) => onValueChange(mode),
|
|
618
|
+
children: PERMISSION_MODES.map((mode) => /* @__PURE__ */ jsxs(MenuRadioItem, {
|
|
619
|
+
value: mode.value,
|
|
620
|
+
closeOnClick: true,
|
|
621
|
+
className: "prompt-input__permissions-option",
|
|
622
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
623
|
+
className: "prompt-input__permissions-option-text",
|
|
624
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
625
|
+
className: "prompt-input__permissions-option-label",
|
|
626
|
+
children: mode.label
|
|
627
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
628
|
+
className: "prompt-input__permissions-option-description",
|
|
629
|
+
children: mode.description
|
|
630
|
+
})]
|
|
631
|
+
}), /* @__PURE__ */ jsx(MenuRadioItemIndicator, { className: "prompt-input__permissions-check" })]
|
|
632
|
+
}, mode.value))
|
|
633
|
+
})]
|
|
634
|
+
}) }) })] });
|
|
635
|
+
}
|
|
528
636
|
function PromptInputSubmit({ className, status = "ready", onStop, variant = "normal", color = "primary", size = "icon", onClick, children, label, ref, ...props }) {
|
|
529
637
|
const isStopping = status === "submitted" || status === "streaming";
|
|
530
638
|
const resolvedLabel = label ?? defaultSubmitLabel(status);
|
|
@@ -549,12 +657,22 @@ function PromptInputSubmit({ className, status = "ready", onStop, variant = "nor
|
|
|
549
657
|
children: children ?? defaultSubmitIcon(status)
|
|
550
658
|
});
|
|
551
659
|
}
|
|
660
|
+
function getPausedExecution(part) {
|
|
661
|
+
if (part.state !== "output-available") return void 0;
|
|
662
|
+
const output = part.output;
|
|
663
|
+
return output?.status === "paused" && typeof output.executionId === "string" ? output : void 0;
|
|
664
|
+
}
|
|
665
|
+
/** The id a response must carry: the approval id, or the paused run's executionId. */
|
|
666
|
+
function getApprovalRequestId(part) {
|
|
667
|
+
if (part.state === "approval-requested") return part.approval.id;
|
|
668
|
+
return getPausedExecution(part)?.executionId;
|
|
669
|
+
}
|
|
552
670
|
/** Derives pending tool approvals straight from the messages — no app-owned shapes. */
|
|
553
|
-
function getPendingToolApprovals(messages) {
|
|
671
|
+
function getPendingToolApprovals(messages, { pausedExecutions = false } = {}) {
|
|
554
672
|
const pending = [];
|
|
555
673
|
messages.forEach((message, messageIndex) => {
|
|
556
674
|
message.parts.forEach((part, partIndex) => {
|
|
557
|
-
if (isToolOrDynamicToolUIPart(part) && part.state === "approval-requested") pending.push({
|
|
675
|
+
if (isToolOrDynamicToolUIPart(part) && (part.state === "approval-requested" || pausedExecutions && getPausedExecution(part))) pending.push({
|
|
558
676
|
message,
|
|
559
677
|
messageIndex,
|
|
560
678
|
part,
|
|
@@ -575,30 +693,33 @@ function formatApprovalValue(value) {
|
|
|
575
693
|
* sends the text as the native approval `reason`. Submit (or Enter) sends the
|
|
576
694
|
* current selection — approve by default.
|
|
577
695
|
*/
|
|
578
|
-
function PromptInputApproval({ part, onRespond, onSkip, question, count, className, ref, ...props }) {
|
|
696
|
+
function PromptInputApproval({ part, onRespond, executionApprovals, onSkip, question, count, className, ref, ...props }) {
|
|
579
697
|
const [selected, setSelected] = React.useState("approve");
|
|
580
698
|
const [reason, setReason] = React.useState("");
|
|
581
699
|
const [busy, setBusy] = React.useState(false);
|
|
582
700
|
const yesRef = React.useRef(null);
|
|
583
701
|
const reasonRef = React.useRef(null);
|
|
584
702
|
const approval = part.state === "approval-requested" ? part.approval : void 0;
|
|
585
|
-
const
|
|
703
|
+
const paused = getPausedExecution(part);
|
|
704
|
+
const approvalId = approval?.id ?? paused?.executionId;
|
|
586
705
|
React.useEffect(() => {
|
|
587
706
|
setSelected("approve");
|
|
588
707
|
setReason("");
|
|
589
708
|
setBusy(false);
|
|
590
709
|
}, [approvalId]);
|
|
591
|
-
if (!
|
|
710
|
+
if (approval ? !onRespond : !(paused && executionApprovals)) return null;
|
|
592
711
|
const toolName = getToolName(part);
|
|
593
712
|
const respond = (approved) => {
|
|
594
713
|
if (busy) return;
|
|
595
714
|
setBusy(true);
|
|
596
715
|
const trimmed = reason.trim();
|
|
597
|
-
|
|
716
|
+
const denyReason = !approved && trimmed ? trimmed : void 0;
|
|
717
|
+
if (approval) onRespond?.({
|
|
598
718
|
id: approval.id,
|
|
599
719
|
approved,
|
|
600
|
-
reason:
|
|
720
|
+
reason: denyReason
|
|
601
721
|
});
|
|
722
|
+
else if (paused) approved ? executionApprovals?.approve(paused.executionId) : executionApprovals?.reject(paused.executionId, denyReason);
|
|
602
723
|
};
|
|
603
724
|
const submit = () => respond(selected === "approve");
|
|
604
725
|
const input = "input" in part ? part.input : void 0;
|
|
@@ -618,16 +739,33 @@ function PromptInputApproval({ part, onRespond, onSkip, question, count, classNa
|
|
|
618
739
|
children: [
|
|
619
740
|
/* @__PURE__ */ jsxs("div", {
|
|
620
741
|
className: "prompt-input__approval-question",
|
|
621
|
-
children: [/* @__PURE__ */ jsx("span", { children: question ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
742
|
+
children: [/* @__PURE__ */ jsx("span", { children: question ?? (paused ? /* @__PURE__ */ jsx(Fragment, { children: "The agent's code is paused on these calls. Allow it to continue?" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
622
743
|
"Do you want to allow the agent to run ",
|
|
623
744
|
/* @__PURE__ */ jsx("code", { children: toolName }),
|
|
624
745
|
"?"
|
|
625
|
-
] }) }), count && count > 1 ? /* @__PURE__ */ jsxs("span", {
|
|
746
|
+
] })) }), count && count > 1 ? /* @__PURE__ */ jsxs("span", {
|
|
626
747
|
className: "prompt-input__approval-count",
|
|
627
748
|
children: ["1 of ", count]
|
|
628
749
|
}) : null]
|
|
629
750
|
}),
|
|
630
|
-
|
|
751
|
+
paused ? /* @__PURE__ */ jsx("div", {
|
|
752
|
+
className: "prompt-input__approval-preview",
|
|
753
|
+
children: paused.pending.map((action) => /* @__PURE__ */ jsxs("div", {
|
|
754
|
+
className: "prompt-input__approval-preview-row",
|
|
755
|
+
children: [
|
|
756
|
+
/* @__PURE__ */ jsxs("span", {
|
|
757
|
+
className: "prompt-input__approval-preview-key",
|
|
758
|
+
children: [
|
|
759
|
+
action.connector,
|
|
760
|
+
".",
|
|
761
|
+
action.method
|
|
762
|
+
]
|
|
763
|
+
}),
|
|
764
|
+
" ",
|
|
765
|
+
action.args === void 0 ? null : formatApprovalValue(action.args)
|
|
766
|
+
]
|
|
767
|
+
}, action.seq))
|
|
768
|
+
}) : input !== void 0 ? /* @__PURE__ */ jsx("div", {
|
|
631
769
|
className: "prompt-input__approval-preview",
|
|
632
770
|
children: inputEntries ? inputEntries.map(([key, value]) => /* @__PURE__ */ jsxs("div", {
|
|
633
771
|
className: "prompt-input__approval-preview-row",
|
|
@@ -844,11 +982,11 @@ function fileToPart(file) {
|
|
|
844
982
|
};
|
|
845
983
|
}
|
|
846
984
|
function revokeFileUrls(files) {
|
|
847
|
-
for (const file of files) if (file.url
|
|
985
|
+
for (const file of files) if (file.url.startsWith("blob:")) URL.revokeObjectURL(file.url);
|
|
848
986
|
}
|
|
849
987
|
async function resolveSubmittedFile(file) {
|
|
850
988
|
const { id: _id, ...part } = file;
|
|
851
|
-
if (!part.url
|
|
989
|
+
if (!part.url.startsWith("blob:")) return part;
|
|
852
990
|
const dataUrl = await blobUrlToDataUrl(part.url);
|
|
853
991
|
return {
|
|
854
992
|
...part,
|
|
@@ -909,4 +1047,4 @@ function createId() {
|
|
|
909
1047
|
return globalThis.crypto?.randomUUID?.() ?? `prompt-input-${Date.now()}-${Math.random()}`;
|
|
910
1048
|
}
|
|
911
1049
|
//#endregion
|
|
912
|
-
export { PromptInput, PromptInputActionAddAttachments, PromptInputActionAddScreenshot, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuItem, PromptInputActionMenuTrigger, PromptInputApproval, PromptInputBody, PromptInputButton, PromptInputCommand, PromptInputCommandEmpty, PromptInputCommandGroup, PromptInputCommandInput, PromptInputCommandItem, PromptInputCommandList, PromptInputCommandSeparator, PromptInputFooter, PromptInputHeader, PromptInputHoverCard, PromptInputHoverCardContent, PromptInputHoverCardTrigger, PromptInputProvider, PromptInputSelect, PromptInputSelectContent, PromptInputSelectItem, PromptInputSelectTrigger, PromptInputSelectValue, PromptInputSubmit, PromptInputTab, PromptInputTabBody, PromptInputTabItem, PromptInputTabLabel, PromptInputTabsList, PromptInputTextarea, PromptInputTools, getPendingToolApprovals, usePromptInputAttachments, usePromptInputController, usePromptInputReferencedSources, useProviderAttachments };
|
|
1050
|
+
export { PromptInput, PromptInputActionAddAttachments, PromptInputActionAddScreenshot, PromptInputActionMenu, PromptInputActionMenuContent, PromptInputActionMenuItem, PromptInputActionMenuTrigger, PromptInputApproval, PromptInputAttachment, PromptInputAttachments, PromptInputBody, PromptInputButton, PromptInputCommand, PromptInputCommandEmpty, PromptInputCommandGroup, PromptInputCommandInput, PromptInputCommandItem, PromptInputCommandList, PromptInputCommandSeparator, PromptInputFooter, PromptInputHeader, PromptInputHoverCard, PromptInputHoverCardContent, PromptInputHoverCardTrigger, PromptInputPermissionSelector, PromptInputProvider, PromptInputSelect, PromptInputSelectContent, PromptInputSelectItem, PromptInputSelectTrigger, PromptInputSelectValue, PromptInputSubmit, PromptInputTab, PromptInputTabBody, PromptInputTabItem, PromptInputTabLabel, PromptInputTabsList, PromptInputTextarea, PromptInputTools, getApprovalRequestId, getPendingToolApprovals, usePromptInputAttachments, usePromptInputController, usePromptInputReferencedSources, useProviderAttachments };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { r as ButtonProps } from "../Button-Ct5I6aTu.js";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
//#region src/components/Suggestion.d.ts
|
|
5
|
+
interface SuggestionsProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
6
|
+
/**
|
|
7
|
+
* A horizontally scrollable row of prompt suggestions, typically shown above
|
|
8
|
+
* the composer or in the empty state. Children are {@link Suggestion} chips.
|
|
9
|
+
*
|
|
10
|
+
* Ported from AI Elements' Suggestions. Sigvelo changes: design-system Button
|
|
11
|
+
* chips and token-based styling; scrollbars hidden, scroll by wheel/touch.
|
|
12
|
+
*/
|
|
13
|
+
declare function Suggestions({
|
|
14
|
+
className,
|
|
15
|
+
children,
|
|
16
|
+
ref,
|
|
17
|
+
...props
|
|
18
|
+
}: SuggestionsProps & {
|
|
19
|
+
ref?: React.Ref<HTMLDivElement>;
|
|
20
|
+
}): React.JSX.Element;
|
|
21
|
+
interface SuggestionProps extends Omit<ButtonProps, "onClick" | "className"> {
|
|
22
|
+
className?: string;
|
|
23
|
+
/** The suggestion text — the chip label and the `onClick` payload. */
|
|
24
|
+
suggestion: string;
|
|
25
|
+
/** Called with the suggestion text; typically feeds `sendMessage`. */
|
|
26
|
+
onClick?: (suggestion: string) => void;
|
|
27
|
+
}
|
|
28
|
+
/** One pill-shaped suggestion chip. Renders `suggestion` unless children are given. */
|
|
29
|
+
declare function Suggestion({
|
|
30
|
+
className,
|
|
31
|
+
suggestion,
|
|
32
|
+
onClick,
|
|
33
|
+
variant,
|
|
34
|
+
color,
|
|
35
|
+
size,
|
|
36
|
+
children,
|
|
37
|
+
ref,
|
|
38
|
+
...props
|
|
39
|
+
}: SuggestionProps & {
|
|
40
|
+
ref?: React.Ref<HTMLButtonElement>;
|
|
41
|
+
}): React.JSX.Element;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { Suggestion, SuggestionProps, Suggestions, SuggestionsProps };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { t as cx } from "../class-names-9UtZwb0J.js";
|
|
2
|
+
import { Button } from "./Button.js";
|
|
3
|
+
import "react";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
//#region src/components/Suggestion.tsx
|
|
6
|
+
/**
|
|
7
|
+
* A horizontally scrollable row of prompt suggestions, typically shown above
|
|
8
|
+
* the composer or in the empty state. Children are {@link Suggestion} chips.
|
|
9
|
+
*
|
|
10
|
+
* Ported from AI Elements' Suggestions. Sigvelo changes: design-system Button
|
|
11
|
+
* chips and token-based styling; scrollbars hidden, scroll by wheel/touch.
|
|
12
|
+
*/
|
|
13
|
+
function Suggestions({ className, children, ref, ...props }) {
|
|
14
|
+
return /* @__PURE__ */ jsx("div", {
|
|
15
|
+
ref,
|
|
16
|
+
className: cx("suggestions", className),
|
|
17
|
+
...props,
|
|
18
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
19
|
+
className: "suggestions__row",
|
|
20
|
+
children
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/** One pill-shaped suggestion chip. Renders `suggestion` unless children are given. */
|
|
25
|
+
function Suggestion({ className, suggestion, onClick, variant = "outline", color = "neutral", size = "sm", children, ref, ...props }) {
|
|
26
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
27
|
+
ref,
|
|
28
|
+
type: "button",
|
|
29
|
+
variant,
|
|
30
|
+
color,
|
|
31
|
+
size,
|
|
32
|
+
className: cx("suggestions__chip", className),
|
|
33
|
+
onClick: () => onClick?.(suggestion),
|
|
34
|
+
...props,
|
|
35
|
+
children: children ?? suggestion
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { Suggestion, Suggestions };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ExecutionApprovals } from "./PromptInput.js";
|
|
1
2
|
import { AgentChatProps, AgentChatRenderPartContext } from "./AgentChat.js";
|
|
2
3
|
import { BrowserLiveView, BrowserLiveViewProps } from "./BrowserLiveView.js";
|
|
3
4
|
import { BrowserReplay, BrowserReplayProps } from "./BrowserReplay.js";
|
|
@@ -34,8 +35,26 @@ interface ThinkChatMessagesProps extends ThinkChatActivityRendererOptions {
|
|
|
34
35
|
placeholder?: string;
|
|
35
36
|
/** Rendered after each message (e.g. per-message actions). */
|
|
36
37
|
renderMessageAfter?: AgentChatProps["renderMessageAfter"];
|
|
38
|
+
/** Prompt suggestions shown above the composer; clicking one sends it. */
|
|
39
|
+
suggestions?: AgentChatProps["suggestions"];
|
|
40
|
+
/**
|
|
41
|
+
* Overrides the built-in connection notice (derived from the hook's
|
|
42
|
+
* `isRecovering`/`connectionError`) shown between transcript and composer.
|
|
43
|
+
*/
|
|
44
|
+
notice?: AgentChatProps["notice"];
|
|
37
45
|
/** Full replacement for part rendering; wins over excludeTools/toolRenderers. */
|
|
38
46
|
renderPart?: AgentChatProps["renderPart"];
|
|
47
|
+
/**
|
|
48
|
+
* Surfaces paused codemode executions in the composer's approval UI —
|
|
49
|
+
* the same confirmation tool approvals use. See {@link ExecutionApprovals}.
|
|
50
|
+
*/
|
|
51
|
+
executionApprovals?: ExecutionApprovals;
|
|
52
|
+
/**
|
|
53
|
+
* Shows a Context ring in the composer footer — how much of the model's
|
|
54
|
+
* window the conversation occupies, from `metadata.usage` on the newest
|
|
55
|
+
* message that reports it.
|
|
56
|
+
*/
|
|
57
|
+
contextWindow?: AgentChatProps["contextWindow"];
|
|
39
58
|
}
|
|
40
59
|
/**
|
|
41
60
|
* Bespoke rendering for one tool inside the activity log. Both hooks fall
|
|
@@ -98,6 +117,8 @@ declare function ThinkChatMessages({
|
|
|
98
117
|
renderPart,
|
|
99
118
|
excludeTools,
|
|
100
119
|
toolRenderers,
|
|
120
|
+
executionApprovals,
|
|
121
|
+
notice,
|
|
101
122
|
...props
|
|
102
123
|
}: ThinkChatMessagesProps): React.JSX.Element;
|
|
103
124
|
/** Agent work (reasoning, tool calls, step boundaries) vs. content shown to the user. */
|
|
@@ -183,4 +204,4 @@ declare const ThinkChat: {
|
|
|
183
204
|
createActivityRenderer: typeof createThinkChatActivityRenderer;
|
|
184
205
|
};
|
|
185
206
|
//#endregion
|
|
186
|
-
export { BrowserLiveView, type BrowserLiveViewProps, BrowserReplay, type BrowserReplayProps, type FileInfo, ThinkChat, ThinkChatActivity, ThinkChatActivityProps, ThinkChatActivityRendererOptions, ThinkChatMessages, ThinkChatMessagesProps, ThinkChatProvider, ThinkChatProviderProps, ThinkChatRoot, ThinkChatRootProps, ThinkChatToolRenderer, ThinkChatToolRenderers, type UseWorkspaceOptions, WorkspaceExplorer, type WorkspaceExplorerProps, type WorkspaceFileState, type WorkspaceInfo, type WorkspaceSource, type WorkspaceState, createThinkChatActivityRenderer, describeThinkChatActivityPart, inferWorkspaceLanguage, isThinkChatActivityPart, renderThinkChatActivityPart, useThinkChat, useWorkspace };
|
|
207
|
+
export { BrowserLiveView, type BrowserLiveViewProps, BrowserReplay, type BrowserReplayProps, type ExecutionApprovals, type FileInfo, ThinkChat, ThinkChatActivity, ThinkChatActivityProps, ThinkChatActivityRendererOptions, ThinkChatMessages, ThinkChatMessagesProps, ThinkChatProvider, ThinkChatProviderProps, ThinkChatRoot, ThinkChatRootProps, ThinkChatToolRenderer, ThinkChatToolRenderers, type UseWorkspaceOptions, WorkspaceExplorer, type WorkspaceExplorerProps, type WorkspaceFileState, type WorkspaceInfo, type WorkspaceSource, type WorkspaceState, createThinkChatActivityRenderer, describeThinkChatActivityPart, inferWorkspaceLanguage, isThinkChatActivityPart, renderThinkChatActivityPart, useThinkChat, useWorkspace };
|
|
@@ -13,7 +13,8 @@ import { useAgent } from "agents/react";
|
|
|
13
13
|
//#region src/components/ThinkChat.tsx
|
|
14
14
|
/**
|
|
15
15
|
* Think-specific chat rendering: interprets a Think agent's UIMessage stream
|
|
16
|
-
* (reasoning + workspace/skill/fetch/browser tool parts) into
|
|
16
|
+
* (reasoning + workspace/skill/fetch/browser/execute tool parts) into
|
|
17
|
+
* Activity blocks.
|
|
17
18
|
*
|
|
18
19
|
* Every shape here mirrors an upstream contract — see docs/think-chat.md
|
|
19
20
|
* ("Upstream source map" and the re-alignment checklist) for where each comes from,
|
|
@@ -55,6 +56,9 @@ const BROWSER_TOOL_NAMES = new Set([
|
|
|
55
56
|
function isBrowserToolPart(part) {
|
|
56
57
|
return isToolUIPart(part) && BROWSER_TOOL_NAMES.has(getToolName(part));
|
|
57
58
|
}
|
|
59
|
+
function isExecuteToolPart(part) {
|
|
60
|
+
return isToolUIPart(part) && getToolName(part) === "execute";
|
|
61
|
+
}
|
|
58
62
|
const ThinkChatContext = React.createContext(null);
|
|
59
63
|
function useThinkChatContext(component) {
|
|
60
64
|
const context = React.useContext(ThinkChatContext);
|
|
@@ -109,13 +113,17 @@ function ThinkChatRoot({ agent: agentOption, name, children }) {
|
|
|
109
113
|
* inside the log) or `excludeTools` + your own `renderPart` (standalone
|
|
110
114
|
* card) — see README "App-specific tools" and the Custom Tool Panel story.
|
|
111
115
|
*/
|
|
112
|
-
function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, ...props }) {
|
|
116
|
+
function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, executionApprovals, notice, ...props }) {
|
|
113
117
|
const chat = useThinkChatContext("ThinkChat.Messages");
|
|
114
118
|
const status = toAgentChatStatus(chat);
|
|
115
119
|
const defaultRenderPart = React.useMemo(() => createThinkChatActivityRenderer({
|
|
116
120
|
excludeTools,
|
|
117
121
|
toolRenderers
|
|
118
122
|
}), [excludeTools, toolRenderers]);
|
|
123
|
+
const connectionNotice = chat.isRecovering ? /* @__PURE__ */ jsx(Fragment, { children: "Reconnecting to the agent…" }) : chat.connectionError ? /* @__PURE__ */ jsxs("span", {
|
|
124
|
+
"data-tone": "danger",
|
|
125
|
+
children: ["Connection lost", chat.connectionError.reason ? ` — ${chat.connectionError.reason}` : ""]
|
|
126
|
+
}) : void 0;
|
|
119
127
|
return /* @__PURE__ */ jsx(AgentChat, {
|
|
120
128
|
...props,
|
|
121
129
|
renderPart: renderPart ?? defaultRenderPart,
|
|
@@ -123,9 +131,15 @@ function ThinkChatMessages({ renderPart, excludeTools, toolRenderers, ...props }
|
|
|
123
131
|
status,
|
|
124
132
|
onStop: chat.stop,
|
|
125
133
|
onApprovalResponse: chat.addToolApprovalResponse,
|
|
126
|
-
|
|
134
|
+
executionApprovals,
|
|
135
|
+
notice: notice ?? connectionNotice,
|
|
136
|
+
onRegenerate: (messageId) => void chat.regenerate({ messageId }),
|
|
137
|
+
onSubmit: ({ text, files }) => {
|
|
127
138
|
const trimmed = text.trim();
|
|
128
|
-
if (trimmed) chat.sendMessage({
|
|
139
|
+
if (trimmed || files.length) chat.sendMessage({
|
|
140
|
+
text: trimmed,
|
|
141
|
+
files
|
|
142
|
+
});
|
|
129
143
|
}
|
|
130
144
|
});
|
|
131
145
|
}
|
|
@@ -177,6 +191,7 @@ function describeThinkChatActivityPart(part, toolRenderers) {
|
|
|
177
191
|
if (isSkillToolPart(part)) return describeSkillToolPart(part);
|
|
178
192
|
if (isFetchToolPart(part)) return describeFetchToolPart(part);
|
|
179
193
|
if (isBrowserToolPart(part)) return describeBrowserToolPart(part);
|
|
194
|
+
if (isExecuteToolPart(part)) return describeExecuteToolPart(part);
|
|
180
195
|
if (isToolUIPart(part)) {
|
|
181
196
|
const name = getToolName(part);
|
|
182
197
|
const verb = isPartFailed(part) ? `${name} failed` : isPartWorking(part) ? `Running ${name}` : `Ran ${name}`;
|
|
@@ -251,6 +266,14 @@ function describeBrowserToolPart(part) {
|
|
|
251
266
|
})();
|
|
252
267
|
return failed ? `${label} · failed` : label;
|
|
253
268
|
}
|
|
269
|
+
function describeExecuteToolPart(part) {
|
|
270
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
271
|
+
if (isPartFailed(part) || output?.status === "error") return "Code failed";
|
|
272
|
+
if (output?.status === "paused") return "Code waiting for approval";
|
|
273
|
+
const label = isPartWorking(part) ? "Running code" : "Ran code";
|
|
274
|
+
const snippet = summarizeToolInput(part.input);
|
|
275
|
+
return snippet ? `${label} · ${snippet}` : label;
|
|
276
|
+
}
|
|
254
277
|
function describeFetchToolPart(part) {
|
|
255
278
|
const target = part.input?.url ?? part.input?.path ?? "";
|
|
256
279
|
const output = part.state === "output-available" ? part.output : void 0;
|
|
@@ -266,6 +289,7 @@ function summarizeActivitySteps(parts) {
|
|
|
266
289
|
let skillParts = 0;
|
|
267
290
|
let fetches = 0;
|
|
268
291
|
let browses = 0;
|
|
292
|
+
let codeRuns = 0;
|
|
269
293
|
for (const part of parts) if (isReasoningUIPart(part)) thoughts++;
|
|
270
294
|
else if (isWorkspaceToolPart(part)) {
|
|
271
295
|
const name = getToolName(part);
|
|
@@ -275,6 +299,7 @@ function summarizeActivitySteps(parts) {
|
|
|
275
299
|
if (typeof part.input?.name === "string") skillNames.add(part.input.name);
|
|
276
300
|
} else if (isFetchToolPart(part)) fetches++;
|
|
277
301
|
else if (isBrowserToolPart(part)) browses++;
|
|
302
|
+
else if (isExecuteToolPart(part)) codeRuns++;
|
|
278
303
|
else if (isToolUIPart(part)) otherTools.add(getToolName(part));
|
|
279
304
|
const count = (name) => byTool.get(name) ?? 0;
|
|
280
305
|
const files = (n) => n === 1 ? "a file" : `${n} files`;
|
|
@@ -292,6 +317,7 @@ function summarizeActivitySteps(parts) {
|
|
|
292
317
|
if (count("list") > 0) segments.push("listed files");
|
|
293
318
|
if (fetches > 0) segments.push(fetches === 1 ? "fetched a URL" : `fetched ${fetches} URLs`);
|
|
294
319
|
if (browses > 0) segments.push("used the browser");
|
|
320
|
+
if (codeRuns > 0) segments.push("ran code");
|
|
295
321
|
if (commands > 0) segments.push(commands === 1 ? "ran a command" : `ran ${commands} commands`);
|
|
296
322
|
for (const tool of otherTools) segments.push(`used ${tool}`);
|
|
297
323
|
if (segments.length === 0) return "Worked";
|
|
@@ -359,6 +385,10 @@ function ThinkChatActivityStep({ text, part, toolRenderers }) {
|
|
|
359
385
|
label: text,
|
|
360
386
|
children: /* @__PURE__ */ jsx(ThinkChatBrowserPanel, { part })
|
|
361
387
|
});
|
|
388
|
+
if (isExecuteToolPart(part)) return /* @__PURE__ */ jsx(ActivityItemGroup, {
|
|
389
|
+
label: text,
|
|
390
|
+
children: /* @__PURE__ */ jsx(ThinkChatExecutePanel, { part })
|
|
391
|
+
});
|
|
362
392
|
return /* @__PURE__ */ jsx(ActivityItem, { children: text });
|
|
363
393
|
}
|
|
364
394
|
function renderDeniedPanel(label, reason) {
|
|
@@ -552,6 +582,50 @@ function findBrowserResultMedia(value, depth = 0) {
|
|
|
552
582
|
visit(value, depth);
|
|
553
583
|
return media;
|
|
554
584
|
}
|
|
585
|
+
function ExecuteCodeView({ code, output, hideResult }) {
|
|
586
|
+
const sections = [
|
|
587
|
+
output && "logs" in output && output.logs?.length ? output.logs.join("\n") : "",
|
|
588
|
+
output?.status === "error" ? output.error : "",
|
|
589
|
+
output?.status === "paused" ? formatPendingActions(output.pending) : "",
|
|
590
|
+
output?.status === "completed" && output.result !== void 0 && !hideResult ? typeof output.result === "string" ? output.result : JSON.stringify(output.result, null, 2) : ""
|
|
591
|
+
].filter(Boolean);
|
|
592
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [code ? /* @__PURE__ */ jsx(CodeBlock, {
|
|
593
|
+
className: "activity__code-block",
|
|
594
|
+
code,
|
|
595
|
+
language: "ts"
|
|
596
|
+
}) : null, sections.length ? /* @__PURE__ */ jsx(ActivityOutput, { children: sections.join("\n\n") }) : null] });
|
|
597
|
+
}
|
|
598
|
+
function formatPendingActions(pending) {
|
|
599
|
+
return pending.map((action) => {
|
|
600
|
+
const args = action.args === void 0 ? "" : JSON.stringify(action.args);
|
|
601
|
+
return `awaiting approval: ${action.connector}.${action.method}(${args})`;
|
|
602
|
+
}).join("\n");
|
|
603
|
+
}
|
|
604
|
+
function executeStatusText(output) {
|
|
605
|
+
if (!output) return void 0;
|
|
606
|
+
return output.status === "paused" ? "paused · needs approval" : output.status;
|
|
607
|
+
}
|
|
608
|
+
/** The codemode execute tool as an activity panel: highlighted TypeScript
|
|
609
|
+
plus logs, result, and the paused pending-approval list. */
|
|
610
|
+
function ThinkChatExecutePanel({ part }) {
|
|
611
|
+
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
612
|
+
label: "Code",
|
|
613
|
+
text: part.errorText,
|
|
614
|
+
status: "error",
|
|
615
|
+
error: true
|
|
616
|
+
});
|
|
617
|
+
if (part.state === "output-denied") return renderDeniedPanel("Code", part.approval.reason);
|
|
618
|
+
const output = part.state === "output-available" ? part.output : void 0;
|
|
619
|
+
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
620
|
+
label: "Code",
|
|
621
|
+
status: executeStatusText(output),
|
|
622
|
+
error: output?.status === "error",
|
|
623
|
+
children: /* @__PURE__ */ jsx(ExecuteCodeView, {
|
|
624
|
+
code: part.input?.code ?? "",
|
|
625
|
+
output
|
|
626
|
+
})
|
|
627
|
+
});
|
|
628
|
+
}
|
|
555
629
|
function ThinkChatBrowserPanel({ part }) {
|
|
556
630
|
const [seenAt] = React.useState(() => Date.now());
|
|
557
631
|
if (part.state === "output-error") return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
@@ -566,12 +640,6 @@ function ThinkChatBrowserPanel({ part }) {
|
|
|
566
640
|
const code = part.input?.code ?? "";
|
|
567
641
|
const media = output?.status === "completed" ? findBrowserResultMedia(output.result) : {};
|
|
568
642
|
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
643
|
const browserView = media.liveViewUrl ? /* @__PURE__ */ jsx(BrowserLiveView, {
|
|
576
644
|
url: media.liveViewUrl,
|
|
577
645
|
expiresAt: seenAt + (media.expiresInMs ?? 300 * 1e3),
|
|
@@ -584,16 +652,16 @@ function ThinkChatBrowserPanel({ part }) {
|
|
|
584
652
|
src: media.screenshot,
|
|
585
653
|
alt: "Browser screenshot"
|
|
586
654
|
}) : null;
|
|
587
|
-
const codeView = /* @__PURE__ */
|
|
588
|
-
className: "activity__code-block",
|
|
655
|
+
const codeView = /* @__PURE__ */ jsx(ExecuteCodeView, {
|
|
589
656
|
code,
|
|
590
|
-
|
|
591
|
-
|
|
657
|
+
output,
|
|
658
|
+
hideResult: hasMedia
|
|
659
|
+
});
|
|
592
660
|
return /* @__PURE__ */ jsx(ActivityPanel, {
|
|
593
661
|
label: "Browser",
|
|
594
|
-
status,
|
|
662
|
+
status: executeStatusText(output),
|
|
595
663
|
error: output?.status === "error",
|
|
596
|
-
children: browserView &&
|
|
664
|
+
children: browserView && code ? /* @__PURE__ */ jsxs(Tabs, {
|
|
597
665
|
className: "activity__browser-tabs",
|
|
598
666
|
defaultValue: "browser",
|
|
599
667
|
children: [
|
|
@@ -616,7 +684,7 @@ function ThinkChatBrowserPanel({ part }) {
|
|
|
616
684
|
children: codeView
|
|
617
685
|
})
|
|
618
686
|
]
|
|
619
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [browserView,
|
|
687
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [browserView, codeView] })
|
|
620
688
|
});
|
|
621
689
|
}
|
|
622
690
|
const output = part.state === "output-available" ? part.output : void 0;
|
|
@@ -19,3 +19,22 @@
|
|
|
19
19
|
padding: 0.75rem;
|
|
20
20
|
background-color: var(--sigvelo-color-surface);
|
|
21
21
|
}
|
|
22
|
+
|
|
23
|
+
/* Transient status pill between transcript and composer (recovery, errors). */
|
|
24
|
+
.agent-chat__notice {
|
|
25
|
+
display: flex;
|
|
26
|
+
align-items: center;
|
|
27
|
+
gap: 0.375rem;
|
|
28
|
+
margin-block-end: 0.5rem;
|
|
29
|
+
font-size: 0.75rem;
|
|
30
|
+
color: var(--sigvelo-color-text-muted);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.agent-chat__notice[data-tone="danger"],
|
|
34
|
+
.agent-chat__notice [data-tone="danger"] {
|
|
35
|
+
color: var(--sigvelo-color-danger-text);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.agent-chat__suggestions {
|
|
39
|
+
margin-block-end: 0.5rem;
|
|
40
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* Click-to-zoom lightbox for image attachments (composer chips + messages).
|
|
2
|
+
Scoped as .dialog__popup.attachment-preview so it beats the base dialog
|
|
3
|
+
sizing regardless of import order. */
|
|
4
|
+
.dialog__popup.attachment-preview {
|
|
5
|
+
width: fit-content;
|
|
6
|
+
max-width: min(90vw, 56rem);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.attachment-preview__image {
|
|
10
|
+
display: block;
|
|
11
|
+
max-width: 100%;
|
|
12
|
+
max-height: 70vh;
|
|
13
|
+
border-radius: var(--sigvelo-radius-md);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.attachment-preview__name {
|
|
17
|
+
margin-block-start: 0.5rem;
|
|
18
|
+
font-size: 0.75rem;
|
|
19
|
+
color: var(--sigvelo-color-text-muted);
|
|
20
|
+
text-align: center;
|
|
21
|
+
}
|