@agents24/chat-react 0.4.0 → 0.5.1

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.
Files changed (132) hide show
  1. package/README.md +92 -18
  2. package/compatibility.json +10 -10
  3. package/dist/chunk-SSUO4EPD.js +189 -0
  4. package/dist/chunk-SSUO4EPD.js.map +1 -0
  5. package/dist/chunk-ZCNHLFRB.js +108 -0
  6. package/dist/chunk-ZCNHLFRB.js.map +1 -0
  7. package/dist/index.cjs +112 -95
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.js +362 -68
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp-oauth.d.ts +13 -0
  13. package/dist/runtime/index.cjs +594 -0
  14. package/dist/runtime/index.cjs.map +1 -0
  15. package/dist/runtime/index.d.ts +2 -0
  16. package/dist/runtime/index.js +458 -0
  17. package/dist/runtime/index.js.map +1 -0
  18. package/dist/runtime/input-contract.d.ts +20 -0
  19. package/dist/runtime/use-agent-chat-runtime.d.ts +83 -0
  20. package/dist/scaffold/app.d.ts +30 -0
  21. package/dist/scaffold/components/chat/chat-composer.d.ts +21 -0
  22. package/dist/scaffold/components/chat/chat-message.d.ts +21 -0
  23. package/dist/scaffold/components/chat/chat-shell.d.ts +8 -0
  24. package/dist/scaffold/components/chat/collapsible-user-message.d.ts +3 -0
  25. package/dist/scaffold/components/chat/external-link-dialog.d.ts +2 -0
  26. package/dist/scaffold/components/chat/identity-control.d.ts +4 -0
  27. package/dist/scaffold/components/chat/message-parts.d.ts +12 -0
  28. package/dist/scaffold/components/chat/theme-menu.d.ts +1 -0
  29. package/dist/scaffold/components/chat/thread-sidebar.d.ts +16 -0
  30. package/dist/scaffold/components/chat/timeline-loading.d.ts +7 -0
  31. package/dist/scaffold/components/chat/turn-outline.d.ts +4 -0
  32. package/dist/scaffold/components/theme-provider.d.ts +20 -0
  33. package/dist/scaffold/components/ui/attachment.d.ts +24 -0
  34. package/dist/scaffold/components/ui/bubble.d.ts +17 -0
  35. package/dist/scaffold/components/ui/button-group.d.ts +11 -0
  36. package/dist/scaffold/components/ui/button.d.ts +10 -0
  37. package/dist/scaffold/components/ui/dialog.d.ts +17 -0
  38. package/dist/scaffold/components/ui/dropdown-menu.d.ts +29 -0
  39. package/dist/scaffold/components/ui/input.d.ts +3 -0
  40. package/dist/scaffold/components/ui/marker.d.ts +11 -0
  41. package/dist/scaffold/components/ui/message-scroller.d.ts +10 -0
  42. package/dist/scaffold/components/ui/message.d.ts +10 -0
  43. package/dist/scaffold/components/ui/popover.d.ts +6 -0
  44. package/dist/scaffold/components/ui/select.d.ts +8 -0
  45. package/dist/scaffold/components/ui/separator.d.ts +4 -0
  46. package/dist/scaffold/components/ui/sheet.d.ts +14 -0
  47. package/dist/scaffold/components/ui/sidebar.d.ts +69 -0
  48. package/dist/scaffold/components/ui/skeleton.d.ts +2 -0
  49. package/dist/scaffold/components/ui/textarea.d.ts +3 -0
  50. package/dist/scaffold/components/ui/tooltip.d.ts +15 -0
  51. package/dist/scaffold/hooks/use-mobile.d.ts +1 -0
  52. package/dist/scaffold/identity.d.ts +10 -0
  53. package/dist/scaffold/index.cjs +2270 -0
  54. package/dist/scaffold/index.cjs.map +1 -0
  55. package/dist/scaffold/index.d.ts +14 -0
  56. package/dist/scaffold/index.js +2244 -0
  57. package/dist/scaffold/index.js.map +1 -0
  58. package/dist/scaffold/lib/utils.d.ts +2 -0
  59. package/dist/styles.css +143 -298
  60. package/dist/types.d.ts +3 -13
  61. package/dist/ui/adapters.d.ts +1 -1
  62. package/dist/ui/agent-chat-actions.d.ts +12 -5
  63. package/dist/ui/agent-chat-composer.d.ts +2 -1
  64. package/dist/ui/agent-chat-context-compression-row.d.ts +5 -0
  65. package/dist/ui/agent-chat-context-status.d.ts +13 -0
  66. package/dist/ui/agent-chat-message.d.ts +7 -5
  67. package/dist/ui/agent-response-timeline.d.ts +29 -7
  68. package/dist/ui/ask-user-interaction-state.d.ts +20 -0
  69. package/dist/ui/ask-user-interaction.d.ts +8 -0
  70. package/dist/ui/index.cjs +1392 -272
  71. package/dist/ui/index.cjs.map +1 -1
  72. package/dist/ui/index.d.ts +5 -0
  73. package/dist/ui/index.js +2380 -65
  74. package/dist/ui/index.js.map +1 -1
  75. package/dist/ui/tool-sessions.d.ts +70 -0
  76. package/dist/ui/use-audio-recorder.d.ts +8 -0
  77. package/package.json +27 -12
  78. package/scaffold/components.json +25 -0
  79. package/scaffold/manifest.json +28 -0
  80. package/scaffold/src/app.tsx +71 -0
  81. package/scaffold/src/chat.css +54 -0
  82. package/scaffold/src/components/chat/chat-composer.tsx +237 -0
  83. package/scaffold/src/components/chat/chat-message.tsx +76 -0
  84. package/scaffold/src/components/chat/chat-shell.tsx +257 -0
  85. package/scaffold/src/components/chat/collapsible-user-message.tsx +37 -0
  86. package/scaffold/src/components/chat/external-link-dialog.tsx +48 -0
  87. package/scaffold/src/components/chat/identity-control.tsx +69 -0
  88. package/scaffold/src/components/chat/message-parts.tsx +83 -0
  89. package/scaffold/src/components/chat/theme-menu.tsx +79 -0
  90. package/scaffold/src/components/chat/thread-sidebar.tsx +114 -0
  91. package/scaffold/src/components/chat/timeline-loading.ts +17 -0
  92. package/scaffold/src/components/chat/turn-outline.tsx +73 -0
  93. package/scaffold/src/components/theme-provider.tsx +91 -0
  94. package/scaffold/src/components/ui/attachment.tsx +204 -0
  95. package/scaffold/src/components/ui/bubble.tsx +125 -0
  96. package/scaffold/src/components/ui/button-group.tsx +83 -0
  97. package/scaffold/src/components/ui/button.tsx +65 -0
  98. package/scaffold/src/components/ui/dialog.tsx +171 -0
  99. package/scaffold/src/components/ui/dropdown-menu.tsx +271 -0
  100. package/scaffold/src/components/ui/input.tsx +19 -0
  101. package/scaffold/src/components/ui/marker.tsx +69 -0
  102. package/scaffold/src/components/ui/message-scroller.tsx +129 -0
  103. package/scaffold/src/components/ui/message.tsx +92 -0
  104. package/scaffold/src/components/ui/popover.tsx +40 -0
  105. package/scaffold/src/components/ui/select.tsx +92 -0
  106. package/scaffold/src/components/ui/separator.tsx +28 -0
  107. package/scaffold/src/components/ui/sheet.tsx +150 -0
  108. package/scaffold/src/components/ui/sidebar.tsx +677 -0
  109. package/scaffold/src/components/ui/skeleton.tsx +13 -0
  110. package/scaffold/src/components/ui/textarea.tsx +18 -0
  111. package/scaffold/src/components/ui/tooltip.tsx +56 -0
  112. package/scaffold/src/hooks/use-mobile.ts +24 -0
  113. package/scaffold/src/identity.ts +17 -0
  114. package/scaffold/src/index.css +222 -0
  115. package/scaffold/src/index.tsx +39 -0
  116. package/scaffold/src/lib/utils.ts +6 -0
  117. package/scaffold/styles.css +3 -0
  118. package/dist/chunk-CHAU7FNW.js +0 -140
  119. package/dist/chunk-CHAU7FNW.js.map +0 -1
  120. package/dist/chunk-LI67M74F.js +0 -445
  121. package/dist/chunk-LI67M74F.js.map +0 -1
  122. package/dist/chunk-NLEOWTZG.js +0 -1327
  123. package/dist/chunk-NLEOWTZG.js.map +0 -1
  124. package/dist/renderers.d.ts +0 -10
  125. package/dist/templates/agent-chat-app.d.ts +0 -18
  126. package/dist/templates/agent-chat-layout.d.ts +0 -69
  127. package/dist/templates/agent-chat-shell.d.ts +0 -37
  128. package/dist/templates/index.cjs +0 -2476
  129. package/dist/templates/index.cjs.map +0 -1
  130. package/dist/templates/index.d.ts +0 -3
  131. package/dist/templates/index.js +0 -723
  132. package/dist/templates/index.js.map +0 -1
@@ -1,723 +0,0 @@
1
- import {
2
- LatestThreadScroller,
3
- validateMcpCallbackResult,
4
- waitForMcpOauthComplete
5
- } from "../chunk-LI67M74F.js";
6
- import {
7
- AgentChatComposer,
8
- AgentChatDefaultActions,
9
- AgentChatMessage,
10
- cn
11
- } from "../chunk-NLEOWTZG.js";
12
- import "../chunk-CHAU7FNW.js";
13
-
14
- // src/templates/agent-chat-shell.tsx
15
- import * as React from "react";
16
- import { jsx, jsxs } from "react/jsx-runtime";
17
- function getAgentChatShellScrollState(viewport) {
18
- const canScroll = viewport.scrollHeight - viewport.clientHeight > 1;
19
- return {
20
- canScroll,
21
- isScrolled: canScroll && viewport.scrollTop > 4
22
- };
23
- }
24
- var defaultOutlineLabel = (content, index) => {
25
- const label = String(content || "").replace(/\s+/g, " ").trim();
26
- return label || `Turn ${index + 1}`;
27
- };
28
- var defaultOutlinePreview = (content) => String(content || "").replace(/\s+/g, " ").trim();
29
- function buildAgentChatOutlineItems(messages, getLabel = (message, index) => defaultOutlineLabel(message.content, index), getResponsePreview = (message) => defaultOutlinePreview(message.content)) {
30
- return messages.reduce((items, message) => {
31
- if (message.role === "user") {
32
- items.push({
33
- id: message.id,
34
- label: getLabel(message, items.length),
35
- messageIds: [message.id]
36
- });
37
- return items;
38
- }
39
- const currentItem = items[items.length - 1];
40
- currentItem?.messageIds?.push(message.id);
41
- if (currentItem && !currentItem.responsePreview && message.role === "assistant") {
42
- currentItem.responsePreview = getResponsePreview(message);
43
- }
44
- return items;
45
- }, []);
46
- }
47
- function AgentChatShell({
48
- className,
49
- composer,
50
- composerOverlayClassName,
51
- composerOverlayHeight,
52
- contentClassName,
53
- defaultComposerOverlayHeight = 192,
54
- dir = "ltr",
55
- emptyInputAttachmentCount = 0,
56
- emptyTitle = "Ready when you are.",
57
- hideComposer = false,
58
- isLoadingOlder = false,
59
- messages,
60
- onLoadOlder,
61
- onScrollStateChange,
62
- outlineItems,
63
- outlineSide = "left",
64
- renderEmptyState,
65
- renderMessage,
66
- renderOlderLoader
67
- }) {
68
- const overlayRef = React.useRef(null);
69
- const viewportRef = React.useRef(null);
70
- const contentRef = React.useRef(null);
71
- const lastScrollStateRef = React.useRef(null);
72
- const [measuredOverlayHeight, setMeasuredOverlayHeight] = React.useState(
73
- defaultComposerOverlayHeight
74
- );
75
- const olderPageRequestRef = React.useRef(false);
76
- const items = React.useMemo(
77
- () => outlineItems || buildAgentChatOutlineItems(messages),
78
- [messages, outlineItems]
79
- );
80
- const updateScrollState = React.useCallback(() => {
81
- const viewport = viewportRef.current;
82
- if (!viewport || !onScrollStateChange) return;
83
- const next = getAgentChatShellScrollState(viewport);
84
- const previous = lastScrollStateRef.current;
85
- if (previous && previous.canScroll === next.canScroll && previous.isScrolled === next.isScrolled) {
86
- return;
87
- }
88
- lastScrollStateRef.current = next;
89
- onScrollStateChange(next);
90
- }, [onScrollStateChange]);
91
- React.useEffect(() => {
92
- const overlay = overlayRef.current;
93
- if (!overlay || hideComposer) return;
94
- const updateHeight = () => {
95
- setMeasuredOverlayHeight(Math.ceil(overlay.getBoundingClientRect().height));
96
- };
97
- updateHeight();
98
- const observer = new ResizeObserver(updateHeight);
99
- observer.observe(overlay);
100
- return () => observer.disconnect();
101
- }, [hideComposer]);
102
- React.useEffect(() => {
103
- if (!onScrollStateChange) return;
104
- updateScrollState();
105
- const viewport = viewportRef.current;
106
- const content = contentRef.current;
107
- if (!viewport || typeof ResizeObserver === "undefined") return;
108
- const observer = new ResizeObserver(updateScrollState);
109
- observer.observe(viewport);
110
- if (content) observer.observe(content);
111
- return () => observer.disconnect();
112
- }, [messages, onScrollStateChange, updateScrollState]);
113
- const handleScroll = React.useCallback(
114
- (event) => {
115
- updateScrollState();
116
- if (!onLoadOlder || isLoadingOlder || olderPageRequestRef.current || event.currentTarget.scrollTop > 48) {
117
- return;
118
- }
119
- olderPageRequestRef.current = true;
120
- void Promise.resolve(onLoadOlder()).finally(() => {
121
- olderPageRequestRef.current = false;
122
- });
123
- },
124
- [isLoadingOlder, onLoadOlder, updateScrollState]
125
- );
126
- const bottomPadding = hideComposer ? 32 : (composerOverlayHeight ?? measuredOverlayHeight) + 72;
127
- const isEmptyState = messages.length === 0;
128
- if (isEmptyState) {
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: [
130
- /* @__PURE__ */ jsx(
131
- "p",
132
- {
133
- className: cn(
134
- "a24-shell__empty-title pb-6 text-3xl font-semibold transition-transform duration-200",
135
- emptyInputAttachmentCount > 0 && "-translate-y-12"
136
- ),
137
- children: emptyTitle
138
- }
139
- ),
140
- !hideComposer && composer ? /* @__PURE__ */ jsx("div", { className: "a24-shell__empty-composer mx-auto w-full max-w-3xl px-4", children: composer }) : null
141
- ] }) }) });
142
- }
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: [
144
- /* @__PURE__ */ jsxs(LatestThreadScroller.Root, { children: [
145
- /* @__PURE__ */ jsx(
146
- LatestThreadScroller.Viewport,
147
- {
148
- onScroll: handleScroll,
149
- onViewportChange: (node) => {
150
- viewportRef.current = node;
151
- updateScrollState();
152
- },
153
- children: /* @__PURE__ */ jsxs(
154
- LatestThreadScroller.Content,
155
- {
156
- ref: contentRef,
157
- className: cn(
158
- "a24-shell__content mx-auto flex min-h-full w-full max-w-3xl flex-col gap-8 px-4 pt-12",
159
- contentClassName
160
- ),
161
- style: { paddingBottom: bottomPadding },
162
- children: [
163
- isLoadingOlder ? renderOlderLoader?.() : null,
164
- messages.map((message, index) => /* @__PURE__ */ jsx(
165
- LatestThreadScroller.Item,
166
- {
167
- messageId: message.id,
168
- scrollAnchor: message.role === "user",
169
- children: renderMessage(message, index)
170
- },
171
- message.id
172
- ))
173
- ]
174
- }
175
- )
176
- }
177
- ),
178
- /* @__PURE__ */ jsx(
179
- LatestThreadScroller.Outline,
180
- {
181
- dir,
182
- items,
183
- side: outlineSide
184
- }
185
- )
186
- ] }),
187
- !hideComposer && composer ? /* @__PURE__ */ jsxs(
188
- "div",
189
- {
190
- ref: overlayRef,
191
- className: cn(
192
- "a24-shell__composer-overlay pointer-events-none absolute inset-x-0 bottom-0 z-20 overflow-visible px-4 pb-4 pt-10",
193
- composerOverlayClassName
194
- ),
195
- style: {
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%)"
197
- },
198
- children: [
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 })
201
- ]
202
- }
203
- ) : null
204
- ] }) });
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
- }
715
- export {
716
- AgentChatApp,
717
- AgentChatLayout,
718
- AgentChatShell,
719
- AgentChatStatePanel,
720
- buildAgentChatOutlineItems,
721
- getAgentChatShellScrollState
722
- };
723
- //# sourceMappingURL=index.js.map