@agent-native/agentkit 0.3.2-nightly-20260927082735 → 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/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +12 -1
- package/dist/adapters/http.js.map +1 -1
- package/dist/client/client.d.ts +36 -2
- package/dist/client/client.d.ts.map +1 -1
- package/dist/client/client.js +123 -1
- package/dist/client/client.js.map +1 -1
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.d.ts.map +1 -1
- package/dist/protocol/index.js.map +1 -1
- package/dist/protocol/validation.d.ts.map +1 -1
- package/dist/protocol/validation.js +4 -0
- package/dist/protocol/validation.js.map +1 -1
- package/dist/react/components.d.ts +18 -3
- package/dist/react/components.d.ts.map +1 -1
- package/dist/react/components.js +662 -123
- package/dist/react/components.js.map +1 -1
- package/dist/react/context.d.ts +55 -2
- package/dist/react/context.d.ts.map +1 -1
- package/dist/react/context.js +40 -4
- package/dist/react/context.js.map +1 -1
- package/dist/react/root.d.ts +4 -2
- package/dist/react/root.d.ts.map +1 -1
- package/dist/react/root.js +8 -2
- package/dist/react/root.js.map +1 -1
- package/dist/react/styles.css +245 -50
- package/package.json +2 -2
package/dist/react/components.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ActionButton, AgentSuggestionBar, IconButton, MessageQueueDrawer, PromptComposer, Surface, TextField, agentSuggestionPrompt, splitMarkdownBlocks, writeClipboardText, } from "@agent-native/toolkit/agentkit";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Dialog, Popover, TextArea } from "@agent-native/toolkit/design-system";
|
|
4
|
+
import { IconActivity, IconAlertCircle, IconArrowDown, IconArrowFork, IconArrowUp, IconBook2, IconBrain, IconChevronLeft, IconChevronRight, IconCircleCheck, IconCode, IconCopy, IconDotsVertical, IconFile, IconFolder, IconFileText, IconCheckbox, IconMail, IconUser, IconPresentation, IconStack2, IconMessageChatbot, IconGitBranch, IconId, IconMessage, IconPlugConnected, IconPlayerPause, IconPlayerPlay, IconPencil, IconRefresh, IconSearch, IconTerminal2, IconChecklist, IconThumbDown, IconThumbUp, IconTool, IconX, } from "@tabler/icons-react";
|
|
5
|
+
import { createContext, Component, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
5
6
|
import ReactMarkdown from "react-markdown";
|
|
6
7
|
import remarkGfm from "remark-gfm";
|
|
7
8
|
import { createAgentKitComposerSubmission, snapshotComposerValue, } from "./composer-submission.js";
|
|
8
9
|
import { inferAgentActivityKind, } from "../protocol/index.js";
|
|
9
10
|
import { useAgentCapability, useAgentConnection, useAgentKit, useAgentKitControl, useAgentKitMutation, useAgentThread, } from "./context.js";
|
|
10
11
|
import { AgentStreamingText } from "./streaming-text.js";
|
|
12
|
+
/** Isolates custom renderers so one extension cannot take down the chat. */
|
|
11
13
|
export class AgentKitErrorBoundary extends Component {
|
|
12
14
|
state = {
|
|
13
15
|
resetKey: this.props.resetKey,
|
|
@@ -55,6 +57,7 @@ function AgentKitRegionSlot({ slot: Slot, threadId, children, }) {
|
|
|
55
57
|
}
|
|
56
58
|
const allowedAgentProtocols = new Set(["http:", "https:", "mailto:", "blob:"]);
|
|
57
59
|
const allowedAgentImageProtocols = new Set(["http:", "https:", "blob:"]);
|
|
60
|
+
/** Keeps an unfinished bold delimiter literal while a streamed message grows. */
|
|
58
61
|
function escapeIncompleteStrongMarkdown(text) {
|
|
59
62
|
const delimiterPositions = new Map();
|
|
60
63
|
let inFence = false;
|
|
@@ -113,6 +116,7 @@ function escapeIncompleteStrongMarkdown(text) {
|
|
|
113
116
|
.sort((left, right) => right.position - left.position)
|
|
114
117
|
.reduce((value, { delimiter, position }) => `${value.slice(0, position)}\\${delimiter[0]}\\${delimiter[1]}${value.slice(position + 2)}`, text);
|
|
115
118
|
}
|
|
119
|
+
/** Rejects executable and embedded-data URLs before they reach a default anchor. */
|
|
116
120
|
export function safeAgentHref(href) {
|
|
117
121
|
if (!href)
|
|
118
122
|
return undefined;
|
|
@@ -140,6 +144,7 @@ function AgentMarkdown({ text }) {
|
|
|
140
144
|
const blocks = useMemo(() => (tail ? [...completedBlocks, tail] : completedBlocks), [completedBlocks, tail]);
|
|
141
145
|
return (_jsx("div", { className: "agentkit-markdown", "data-format": "markdown", children: blocks.map((block, index) => (_jsx(AgentMarkdownBlock, { text: block }, index))) }));
|
|
142
146
|
}
|
|
147
|
+
/** Keeps participant avatars on image-capable, non-executable URL schemes. */
|
|
143
148
|
export function safeAgentImageSrc(src) {
|
|
144
149
|
if (!src)
|
|
145
150
|
return undefined;
|
|
@@ -458,6 +463,17 @@ export function AgentActivityGroup({ runId, afterSequence, throughSequence, excl
|
|
|
458
463
|
const tool = toolMap.get(id);
|
|
459
464
|
return tool ? [toolToActivity(tool)] : [];
|
|
460
465
|
});
|
|
466
|
+
const durableToolResults = items.flatMap((activity) => {
|
|
467
|
+
const tool = toolMap.get(activity.id);
|
|
468
|
+
const Renderer = tool
|
|
469
|
+
? (registry.tools?.[tool.name] ?? slots.tool)
|
|
470
|
+
: undefined;
|
|
471
|
+
return tool && Renderer && tool.status !== "running"
|
|
472
|
+
? [{ tool, Renderer }]
|
|
473
|
+
: [];
|
|
474
|
+
});
|
|
475
|
+
const durableToolResultIds = new Set(durableToolResults.map(({ tool }) => tool.id));
|
|
476
|
+
const activityItems = items.filter((activity) => !durableToolResultIds.has(activity.id));
|
|
461
477
|
const running = items.some((item) => item.status === "running");
|
|
462
478
|
const run = runId ? thread.runs[runId] : undefined;
|
|
463
479
|
const segmentStartedEvent = firstWorkEvents(runEvents).find((event) => sequenceInRange(event.sequence, { afterSequence, throughSequence }));
|
|
@@ -507,7 +523,7 @@ export function AgentActivityGroup({ runId, afterSequence, throughSequence, excl
|
|
|
507
523
|
if (items.length === 0)
|
|
508
524
|
return null;
|
|
509
525
|
const displayGroups = [];
|
|
510
|
-
for (const activity of
|
|
526
|
+
for (const activity of activityItems) {
|
|
511
527
|
const sourceTool = toolMap.get(activity.id);
|
|
512
528
|
const hasCustomToolRenderer = Boolean(sourceTool &&
|
|
513
529
|
!activityMap.has(activity.id) &&
|
|
@@ -525,7 +541,7 @@ export function AgentActivityGroup({ runId, afterSequence, throughSequence, excl
|
|
|
525
541
|
displayGroups.push([activity]);
|
|
526
542
|
}
|
|
527
543
|
}
|
|
528
|
-
const labelsSummary = Array.from(new Set(
|
|
544
|
+
const labelsSummary = Array.from(new Set(activityItems.map((item) => item.label.trim()).filter(Boolean)));
|
|
529
545
|
const remaining = Math.max(0, labelsSummary.length - 2);
|
|
530
546
|
const summary = `${labelsSummary.slice(0, 2).join(", ")}${remaining ? ` +${remaining}` : ""}`;
|
|
531
547
|
const formatDuration = (ms) => formatAgentKitDuration(ms, {
|
|
@@ -542,26 +558,26 @@ export function AgentActivityGroup({ runId, afterSequence, throughSequence, excl
|
|
|
542
558
|
? labels.workedFor.replace("{{duration}}", formatDuration(durationMs))
|
|
543
559
|
: labels.worked
|
|
544
560
|
: summary || labels.activities;
|
|
545
|
-
return (_jsxs("details", { className: "agentkit-activities", "data-running": visiblyRunning ? "true" : undefined, open: open, children: [_jsxs("summary", { className: "agentkit-activities-summary", onClick: (event) => {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
561
|
+
return (_jsxs(_Fragment, { children: [durableToolResults.length ? (_jsx("div", { "data-agentkit-tool-results": "true", children: durableToolResults.map(({ tool, Renderer }) => (_jsx(Renderer, { value: tool, threadId: threadId }, tool.id))) })) : null, displayGroups.length ? (_jsxs("details", { className: "agentkit-activities", "data-running": visiblyRunning ? "true" : undefined, open: open, children: [_jsxs("summary", { className: "agentkit-activities-summary", onClick: (event) => {
|
|
562
|
+
event.preventDefault();
|
|
563
|
+
setOpen((current) => !current);
|
|
564
|
+
}, children: [_jsx(IconActivity, { "aria-hidden": "true", className: "agentkit-icon" }), _jsx("span", { className: "agentkit-activities-label", children: summaryLabel }), !completedRunSummary ? (_jsx("span", { className: "agentkit-activities-count", children: activityItems.length })) : null, _jsx(IconChevronRight, { "aria-hidden": "true", className: "agentkit-icon agentkit-summary-chevron" })] }), _jsx("div", { className: "agentkit-activities-list", children: displayGroups.map((activities) => {
|
|
565
|
+
const activity = activities[0];
|
|
566
|
+
if (activities.length > 1) {
|
|
567
|
+
return (_jsx(RepeatedActivityCluster, { activities: activities, threadId: threadId }, `cluster:${activity.id}`));
|
|
568
|
+
}
|
|
569
|
+
const sourceTool = toolMap.get(activity.id);
|
|
570
|
+
const ToolRenderer = sourceTool
|
|
571
|
+
? (registry.tools?.[sourceTool.name] ?? slots.tool)
|
|
572
|
+
: undefined;
|
|
573
|
+
if (sourceTool && ToolRenderer && !activityMap.has(activity.id)) {
|
|
574
|
+
return (_jsx(ToolRenderer, { value: sourceTool, threadId: threadId }, sourceTool.id));
|
|
575
|
+
}
|
|
576
|
+
const Renderer = registry.activities?.[activity.kind] ??
|
|
577
|
+
slots.activity ??
|
|
578
|
+
AgentActivityItem;
|
|
579
|
+
return (_jsx(Renderer, { value: activity, threadId: threadId }, activity.id));
|
|
580
|
+
}) })] })) : null] }));
|
|
565
581
|
}
|
|
566
582
|
export function AgentCollaborationFeed({ runId, afterSequence, throughSequence, }) {
|
|
567
583
|
const { threadId, slots, registry, labels } = useAgentKit();
|
|
@@ -770,7 +786,91 @@ export function AgentWidgetView({ value: widget, threadId, }) {
|
|
|
770
786
|
const titleId = `${widget.id}-title`;
|
|
771
787
|
return (_jsx("section", { className: "agentkit-widget-shell", "data-widget-kind": widget.kind, "aria-labelledby": widget.title ? titleId : undefined, children: _jsxs(Surface, { className: "agentkit-widget", elevation: "low", padding: "none", children: [widget.title ? _jsx("h3", { id: titleId, children: widget.title }) : null, typeof widget.data === "string" ? _jsx("p", { children: widget.data }) : null, widget.actions?.length ? (_jsx("div", { className: "agentkit-widget-actions", children: widget.actions.map((action) => (_jsx(AgentWidgetActionButton, { widget: widget, action: action, threadId: threadId }, action.id))) })) : null] }) }));
|
|
772
788
|
}
|
|
773
|
-
|
|
789
|
+
function AgentMentionIcon({ icon }) {
|
|
790
|
+
const props = { size: 14, className: "agentkit-mention-icon" };
|
|
791
|
+
switch (icon) {
|
|
792
|
+
case "folder":
|
|
793
|
+
return _jsx(IconFolder, { ...props });
|
|
794
|
+
case "document":
|
|
795
|
+
return _jsx(IconFileText, { ...props });
|
|
796
|
+
case "form":
|
|
797
|
+
return _jsx(IconCheckbox, { ...props });
|
|
798
|
+
case "email":
|
|
799
|
+
return _jsx(IconMail, { ...props });
|
|
800
|
+
case "user":
|
|
801
|
+
return _jsx(IconUser, { ...props });
|
|
802
|
+
case "deck":
|
|
803
|
+
return _jsx(IconPresentation, { ...props });
|
|
804
|
+
case "agent":
|
|
805
|
+
return _jsx(IconMessageChatbot, { ...props });
|
|
806
|
+
case "file":
|
|
807
|
+
return _jsx(IconFile, { ...props });
|
|
808
|
+
default:
|
|
809
|
+
return _jsx(IconStack2, { ...props });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function renderUserMessageText(text) {
|
|
813
|
+
const richMatches = Array.from(text.matchAll(/@\[([^\]|]+)\|([^\]]+)\]/g));
|
|
814
|
+
if (richMatches.length) {
|
|
815
|
+
const parts = [];
|
|
816
|
+
let lastIndex = 0;
|
|
817
|
+
richMatches.forEach((match, index) => {
|
|
818
|
+
const start = match.index ?? 0;
|
|
819
|
+
if (start > lastIndex)
|
|
820
|
+
parts.push(text.slice(lastIndex, start));
|
|
821
|
+
const label = match[1] ?? "";
|
|
822
|
+
parts.push(_jsxs("span", { className: "agentkit-mention", "data-mention-label": label, children: [_jsx(AgentMentionIcon, { icon: match[2] ?? "" }), _jsx("span", { className: "agentkit-mention-label", children: label })] }, `rich-mention:${start}:${index}`));
|
|
823
|
+
lastIndex = start + match[0].length;
|
|
824
|
+
});
|
|
825
|
+
if (lastIndex < text.length)
|
|
826
|
+
parts.push(text.slice(lastIndex));
|
|
827
|
+
return parts;
|
|
828
|
+
}
|
|
829
|
+
const plainMatches = Array.from(text.matchAll(/(^|\s)@(\w+)/g));
|
|
830
|
+
if (!plainMatches.length)
|
|
831
|
+
return [text];
|
|
832
|
+
const parts = [];
|
|
833
|
+
let lastIndex = 0;
|
|
834
|
+
plainMatches.forEach((match, index) => {
|
|
835
|
+
const matchIndex = match.index ?? 0;
|
|
836
|
+
const start = matchIndex + (match[1]?.length ?? 0);
|
|
837
|
+
const end = matchIndex + match[0].length;
|
|
838
|
+
if (start > lastIndex)
|
|
839
|
+
parts.push(text.slice(lastIndex, start));
|
|
840
|
+
const label = match[2] ?? "";
|
|
841
|
+
parts.push(_jsxs("span", { className: "agentkit-mention agentkit-mention--plain", "data-mention-label": label, children: ["@", label] }, `plain-mention:${start}:${index}`));
|
|
842
|
+
lastIndex = end;
|
|
843
|
+
});
|
|
844
|
+
if (lastIndex < text.length)
|
|
845
|
+
parts.push(text.slice(lastIndex));
|
|
846
|
+
return parts;
|
|
847
|
+
}
|
|
848
|
+
function AgentUserMessageText({ text }) {
|
|
849
|
+
const { labels } = useAgentKit();
|
|
850
|
+
const [expanded, setExpanded] = useState(false);
|
|
851
|
+
const [expandable, setExpandable] = useState(false);
|
|
852
|
+
const contentRef = useRef(null);
|
|
853
|
+
const content = useMemo(() => renderUserMessageText(text), [text]);
|
|
854
|
+
useLayoutEffect(() => {
|
|
855
|
+
const element = contentRef.current;
|
|
856
|
+
if (!element)
|
|
857
|
+
return;
|
|
858
|
+
const measure = () => setExpandable(element.scrollHeight > 200);
|
|
859
|
+
measure();
|
|
860
|
+
if (typeof ResizeObserver === "undefined")
|
|
861
|
+
return;
|
|
862
|
+
const observer = new ResizeObserver(measure);
|
|
863
|
+
observer.observe(element);
|
|
864
|
+
return () => observer.disconnect();
|
|
865
|
+
}, [content]);
|
|
866
|
+
return (_jsxs("div", { className: "agentkit-user-message-text", children: [_jsx("div", { className: "agentkit-user-message-text-clip", "data-clamped": !expanded && expandable ? "true" : undefined, children: _jsx("div", { ref: contentRef, className: "agentkit-user-message-text-content", children: content }) }), expandable ? (_jsx("button", { type: "button", className: "agentkit-message-expand", "aria-expanded": expanded, onClick: () => setExpanded((value) => !value), children: expanded ? labels.collapseMessage : labels.expandMessage })) : null] }));
|
|
867
|
+
}
|
|
868
|
+
function AgentImageAttachmentPreview({ src, alt, }) {
|
|
869
|
+
const { labels } = useAgentKit();
|
|
870
|
+
const [open, setOpen] = useState(false);
|
|
871
|
+
return (_jsx(Dialog, { open: open, onOpenChange: setOpen, title: alt || labels.imagePreview, closeLabel: labels.closePreview, size: "fullscreen", trigger: _jsx("button", { type: "button", className: "agentkit-image-attachment-trigger", "aria-label": labels.previewAttachment.replace("{{name}}", alt), title: alt, children: _jsx("img", { src: src, alt: alt }) }), children: _jsx("div", { className: "agentkit-image-preview", children: _jsx("img", { src: src, alt: alt, draggable: false }) }) }));
|
|
872
|
+
}
|
|
873
|
+
export function AgentMessagePartView({ value: part, threadId, active = false, resetKey = `${threadId}:${part.type}`, userMessage = false, }) {
|
|
774
874
|
const { labels, slots, registry } = useAgentKit();
|
|
775
875
|
const RegisteredRenderer = registry.messageParts?.[part.type];
|
|
776
876
|
if (RegisteredRenderer) {
|
|
@@ -778,8 +878,8 @@ export function AgentMessagePartView({ value: part, threadId, active = false, re
|
|
|
778
878
|
}
|
|
779
879
|
switch (part.type) {
|
|
780
880
|
case "text": {
|
|
781
|
-
const Renderer = slots.text;
|
|
782
|
-
return Renderer ? (_jsx(Renderer, { value: part, threadId: threadId, active: active, resetKey: resetKey })) : part.format === "markdown" ? (_jsx(AgentStreamingText, { text: part.text, active: active, resetKey: resetKey, children: (visibleText) => _jsx(AgentMarkdown, { text: visibleText }) })) : (_jsx("p", { "data-format": "plain", children: _jsx(AgentStreamingText, { text: part.text, active: active, resetKey: resetKey }) }));
|
|
881
|
+
const Renderer = userMessage ? undefined : slots.text;
|
|
882
|
+
return Renderer ? (_jsx(Renderer, { value: part, threadId: threadId, active: active, resetKey: resetKey })) : userMessage ? (_jsx(AgentUserMessageText, { text: part.text })) : part.format === "markdown" ? (_jsx(AgentStreamingText, { text: part.text, active: active, resetKey: resetKey, children: (visibleText) => _jsx(AgentMarkdown, { text: visibleText }) })) : (_jsx("p", { "data-format": "plain", children: _jsx(AgentStreamingText, { text: part.text, active: active, resetKey: resetKey }) }));
|
|
783
883
|
}
|
|
784
884
|
case "reasoning": {
|
|
785
885
|
const Renderer = slots.reasoning;
|
|
@@ -804,6 +904,12 @@ export function AgentMessagePartView({ value: part, threadId, active = false, re
|
|
|
804
904
|
if (Renderer)
|
|
805
905
|
return _jsx(Renderer, { value: part, threadId: threadId });
|
|
806
906
|
const href = safeAgentHref(part.url);
|
|
907
|
+
if (userMessage && part.mediaType?.startsWith("image/") && href) {
|
|
908
|
+
return _jsx(AgentImageAttachmentPreview, { src: href, alt: part.name });
|
|
909
|
+
}
|
|
910
|
+
if (userMessage && part.name.startsWith("pasted-text-")) {
|
|
911
|
+
return href ? (_jsxs("a", { className: "agentkit-file agentkit-file--pasted-text", href: href, download: part.name, title: part.name, children: [_jsx(IconFileText, { "aria-hidden": "true", className: "agentkit-icon" }), _jsx("span", { children: labels.pastedText })] })) : (_jsxs("span", { className: "agentkit-file agentkit-file--pasted-text", children: [_jsx(IconFileText, { "aria-hidden": "true", className: "agentkit-icon" }), _jsx("span", { children: labels.pastedText })] }));
|
|
912
|
+
}
|
|
807
913
|
return href ? (_jsxs("a", { className: "agentkit-file", href: href, download: part.name, children: [_jsx(IconFile, { "aria-hidden": "true", className: "agentkit-icon" }), _jsx("span", { children: part.name })] })) : (_jsxs("span", { className: "agentkit-file", children: [_jsx(IconFile, { "aria-hidden": "true", className: "agentkit-icon" }), _jsx("span", { children: part.name })] }));
|
|
808
914
|
}
|
|
809
915
|
case "widget": {
|
|
@@ -818,6 +924,7 @@ export function AgentMessagePartView({ value: part, threadId, active = false, re
|
|
|
818
924
|
return null;
|
|
819
925
|
}
|
|
820
926
|
}
|
|
927
|
+
/** Resolves the stable server request identity without exposing local UI ids. */
|
|
821
928
|
export function resolveAgentMessageRequestId(message, events) {
|
|
822
929
|
const metadata = message.metadata;
|
|
823
930
|
for (const source of [metadata?.custom, metadata]) {
|
|
@@ -840,24 +947,111 @@ export function resolveAgentMessageRequestId(message, events) {
|
|
|
840
947
|
}
|
|
841
948
|
return undefined;
|
|
842
949
|
}
|
|
950
|
+
function resolveAgentMessageRunId(message, events) {
|
|
951
|
+
const metadata = message.metadata;
|
|
952
|
+
for (const source of [metadata?.custom, metadata]) {
|
|
953
|
+
const value = source?.runId;
|
|
954
|
+
if (typeof value === "string" && value)
|
|
955
|
+
return value;
|
|
956
|
+
}
|
|
957
|
+
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
958
|
+
const event = events[index];
|
|
959
|
+
if (((event.type === "message.created" ||
|
|
960
|
+
event.type === "message.completed") &&
|
|
961
|
+
event.message.id === message.id) ||
|
|
962
|
+
((event.type === "message.delta" || event.type === "reasoning.delta") &&
|
|
963
|
+
event.messageId === message.id)) {
|
|
964
|
+
return event.runId;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
return undefined;
|
|
968
|
+
}
|
|
969
|
+
const AgentMessageEditContext = createContext(null);
|
|
970
|
+
function messageText(message) {
|
|
971
|
+
return message.parts
|
|
972
|
+
.filter((part) => part.type === "text")
|
|
973
|
+
.map((part) => part.text)
|
|
974
|
+
.join("\n");
|
|
975
|
+
}
|
|
976
|
+
function messageRunOptions(message) {
|
|
977
|
+
const metadata = message.metadata ?? {};
|
|
978
|
+
const effort = metadata.reasoningEffort ?? metadata.effort;
|
|
979
|
+
const reasoningEffort = [
|
|
980
|
+
"none",
|
|
981
|
+
"minimal",
|
|
982
|
+
"low",
|
|
983
|
+
"medium",
|
|
984
|
+
"high",
|
|
985
|
+
"xhigh",
|
|
986
|
+
].includes(String(effort))
|
|
987
|
+
? String(effort)
|
|
988
|
+
: undefined;
|
|
989
|
+
return {
|
|
990
|
+
...(typeof metadata.agentId === "string"
|
|
991
|
+
? { agentId: metadata.agentId }
|
|
992
|
+
: {}),
|
|
993
|
+
...(typeof metadata.model === "string" ? { model: metadata.model } : {}),
|
|
994
|
+
...(typeof (metadata.requestMode ?? metadata.mode) === "string"
|
|
995
|
+
? { mode: String(metadata.requestMode ?? metadata.mode) }
|
|
996
|
+
: {}),
|
|
997
|
+
...(reasoningEffort ? { reasoningEffort } : {}),
|
|
998
|
+
metadata,
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
function findPreviousMessage(messages, messageId) {
|
|
1002
|
+
const index = messages.findIndex((message) => message.id === messageId);
|
|
1003
|
+
return index < 0 ? null : messages[index - 1];
|
|
1004
|
+
}
|
|
1005
|
+
function findPreviousUserMessage(messages, messageId) {
|
|
1006
|
+
const index = messages.findIndex((message) => message.id === messageId);
|
|
1007
|
+
if (index < 0)
|
|
1008
|
+
return undefined;
|
|
1009
|
+
for (let previous = index - 1; previous >= 0; previous -= 1) {
|
|
1010
|
+
const message = messages[previous];
|
|
1011
|
+
if (message?.role === "user")
|
|
1012
|
+
return message;
|
|
1013
|
+
}
|
|
1014
|
+
return undefined;
|
|
1015
|
+
}
|
|
1016
|
+
async function forkAndResubmitMessage({ controller, threadId, messages, sourceMessage, text, attachments, options, metadata, onThreadForked, messageUnavailable, }) {
|
|
1017
|
+
const previousMessage = findPreviousMessage(messages, sourceMessage.id);
|
|
1018
|
+
if (previousMessage === null) {
|
|
1019
|
+
throw new Error(messageUnavailable);
|
|
1020
|
+
}
|
|
1021
|
+
const forkedThread = await controller.forkThread(threadId, previousMessage?.id);
|
|
1022
|
+
onThreadForked?.(forkedThread);
|
|
1023
|
+
await controller.sendMessage({
|
|
1024
|
+
threadId: forkedThread.id,
|
|
1025
|
+
text,
|
|
1026
|
+
attachments,
|
|
1027
|
+
options,
|
|
1028
|
+
metadata,
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
843
1031
|
export function AgentMessageActions({ value: message, threadId, }) {
|
|
844
|
-
const { labels, onThreadForked } = useAgentKit();
|
|
1032
|
+
const { controller, labels, slots, onThreadForked, onCopyMessage, branchNavigation, } = useAgentKit();
|
|
845
1033
|
const thread = useAgentThread(threadId);
|
|
846
1034
|
const feedbackCapability = useAgentCapability("feedback");
|
|
847
1035
|
const forkingCapability = useAgentCapability("threadForking");
|
|
848
1036
|
const control = useAgentKitControl(threadId);
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
.
|
|
1037
|
+
const editContext = useContext(AgentMessageEditContext);
|
|
1038
|
+
const text = messageText(message);
|
|
1039
|
+
const previousUserMessage = message.role === "assistant"
|
|
1040
|
+
? findPreviousUserMessage(thread.messages, message.id)
|
|
1041
|
+
: undefined;
|
|
1042
|
+
const isLastAssistantMessage = message.role === "assistant" && thread.messages.at(-1)?.id === message.id;
|
|
853
1043
|
const [copied, setCopied] = useState(false);
|
|
854
1044
|
const [feedback, setFeedback] = useState(null);
|
|
1045
|
+
const [feedbackReason, setFeedbackReason] = useState("");
|
|
1046
|
+
const [feedbackReasonOpen, setFeedbackReasonOpen] = useState(false);
|
|
1047
|
+
const [feedbackReasonSubmitted, setFeedbackReasonSubmitted] = useState(false);
|
|
855
1048
|
const copiedTimer = useRef(null);
|
|
856
1049
|
const [requestIdCopied, setRequestIdCopied] = useState(false);
|
|
857
|
-
const [
|
|
858
|
-
const actionPanelId = useId();
|
|
1050
|
+
const [actionsMenuOpen, setActionsMenuOpen] = useState(false);
|
|
859
1051
|
const requestIdCopiedTimer = useRef(null);
|
|
860
1052
|
const requestId = useMemo(() => resolveAgentMessageRequestId(message, thread.events), [message, thread.events]);
|
|
1053
|
+
const runId = useMemo(() => resolveAgentMessageRunId(message, thread.events), [message, thread.events]);
|
|
1054
|
+
const messageSeq = thread.messages.findIndex((item) => item.id === message.id);
|
|
861
1055
|
useEffect(() => () => {
|
|
862
1056
|
if (copiedTimer.current)
|
|
863
1057
|
clearTimeout(copiedTimer.current);
|
|
@@ -865,7 +1059,8 @@ export function AgentMessageActions({ value: message, threadId, }) {
|
|
|
865
1059
|
clearTimeout(requestIdCopiedTimer.current);
|
|
866
1060
|
}, [message.id]);
|
|
867
1061
|
const copyAction = useAgentKitMutation(async () => {
|
|
868
|
-
|
|
1062
|
+
const handled = await onCopyMessage?.({ message, text });
|
|
1063
|
+
if (!handled && !(await writeClipboardText(text))) {
|
|
869
1064
|
throw new Error(labels.copyUnavailable);
|
|
870
1065
|
}
|
|
871
1066
|
setCopied(true);
|
|
@@ -873,7 +1068,22 @@ export function AgentMessageActions({ value: message, threadId, }) {
|
|
|
873
1068
|
clearTimeout(copiedTimer.current);
|
|
874
1069
|
copiedTimer.current = setTimeout(() => setCopied(false), 1_400);
|
|
875
1070
|
}, `${threadId}:${message.id}:copy`);
|
|
876
|
-
const feedbackAction = useAgentKitMutation((value
|
|
1071
|
+
const feedbackAction = useAgentKitMutation(({ value, reason, }) => {
|
|
1072
|
+
const options = {
|
|
1073
|
+
...(runId ? { runId } : {}),
|
|
1074
|
+
...(messageSeq >= 0 ? { messageSeq } : {}),
|
|
1075
|
+
...(reason ? { reason } : {}),
|
|
1076
|
+
};
|
|
1077
|
+
return Object.keys(options).length
|
|
1078
|
+
? control.submitFeedback(message.id, value, options)
|
|
1079
|
+
: control.submitFeedback(message.id, value);
|
|
1080
|
+
}, `${threadId}:${message.id}:feedback`);
|
|
1081
|
+
const branchNavigationAction = useAgentKitMutation(async (direction) => {
|
|
1082
|
+
if (direction === "previous")
|
|
1083
|
+
await branchNavigation?.onPrevious();
|
|
1084
|
+
else
|
|
1085
|
+
await branchNavigation?.onNext();
|
|
1086
|
+
}, `${threadId}:${message.id}:branch-navigation`);
|
|
877
1087
|
const requestIdAction = useAgentKitMutation(async () => {
|
|
878
1088
|
if (!requestId)
|
|
879
1089
|
throw new Error(labels.requestIdUnavailable);
|
|
@@ -891,41 +1101,106 @@ export function AgentMessageActions({ value: message, threadId, }) {
|
|
|
891
1101
|
const thread = await control.fork(message.id);
|
|
892
1102
|
onThreadForked?.(thread);
|
|
893
1103
|
}, `${threadId}:${message.id}:fork`);
|
|
1104
|
+
const regenerateAction = useAgentKitMutation(async () => {
|
|
1105
|
+
if (!previousUserMessage)
|
|
1106
|
+
return;
|
|
1107
|
+
await forkAndResubmitMessage({
|
|
1108
|
+
controller,
|
|
1109
|
+
threadId,
|
|
1110
|
+
messages: thread.messages,
|
|
1111
|
+
sourceMessage: previousUserMessage,
|
|
1112
|
+
text: messageText(previousUserMessage),
|
|
1113
|
+
attachments: previousUserMessage.parts.filter((part) => part.type === "file"),
|
|
1114
|
+
options: messageRunOptions(previousUserMessage),
|
|
1115
|
+
metadata: previousUserMessage.metadata,
|
|
1116
|
+
onThreadForked,
|
|
1117
|
+
messageUnavailable: labels.messageUnavailable,
|
|
1118
|
+
});
|
|
1119
|
+
}, `${threadId}:${message.id}:regenerate`);
|
|
894
1120
|
const updateFeedback = async (value) => {
|
|
895
1121
|
const previous = feedback;
|
|
896
1122
|
if (previous === value)
|
|
897
1123
|
return;
|
|
898
1124
|
setFeedback(value);
|
|
899
1125
|
try {
|
|
900
|
-
await feedbackAction.execute(value);
|
|
1126
|
+
await feedbackAction.execute({ value });
|
|
901
1127
|
}
|
|
902
1128
|
catch {
|
|
903
1129
|
setFeedback(previous);
|
|
904
1130
|
}
|
|
905
1131
|
};
|
|
1132
|
+
const submitFeedbackReason = async (event) => {
|
|
1133
|
+
event.preventDefault();
|
|
1134
|
+
const reason = feedbackReason.trim();
|
|
1135
|
+
if (!reason)
|
|
1136
|
+
return;
|
|
1137
|
+
try {
|
|
1138
|
+
await feedbackAction.execute({ value: "negative", reason });
|
|
1139
|
+
setFeedbackReason("");
|
|
1140
|
+
setFeedbackReasonSubmitted(true);
|
|
1141
|
+
setFeedbackReasonOpen(false);
|
|
1142
|
+
}
|
|
1143
|
+
catch {
|
|
1144
|
+
// coercion-ok: feedbackAction.error is rendered below and the draft stays available for retry.
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
906
1147
|
const actionError = copyAction.error ??
|
|
907
1148
|
feedbackAction.error ??
|
|
1149
|
+
branchNavigationAction.error ??
|
|
908
1150
|
requestIdAction.error ??
|
|
909
|
-
forkAction.error
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1151
|
+
forkAction.error ??
|
|
1152
|
+
regenerateAction.error;
|
|
1153
|
+
return (_jsxs("div", { className: "agentkit-message-actions", children: [message.role === "assistant" ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "agentkit-message-actions-leading", children: [_jsx(IconButton, { label: copied ? labels.copied : labels.copy, icon: copied ? (_jsx(IconCircleCheck, { "aria-hidden": "true" })) : (_jsx(IconCopy, { "aria-hidden": "true" })), size: "compact", pending: copyAction.pending, onPress: () => void copyAction.execute().catch(() => undefined) }), feedbackCapability.visible ? (_jsxs(_Fragment, { children: [_jsx(IconButton, { label: labels.positiveFeedback, icon: _jsx(IconThumbUp, { "aria-hidden": "true" }), size: "compact", "aria-pressed": feedback === "positive", pending: feedbackAction.pending && feedback === "positive", disabled: feedbackAction.pending || !feedbackCapability.enabled, title: feedbackCapability.reason, onPress: () => void updateFeedback("positive") }), _jsx(Popover, { open: feedbackReasonOpen, onOpenChange: (open) => {
|
|
1154
|
+
setFeedbackReasonOpen(open);
|
|
1155
|
+
if (open)
|
|
1156
|
+
setFeedbackReasonSubmitted(false);
|
|
1157
|
+
}, placement: "bottom", align: "start", className: "agentkit-feedback-popover", trigger: _jsx(IconButton, { label: labels.negativeFeedback, icon: _jsx(IconThumbDown, { "aria-hidden": "true" }), size: "compact", "aria-pressed": feedback === "negative", pending: feedbackAction.pending && feedback === "negative", disabled: feedbackAction.pending || !feedbackCapability.enabled, title: feedbackCapability.reason, onPress: () => void updateFeedback("negative") }), children: _jsxs("form", { className: "agentkit-feedback-form", onSubmit: submitFeedbackReason, children: [_jsx(TextArea, { label: labels.feedbackWhatWentWrong, value: feedbackReason, onChange: setFeedbackReason, placeholder: labels.feedbackPlaceholder, rows: 3, maxLength: 2_000, disabled: feedbackAction.pending, className: "agentkit-feedback-textarea" }), _jsxs("div", { className: "agentkit-feedback-footer", children: [_jsx("span", { children: labels.feedbackKeyboardHint.replace("{{shortcut}}", typeof navigator !== "undefined" &&
|
|
1158
|
+
/Mac|iPhone|iPad/.test(navigator.userAgent)
|
|
1159
|
+
? "⌘"
|
|
1160
|
+
: "Ctrl") }), _jsx(ActionButton, { type: "submit", intent: "primary", size: "compact", pending: feedbackAction.pending, disabled: feedbackAction.pending || !feedbackReason.trim(), children: labels.feedbackSubmit })] })] }) }), feedbackReasonSubmitted ? (_jsx("span", { className: "agentkit-visually-hidden", "aria-live": "polite", children: labels.feedbackSubmitted })) : null] })) : null] }), _jsxs("div", { className: "agentkit-message-actions-trailing", children: [isLastAssistantMessage &&
|
|
1161
|
+
previousUserMessage &&
|
|
1162
|
+
forkingCapability.visible &&
|
|
1163
|
+
onThreadForked ? (_jsx(IconButton, { label: labels.regenerateResponse, icon: _jsx(IconRefresh, { "aria-hidden": "true" }), size: "compact", pending: regenerateAction.pending, disabled: regenerateAction.pending ||
|
|
1164
|
+
thread.activeRunIds.length > 0 ||
|
|
1165
|
+
!forkingCapability.enabled, title: forkingCapability.reason, onPress: () => void regenerateAction.execute().catch(() => undefined) })) : null, branchNavigation && branchNavigation.count > 1 ? (_jsxs("div", { className: "agentkit-branch-navigation", "aria-label": labels.branchPosition
|
|
1166
|
+
.replace("{{index}}", String(branchNavigation.index))
|
|
1167
|
+
.replace("{{count}}", String(branchNavigation.count)), children: [_jsx(IconButton, { label: labels.previousBranch, icon: _jsx(IconChevronLeft, { "aria-hidden": "true" }), size: "compact", disabled: branchNavigation.index <= 1 ||
|
|
1168
|
+
branchNavigationAction.pending, pending: branchNavigationAction.pending, onPress: () => void branchNavigationAction
|
|
1169
|
+
.execute("previous")
|
|
1170
|
+
.catch(() => undefined) }), _jsx("span", { "aria-live": "polite", children: labels.branchPosition
|
|
1171
|
+
.replace("{{index}}", String(branchNavigation.index))
|
|
1172
|
+
.replace("{{count}}", String(branchNavigation.count)) }), _jsx(IconButton, { label: labels.nextBranch, icon: _jsx(IconChevronRight, { "aria-hidden": "true" }), size: "compact", disabled: branchNavigation.index >= branchNavigation.count ||
|
|
1173
|
+
branchNavigationAction.pending, pending: branchNavigationAction.pending, onPress: () => void branchNavigationAction
|
|
1174
|
+
.execute("next")
|
|
1175
|
+
.catch(() => undefined) })] })) : null, message.createdAt ? (_jsx("time", { dateTime: message.createdAt, children: labels.formatTimestamp?.(message.createdAt) ??
|
|
1176
|
+
new Date(message.createdAt).toLocaleTimeString([], {
|
|
1177
|
+
hour: "numeric",
|
|
1178
|
+
minute: "2-digit",
|
|
1179
|
+
}) })) : null, slots.messageActionsTrailing ? (_jsx(slots.messageActionsTrailing, { value: message, threadId: threadId })) : null, _jsx(Popover, { open: actionsMenuOpen, onOpenChange: (open) => {
|
|
1180
|
+
setActionsMenuOpen(open);
|
|
1181
|
+
if (!open)
|
|
1182
|
+
setRequestIdCopied(false);
|
|
1183
|
+
}, placement: "bottom", align: "end", className: "agentkit-message-menu", trigger: _jsx(IconButton, { label: labels.messageActions, icon: _jsx(IconDotsVertical, { "aria-hidden": "true" }), size: "compact", "aria-expanded": actionsMenuOpen, title: labels.messageActions }), children: _jsxs("div", { className: "agentkit-message-menu-items", role: "menu", children: [_jsxs("button", { type: "button", role: "menuitem", className: "agentkit-message-menu-item", disabled: !requestId || requestIdAction.pending, title: requestId
|
|
1184
|
+
? requestIdCopied
|
|
1185
|
+
? labels.copied
|
|
1186
|
+
: labels.copyRequestId
|
|
1187
|
+
: labels.requestIdUnavailable, onClick: () => {
|
|
1188
|
+
setActionsMenuOpen(false);
|
|
1189
|
+
void requestIdAction.execute().catch(() => undefined);
|
|
1190
|
+
}, children: [requestIdCopied ? (_jsx(IconCircleCheck, { "aria-hidden": "true" })) : (_jsx(IconId, { "aria-hidden": "true" })), _jsx("span", { children: requestIdCopied
|
|
1191
|
+
? labels.copied
|
|
1192
|
+
: requestId
|
|
1193
|
+
? labels.copyRequestId
|
|
1194
|
+
: labels.requestIdUnavailable })] }), forkingCapability.visible && onThreadForked ? (_jsxs("button", { type: "button", role: "menuitem", className: "agentkit-message-menu-item", disabled: !forkingCapability.enabled || forkAction.pending, title: forkingCapability.reason, onClick: () => {
|
|
1195
|
+
setActionsMenuOpen(false);
|
|
1196
|
+
void forkAction.execute().catch(() => undefined);
|
|
1197
|
+
}, children: [_jsx(IconGitBranch, { "aria-hidden": "true" }), _jsx("span", { children: labels.fork })] })) : null] }) })] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "agentkit-message-actions-leading", children: [_jsx(IconButton, { label: copied ? labels.copied : labels.copy, icon: copied ? (_jsx(IconCircleCheck, { "aria-hidden": "true" })) : (_jsx(IconCopy, { "aria-hidden": "true" })), size: "compact", pending: copyAction.pending, onPress: () => void copyAction.execute().catch(() => undefined) }), editContext?.enabled &&
|
|
1198
|
+
forkingCapability.visible &&
|
|
1199
|
+
text.trim() ? (_jsx(IconButton, { label: labels.editMessage, icon: _jsx(IconPencil, { "aria-hidden": "true" }), size: "compact", disabled: thread.activeRunIds.length > 0 || !forkingCapability.enabled, title: forkingCapability.reason, "aria-pressed": editContext.message?.id === message.id, onPress: () => editContext.setMessage(message) })) : null] }), _jsx("div", { className: "agentkit-message-actions-trailing", children: message.createdAt ? (_jsx("time", { dateTime: message.createdAt, children: labels.formatTimestamp?.(message.createdAt) ??
|
|
1200
|
+
new Date(message.createdAt).toLocaleTimeString([], {
|
|
1201
|
+
hour: "numeric",
|
|
1202
|
+
minute: "2-digit",
|
|
1203
|
+
}) })) : null })] })), actionError ? (_jsx("span", { className: "agentkit-command-error", role: "alert", children: actionError.message })) : null] }));
|
|
929
1204
|
}
|
|
930
1205
|
export function AgentMessageView({ value: message, threadId, }) {
|
|
931
1206
|
const { labels, slots } = useAgentKit();
|
|
@@ -938,7 +1213,7 @@ export function AgentMessageView({ value: message, threadId, }) {
|
|
|
938
1213
|
!embeddedWidgetIds.has(widget.id));
|
|
939
1214
|
const attachedAnnotations = Object.values(thread.annotations).filter((annotation) => thread.annotationMessageIds[annotation.id] === message.id &&
|
|
940
1215
|
!embeddedAnnotationIds.has(annotation.id));
|
|
941
|
-
return (_jsxs("article", { className: "agentkit-message", "data-role": message.role, "data-message-id": message.id, "aria-label": message.role === "user" ? labels.you : labels.assistant, "aria-busy": message.status === "streaming", children: [_jsxs("div", { className: "agentkit-message-content", children: [message.parts.map((part, index) => (_jsx(AgentMessagePartView, { value: part, threadId: threadId, active: message.status === "streaming", resetKey: `${threadId}:${message.id}:${index}
|
|
1216
|
+
return (_jsxs("article", { className: "agentkit-message", "data-role": message.role, "data-message-id": message.id, "aria-label": message.role === "user" ? labels.you : labels.assistant, "aria-busy": message.status === "streaming", children: [_jsxs("div", { className: "agentkit-message-content", children: [message.parts.map((part, index) => (_jsx(AgentMessagePartView, { value: part, threadId: threadId, active: message.status === "streaming", resetKey: `${threadId}:${message.id}:${index}`, userMessage: message.role === "user" }, `${message.id}-${index}`))), attachedWidgets.map((widget) => (_jsx(AgentMessagePartView, { value: { type: "widget", widget }, threadId: threadId }, widget.id))), attachedAnnotations.length ? (_jsx("div", { className: "agentkit-annotations", children: attachedAnnotations.map((annotation) => (_jsx(AgentMessagePartView, { value: { type: "annotation", annotation }, threadId: threadId }, annotation.id))) })) : null, Supplement ? _jsx(Supplement, { value: message, threadId: threadId }) : null] }), _jsx(Actions, { value: message, threadId: threadId })] }));
|
|
942
1217
|
}
|
|
943
1218
|
export function AgentRunFailure({ error, runId, threadId, }) {
|
|
944
1219
|
const { labels } = useAgentKit();
|
|
@@ -948,15 +1223,20 @@ export function AgentConnectionErrorView({ error, threadId, recover, recovering,
|
|
|
948
1223
|
const { labels } = useAgentKit();
|
|
949
1224
|
return (_jsxs("div", { className: "agentkit-error", "data-error-code": error.code, "data-thread-id": threadId, role: "alert", children: [_jsx("strong", { children: labels.error }), _jsx("span", { children: error.message }), error.retryable ? (_jsx("div", { className: "agentkit-error-actions", children: _jsx(ActionButton, { emphasis: "outline", size: "compact", pending: recovering, onPress: () => void recover().catch(() => undefined), children: labels.reconnect }) })) : null, recoveryError ? (_jsx("span", { className: "agentkit-command-error", children: recoveryError.message })) : null] }));
|
|
950
1225
|
}
|
|
951
|
-
export function AgentKitComposer({ threadId: requestedThreadId, className, queueWhileRunning = true, showModelSelector = true, slashCommands, slashSkills, includeDefaultSlashCommands, includeDefaultSlashSkills, onSlashCommand, plusMenuMode, voiceEnabled = false, autoFocus = true, mode, defaultMode = "act", onModeChange, contextItems, onRemoveContextItem, onInspectContextItem, onRetryContextItem, contextMenuItems, attachmentAdapter, inlineTextAttachments, beforeSend, }) {
|
|
952
|
-
const { threadId: contextThreadId, labels, registerComposerFocus, slots, } = useAgentKit();
|
|
1226
|
+
export function AgentKitComposer({ threadId: requestedThreadId, className, queueWhileRunning = true, showModelSelector = true, slashCommands, slashSkills, includeDefaultSlashCommands, includeDefaultSlashSkills, onSlashCommand, plusMenuMode, voiceEnabled = false, autoFocus = true, mode, defaultMode = "act", onModeChange, toolbarSlot, disabled, onDisabledClick, placeholder, initialText, initialTextKey, layoutVariant, availableModels, modelListLoading, selectedModel, selectedEngine, selectedEffort, onConnectProvider, onConnectLocalRuntime, imageModelMenu, onModelChange, onEffortChange, availableAgents, selectedAgent, agentOnly, onAgentChange, onModelSelectorOpenChange, modelStatusChecksEnabled, attachmentsEnabled, onAttachmentRequest, contextButtonTooltipDisabled, onTextChange, contextItems, onRemoveContextItem, extraActionButton, onSubmit: onSubmitOverride, onBeforeSubmit, onAttachmentError, interceptBuildRequestsForBuilder, planModeDisabled, planModeDisabledReason, stopButton, onInspectContextItem, onRetryContextItem, contextMenuItems, attachmentAdapter, inlineTextAttachments, beforeSend, }) {
|
|
1227
|
+
const { threadId: contextThreadId, controller, labels, onThreadForked, registerComposerFocus, slots, } = useAgentKit();
|
|
1228
|
+
const editContext = useContext(AgentMessageEditContext);
|
|
1229
|
+
const editingMessage = editContext?.message ?? null;
|
|
953
1230
|
const threadId = requestedThreadId ?? contextThreadId;
|
|
954
1231
|
const queueCapability = useAgentCapability("messageQueue");
|
|
955
1232
|
const suggestionsCapability = useAgentCapability("suggestions");
|
|
956
1233
|
const uploadsCapability = useAgentCapability("uploads");
|
|
957
1234
|
const modelSelectionCapability = useAgentCapability("modelSelection");
|
|
958
1235
|
const canQueue = queueCapability.enabled;
|
|
959
|
-
const canUpload = uploadsCapability.enabled;
|
|
1236
|
+
const canUpload = uploadsCapability.enabled && attachmentsEnabled !== false;
|
|
1237
|
+
// The host opts in through showModelSelector, so a capability the backend
|
|
1238
|
+
// never reported keeps the selector instead of silently removing a control
|
|
1239
|
+
// the host asked for. Only an explicit denial or outage takes it away.
|
|
960
1240
|
const canSelectModel = modelSelectionCapability.state === "unknown" ||
|
|
961
1241
|
modelSelectionCapability.enabled;
|
|
962
1242
|
const control = useAgentKitControl(threadId);
|
|
@@ -964,8 +1244,15 @@ export function AgentKitComposer({ threadId: requestedThreadId, className, queue
|
|
|
964
1244
|
const command = useAgentKitMutation((execute) => execute(), threadId);
|
|
965
1245
|
const composerRef = useRef(null);
|
|
966
1246
|
const [uncontrolledMode, setUncontrolledMode] = useState(defaultMode);
|
|
1247
|
+
const [attachmentError, setAttachmentError] = useState(null);
|
|
967
1248
|
const executionMode = mode ?? uncontrolledMode;
|
|
968
1249
|
const active = thread.activeRunIds.length > 0;
|
|
1250
|
+
const composerInitialText = editingMessage
|
|
1251
|
+
? messageText(editingMessage)
|
|
1252
|
+
: initialText;
|
|
1253
|
+
const composerInitialTextKey = editingMessage
|
|
1254
|
+
? `edit:${editingMessage.id}`
|
|
1255
|
+
: initialTextKey;
|
|
969
1256
|
const focusComposer = useCallback(() => {
|
|
970
1257
|
if (!autoFocus)
|
|
971
1258
|
return;
|
|
@@ -974,84 +1261,224 @@ export function AgentKitComposer({ threadId: requestedThreadId, className, queue
|
|
|
974
1261
|
globalThis.requestAnimationFrame(() => composerRef.current?.focus());
|
|
975
1262
|
}
|
|
976
1263
|
}, [autoFocus]);
|
|
1264
|
+
const reportAttachmentError = useCallback((message) => {
|
|
1265
|
+
if (onAttachmentError)
|
|
1266
|
+
onAttachmentError(message);
|
|
1267
|
+
else
|
|
1268
|
+
setAttachmentError(message);
|
|
1269
|
+
}, [onAttachmentError]);
|
|
1270
|
+
const addDroppedAttachment = useCallback(async (file) => {
|
|
1271
|
+
try {
|
|
1272
|
+
const composer = composerRef.current;
|
|
1273
|
+
if (!composer)
|
|
1274
|
+
throw new Error(labels.error);
|
|
1275
|
+
await composer.addAttachment(file);
|
|
1276
|
+
setAttachmentError(null);
|
|
1277
|
+
}
|
|
1278
|
+
catch (error) {
|
|
1279
|
+
const message = error instanceof Error ? error.message : labels.error;
|
|
1280
|
+
reportAttachmentError(message);
|
|
1281
|
+
throw error;
|
|
1282
|
+
}
|
|
1283
|
+
}, [labels.error, reportAttachmentError]);
|
|
1284
|
+
useEffect(() => {
|
|
1285
|
+
const receiveAttachments = (event) => {
|
|
1286
|
+
const detail = event.detail;
|
|
1287
|
+
if (detail?.threadId !== threadId || !canUpload)
|
|
1288
|
+
return;
|
|
1289
|
+
void Promise.all(detail.files.map((file) => addDroppedAttachment(file))).catch(() => undefined);
|
|
1290
|
+
};
|
|
1291
|
+
window.addEventListener("agentkit:attach-files", receiveAttachments);
|
|
1292
|
+
return () => window.removeEventListener("agentkit:attach-files", receiveAttachments);
|
|
1293
|
+
}, [addDroppedAttachment, canUpload, threadId]);
|
|
1294
|
+
const composerToolbarSlot = editingMessage ? (_jsxs(_Fragment, { children: [_jsx(IconButton, { label: labels.cancelEditing, icon: _jsx(IconX, { "aria-hidden": "true" }), size: "compact", onPress: () => {
|
|
1295
|
+
composerRef.current?.setText(initialText ?? "");
|
|
1296
|
+
editContext?.setMessage(null);
|
|
1297
|
+
focusComposer();
|
|
1298
|
+
} }), toolbarSlot] })) : (toolbarSlot);
|
|
977
1299
|
useEffect(() => registerComposerFocus(threadId, focusComposer), [focusComposer, registerComposerFocus, threadId]);
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1300
|
+
// i18n-ignore: These type-level callback property names are not visible copy.
|
|
1301
|
+
const submitMessage = async (text, files, references, // i18n-ignore: Type-only callback tuple, not rendered copy.
|
|
1302
|
+
options) => {
|
|
1303
|
+
const effort = options.effort;
|
|
1304
|
+
const contextItemsForSubmission = options.contextItems ?? contextItems;
|
|
1305
|
+
const metadata = {
|
|
1306
|
+
...(editingMessage?.metadata ?? {}),
|
|
1307
|
+
...(contextItemsForSubmission === undefined
|
|
1308
|
+
? {}
|
|
1309
|
+
: { contextItems: contextItemsForSubmission }),
|
|
1310
|
+
...(references.length ? { references } : {}),
|
|
1311
|
+
...(options.engine ? { engine: options.engine } : {}),
|
|
1312
|
+
...(options.model ? { model: options.model } : {}),
|
|
1313
|
+
...(effort ? { effort } : {}),
|
|
1314
|
+
...(selectedAgent ? { agentId: selectedAgent } : {}),
|
|
1315
|
+
mode: executionMode,
|
|
1316
|
+
requestMode: executionMode,
|
|
1317
|
+
};
|
|
1318
|
+
const runOptions = {
|
|
1319
|
+
...(selectedAgent ? { agentId: selectedAgent } : {}),
|
|
1320
|
+
model: options.model,
|
|
1321
|
+
mode: executionMode,
|
|
1322
|
+
reasoningEffort: effort && !["auto", "max"].includes(effort)
|
|
1323
|
+
? effort
|
|
1324
|
+
: undefined,
|
|
1325
|
+
metadata,
|
|
1326
|
+
};
|
|
1327
|
+
const uploadFiles = files.map((file) => ({
|
|
1328
|
+
name: file.name,
|
|
1329
|
+
mediaType: file.type || "application/octet-stream",
|
|
1330
|
+
size: file.size,
|
|
1331
|
+
body: file,
|
|
1332
|
+
}));
|
|
1333
|
+
if (editingMessage) {
|
|
1334
|
+
if (!onThreadForked) {
|
|
1335
|
+
throw new Error(labels.navigationUnavailable);
|
|
1336
|
+
}
|
|
1337
|
+
const previousMessage = findPreviousMessage(thread.messages, editingMessage.id);
|
|
1338
|
+
if (previousMessage === null) {
|
|
1339
|
+
throw new Error(labels.messageUnavailable);
|
|
1340
|
+
}
|
|
1341
|
+
const forkedThread = await controller.forkThread(threadId, previousMessage?.id);
|
|
1342
|
+
onThreadForked(forkedThread);
|
|
1343
|
+
const uploadedAttachments = uploadFiles.length && canUpload
|
|
1344
|
+
? await controller.uploadFiles(forkedThread.id, uploadFiles)
|
|
1345
|
+
: [];
|
|
1346
|
+
const previousAttachments = editingMessage.parts.filter((part) => part.type === "file");
|
|
981
1347
|
const draft = createAgentKitComposerSubmission({
|
|
982
|
-
threadId,
|
|
983
|
-
intent:
|
|
984
|
-
(options.intent === "queued" || (active && queueWhileRunning))
|
|
985
|
-
? "queued"
|
|
986
|
-
: "immediate",
|
|
1348
|
+
threadId: forkedThread.id,
|
|
1349
|
+
intent: "immediate",
|
|
987
1350
|
text,
|
|
988
|
-
contextItems:
|
|
1351
|
+
contextItems: contextItemsForSubmission,
|
|
989
1352
|
references,
|
|
990
|
-
options:
|
|
991
|
-
model: options.model,
|
|
992
|
-
mode: executionMode,
|
|
993
|
-
reasoningEffort: effort && !["auto", "max"].includes(effort)
|
|
994
|
-
? effort
|
|
995
|
-
: undefined,
|
|
996
|
-
},
|
|
1353
|
+
options: runOptions,
|
|
997
1354
|
});
|
|
998
|
-
const attachments = files.length && canUpload
|
|
999
|
-
? await control.uploadFiles(files.map((file) => ({
|
|
1000
|
-
name: file.name,
|
|
1001
|
-
mediaType: file.type || "application/octet-stream",
|
|
1002
|
-
size: file.size,
|
|
1003
|
-
body: file,
|
|
1004
|
-
})))
|
|
1005
|
-
: [];
|
|
1006
1355
|
const payload = Object.freeze({
|
|
1007
1356
|
...draft,
|
|
1008
|
-
attachments: snapshotComposerValue(
|
|
1357
|
+
attachments: snapshotComposerValue([
|
|
1358
|
+
...previousAttachments,
|
|
1359
|
+
...uploadedAttachments,
|
|
1360
|
+
]),
|
|
1009
1361
|
});
|
|
1010
1362
|
await beforeSend?.(payload);
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1363
|
+
const sendMetadata = {
|
|
1364
|
+
...payload.options.metadata,
|
|
1365
|
+
...(payload.references.length
|
|
1366
|
+
? { references: payload.references }
|
|
1367
|
+
: {}),
|
|
1368
|
+
...(payload.contextItems === undefined
|
|
1369
|
+
? {}
|
|
1370
|
+
: { contextItems: payload.contextItems }),
|
|
1371
|
+
};
|
|
1372
|
+
await controller.sendMessage({
|
|
1373
|
+
threadId: forkedThread.id,
|
|
1022
1374
|
text: payload.text,
|
|
1023
1375
|
attachments: [...payload.attachments],
|
|
1024
1376
|
options: payload.options,
|
|
1025
|
-
metadata,
|
|
1026
|
-
};
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1377
|
+
metadata: sendMetadata,
|
|
1378
|
+
});
|
|
1379
|
+
editContext?.setMessage(null);
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
if (onSubmitOverride) {
|
|
1383
|
+
await onSubmitOverride(text, files, references, options);
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const draft = createAgentKitComposerSubmission({
|
|
1387
|
+
threadId,
|
|
1388
|
+
intent: canQueue &&
|
|
1389
|
+
(options.intent === "queued" || (active && queueWhileRunning))
|
|
1390
|
+
? "queued"
|
|
1391
|
+
: "immediate",
|
|
1392
|
+
text,
|
|
1393
|
+
contextItems: contextItemsForSubmission,
|
|
1394
|
+
references,
|
|
1395
|
+
options: runOptions,
|
|
1396
|
+
});
|
|
1397
|
+
const attachments = uploadFiles.length && canUpload
|
|
1398
|
+
? await control.uploadFiles(uploadFiles)
|
|
1399
|
+
: [];
|
|
1400
|
+
const payload = Object.freeze({
|
|
1401
|
+
...draft,
|
|
1402
|
+
attachments: snapshotComposerValue(attachments),
|
|
1033
1403
|
});
|
|
1034
|
-
|
|
1035
|
-
|
|
1404
|
+
await beforeSend?.(payload);
|
|
1405
|
+
const sendMetadata = {
|
|
1406
|
+
...payload.options.metadata,
|
|
1407
|
+
...(payload.references.length ? { references: payload.references } : {}),
|
|
1408
|
+
...(payload.contextItems === undefined
|
|
1409
|
+
? {}
|
|
1410
|
+
: { contextItems: payload.contextItems }),
|
|
1411
|
+
};
|
|
1412
|
+
const message = {
|
|
1413
|
+
text: payload.text,
|
|
1414
|
+
attachments: [...payload.attachments],
|
|
1415
|
+
options: payload.options,
|
|
1416
|
+
metadata: sendMetadata,
|
|
1417
|
+
};
|
|
1418
|
+
if (payload.intent === "queued") {
|
|
1419
|
+
await control.queueMessage(message);
|
|
1420
|
+
}
|
|
1421
|
+
else {
|
|
1422
|
+
await control.sendMessage(message);
|
|
1423
|
+
}
|
|
1424
|
+
};
|
|
1425
|
+
const prepareHostSubmit = async () => {
|
|
1426
|
+
if (disabled) {
|
|
1427
|
+
onDisabledClick?.();
|
|
1428
|
+
return false;
|
|
1429
|
+
}
|
|
1430
|
+
if (editingMessage)
|
|
1431
|
+
return true;
|
|
1432
|
+
return !onBeforeSubmit || (await onBeforeSubmit());
|
|
1036
1433
|
};
|
|
1037
|
-
const steerQueued = !
|
|
1434
|
+
const steerQueued = !disabled
|
|
1038
1435
|
? (item) => void command
|
|
1039
|
-
.execute(() =>
|
|
1436
|
+
.execute(async () => {
|
|
1437
|
+
if (!(await prepareHostSubmit()))
|
|
1438
|
+
return;
|
|
1439
|
+
if (onSubmitOverride) {
|
|
1440
|
+
await submitMessage(item.text, [], [], {
|
|
1441
|
+
intent: "immediate",
|
|
1442
|
+
attachments: item.attachments,
|
|
1443
|
+
});
|
|
1444
|
+
await control.removeQueued(item.id);
|
|
1445
|
+
}
|
|
1446
|
+
else {
|
|
1447
|
+
await control.steerQueued(item.id);
|
|
1448
|
+
}
|
|
1449
|
+
})
|
|
1040
1450
|
.catch(() => undefined)
|
|
1041
1451
|
.finally(focusComposer)
|
|
1042
1452
|
: undefined;
|
|
1453
|
+
const supportsQueueReordering = controller.supportsQueuedMessageReordering?.() ?? false;
|
|
1454
|
+
const moveQueuedToTop = useAgentKitMutation((messageId) => control.moveQueuedMessageToTop(messageId), `${threadId}:move-queued-to-top`);
|
|
1043
1455
|
const removeQueued = (item) => void command
|
|
1044
1456
|
.execute(() => control.removeQueued(item.id))
|
|
1045
1457
|
.catch(() => undefined)
|
|
1046
1458
|
.finally(focusComposer);
|
|
1047
|
-
const selectSuggestion = (suggestion) => void
|
|
1048
|
-
|
|
1459
|
+
const selectSuggestion = (suggestion) => void command
|
|
1460
|
+
.execute(async () => {
|
|
1461
|
+
if (!(await prepareHostSubmit()))
|
|
1462
|
+
return;
|
|
1463
|
+
await submitMessage(agentSuggestionPrompt(suggestion), [], [], {
|
|
1464
|
+
intent: active && queueWhileRunning && canQueue ? "queued" : "immediate",
|
|
1465
|
+
contextItems,
|
|
1466
|
+
});
|
|
1049
1467
|
})
|
|
1050
1468
|
.catch(() => undefined)
|
|
1051
1469
|
.finally(focusComposer);
|
|
1052
1470
|
const Queue = slots.queue;
|
|
1053
1471
|
const Suggestions = slots.suggestions;
|
|
1054
|
-
return (_jsxs("div", { className: `agentkit-composer-stack ${className ?? ""}`, children: [queueCapability.visible ? (Queue ? (_jsx(Queue, { items: thread.queuedMessages, threadId: threadId, active: active, pending: command.pending, onSteer: steerQueued, onRemove: removeQueued })) : (_jsx(MessageQueueDrawer, { variant: "recessed", items: thread.queuedMessages
|
|
1472
|
+
return (_jsxs("div", { className: `agentkit-composer-stack ${className ?? ""}`, children: [queueCapability.visible ? (Queue ? (_jsx(Queue, { items: thread.queuedMessages, threadId: threadId, active: active, pending: command.pending || Boolean(disabled), onSteer: steerQueued, onRemove: removeQueued })) : (_jsx(MessageQueueDrawer, { variant: "recessed", items: thread.queuedMessages.map((message) => ({
|
|
1473
|
+
id: message.id,
|
|
1474
|
+
text: message.text,
|
|
1475
|
+
images: (message.attachments ?? []).flatMap((attachment) => {
|
|
1476
|
+
if (!attachment.mediaType?.startsWith("image/"))
|
|
1477
|
+
return [];
|
|
1478
|
+
const src = safeAgentImageSrc(attachment.url);
|
|
1479
|
+
return src ? [src] : [];
|
|
1480
|
+
}),
|
|
1481
|
+
})), disabled: command.pending || Boolean(disabled), onSteer: steerQueued
|
|
1055
1482
|
? (item) => {
|
|
1056
1483
|
const message = thread.queuedMessages.find((candidate) => candidate.id === item.id);
|
|
1057
1484
|
if (message)
|
|
@@ -1067,15 +1494,42 @@ export function AgentKitComposer({ threadId: requestedThreadId, className, queue
|
|
|
1067
1494
|
steerHint: labels.queueSteerHint,
|
|
1068
1495
|
remove: labels.queueRemove,
|
|
1069
1496
|
moreActions: labels.queueMore,
|
|
1070
|
-
}
|
|
1497
|
+
}, getItemActions: supportsQueueReordering
|
|
1498
|
+
? (item) => [
|
|
1499
|
+
{
|
|
1500
|
+
id: "move-to-top",
|
|
1501
|
+
label: labels.queueMoveToTop,
|
|
1502
|
+
icon: _jsx(IconArrowUp, { "aria-hidden": "true", size: 14 }),
|
|
1503
|
+
disabled: item.id === thread.queuedMessages[0]?.id ||
|
|
1504
|
+
moveQueuedToTop.pending,
|
|
1505
|
+
onSelect: (selected) => void command
|
|
1506
|
+
.execute(() => moveQueuedToTop.execute(selected.id))
|
|
1507
|
+
.catch(() => undefined),
|
|
1508
|
+
},
|
|
1509
|
+
]
|
|
1510
|
+
: undefined }))) : null, suggestionsCapability.visible && thread.suggestions.length ? (Suggestions ? (_jsx(Suggestions, { suggestions: thread.suggestions, threadId: threadId, pending: command.pending, onSelect: selectSuggestion })) : (_jsx(AgentSuggestionBar, { suggestions: thread.suggestions.map((suggestion) => ({
|
|
1071
1511
|
...suggestion,
|
|
1072
|
-
disabled: command.pending,
|
|
1073
|
-
})), ariaLabel: labels.suggestions, onSelect: selectSuggestion, className: "agentkit-suggestions" }))) : null, _jsx(PromptComposer, { contextItems: contextItems, onRemoveContextItem: onRemoveContextItem, onInspectContextItem: onInspectContextItem, onRetryContextItem: onRetryContextItem, contextMenuItems: contextMenuItems, attachmentAdapter: attachmentAdapter, inlineTextAttachments: inlineTextAttachments, rootClassName: "agentkit-composer",
|
|
1512
|
+
disabled: command.pending || Boolean(disabled),
|
|
1513
|
+
})), ariaLabel: labels.suggestions, onSelect: selectSuggestion, className: "agentkit-suggestions" }))) : null, _jsx(PromptComposer, { contextItems: contextItems, onRemoveContextItem: onRemoveContextItem, onInspectContextItem: onInspectContextItem, onRetryContextItem: onRetryContextItem, contextMenuItems: contextMenuItems, attachmentAdapter: attachmentAdapter, inlineTextAttachments: inlineTextAttachments, rootClassName: "agentkit-composer", draftScope: `agentkit:${threadId}${editingMessage ? `:edit:${editingMessage.id}` : ""}`, ariaLabel: labels.composerLabel, placeholder: placeholder ?? labels.composerPlaceholder, disabled: disabled, onDisabledClick: onDisabledClick, onConnectProvider: onConnectProvider, onConnectLocalRuntime: onConnectLocalRuntime, imageModelMenu: imageModelMenu, autoFocus: autoFocus, composerRef: composerRef, submitting: command.pending, willQueue: active && queueWhileRunning && canQueue, showModelSelector: showModelSelector && canSelectModel, availableModels: availableModels, modelListLoading: modelListLoading, selectedModel: selectedModel, selectedEngine: selectedEngine, selectedEffort: selectedEffort, onModelChange: onModelChange, onEffortChange: onEffortChange, availableAgents: availableAgents, selectedAgent: selectedAgent, agentOnly: agentOnly, onAgentChange: onAgentChange, onModelSelectorOpenChange: onModelSelectorOpenChange, modelStatusChecksEnabled: modelStatusChecksEnabled, layoutVariant: layoutVariant, toolbarSlot: composerToolbarSlot, initialText: composerInitialText, initialTextKey: composerInitialTextKey, onTextChange: onTextChange, extraActionButton: extraActionButton, onBeforeSubmit: onBeforeSubmit, onAttachmentError: reportAttachmentError, interceptBuildRequestsForBuilder: interceptBuildRequestsForBuilder, planModeDisabled: planModeDisabled, planModeDisabledReason: planModeDisabledReason, stopButton: stopButton, attachmentsEnabled: canUpload, onAttachmentRequest: onAttachmentRequest, contextButtonTooltipDisabled: contextButtonTooltipDisabled, slashCommands: slashCommands, slashSkills: slashSkills, includeDefaultSlashCommands: includeDefaultSlashCommands ?? false, includeDefaultSlashSkills: includeDefaultSlashSkills ?? false, onSlashCommand: onSlashCommand, plusMenuMode: plusMenuMode ?? "full", voiceEnabled: voiceEnabled, execMode: executionMode === "plan" ? "plan" : "build", onExecModeChange: (nextMode) => {
|
|
1074
1514
|
const next = nextMode === "plan" ? "plan" : "act";
|
|
1075
1515
|
if (mode === undefined)
|
|
1076
1516
|
setUncontrolledMode(next);
|
|
1077
1517
|
onModeChange?.(next);
|
|
1078
|
-
}, onSubmit:
|
|
1518
|
+
}, onSubmit: async (text, files, references, options) => {
|
|
1519
|
+
if (disabled) {
|
|
1520
|
+
onDisabledClick?.();
|
|
1521
|
+
return;
|
|
1522
|
+
}
|
|
1523
|
+
focusComposer();
|
|
1524
|
+
try {
|
|
1525
|
+
await command.execute(async () => {
|
|
1526
|
+
await submitMessage(text, files, references, options);
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
finally {
|
|
1530
|
+
focusComposer();
|
|
1531
|
+
}
|
|
1532
|
+
} }), command.error ? (_jsxs("div", { className: "agentkit-composer-error", role: "alert", children: [_jsx(IconAlertCircle, { "aria-hidden": "true", className: "agentkit-icon" }), _jsx("span", { children: command.error.message })] })) : null, attachmentError ? (_jsxs("div", { className: "agentkit-composer-error", role: "alert", children: [_jsx(IconAlertCircle, { "aria-hidden": "true", className: "agentkit-icon" }), _jsx("span", { children: attachmentError })] })) : null] }));
|
|
1079
1533
|
}
|
|
1080
1534
|
const AGENTKIT_TRANSCRIPT_BOTTOM_THRESHOLD = 24;
|
|
1081
1535
|
const AGENTKIT_TRANSCRIPT_SCROLLBAR_IDLE_MS = 900;
|
|
@@ -1092,11 +1546,23 @@ export function isAgentKitTranscriptNearBottom(element, threshold = AGENTKIT_TRA
|
|
|
1092
1546
|
return (element.scrollHeight - element.clientHeight - element.scrollTop <= threshold);
|
|
1093
1547
|
}
|
|
1094
1548
|
export function AgentKitChat({ title, toolbar, composer = true, composerProps, emptyComposerPlacement = "center", autoScroll = true, className, }) {
|
|
1095
|
-
const { threadId, slots, labels } = useAgentKit();
|
|
1549
|
+
const { threadId, slots, labels, onThreadForked } = useAgentKit();
|
|
1096
1550
|
const connection = useAgentConnection();
|
|
1551
|
+
const uploadsCapability = useAgentCapability("uploads");
|
|
1097
1552
|
const control = useAgentKitControl(threadId);
|
|
1098
1553
|
const recovery = useAgentKitMutation(() => control.load(), threadId);
|
|
1099
1554
|
const thread = useAgentThread(threadId);
|
|
1555
|
+
const [pendingEdit, setPendingEdit] = useState(null);
|
|
1556
|
+
const editingMessage = pendingEdit?.threadId === threadId ? pendingEdit.message : null;
|
|
1557
|
+
const setEditingMessage = useCallback((message) => {
|
|
1558
|
+
setPendingEdit(message ? { threadId, message } : null);
|
|
1559
|
+
}, [threadId]);
|
|
1560
|
+
const messageEditContext = {
|
|
1561
|
+
enabled: composer && Boolean(onThreadForked),
|
|
1562
|
+
message: editingMessage,
|
|
1563
|
+
setMessage: setEditingMessage,
|
|
1564
|
+
};
|
|
1565
|
+
useEffect(() => setPendingEdit(null), [threadId]);
|
|
1100
1566
|
const hasConversation = thread.messages.length > 0;
|
|
1101
1567
|
const Message = slots.message ?? AgentMessageView;
|
|
1102
1568
|
const EmptyState = slots.emptyState;
|
|
@@ -1113,6 +1579,7 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1113
1579
|
const Footer = slots.footer;
|
|
1114
1580
|
const transcriptRef = useRef(null);
|
|
1115
1581
|
const transcriptContentRef = useRef(null);
|
|
1582
|
+
const fileDragDepthRef = useRef(0);
|
|
1116
1583
|
const followsTranscriptRef = useRef(true);
|
|
1117
1584
|
const transcriptThreadIdRef = useRef(threadId);
|
|
1118
1585
|
const previousLastMessageIdRef = useRef(undefined);
|
|
@@ -1120,9 +1587,59 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1120
1587
|
const transcriptScrollbarInteractionRef = useRef(false);
|
|
1121
1588
|
const transcriptScrollbarTimeoutRef = useRef(undefined);
|
|
1122
1589
|
const [transcriptScrollbarVisible, setTranscriptScrollbarVisible] = useState(false);
|
|
1590
|
+
const [transcriptAwayFromBottom, setTranscriptAwayFromBottom] = useState(false);
|
|
1591
|
+
const [fileDragActive, setFileDragActive] = useState(false);
|
|
1123
1592
|
const lastMessage = thread.messages.at(-1);
|
|
1124
1593
|
const lastEventId = thread.events.at(-1)?.id;
|
|
1125
1594
|
const connectionError = connection.status === "error" ? connection.error : undefined;
|
|
1595
|
+
const canDropFiles = composer &&
|
|
1596
|
+
uploadsCapability.enabled &&
|
|
1597
|
+
composerProps?.attachmentsEnabled !== false;
|
|
1598
|
+
const hasFiles = (event) => Array.from(event.dataTransfer.types).includes("Files");
|
|
1599
|
+
const handleFileDragEnter = (event) => {
|
|
1600
|
+
if (!canDropFiles || event.defaultPrevented || !hasFiles(event))
|
|
1601
|
+
return;
|
|
1602
|
+
if (event.relatedTarget instanceof Node &&
|
|
1603
|
+
event.currentTarget.contains(event.relatedTarget)) {
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
event.preventDefault();
|
|
1607
|
+
fileDragDepthRef.current += 1;
|
|
1608
|
+
setFileDragActive(true);
|
|
1609
|
+
};
|
|
1610
|
+
const handleFileDragOver = (event) => {
|
|
1611
|
+
if (!canDropFiles || event.defaultPrevented || !hasFiles(event))
|
|
1612
|
+
return;
|
|
1613
|
+
event.preventDefault();
|
|
1614
|
+
event.dataTransfer.dropEffect = "copy";
|
|
1615
|
+
setFileDragActive(true);
|
|
1616
|
+
};
|
|
1617
|
+
const handleFileDragLeave = (event) => {
|
|
1618
|
+
if (!canDropFiles || !hasFiles(event))
|
|
1619
|
+
return;
|
|
1620
|
+
if (event.relatedTarget instanceof Node &&
|
|
1621
|
+
event.currentTarget.contains(event.relatedTarget)) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
fileDragDepthRef.current = Math.max(0, fileDragDepthRef.current - 1);
|
|
1625
|
+
setFileDragActive(fileDragDepthRef.current > 0);
|
|
1626
|
+
};
|
|
1627
|
+
const handleFileDrop = (event) => {
|
|
1628
|
+
if (!canDropFiles || !hasFiles(event))
|
|
1629
|
+
return;
|
|
1630
|
+
fileDragDepthRef.current = 0;
|
|
1631
|
+
setFileDragActive(false);
|
|
1632
|
+
if (event.defaultPrevented)
|
|
1633
|
+
return;
|
|
1634
|
+
event.preventDefault();
|
|
1635
|
+
event.stopPropagation();
|
|
1636
|
+
const files = Array.from(event.dataTransfer.files);
|
|
1637
|
+
if (!files.length)
|
|
1638
|
+
return;
|
|
1639
|
+
window.dispatchEvent(new CustomEvent("agentkit:attach-files", {
|
|
1640
|
+
detail: { threadId, files },
|
|
1641
|
+
}));
|
|
1642
|
+
};
|
|
1126
1643
|
const clearTranscriptScrollbarTimeout = useCallback(() => {
|
|
1127
1644
|
if (transcriptScrollbarTimeoutRef.current === undefined)
|
|
1128
1645
|
return;
|
|
@@ -1159,6 +1676,15 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1159
1676
|
return;
|
|
1160
1677
|
transcript.scrollTop = nextScrollTop;
|
|
1161
1678
|
}, [autoScroll]);
|
|
1679
|
+
const returnTranscriptToBottom = useCallback(() => {
|
|
1680
|
+
const transcript = transcriptRef.current;
|
|
1681
|
+
if (!transcript)
|
|
1682
|
+
return;
|
|
1683
|
+
followsTranscriptRef.current = true;
|
|
1684
|
+
const nextScrollTop = Math.max(0, transcript.scrollHeight - transcript.clientHeight);
|
|
1685
|
+
transcript.scrollTop = nextScrollTop;
|
|
1686
|
+
setTranscriptAwayFromBottom(false);
|
|
1687
|
+
}, []);
|
|
1162
1688
|
const cancelScheduledTranscriptFollow = useCallback(() => {
|
|
1163
1689
|
if (transcriptFollowFrameRef.current === undefined)
|
|
1164
1690
|
return;
|
|
@@ -1188,6 +1714,7 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1188
1714
|
transcriptThreadIdRef.current = threadId;
|
|
1189
1715
|
previousLastMessageIdRef.current = undefined;
|
|
1190
1716
|
followsTranscriptRef.current = true;
|
|
1717
|
+
setTranscriptAwayFromBottom(false);
|
|
1191
1718
|
hideTranscriptScrollbar();
|
|
1192
1719
|
}
|
|
1193
1720
|
const previousLastMessageId = previousLastMessageIdRef.current;
|
|
@@ -1235,13 +1762,15 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1235
1762
|
const transcript = transcriptRef.current;
|
|
1236
1763
|
if (!transcript)
|
|
1237
1764
|
return;
|
|
1238
|
-
|
|
1239
|
-
|
|
1765
|
+
const nearBottom = isAgentKitTranscriptNearBottom(transcript);
|
|
1766
|
+
if (nearBottom || transcriptScrollbarInteractionRef.current) {
|
|
1767
|
+
followsTranscriptRef.current = nearBottom;
|
|
1240
1768
|
}
|
|
1769
|
+
setTranscriptAwayFromBottom(!nearBottom);
|
|
1241
1770
|
if (transcriptScrollbarInteractionRef.current) {
|
|
1242
1771
|
scheduleTranscriptScrollbarHide();
|
|
1243
1772
|
}
|
|
1244
|
-
}, [
|
|
1773
|
+
}, [scheduleTranscriptScrollbarHide]);
|
|
1245
1774
|
const handleTranscriptPointerDown = useCallback((event) => {
|
|
1246
1775
|
const bounds = event.currentTarget.getBoundingClientRect();
|
|
1247
1776
|
const edgeDistance = Math.min(Math.abs(event.clientX - bounds.left), Math.abs(bounds.right - event.clientX));
|
|
@@ -1267,7 +1796,8 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1267
1796
|
return result;
|
|
1268
1797
|
}, [thread.events]);
|
|
1269
1798
|
const messageBoundarySequences = useMemo(() => {
|
|
1270
|
-
const
|
|
1799
|
+
const firstVisibleSequence = new Map();
|
|
1800
|
+
const lastTextDeltaSequence = new Map();
|
|
1271
1801
|
const assistantMessageIds = new Set(thread.messages
|
|
1272
1802
|
.filter((message) => message.role === "assistant")
|
|
1273
1803
|
.map((message) => message.id));
|
|
@@ -1282,7 +1812,16 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1282
1812
|
: undefined;
|
|
1283
1813
|
if (!messageId)
|
|
1284
1814
|
continue;
|
|
1285
|
-
|
|
1815
|
+
if (!firstVisibleSequence.has(messageId)) {
|
|
1816
|
+
firstVisibleSequence.set(messageId, event.sequence);
|
|
1817
|
+
}
|
|
1818
|
+
if (event.type === "message.delta") {
|
|
1819
|
+
lastTextDeltaSequence.set(messageId, event.sequence);
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
const result = new Map();
|
|
1823
|
+
for (const [messageId, sequence] of firstVisibleSequence) {
|
|
1824
|
+
result.set(messageId, lastTextDeltaSequence.get(messageId) ?? sequence);
|
|
1286
1825
|
}
|
|
1287
1826
|
return result;
|
|
1288
1827
|
}, [thread.events, thread.messages]);
|
|
@@ -1370,6 +1909,6 @@ export function AgentKitChat({ title, toolbar, composer = true, composerProps, e
|
|
|
1370
1909
|
}
|
|
1371
1910
|
transcriptItems.push(_jsx(AgentKitSurfaceBoundary, { surface: "activity", resetKey: `${runId}:${thread.events.length}`, children: renderRunFailure(runId) }, `run-failure:${runId}`));
|
|
1372
1911
|
}
|
|
1373
|
-
return (_jsxs("section", { "aria-label": typeof title === "string" ? title : labels.conversation, className: `agentkit-chat ${className ?? ""}`, "data-empty": !hasConversation, "data-empty-composer-placement": emptyComposerPlacement, children: [hasConversation && (title || toolbar) ? (_jsx(AgentKitSurfaceBoundary, { surface: "header", resetKey: threadId, children: _jsx(AgentKitRegionSlot, { slot: Header, threadId: threadId, children: _jsxs("header", { className: "agentkit-chat-header", children: [_jsx("div", { className: "agentkit-chat-title", children: title }), _jsx(AgentKitRegionSlot, { slot: Toolbar, threadId: threadId, children: _jsx("div", { className: "agentkit-chat-toolbar", children: toolbar }) })] }) }) })) : null,
|
|
1912
|
+
return (_jsx(AgentMessageEditContext.Provider, { value: messageEditContext, children: _jsxs("section", { "aria-label": typeof title === "string" ? title : labels.conversation, className: `agentkit-chat ${className ?? ""}`, "data-empty": !hasConversation, "data-empty-composer-placement": emptyComposerPlacement, onDragEnter: handleFileDragEnter, onDragOver: handleFileDragOver, onDragLeave: handleFileDragLeave, onDrop: handleFileDrop, children: [fileDragActive ? (_jsx("div", { className: "agentkit-file-drop-overlay", role: "status", children: labels.dropFilesToAttach })) : null, hasConversation && (title || toolbar) ? (_jsx(AgentKitSurfaceBoundary, { surface: "header", resetKey: threadId, children: _jsx(AgentKitRegionSlot, { slot: Header, threadId: threadId, children: _jsxs("header", { className: "agentkit-chat-header", children: [_jsx("div", { className: "agentkit-chat-title", children: title }), _jsx(AgentKitRegionSlot, { slot: Toolbar, threadId: threadId, children: _jsx("div", { className: "agentkit-chat-toolbar", children: toolbar }) })] }) }) })) : null, _jsxs("div", { ref: transcriptRef, className: "agentkit-transcript", "aria-live": "off", "data-scrollbar-visible": transcriptScrollbarVisible, onKeyDown: handleTranscriptKeyDown, onPointerDown: handleTranscriptPointerDown, onScroll: handleTranscriptScroll, onTouchMove: revealTranscriptScrollbar, onWheel: revealTranscriptScrollbar, children: [_jsx("div", { ref: transcriptContentRef, className: "agentkit-transcript-content", children: _jsxs(AgentKitRegionSlot, { slot: Transcript, threadId: threadId, children: [!hasConversation && EmptyState ? (_jsx(EmptyState, { threadId: threadId })) : null, transcriptItems, approvalEntries.map(([approvalId, request]) => (_jsx(AgentKitSurfaceBoundary, { surface: "approval", resetKey: `${approvalId}:${thread.events.length}`, children: Approval ? (_jsx(Approval, { value: request, threadId: threadId, runId: thread.approvalRunIds[approvalId] ?? "" })) : (_jsx(AgentApprovalPrompt, { request: request, runId: thread.approvalRunIds[approvalId] ?? "" })) }, approvalId))), connectionRequestEntries.map(([requestId, request]) => (_jsx(AgentKitSurfaceBoundary, { surface: "connection-request", resetKey: `${requestId}:${request.status}`, children: ConnectionRequest ? (_jsx(ConnectionRequest, { value: request, threadId: threadId, runId: thread.connectionRequestRunIds[requestId] ?? "" })) : (_jsx(AgentConnectionRequestCard, { request: request, runId: thread.connectionRequestRunIds[requestId] ?? "" })) }, requestId))), connectionError ? (_jsx(AgentKitSurfaceBoundary, { surface: "connection-error", resetKey: `${threadId}:${connectionError.code}`, children: _jsx(ErrorState, { error: connectionError, threadId: threadId, recover: recovery.execute, recovering: recovery.pending, recoveryError: recovery.error }) })) : null] }) }), transcriptAwayFromBottom ? (_jsx("div", { className: "agentkit-scroll-to-bottom-control", children: _jsx(IconButton, { label: labels.scrollToBottom, icon: _jsx(IconArrowDown, { "aria-hidden": "true" }), size: "compact", onPress: returnTranscriptToBottom }) })) : null] }), composer ? (_jsx("footer", { className: "agentkit-chat-footer", children: _jsx(AgentKitSurfaceBoundary, { surface: "composer", resetKey: threadId, children: _jsx(AgentKitRegionSlot, { slot: Footer, threadId: threadId, children: slots.composer ? (_jsx(Composer, { threadId: threadId })) : (_jsx(AgentKitComposer, { ...composerProps, threadId: threadId })) }) }) })) : null] }) }));
|
|
1374
1913
|
}
|
|
1375
1914
|
//# sourceMappingURL=components.js.map
|