@agents24/chat-react 0.3.2 → 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/LICENSE +19 -0
- package/README.md +21 -66
- package/compatibility.json +54 -0
- package/dist/active-content.d.ts +10 -0
- package/dist/{chunk-EWZO4QJI.js → chunk-CHAU7FNW.js} +3 -3
- package/dist/chunk-CHAU7FNW.js.map +1 -0
- package/dist/{chunk-FFO6KEB4.js → chunk-LI67M74F.js} +98 -12
- package/dist/chunk-LI67M74F.js.map +1 -0
- package/dist/chunk-NLEOWTZG.js +1327 -0
- package/dist/chunk-NLEOWTZG.js.map +1 -0
- package/dist/index.cjs +66 -1852
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -6
- package/dist/index.js +6 -1841
- package/dist/index.js.map +1 -1
- package/dist/latest-thread-scroller.d.ts +7 -7
- package/dist/mcp-oauth.d.ts +3 -1
- package/dist/renderers.d.ts +3 -3
- package/dist/styles.css +407 -0
- package/dist/templates/agent-chat-app.d.ts +18 -0
- package/dist/templates/agent-chat-layout.d.ts +69 -0
- package/dist/templates/agent-chat-shell.d.ts +1 -1
- package/dist/templates/index.cjs +1904 -18
- package/dist/templates/index.cjs.map +1 -1
- package/dist/templates/index.d.ts +2 -0
- package/dist/templates/index.js +529 -11
- package/dist/templates/index.js.map +1 -1
- package/dist/types.d.ts +12 -380
- package/dist/ui/agent-chat-actions.d.ts +1 -2
- package/dist/ui/agent-chat-composer.d.ts +2 -1
- package/dist/ui/agent-chat-message.d.ts +3 -3
- package/dist/ui/agent-response-timeline.d.ts +2 -2
- package/dist/ui/attachment.d.ts +9 -9
- package/dist/ui/bubble.d.ts +4 -4
- package/dist/ui/index.cjs +137 -43
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.js +62 -1220
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/marker.d.ts +3 -3
- package/dist/ui/mcp-connect-required-card.d.ts +1 -1
- package/dist/ui/message-response.d.ts +1 -1
- package/dist/ui/message.d.ts +6 -6
- package/package.json +27 -8
- package/dist/chunk-EWZO4QJI.js.map +0 -1
- package/dist/chunk-FFO6KEB4.js.map +0 -1
- package/dist/chunk-UU7OXHL3.js +0 -11
- package/dist/chunk-UU7OXHL3.js.map +0 -1
- package/dist/context-window.d.ts +0 -38
- package/dist/controller-actions.d.ts +0 -45
- package/dist/controller-helpers.d.ts +0 -27
- package/dist/controller-hitl.d.ts +0 -22
- package/dist/controller-options.d.ts +0 -15
- package/dist/controller-thread-events.d.ts +0 -7
- package/dist/controller.d.ts +0 -5
- package/dist/message-lifecycle.d.ts +0 -15
- package/dist/model.d.ts +0 -30
- package/dist/sse.d.ts +0 -5
- package/dist/transport.d.ts +0 -43
package/dist/templates/index.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
|
-
LatestThreadScroller
|
|
3
|
-
|
|
2
|
+
LatestThreadScroller,
|
|
3
|
+
validateMcpCallbackResult,
|
|
4
|
+
waitForMcpOauthComplete
|
|
5
|
+
} from "../chunk-LI67M74F.js";
|
|
4
6
|
import {
|
|
7
|
+
AgentChatComposer,
|
|
8
|
+
AgentChatDefaultActions,
|
|
9
|
+
AgentChatMessage,
|
|
5
10
|
cn
|
|
6
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-NLEOWTZG.js";
|
|
12
|
+
import "../chunk-CHAU7FNW.js";
|
|
7
13
|
|
|
8
14
|
// src/templates/agent-chat-shell.tsx
|
|
9
15
|
import * as React from "react";
|
|
@@ -120,21 +126,21 @@ function AgentChatShell({
|
|
|
120
126
|
const bottomPadding = hideComposer ? 32 : (composerOverlayHeight ?? measuredOverlayHeight) + 72;
|
|
121
127
|
const isEmptyState = messages.length === 0;
|
|
122
128
|
if (isEmptyState) {
|
|
123
|
-
return /* @__PURE__ */ jsx("div", { className: cn("relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ jsx("div", { className: "relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
|
|
129
|
+
return /* @__PURE__ */ jsx("div", { className: cn("a24-shell a24-shell--empty relative flex h-full min-h-0 w-full flex-col overflow-visible px-4", className), dir, children: renderEmptyState ? renderEmptyState({ composer: hideComposer ? void 0 : composer, title: emptyTitle }) : /* @__PURE__ */ jsx("div", { className: "a24-shell__empty relative z-10 flex h-full flex-1 flex-col justify-center p-0 pt-13", children: /* @__PURE__ */ jsxs("div", { className: "a24-shell__empty-content flex w-full flex-col items-center pb-34 text-center animate-in fade-in slide-in-from-bottom-4 duration-500", children: [
|
|
124
130
|
/* @__PURE__ */ jsx(
|
|
125
131
|
"p",
|
|
126
132
|
{
|
|
127
133
|
className: cn(
|
|
128
|
-
"pb-6 text-3xl font-semibold transition-transform duration-200",
|
|
134
|
+
"a24-shell__empty-title pb-6 text-3xl font-semibold transition-transform duration-200",
|
|
129
135
|
emptyInputAttachmentCount > 0 && "-translate-y-12"
|
|
130
136
|
),
|
|
131
137
|
children: emptyTitle
|
|
132
138
|
}
|
|
133
139
|
),
|
|
134
|
-
!hideComposer && composer ? /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-3xl px-4", children: composer }) : null
|
|
140
|
+
!hideComposer && composer ? /* @__PURE__ */ jsx("div", { className: "a24-shell__empty-composer mx-auto w-full max-w-3xl px-4", children: composer }) : null
|
|
135
141
|
] }) }) });
|
|
136
142
|
}
|
|
137
|
-
return /* @__PURE__ */ jsx("div", { className: cn("relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ jsxs(LatestThreadScroller.Provider, { children: [
|
|
143
|
+
return /* @__PURE__ */ jsx("div", { className: cn("a24-shell relative flex h-full min-h-0 w-full flex-col", className), dir, children: /* @__PURE__ */ jsxs(LatestThreadScroller.Provider, { children: [
|
|
138
144
|
/* @__PURE__ */ jsxs(LatestThreadScroller.Root, { children: [
|
|
139
145
|
/* @__PURE__ */ jsx(
|
|
140
146
|
LatestThreadScroller.Viewport,
|
|
@@ -149,7 +155,7 @@ function AgentChatShell({
|
|
|
149
155
|
{
|
|
150
156
|
ref: contentRef,
|
|
151
157
|
className: cn(
|
|
152
|
-
"mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
|
|
158
|
+
"a24-shell__content mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
|
|
153
159
|
contentClassName
|
|
154
160
|
),
|
|
155
161
|
style: { paddingBottom: bottomPadding },
|
|
@@ -183,22 +189,534 @@ function AgentChatShell({
|
|
|
183
189
|
{
|
|
184
190
|
ref: overlayRef,
|
|
185
191
|
className: cn(
|
|
186
|
-
"pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
|
|
192
|
+
"a24-shell__composer-overlay pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
|
|
187
193
|
composerOverlayClassName
|
|
188
194
|
),
|
|
189
195
|
style: {
|
|
190
196
|
background: "linear-gradient(to bottom, transparent 0%, color-mix(in oklch, var(--chat-background, var(--background)) 85%, transparent) 45%, var(--chat-background, var(--background)) 65%, var(--chat-background, var(--background)) 100%)"
|
|
191
197
|
},
|
|
192
198
|
children: [
|
|
193
|
-
/* @__PURE__ */ jsx("div", { className: "pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ jsx(LatestThreadScroller.Button, {}) }),
|
|
194
|
-
/* @__PURE__ */ jsx("div", { className: "pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
|
|
199
|
+
/* @__PURE__ */ jsx("div", { className: "a24-shell__scroll-button pointer-events-auto absolute inset-x-0 top-0 flex -translate-y-full justify-center pb-3", children: /* @__PURE__ */ jsx(LatestThreadScroller.Button, {}) }),
|
|
200
|
+
/* @__PURE__ */ jsx("div", { className: "a24-shell__composer-inner pointer-events-auto mx-auto w-full max-w-3xl px-4", children: composer })
|
|
195
201
|
]
|
|
196
202
|
}
|
|
197
203
|
) : null
|
|
198
204
|
] }) });
|
|
199
205
|
}
|
|
206
|
+
|
|
207
|
+
// src/templates/agent-chat-layout.tsx
|
|
208
|
+
import * as React2 from "react";
|
|
209
|
+
import {
|
|
210
|
+
ChevronDown,
|
|
211
|
+
Menu,
|
|
212
|
+
MessageSquare,
|
|
213
|
+
MoreHorizontal,
|
|
214
|
+
Plus,
|
|
215
|
+
RefreshCw,
|
|
216
|
+
Search,
|
|
217
|
+
Trash2,
|
|
218
|
+
Wifi,
|
|
219
|
+
WifiOff,
|
|
220
|
+
X
|
|
221
|
+
} from "lucide-react";
|
|
222
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
223
|
+
var stateCopy = {
|
|
224
|
+
loading: { title: "Loading chat", detail: "Your conversation is being prepared." },
|
|
225
|
+
reconnecting: { title: "Reconnecting", detail: "Messages are safe. Trying the connection again." },
|
|
226
|
+
offline: { title: "You\u2019re offline", detail: "Reconnect to continue this conversation." },
|
|
227
|
+
denied: { title: "Access denied", detail: "This deployment does not allow this request." },
|
|
228
|
+
quota: { title: "Usage limit reached", detail: "Try again after the deployment limit resets." },
|
|
229
|
+
failed: { title: "Something went wrong", detail: "The request could not be completed safely." },
|
|
230
|
+
expired: { title: "Session expired", detail: "Reconnect to start a new secure session." }
|
|
231
|
+
};
|
|
232
|
+
function AgentChatStatePanel({
|
|
233
|
+
errorMessage,
|
|
234
|
+
onRetry,
|
|
235
|
+
state
|
|
236
|
+
}) {
|
|
237
|
+
const copy = stateCopy[state];
|
|
238
|
+
const retryable = state === "offline" || state === "failed" || state === "expired";
|
|
239
|
+
return /* @__PURE__ */ jsxs2("div", { className: "a24-state", "data-state": state, role: state === "failed" ? "alert" : "status", children: [
|
|
240
|
+
/* @__PURE__ */ jsx2("div", { className: "a24-state__icon", "aria-hidden": "true", children: state === "offline" ? /* @__PURE__ */ jsx2(WifiOff, {}) : state === "reconnecting" ? /* @__PURE__ */ jsx2(RefreshCw, {}) : /* @__PURE__ */ jsx2(MessageSquare, {}) }),
|
|
241
|
+
/* @__PURE__ */ jsx2("h2", { children: copy.title }),
|
|
242
|
+
/* @__PURE__ */ jsx2("p", { children: errorMessage || copy.detail }),
|
|
243
|
+
retryable && onRetry ? /* @__PURE__ */ jsx2("button", { className: "a24-button a24-button--primary", onClick: () => void onRetry(), type: "button", children: "Try again" }) : null
|
|
244
|
+
] });
|
|
245
|
+
}
|
|
246
|
+
function connectionLabel(state) {
|
|
247
|
+
if (state === "connected") return "Connected";
|
|
248
|
+
if (state === "connecting") return "Connecting";
|
|
249
|
+
if (state === "reconnecting") return "Reconnecting";
|
|
250
|
+
if (state === "expired") return "Session expired";
|
|
251
|
+
return "Offline";
|
|
252
|
+
}
|
|
253
|
+
function AgentAvatar({ agent }) {
|
|
254
|
+
return agent.avatarUrl ? /* @__PURE__ */ jsx2("img", { alt: "", className: "a24-avatar", height: 36, src: agent.avatarUrl, width: 36 }) : /* @__PURE__ */ jsx2("span", { "aria-hidden": "true", className: "a24-avatar a24-avatar--fallback", children: agent.name.trim().slice(0, 1).toUpperCase() || "A" });
|
|
255
|
+
}
|
|
256
|
+
function ThreadNavigation({
|
|
257
|
+
activeThreadId,
|
|
258
|
+
close,
|
|
259
|
+
hasMoreThreads,
|
|
260
|
+
isDeletingThread,
|
|
261
|
+
isLoadingThreads,
|
|
262
|
+
onDeleteThread,
|
|
263
|
+
onLoadMoreThreads,
|
|
264
|
+
onNewThread,
|
|
265
|
+
onOpenThread,
|
|
266
|
+
slots,
|
|
267
|
+
threads
|
|
268
|
+
}) {
|
|
269
|
+
const [query, setQuery] = React2.useState("");
|
|
270
|
+
const normalizedQuery = query.trim().toLocaleLowerCase();
|
|
271
|
+
const visibleThreads = normalizedQuery ? threads.filter((thread) => String(thread.title || "New chat").toLocaleLowerCase().includes(normalizedQuery)) : threads;
|
|
272
|
+
return /* @__PURE__ */ jsxs2("div", { className: "a24-sidebar__inner", children: [
|
|
273
|
+
/* @__PURE__ */ jsxs2("div", { className: "a24-sidebar__top", children: [
|
|
274
|
+
/* @__PURE__ */ jsxs2(
|
|
275
|
+
"button",
|
|
276
|
+
{
|
|
277
|
+
"aria-label": "New chat",
|
|
278
|
+
className: "a24-button a24-button--primary a24-sidebar__new",
|
|
279
|
+
onClick: () => {
|
|
280
|
+
onNewThread();
|
|
281
|
+
close();
|
|
282
|
+
},
|
|
283
|
+
type: "button",
|
|
284
|
+
children: [
|
|
285
|
+
/* @__PURE__ */ jsx2(Plus, { "aria-hidden": "true" }),
|
|
286
|
+
"New chat"
|
|
287
|
+
]
|
|
288
|
+
}
|
|
289
|
+
),
|
|
290
|
+
/* @__PURE__ */ jsx2("button", { "aria-label": "Close conversations", className: "a24-icon-button a24-sidebar__mobile-close", onClick: close, type: "button", children: /* @__PURE__ */ jsx2(X, { "aria-hidden": "true" }) })
|
|
291
|
+
] }),
|
|
292
|
+
/* @__PURE__ */ jsxs2("label", { className: "a24-search", children: [
|
|
293
|
+
/* @__PURE__ */ jsx2(Search, { "aria-hidden": "true" }),
|
|
294
|
+
/* @__PURE__ */ jsx2("span", { className: "a24-sr-only", children: "Search conversations" }),
|
|
295
|
+
/* @__PURE__ */ jsx2(
|
|
296
|
+
"input",
|
|
297
|
+
{
|
|
298
|
+
autoComplete: "off",
|
|
299
|
+
name: "conversation-search",
|
|
300
|
+
onChange: (event) => setQuery(event.target.value),
|
|
301
|
+
placeholder: "Search conversations\u2026",
|
|
302
|
+
type: "search",
|
|
303
|
+
value: query
|
|
304
|
+
}
|
|
305
|
+
)
|
|
306
|
+
] }),
|
|
307
|
+
/* @__PURE__ */ jsxs2("nav", { "aria-label": "Conversations", className: "a24-thread-list", children: [
|
|
308
|
+
isLoadingThreads && threads.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "a24-sidebar__hint", role: "status", children: "Loading conversations\u2026" }) : null,
|
|
309
|
+
!isLoadingThreads && visibleThreads.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "a24-sidebar__hint", children: "No conversations yet." }) : null,
|
|
310
|
+
visibleThreads.map((thread) => {
|
|
311
|
+
const selected = thread.id === activeThreadId;
|
|
312
|
+
return /* @__PURE__ */ jsxs2("div", { className: "a24-thread", "data-selected": selected || void 0, children: [
|
|
313
|
+
/* @__PURE__ */ jsxs2(
|
|
314
|
+
"button",
|
|
315
|
+
{
|
|
316
|
+
"aria-current": selected ? "page" : void 0,
|
|
317
|
+
className: "a24-thread__open",
|
|
318
|
+
onClick: () => {
|
|
319
|
+
void onOpenThread(thread.id);
|
|
320
|
+
close();
|
|
321
|
+
},
|
|
322
|
+
type: "button",
|
|
323
|
+
children: [
|
|
324
|
+
/* @__PURE__ */ jsx2("span", { className: "a24-thread__title", children: thread.title || "New chat" }),
|
|
325
|
+
thread.status ? /* @__PURE__ */ jsx2("span", { className: "a24-thread__status", children: thread.status }) : null
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
),
|
|
329
|
+
slots?.threadActions ? slots.threadActions(thread) : onDeleteThread ? /* @__PURE__ */ jsx2(
|
|
330
|
+
"button",
|
|
331
|
+
{
|
|
332
|
+
"aria-label": `Delete ${thread.title || "conversation"}`,
|
|
333
|
+
className: "a24-icon-button a24-thread__delete",
|
|
334
|
+
disabled: isDeletingThread,
|
|
335
|
+
onClick: () => void onDeleteThread(thread.id),
|
|
336
|
+
type: "button",
|
|
337
|
+
children: /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": "true" })
|
|
338
|
+
}
|
|
339
|
+
) : null
|
|
340
|
+
] }, thread.id);
|
|
341
|
+
})
|
|
342
|
+
] }),
|
|
343
|
+
hasMoreThreads && onLoadMoreThreads ? /* @__PURE__ */ jsxs2("button", { className: "a24-button a24-button--quiet a24-sidebar__more", onClick: () => void onLoadMoreThreads(), type: "button", children: [
|
|
344
|
+
/* @__PURE__ */ jsx2(MoreHorizontal, { "aria-hidden": "true" }),
|
|
345
|
+
"Load more"
|
|
346
|
+
] }) : null
|
|
347
|
+
] });
|
|
348
|
+
}
|
|
349
|
+
function AgentChatLayout({
|
|
350
|
+
activeThreadId,
|
|
351
|
+
agent,
|
|
352
|
+
allowAttachments = true,
|
|
353
|
+
className,
|
|
354
|
+
connectionState = "connected",
|
|
355
|
+
dir = "ltr",
|
|
356
|
+
errorMessage,
|
|
357
|
+
hasMoreThreads,
|
|
358
|
+
isDeletingThread,
|
|
359
|
+
isLoadingOlder,
|
|
360
|
+
isLoadingThreads,
|
|
361
|
+
isRunning = false,
|
|
362
|
+
messages,
|
|
363
|
+
modelId,
|
|
364
|
+
models = [],
|
|
365
|
+
onDeleteThread,
|
|
366
|
+
onHitlAction,
|
|
367
|
+
onLoadMoreThreads,
|
|
368
|
+
onLoadOlder,
|
|
369
|
+
onModelChange,
|
|
370
|
+
onNewThread,
|
|
371
|
+
onOpenThread,
|
|
372
|
+
onRetryConnection,
|
|
373
|
+
onStop,
|
|
374
|
+
onSubmit,
|
|
375
|
+
placeholder,
|
|
376
|
+
slots,
|
|
377
|
+
state = "ready",
|
|
378
|
+
streamingMessageId,
|
|
379
|
+
threads,
|
|
380
|
+
title
|
|
381
|
+
}) {
|
|
382
|
+
const [sidebarOpen, setSidebarOpen] = React2.useState(false);
|
|
383
|
+
const menuButtonRef = React2.useRef(null);
|
|
384
|
+
const closeSidebar = React2.useCallback(() => {
|
|
385
|
+
setSidebarOpen(false);
|
|
386
|
+
menuButtonRef.current?.focus();
|
|
387
|
+
}, []);
|
|
388
|
+
React2.useEffect(() => {
|
|
389
|
+
if (!sidebarOpen) return void 0;
|
|
390
|
+
const onKeyDown = (event) => {
|
|
391
|
+
if (event.key !== "Escape") return;
|
|
392
|
+
event.preventDefault();
|
|
393
|
+
closeSidebar();
|
|
394
|
+
};
|
|
395
|
+
window.addEventListener("keydown", onKeyDown);
|
|
396
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
397
|
+
}, [closeSidebar, sidebarOpen]);
|
|
398
|
+
const shellState = state === "streaming" || state === "paused" ? "ready" : state;
|
|
399
|
+
const showState = shellState !== "ready";
|
|
400
|
+
const liveLabel = state === "streaming" ? "Agent is responding" : state === "paused" ? "Waiting for your response" : connectionLabel(connectionState);
|
|
401
|
+
const composer = /* @__PURE__ */ jsx2(
|
|
402
|
+
AgentChatComposer,
|
|
403
|
+
{
|
|
404
|
+
allowAttachments,
|
|
405
|
+
disabled: state === "denied" || state === "quota" || state === "expired" || state === "offline",
|
|
406
|
+
inputToolbarContent: models.length > 0 ? /* @__PURE__ */ jsxs2("label", { className: "a24-model-select", children: [
|
|
407
|
+
/* @__PURE__ */ jsx2("span", { className: "a24-sr-only", children: "Model" }),
|
|
408
|
+
/* @__PURE__ */ jsx2("select", { "aria-label": "Model", onChange: (event) => onModelChange?.(event.target.value), value: modelId || models[0]?.id, children: models.map((model) => /* @__PURE__ */ jsx2("option", { value: model.id, children: model.label }, model.id)) }),
|
|
409
|
+
/* @__PURE__ */ jsx2(ChevronDown, { "aria-hidden": "true" })
|
|
410
|
+
] }) : void 0,
|
|
411
|
+
isRunning,
|
|
412
|
+
onStop,
|
|
413
|
+
onSubmit,
|
|
414
|
+
placeholder
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
return /* @__PURE__ */ jsxs2("section", { className: cn("a24-chat", className), "data-agents24-chat-layout": "", dir, children: [
|
|
418
|
+
/* @__PURE__ */ jsx2("a", { className: "a24-skip-link", href: "#agents24-chat-main", children: "Skip to messages" }),
|
|
419
|
+
/* @__PURE__ */ jsx2("nav", { "aria-label": "Chat navigation", className: "a24-sidebar", "data-open": sidebarOpen || void 0, children: /* @__PURE__ */ jsx2(
|
|
420
|
+
ThreadNavigation,
|
|
421
|
+
{
|
|
422
|
+
activeThreadId,
|
|
423
|
+
close: closeSidebar,
|
|
424
|
+
hasMoreThreads,
|
|
425
|
+
isDeletingThread,
|
|
426
|
+
isLoadingThreads,
|
|
427
|
+
onDeleteThread,
|
|
428
|
+
onLoadMoreThreads,
|
|
429
|
+
onNewThread,
|
|
430
|
+
onOpenThread,
|
|
431
|
+
slots,
|
|
432
|
+
threads
|
|
433
|
+
}
|
|
434
|
+
) }),
|
|
435
|
+
sidebarOpen ? /* @__PURE__ */ jsx2("button", { "aria-label": "Close conversations", className: "a24-sidebar-backdrop", onClick: closeSidebar, type: "button" }) : null,
|
|
436
|
+
/* @__PURE__ */ jsxs2("div", { className: "a24-chat__body", children: [
|
|
437
|
+
/* @__PURE__ */ jsxs2("header", { className: "a24-header", role: "banner", children: [
|
|
438
|
+
/* @__PURE__ */ jsx2("button", { "aria-label": "Open conversations", className: "a24-icon-button a24-header__menu", onClick: () => setSidebarOpen(true), ref: menuButtonRef, type: "button", children: /* @__PURE__ */ jsx2(Menu, { "aria-hidden": "true" }) }),
|
|
439
|
+
/* @__PURE__ */ jsx2(AgentAvatar, { agent }),
|
|
440
|
+
/* @__PURE__ */ jsxs2("div", { className: "a24-header__identity", children: [
|
|
441
|
+
/* @__PURE__ */ jsx2("h1", { children: title || agent.name }),
|
|
442
|
+
/* @__PURE__ */ jsxs2("div", { className: "a24-connection", "data-state": connectionState, children: [
|
|
443
|
+
connectionState === "offline" || connectionState === "expired" ? /* @__PURE__ */ jsx2(WifiOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx2(Wifi, { "aria-hidden": "true" }),
|
|
444
|
+
/* @__PURE__ */ jsx2("span", { children: connectionLabel(connectionState) })
|
|
445
|
+
] })
|
|
446
|
+
] }),
|
|
447
|
+
/* @__PURE__ */ jsx2("div", { className: "a24-header__actions", children: slots?.headerActions })
|
|
448
|
+
] }),
|
|
449
|
+
/* @__PURE__ */ jsx2("main", { "aria-busy": state === "loading" || state === "reconnecting", className: "a24-main", id: "agents24-chat-main", children: showState ? /* @__PURE__ */ jsx2(AgentChatStatePanel, { errorMessage, onRetry: onRetryConnection, state: shellState }) : /* @__PURE__ */ jsx2(
|
|
450
|
+
AgentChatShell,
|
|
451
|
+
{
|
|
452
|
+
composer,
|
|
453
|
+
dir,
|
|
454
|
+
isLoadingOlder,
|
|
455
|
+
messages,
|
|
456
|
+
onLoadOlder,
|
|
457
|
+
renderEmptyState: slots?.empty ? () => slots.empty : void 0,
|
|
458
|
+
renderMessage: (message) => /* @__PURE__ */ jsx2(
|
|
459
|
+
AgentChatMessage,
|
|
460
|
+
{
|
|
461
|
+
actions: slots?.messageActions?.(message),
|
|
462
|
+
message,
|
|
463
|
+
onHitlAction: (part, action, comment) => void onHitlAction?.(part, action, comment),
|
|
464
|
+
renderAssistantContent: slots?.assistantBody,
|
|
465
|
+
streamingMessageId
|
|
466
|
+
}
|
|
467
|
+
)
|
|
468
|
+
}
|
|
469
|
+
) }),
|
|
470
|
+
/* @__PURE__ */ jsx2("div", { "aria-live": "polite", "aria-atomic": "true", className: "a24-sr-only", children: liveLabel })
|
|
471
|
+
] })
|
|
472
|
+
] });
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// src/templates/agent-chat-app.tsx
|
|
476
|
+
import {
|
|
477
|
+
Agents24Provider,
|
|
478
|
+
useAgentAttachments,
|
|
479
|
+
useAgentChat,
|
|
480
|
+
useAgentMcp
|
|
481
|
+
} from "@agents24/react";
|
|
482
|
+
import * as React3 from "react";
|
|
483
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
484
|
+
function browserCrypto() {
|
|
485
|
+
const value = globalThis.crypto;
|
|
486
|
+
if (!value?.subtle || !value.randomUUID) throw new Error("Secure browser crypto is required for MCP authorization.");
|
|
487
|
+
return value;
|
|
488
|
+
}
|
|
489
|
+
function base64Url(bytes) {
|
|
490
|
+
let binary = "";
|
|
491
|
+
bytes.forEach((byte) => {
|
|
492
|
+
binary += String.fromCharCode(byte);
|
|
493
|
+
});
|
|
494
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
495
|
+
}
|
|
496
|
+
async function createPkceProof() {
|
|
497
|
+
const crypto = browserCrypto();
|
|
498
|
+
const verifier = base64Url(crypto.getRandomValues(new Uint8Array(32)));
|
|
499
|
+
const bytes = new TextEncoder().encode(verifier);
|
|
500
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
501
|
+
return { verifier, challenge: base64Url(new Uint8Array(digest)) };
|
|
502
|
+
}
|
|
503
|
+
function stateFromError(error) {
|
|
504
|
+
const typed = error;
|
|
505
|
+
if (typed?.kind === "authentication") return "expired";
|
|
506
|
+
if (typed?.kind === "authorization") return "denied";
|
|
507
|
+
if (typed?.kind === "quota" || typed?.kind === "rate_limit") return "quota";
|
|
508
|
+
if (typed?.kind === "network") return "offline";
|
|
509
|
+
return "failed";
|
|
510
|
+
}
|
|
511
|
+
function BoundAgentChatApp({
|
|
512
|
+
agent: agentOverride,
|
|
513
|
+
client,
|
|
514
|
+
completeMcpAuthorization,
|
|
515
|
+
mcpRedirectUri,
|
|
516
|
+
modelId,
|
|
517
|
+
models: modelOverrides,
|
|
518
|
+
onModelChange,
|
|
519
|
+
slots,
|
|
520
|
+
...layoutProps
|
|
521
|
+
}) {
|
|
522
|
+
const controller = useAgentChat();
|
|
523
|
+
const attachments = useAgentAttachments();
|
|
524
|
+
const mcp = useAgentMcp();
|
|
525
|
+
const [agent, setAgent] = React3.useState(agentOverride || { name: "Agent" });
|
|
526
|
+
const [capabilities, setCapabilities] = React3.useState([]);
|
|
527
|
+
const [bootstrapModels, setBootstrapModels] = React3.useState([]);
|
|
528
|
+
const [selectedModelId, setSelectedModelId] = React3.useState(modelId || null);
|
|
529
|
+
const [bootstrapLoading, setBootstrapLoading] = React3.useState(true);
|
|
530
|
+
const [localError, setLocalError] = React3.useState(null);
|
|
531
|
+
const [liked, setLiked] = React3.useState({});
|
|
532
|
+
const [disliked, setDisliked] = React3.useState({});
|
|
533
|
+
const loadBootstrap = React3.useCallback(async () => {
|
|
534
|
+
setBootstrapLoading(true);
|
|
535
|
+
setLocalError(null);
|
|
536
|
+
try {
|
|
537
|
+
const bootstrap = await client.bootstrap();
|
|
538
|
+
if (!agentOverride) setAgent({ name: bootstrap.deployment.name || "Agent" });
|
|
539
|
+
setCapabilities(bootstrap.capabilities);
|
|
540
|
+
const modelFeatures = bootstrap.features.models;
|
|
541
|
+
const allowedModelIds = Array.isArray(modelFeatures?.allowed_ids) ? modelFeatures.allowed_ids.filter((value) => typeof value === "string") : [];
|
|
542
|
+
if (bootstrap.capabilities.includes("models.select") && modelFeatures?.selectable) {
|
|
543
|
+
setBootstrapModels(allowedModelIds.map((id) => ({ id, label: id })));
|
|
544
|
+
if (!modelId) {
|
|
545
|
+
const defaultId = typeof modelFeatures.default_id === "string" ? modelFeatures.default_id : allowedModelIds[0];
|
|
546
|
+
setSelectedModelId(defaultId || null);
|
|
547
|
+
}
|
|
548
|
+
} else {
|
|
549
|
+
setBootstrapModels([]);
|
|
550
|
+
}
|
|
551
|
+
await controller.refreshThreads();
|
|
552
|
+
} catch (error2) {
|
|
553
|
+
setLocalError(error2);
|
|
554
|
+
} finally {
|
|
555
|
+
setBootstrapLoading(false);
|
|
556
|
+
}
|
|
557
|
+
}, [agentOverride, client, controller.refreshThreads, modelId]);
|
|
558
|
+
React3.useEffect(() => {
|
|
559
|
+
if (modelId !== void 0) setSelectedModelId(modelId);
|
|
560
|
+
}, [modelId]);
|
|
561
|
+
React3.useEffect(() => {
|
|
562
|
+
void loadBootstrap();
|
|
563
|
+
}, [loadBootstrap]);
|
|
564
|
+
const submit = React3.useCallback(async (message) => {
|
|
565
|
+
setLocalError(null);
|
|
566
|
+
try {
|
|
567
|
+
const uploaded = await Promise.all(message.files.map((file) => attachments.upload({
|
|
568
|
+
data: file.source,
|
|
569
|
+
name: file.filename,
|
|
570
|
+
mediaType: file.mediaType,
|
|
571
|
+
threadId: controller.activeThreadId || void 0
|
|
572
|
+
})));
|
|
573
|
+
await controller.submit({
|
|
574
|
+
text: message.text,
|
|
575
|
+
attachmentIds: uploaded.flatMap((attachment) => attachment.id ? [attachment.id] : []),
|
|
576
|
+
attachments: uploaded,
|
|
577
|
+
requestedModelId: selectedModelId || void 0
|
|
578
|
+
});
|
|
579
|
+
} catch (error2) {
|
|
580
|
+
setLocalError(error2);
|
|
581
|
+
}
|
|
582
|
+
}, [attachments, controller, selectedModelId]);
|
|
583
|
+
const deleteThread = React3.useCallback(async (threadId) => {
|
|
584
|
+
if (!window.confirm("Delete this conversation? This cannot be undone.")) return;
|
|
585
|
+
await controller.deleteThread(threadId);
|
|
586
|
+
}, [controller.deleteThread]);
|
|
587
|
+
const changeModel = React3.useCallback((nextModelId) => {
|
|
588
|
+
if (modelId === void 0) setSelectedModelId(nextModelId);
|
|
589
|
+
onModelChange?.(nextModelId);
|
|
590
|
+
}, [modelId, onModelChange]);
|
|
591
|
+
const handleMcp = React3.useCallback(async (part) => {
|
|
592
|
+
if (!mcpRedirectUri) throw new Error("mcpRedirectUri is required for MCP Connect.");
|
|
593
|
+
const serverId = String(part.presentation.server_id || "").trim();
|
|
594
|
+
if (!serverId) throw new Error("MCP authorization is missing its server identity.");
|
|
595
|
+
const runId = controller.activeRunId || controller.messages.find((message) => message.parts.includes(part))?.runId;
|
|
596
|
+
if (!runId) throw new Error("MCP authorization is missing its paused run.");
|
|
597
|
+
const popupNonce = browserCrypto().randomUUID();
|
|
598
|
+
const proof = await createPkceProof();
|
|
599
|
+
const popup = window.open("about:blank", "agents24-mcp", "popup,width=560,height=720");
|
|
600
|
+
try {
|
|
601
|
+
const start = await mcp.startAuthorization({
|
|
602
|
+
runId,
|
|
603
|
+
serverId,
|
|
604
|
+
interruptId: part.interruptId,
|
|
605
|
+
redirectUri: mcpRedirectUri,
|
|
606
|
+
popupNonce,
|
|
607
|
+
codeChallenge: proof.challenge
|
|
608
|
+
});
|
|
609
|
+
if (!start.callback_origin) throw new Error("MCP authorization did not return a callback origin.");
|
|
610
|
+
if (popup) popup.location.replace(start.authorization_url);
|
|
611
|
+
const completion = await waitForMcpOauthComplete({
|
|
612
|
+
popup,
|
|
613
|
+
callbackOrigin: start.callback_origin,
|
|
614
|
+
popupNonce,
|
|
615
|
+
requireConnectionId: Boolean(completeMcpAuthorization),
|
|
616
|
+
serverId
|
|
617
|
+
});
|
|
618
|
+
if (completeMcpAuthorization) {
|
|
619
|
+
await completeMcpAuthorization({ completion, codeVerifier: proof.verifier, serverId });
|
|
620
|
+
} else {
|
|
621
|
+
const redeemed = await mcp.redeemCallback({ code: completion.code, codeVerifier: proof.verifier });
|
|
622
|
+
validateMcpCallbackResult(redeemed, serverId);
|
|
623
|
+
}
|
|
624
|
+
popup?.close();
|
|
625
|
+
await controller.resumeHitl(part, "connect");
|
|
626
|
+
} catch (error2) {
|
|
627
|
+
popup?.close();
|
|
628
|
+
throw error2;
|
|
629
|
+
}
|
|
630
|
+
}, [completeMcpAuthorization, controller, mcp, mcpRedirectUri]);
|
|
631
|
+
const onHitlAction = React3.useCallback(async (part, action, comment) => {
|
|
632
|
+
setLocalError(null);
|
|
633
|
+
try {
|
|
634
|
+
if (part.hitlKind === "mcp_auth" && action === "connect") await handleMcp(part);
|
|
635
|
+
else await controller.resumeHitl(part, action, comment);
|
|
636
|
+
} catch (error2) {
|
|
637
|
+
setLocalError(error2);
|
|
638
|
+
}
|
|
639
|
+
}, [controller, handleMcp]);
|
|
640
|
+
const error = localError || controller.error;
|
|
641
|
+
const state = bootstrapLoading ? "loading" : error ? stateFromError(error) : controller.runState === "streaming" ? "streaming" : controller.runState === "reconnecting" ? "reconnecting" : controller.runState === "paused" ? "paused" : "ready";
|
|
642
|
+
const connectionState = state === "offline" ? "offline" : state === "expired" ? "expired" : state === "reconnecting" ? "reconnecting" : bootstrapLoading ? "connecting" : "connected";
|
|
643
|
+
const defaultMessageActions = (message) => message.role === "assistant" ? /* @__PURE__ */ jsx3(
|
|
644
|
+
AgentChatDefaultActions,
|
|
645
|
+
{
|
|
646
|
+
handlers: {
|
|
647
|
+
copied: false,
|
|
648
|
+
disliked: Boolean(disliked[message.id]),
|
|
649
|
+
liked: Boolean(liked[message.id]),
|
|
650
|
+
onCopy: async (content) => navigator.clipboard.writeText(content),
|
|
651
|
+
onDislike: () => {
|
|
652
|
+
setDisliked((current) => ({ ...current, [message.id]: !current[message.id] }));
|
|
653
|
+
setLiked((current) => ({ ...current, [message.id]: false }));
|
|
654
|
+
},
|
|
655
|
+
onLike: () => {
|
|
656
|
+
setLiked((current) => ({ ...current, [message.id]: !current[message.id] }));
|
|
657
|
+
setDisliked((current) => ({ ...current, [message.id]: false }));
|
|
658
|
+
},
|
|
659
|
+
onRetry: () => controller.regenerate(message)
|
|
660
|
+
},
|
|
661
|
+
message
|
|
662
|
+
}
|
|
663
|
+
) : null;
|
|
664
|
+
return /* @__PURE__ */ jsx3(
|
|
665
|
+
AgentChatLayout,
|
|
666
|
+
{
|
|
667
|
+
...layoutProps,
|
|
668
|
+
activeThreadId: controller.activeThreadId,
|
|
669
|
+
agent,
|
|
670
|
+
allowAttachments: layoutProps.allowAttachments ?? capabilities.includes("attachments.write"),
|
|
671
|
+
connectionState,
|
|
672
|
+
errorMessage: error instanceof Error ? error.message : null,
|
|
673
|
+
isLoadingOlder: controller.isLoadingOlder,
|
|
674
|
+
isLoadingThreads: controller.isLoadingThreads,
|
|
675
|
+
isRunning: controller.runState === "streaming" || controller.isSubmitting,
|
|
676
|
+
messages: controller.messages,
|
|
677
|
+
hasMoreThreads: controller.hasMoreThreads,
|
|
678
|
+
modelId: selectedModelId,
|
|
679
|
+
models: modelOverrides ?? bootstrapModels,
|
|
680
|
+
onDeleteThread: deleteThread,
|
|
681
|
+
onHitlAction,
|
|
682
|
+
onLoadOlder: controller.hasOlderTurns ? controller.loadOlder : void 0,
|
|
683
|
+
onLoadMoreThreads: controller.hasMoreThreads ? controller.loadMoreThreads : void 0,
|
|
684
|
+
onModelChange: changeModel,
|
|
685
|
+
onNewThread: controller.startNewThread,
|
|
686
|
+
onOpenThread: controller.openThread,
|
|
687
|
+
onRetryConnection: loadBootstrap,
|
|
688
|
+
onStop: () => void controller.cancelRun(),
|
|
689
|
+
onSubmit: submit,
|
|
690
|
+
slots: { ...slots, messageActions: slots?.messageActions || defaultMessageActions },
|
|
691
|
+
state,
|
|
692
|
+
streamingMessageId: controller.streamingMessageId,
|
|
693
|
+
threads: controller.threads.map((thread) => ({ id: thread.id, title: thread.title, status: thread.last_run_status || thread.status, updatedAt: thread.updated_at }))
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
function AgentChatApp({
|
|
698
|
+
client,
|
|
699
|
+
createAbortController,
|
|
700
|
+
createId,
|
|
701
|
+
storage,
|
|
702
|
+
...props
|
|
703
|
+
}) {
|
|
704
|
+
return /* @__PURE__ */ jsx3(
|
|
705
|
+
Agents24Provider,
|
|
706
|
+
{
|
|
707
|
+
client,
|
|
708
|
+
createAbortController: createAbortController || (() => new AbortController()),
|
|
709
|
+
createId,
|
|
710
|
+
storage,
|
|
711
|
+
children: /* @__PURE__ */ jsx3(BoundAgentChatApp, { client, ...props })
|
|
712
|
+
}
|
|
713
|
+
);
|
|
714
|
+
}
|
|
200
715
|
export {
|
|
716
|
+
AgentChatApp,
|
|
717
|
+
AgentChatLayout,
|
|
201
718
|
AgentChatShell,
|
|
719
|
+
AgentChatStatePanel,
|
|
202
720
|
buildAgentChatOutlineItems,
|
|
203
721
|
getAgentChatShellScrollState
|
|
204
722
|
};
|