@agents24/chat-react 0.5.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 (133) hide show
  1. package/README.md +90 -21
  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 +137 -378
  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 +1391 -271
  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-R7WFL3YR.js +0 -1327
  123. package/dist/chunk-R7WFL3YR.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 -77
  127. package/dist/templates/agent-chat-shell.d.ts +0 -37
  128. package/dist/templates/appearance-control.d.ts +0 -5
  129. package/dist/templates/index.cjs +0 -2600
  130. package/dist/templates/index.cjs.map +0 -1
  131. package/dist/templates/index.d.ts +0 -4
  132. package/dist/templates/index.js +0 -846
  133. package/dist/templates/index.js.map +0 -1
@@ -1,846 +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-R7WFL3YR.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 AgentTargetSelector({ agent, selector }) {
257
- const rootRef = React2.useRef(null);
258
- const triggerRef = React2.useRef(null);
259
- const inputId = React2.useId();
260
- const [draft, setDraft] = React2.useState(selector.value);
261
- const [error, setError] = React2.useState(null);
262
- const [open, setOpen] = React2.useState(false);
263
- const [submitting, setSubmitting] = React2.useState(false);
264
- React2.useEffect(() => setDraft(selector.value), [selector.value]);
265
- React2.useEffect(() => {
266
- if (!open) return void 0;
267
- const closeOnPointerDown = (event) => {
268
- if (!rootRef.current?.contains(event.target)) setOpen(false);
269
- };
270
- const closeOnEscape = (event) => {
271
- if (event.key !== "Escape") return;
272
- event.preventDefault();
273
- setOpen(false);
274
- triggerRef.current?.focus();
275
- };
276
- document.addEventListener("pointerdown", closeOnPointerDown);
277
- document.addEventListener("keydown", closeOnEscape);
278
- return () => {
279
- document.removeEventListener("pointerdown", closeOnPointerDown);
280
- document.removeEventListener("keydown", closeOnEscape);
281
- };
282
- }, [open]);
283
- const apply = async (event) => {
284
- event.preventDefault();
285
- const value = draft.trim();
286
- if (!value) {
287
- setError(`${selector.label} is required.`);
288
- return;
289
- }
290
- setSubmitting(true);
291
- setError(null);
292
- try {
293
- await selector.onApply(value);
294
- setOpen(false);
295
- } catch (nextError) {
296
- setError(nextError instanceof Error ? nextError.message : "The target could not be changed.");
297
- } finally {
298
- setSubmitting(false);
299
- }
300
- };
301
- return /* @__PURE__ */ jsxs2("div", { className: "a24-target-selector", "data-open": open || void 0, ref: rootRef, children: [
302
- /* @__PURE__ */ jsxs2(
303
- "button",
304
- {
305
- "aria-expanded": open,
306
- "aria-haspopup": "dialog",
307
- "aria-label": `Change ${selector.label}`,
308
- className: "a24-target-selector__trigger",
309
- onClick: () => setOpen((value) => !value),
310
- ref: triggerRef,
311
- type: "button",
312
- children: [
313
- /* @__PURE__ */ jsx2(AgentAvatar, { agent }),
314
- /* @__PURE__ */ jsxs2("span", { className: "a24-target-selector__copy", children: [
315
- /* @__PURE__ */ jsx2("strong", { children: agent.name }),
316
- /* @__PURE__ */ jsx2("span", { children: agent.description || "Click to change target" })
317
- ] }),
318
- /* @__PURE__ */ jsx2(ChevronDown, { "aria-hidden": "true" })
319
- ]
320
- }
321
- ),
322
- open ? /* @__PURE__ */ jsxs2("form", { "aria-label": `Change ${selector.label}`, className: "a24-target-selector__panel", onSubmit: (event) => void apply(event), children: [
323
- /* @__PURE__ */ jsx2("label", { htmlFor: inputId, children: selector.label }),
324
- /* @__PURE__ */ jsx2("input", { autoComplete: "off", id: inputId, onChange: (event) => setDraft(event.target.value), placeholder: selector.placeholder, spellCheck: false, value: draft }),
325
- selector.helperText ? /* @__PURE__ */ jsx2("p", { children: selector.helperText }) : null,
326
- error ? /* @__PURE__ */ jsx2("p", { className: "a24-target-selector__error", role: "alert", children: error }) : null,
327
- /* @__PURE__ */ jsx2("button", { className: "a24-button a24-target-selector__apply", disabled: submitting || !draft.trim(), type: "submit", children: submitting ? "Switching\u2026" : "Use target" })
328
- ] }) : null
329
- ] });
330
- }
331
- function ThreadNavigation({
332
- activeThreadId,
333
- agent,
334
- close,
335
- hasMoreThreads,
336
- isDeletingThread,
337
- isLoadingThreads,
338
- onDeleteThread,
339
- onLoadMoreThreads,
340
- onNewThread,
341
- onOpenThread,
342
- slots,
343
- targetSelector,
344
- threads
345
- }) {
346
- const [query, setQuery] = React2.useState("");
347
- const normalizedQuery = query.trim().toLocaleLowerCase();
348
- const visibleThreads = normalizedQuery ? threads.filter((thread) => String(thread.title || "New chat").toLocaleLowerCase().includes(normalizedQuery)) : threads;
349
- return /* @__PURE__ */ jsxs2("div", { className: "a24-sidebar__inner", children: [
350
- targetSelector ? /* @__PURE__ */ jsx2(AgentTargetSelector, { agent, selector: targetSelector }) : null,
351
- /* @__PURE__ */ jsxs2("div", { className: "a24-sidebar__top", children: [
352
- /* @__PURE__ */ jsxs2(
353
- "button",
354
- {
355
- "aria-label": "New chat",
356
- className: "a24-button a24-button--primary a24-sidebar__new",
357
- onClick: () => {
358
- onNewThread();
359
- close();
360
- },
361
- type: "button",
362
- children: [
363
- /* @__PURE__ */ jsx2(Plus, { "aria-hidden": "true" }),
364
- "New chat"
365
- ]
366
- }
367
- ),
368
- /* @__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" }) })
369
- ] }),
370
- /* @__PURE__ */ jsxs2("label", { className: "a24-search", children: [
371
- /* @__PURE__ */ jsx2(Search, { "aria-hidden": "true" }),
372
- /* @__PURE__ */ jsx2("span", { className: "a24-sr-only", children: "Search conversations" }),
373
- /* @__PURE__ */ jsx2(
374
- "input",
375
- {
376
- autoComplete: "off",
377
- name: "conversation-search",
378
- onChange: (event) => setQuery(event.target.value),
379
- placeholder: "Search conversations\u2026",
380
- type: "search",
381
- value: query
382
- }
383
- )
384
- ] }),
385
- /* @__PURE__ */ jsxs2("nav", { "aria-label": "Conversations", className: "a24-thread-list", children: [
386
- isLoadingThreads && threads.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "a24-sidebar__hint", role: "status", children: "Loading conversations\u2026" }) : null,
387
- !isLoadingThreads && visibleThreads.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "a24-sidebar__hint", children: "No conversations yet." }) : null,
388
- visibleThreads.map((thread) => {
389
- const selected = thread.id === activeThreadId;
390
- return /* @__PURE__ */ jsxs2("div", { className: "a24-thread", "data-selected": selected || void 0, children: [
391
- /* @__PURE__ */ jsxs2(
392
- "button",
393
- {
394
- "aria-current": selected ? "page" : void 0,
395
- className: "a24-thread__open",
396
- onClick: () => {
397
- void onOpenThread(thread.id);
398
- close();
399
- },
400
- type: "button",
401
- children: [
402
- /* @__PURE__ */ jsx2("span", { className: "a24-thread__title", children: thread.title || "New chat" }),
403
- thread.status ? /* @__PURE__ */ jsx2("span", { className: "a24-thread__status", children: thread.status }) : null
404
- ]
405
- }
406
- ),
407
- slots?.threadActions ? slots.threadActions(thread) : onDeleteThread ? /* @__PURE__ */ jsx2(
408
- "button",
409
- {
410
- "aria-label": `Delete ${thread.title || "conversation"}`,
411
- className: "a24-icon-button a24-thread__delete",
412
- disabled: isDeletingThread,
413
- onClick: () => void onDeleteThread(thread.id),
414
- type: "button",
415
- children: /* @__PURE__ */ jsx2(Trash2, { "aria-hidden": "true" })
416
- }
417
- ) : null
418
- ] }, thread.id);
419
- })
420
- ] }),
421
- hasMoreThreads && onLoadMoreThreads ? /* @__PURE__ */ jsxs2("button", { className: "a24-button a24-button--quiet a24-sidebar__more", onClick: () => void onLoadMoreThreads(), type: "button", children: [
422
- /* @__PURE__ */ jsx2(MoreHorizontal, { "aria-hidden": "true" }),
423
- "Load more"
424
- ] }) : null
425
- ] });
426
- }
427
- function AgentChatLayout({
428
- activeThreadId,
429
- agent,
430
- allowAttachments = true,
431
- className,
432
- connectionState = "connected",
433
- dir = "ltr",
434
- errorMessage,
435
- hasMoreThreads,
436
- isDeletingThread,
437
- isLoadingOlder,
438
- isLoadingThreads,
439
- isRunning = false,
440
- messages,
441
- modelId,
442
- models = [],
443
- onDeleteThread,
444
- onHitlAction,
445
- onLoadMoreThreads,
446
- onLoadOlder,
447
- onModelChange,
448
- onNewThread,
449
- onOpenThread,
450
- onRetryConnection,
451
- onStop,
452
- onSubmit,
453
- placeholder,
454
- slots,
455
- state = "ready",
456
- streamingMessageId,
457
- targetSelector,
458
- threads,
459
- title
460
- }) {
461
- const [sidebarOpen, setSidebarOpen] = React2.useState(false);
462
- const menuButtonRef = React2.useRef(null);
463
- const closeSidebar = React2.useCallback(() => {
464
- setSidebarOpen(false);
465
- menuButtonRef.current?.focus();
466
- }, []);
467
- React2.useEffect(() => {
468
- if (!sidebarOpen) return void 0;
469
- const onKeyDown = (event) => {
470
- if (event.key !== "Escape") return;
471
- event.preventDefault();
472
- closeSidebar();
473
- };
474
- window.addEventListener("keydown", onKeyDown);
475
- return () => window.removeEventListener("keydown", onKeyDown);
476
- }, [closeSidebar, sidebarOpen]);
477
- const shellState = state === "streaming" || state === "paused" ? "ready" : state;
478
- const showState = shellState !== "ready";
479
- const liveLabel = state === "streaming" ? "Agent is responding" : state === "paused" ? "Waiting for your response" : connectionLabel(connectionState);
480
- const composer = /* @__PURE__ */ jsx2(
481
- AgentChatComposer,
482
- {
483
- allowAttachments,
484
- disabled: state === "denied" || state === "quota" || state === "expired" || state === "offline",
485
- inputToolbarContent: models.length > 0 ? /* @__PURE__ */ jsxs2("label", { className: "a24-model-select", children: [
486
- /* @__PURE__ */ jsx2("span", { className: "a24-sr-only", children: "Model" }),
487
- /* @__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)) }),
488
- /* @__PURE__ */ jsx2(ChevronDown, { "aria-hidden": "true" })
489
- ] }) : void 0,
490
- isRunning,
491
- onStop,
492
- onSubmit,
493
- placeholder
494
- }
495
- );
496
- return /* @__PURE__ */ jsxs2("section", { className: cn("a24-chat", className), "data-agents24-chat-layout": "", dir, children: [
497
- /* @__PURE__ */ jsx2("a", { className: "a24-skip-link", href: "#agents24-chat-main", children: "Skip to messages" }),
498
- /* @__PURE__ */ jsx2("nav", { "aria-label": "Chat navigation", className: "a24-sidebar", "data-open": sidebarOpen || void 0, children: /* @__PURE__ */ jsx2(
499
- ThreadNavigation,
500
- {
501
- activeThreadId,
502
- agent,
503
- close: closeSidebar,
504
- hasMoreThreads,
505
- isDeletingThread,
506
- isLoadingThreads,
507
- onDeleteThread,
508
- onLoadMoreThreads,
509
- onNewThread,
510
- onOpenThread,
511
- slots,
512
- targetSelector,
513
- threads
514
- }
515
- ) }),
516
- sidebarOpen ? /* @__PURE__ */ jsx2("button", { "aria-label": "Close conversations", className: "a24-sidebar-backdrop", onClick: closeSidebar, type: "button" }) : null,
517
- /* @__PURE__ */ jsxs2("div", { className: "a24-chat__body", children: [
518
- /* @__PURE__ */ jsxs2("header", { className: "a24-header", role: "banner", children: [
519
- /* @__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" }) }),
520
- /* @__PURE__ */ jsx2(AgentAvatar, { agent }),
521
- /* @__PURE__ */ jsxs2("div", { className: "a24-header__identity", children: [
522
- /* @__PURE__ */ jsx2("h1", { children: title || agent.name }),
523
- /* @__PURE__ */ jsxs2("div", { className: "a24-connection", "data-state": connectionState, children: [
524
- connectionState === "offline" || connectionState === "expired" ? /* @__PURE__ */ jsx2(WifiOff, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx2(Wifi, { "aria-hidden": "true" }),
525
- /* @__PURE__ */ jsx2("span", { children: connectionLabel(connectionState) })
526
- ] })
527
- ] }),
528
- /* @__PURE__ */ jsx2("div", { className: "a24-header__actions", children: slots?.headerActions })
529
- ] }),
530
- /* @__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(
531
- AgentChatShell,
532
- {
533
- composer,
534
- dir,
535
- isLoadingOlder,
536
- messages,
537
- onLoadOlder,
538
- renderEmptyState: slots?.empty ? () => slots.empty : void 0,
539
- renderMessage: (message) => /* @__PURE__ */ jsx2(
540
- AgentChatMessage,
541
- {
542
- actions: slots?.messageActions?.(message),
543
- message,
544
- onHitlAction: (part, action, comment) => void onHitlAction?.(part, action, comment),
545
- renderAssistantContent: slots?.assistantBody,
546
- streamingMessageId
547
- }
548
- )
549
- }
550
- ) }),
551
- /* @__PURE__ */ jsx2("div", { "aria-live": "polite", "aria-atomic": "true", className: "a24-sr-only", children: liveLabel })
552
- ] })
553
- ] });
554
- }
555
-
556
- // src/templates/agent-chat-app.tsx
557
- import {
558
- Agents24Provider,
559
- useAgentAttachments,
560
- useAgentChat,
561
- useAgentMcp
562
- } from "@agents24/react";
563
- import * as React3 from "react";
564
- import { jsx as jsx3 } from "react/jsx-runtime";
565
- function browserCrypto() {
566
- const value = globalThis.crypto;
567
- if (!value?.subtle || !value.randomUUID) throw new Error("Secure browser crypto is required for MCP authorization.");
568
- return value;
569
- }
570
- function base64Url(bytes) {
571
- let binary = "";
572
- bytes.forEach((byte) => {
573
- binary += String.fromCharCode(byte);
574
- });
575
- return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
576
- }
577
- async function createPkceProof() {
578
- const crypto = browserCrypto();
579
- const verifier = base64Url(crypto.getRandomValues(new Uint8Array(32)));
580
- const bytes = new TextEncoder().encode(verifier);
581
- const digest = await crypto.subtle.digest("SHA-256", bytes);
582
- return { verifier, challenge: base64Url(new Uint8Array(digest)) };
583
- }
584
- function stateFromError(error) {
585
- const typed = error;
586
- if (typed?.kind === "authentication") return "expired";
587
- if (typed?.kind === "authorization") return "denied";
588
- if (typed?.kind === "quota" || typed?.kind === "rate_limit") return "quota";
589
- if (typed?.kind === "network") return "offline";
590
- return "failed";
591
- }
592
- function BoundAgentChatApp({
593
- agent: agentOverride,
594
- client,
595
- completeMcpAuthorization,
596
- mcpRedirectUri,
597
- modelId,
598
- models: modelOverrides,
599
- onModelChange,
600
- slots,
601
- ...layoutProps
602
- }) {
603
- const controller = useAgentChat();
604
- const attachments = useAgentAttachments();
605
- const mcp = useAgentMcp();
606
- const [agent, setAgent] = React3.useState(agentOverride || { name: "Agent" });
607
- const [capabilities, setCapabilities] = React3.useState([]);
608
- const [bootstrapModels, setBootstrapModels] = React3.useState([]);
609
- const [selectedModelId, setSelectedModelId] = React3.useState(modelId || null);
610
- const [bootstrapLoading, setBootstrapLoading] = React3.useState(true);
611
- const [localError, setLocalError] = React3.useState(null);
612
- const [liked, setLiked] = React3.useState({});
613
- const [disliked, setDisliked] = React3.useState({});
614
- const loadBootstrap = React3.useCallback(async () => {
615
- setBootstrapLoading(true);
616
- setLocalError(null);
617
- try {
618
- const bootstrap = await client.bootstrap();
619
- if (!agentOverride) setAgent({ name: bootstrap.deployment.name || "Agent" });
620
- setCapabilities(bootstrap.capabilities);
621
- const modelFeatures = bootstrap.features.models;
622
- const allowedModelIds = Array.isArray(modelFeatures?.allowed_ids) ? modelFeatures.allowed_ids.filter((value) => typeof value === "string") : [];
623
- if (bootstrap.capabilities.includes("models.select") && modelFeatures?.selectable) {
624
- setBootstrapModels(allowedModelIds.map((id) => ({ id, label: id })));
625
- if (!modelId) {
626
- const defaultId = typeof modelFeatures.default_id === "string" ? modelFeatures.default_id : allowedModelIds[0];
627
- setSelectedModelId(defaultId || null);
628
- }
629
- } else {
630
- setBootstrapModels([]);
631
- }
632
- await controller.refreshThreads();
633
- } catch (error2) {
634
- setLocalError(error2);
635
- } finally {
636
- setBootstrapLoading(false);
637
- }
638
- }, [agentOverride, client, controller.refreshThreads, modelId]);
639
- React3.useEffect(() => {
640
- if (modelId !== void 0) setSelectedModelId(modelId);
641
- }, [modelId]);
642
- React3.useEffect(() => {
643
- void loadBootstrap();
644
- }, [loadBootstrap]);
645
- const submit = React3.useCallback(async (message) => {
646
- setLocalError(null);
647
- try {
648
- const uploaded = await Promise.all(message.files.map((file) => attachments.upload({
649
- data: file.source,
650
- name: file.filename,
651
- mediaType: file.mediaType,
652
- threadId: controller.activeThreadId || void 0
653
- })));
654
- await controller.submit({
655
- text: message.text,
656
- attachmentIds: uploaded.flatMap((attachment) => attachment.id ? [attachment.id] : []),
657
- attachments: uploaded,
658
- requestedModelId: selectedModelId || void 0
659
- });
660
- } catch (error2) {
661
- setLocalError(error2);
662
- }
663
- }, [attachments, controller, selectedModelId]);
664
- const deleteThread = React3.useCallback(async (threadId) => {
665
- if (!window.confirm("Delete this conversation? This cannot be undone.")) return;
666
- await controller.deleteThread(threadId);
667
- }, [controller.deleteThread]);
668
- const changeModel = React3.useCallback((nextModelId) => {
669
- if (modelId === void 0) setSelectedModelId(nextModelId);
670
- onModelChange?.(nextModelId);
671
- }, [modelId, onModelChange]);
672
- const handleMcp = React3.useCallback(async (part) => {
673
- if (!mcpRedirectUri) throw new Error("mcpRedirectUri is required for MCP Connect.");
674
- const serverId = String(part.presentation.server_id || "").trim();
675
- if (!serverId) throw new Error("MCP authorization is missing its server identity.");
676
- const runId = controller.activeRunId || controller.messages.find((message) => message.parts.includes(part))?.runId;
677
- if (!runId) throw new Error("MCP authorization is missing its paused run.");
678
- const popupNonce = browserCrypto().randomUUID();
679
- const proof = await createPkceProof();
680
- const popup = window.open("about:blank", "agents24-mcp", "popup,width=560,height=720");
681
- try {
682
- const start = await mcp.startAuthorization({
683
- runId,
684
- serverId,
685
- interruptId: part.interruptId,
686
- redirectUri: mcpRedirectUri,
687
- popupNonce,
688
- codeChallenge: proof.challenge
689
- });
690
- if (!start.callback_origin) throw new Error("MCP authorization did not return a callback origin.");
691
- if (popup) popup.location.replace(start.authorization_url);
692
- const completion = await waitForMcpOauthComplete({
693
- popup,
694
- callbackOrigin: start.callback_origin,
695
- popupNonce,
696
- requireConnectionId: Boolean(completeMcpAuthorization),
697
- serverId
698
- });
699
- if (completeMcpAuthorization) {
700
- await completeMcpAuthorization({ completion, codeVerifier: proof.verifier, serverId });
701
- } else {
702
- const redeemed = await mcp.redeemCallback({ code: completion.code, codeVerifier: proof.verifier });
703
- validateMcpCallbackResult(redeemed, serverId);
704
- }
705
- popup?.close();
706
- await controller.resumeHitl(part, "connect");
707
- } catch (error2) {
708
- popup?.close();
709
- throw error2;
710
- }
711
- }, [completeMcpAuthorization, controller, mcp, mcpRedirectUri]);
712
- const onHitlAction = React3.useCallback(async (part, action, comment) => {
713
- setLocalError(null);
714
- try {
715
- if (part.hitlKind === "mcp_auth" && action === "connect") await handleMcp(part);
716
- else await controller.resumeHitl(part, action, comment);
717
- } catch (error2) {
718
- setLocalError(error2);
719
- }
720
- }, [controller, handleMcp]);
721
- const error = localError || controller.error;
722
- const state = bootstrapLoading ? "loading" : error ? stateFromError(error) : controller.runState === "streaming" ? "streaming" : controller.runState === "reconnecting" ? "reconnecting" : controller.runState === "paused" ? "paused" : "ready";
723
- const connectionState = state === "offline" ? "offline" : state === "expired" ? "expired" : state === "reconnecting" ? "reconnecting" : bootstrapLoading ? "connecting" : "connected";
724
- const defaultMessageActions = (message) => message.role === "assistant" ? /* @__PURE__ */ jsx3(
725
- AgentChatDefaultActions,
726
- {
727
- handlers: {
728
- copied: false,
729
- disliked: Boolean(disliked[message.id]),
730
- liked: Boolean(liked[message.id]),
731
- onCopy: async (content) => navigator.clipboard.writeText(content),
732
- onDislike: () => {
733
- setDisliked((current) => ({ ...current, [message.id]: !current[message.id] }));
734
- setLiked((current) => ({ ...current, [message.id]: false }));
735
- },
736
- onLike: () => {
737
- setLiked((current) => ({ ...current, [message.id]: !current[message.id] }));
738
- setDisliked((current) => ({ ...current, [message.id]: false }));
739
- },
740
- onRetry: () => controller.regenerate(message)
741
- },
742
- message
743
- }
744
- ) : null;
745
- return /* @__PURE__ */ jsx3(
746
- AgentChatLayout,
747
- {
748
- ...layoutProps,
749
- activeThreadId: controller.activeThreadId,
750
- agent,
751
- allowAttachments: layoutProps.allowAttachments ?? capabilities.includes("attachments.write"),
752
- connectionState,
753
- errorMessage: error instanceof Error ? error.message : null,
754
- isLoadingOlder: controller.isLoadingOlder,
755
- isLoadingThreads: controller.isLoadingThreads,
756
- isRunning: controller.runState === "streaming" || controller.isSubmitting,
757
- messages: controller.messages,
758
- hasMoreThreads: controller.hasMoreThreads,
759
- modelId: selectedModelId,
760
- models: modelOverrides ?? bootstrapModels,
761
- onDeleteThread: deleteThread,
762
- onHitlAction,
763
- onLoadOlder: controller.hasOlderTurns ? controller.loadOlder : void 0,
764
- onLoadMoreThreads: controller.hasMoreThreads ? controller.loadMoreThreads : void 0,
765
- onModelChange: changeModel,
766
- onNewThread: controller.startNewThread,
767
- onOpenThread: controller.openThread,
768
- onRetryConnection: loadBootstrap,
769
- onStop: () => void controller.cancelRun(),
770
- onSubmit: submit,
771
- slots: { ...slots, messageActions: slots?.messageActions || defaultMessageActions },
772
- state,
773
- streamingMessageId: controller.streamingMessageId,
774
- threads: controller.threads.map((thread) => ({ id: thread.id, title: thread.title, status: thread.last_run_status || thread.status, updatedAt: thread.updated_at }))
775
- }
776
- );
777
- }
778
- function AgentChatApp({
779
- client,
780
- createAbortController,
781
- createId,
782
- storage,
783
- ...props
784
- }) {
785
- return /* @__PURE__ */ jsx3(
786
- Agents24Provider,
787
- {
788
- client,
789
- createAbortController: createAbortController || (() => new AbortController()),
790
- createId,
791
- storage,
792
- children: /* @__PURE__ */ jsx3(BoundAgentChatApp, { client, ...props })
793
- }
794
- );
795
- }
796
-
797
- // src/templates/appearance-control.tsx
798
- import { Monitor, Moon, Sun } from "lucide-react";
799
- import * as React4 from "react";
800
- import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
801
- var MODES = ["system", "light", "dark"];
802
- function storedMode(storageKey) {
803
- if (typeof window === "undefined") return "system";
804
- const value = window.localStorage.getItem(storageKey);
805
- return value === "light" || value === "dark" ? value : "system";
806
- }
807
- function applyMode(mode) {
808
- if (typeof document === "undefined") return;
809
- document.documentElement.dataset.agents24ColorMode = mode;
810
- }
811
- function AgentChatAppearanceControl({
812
- storageKey = "agents24-color-mode"
813
- }) {
814
- const [mode, setMode] = React4.useState(() => storedMode(storageKey));
815
- React4.useEffect(() => applyMode(mode), [mode]);
816
- const label = `Appearance: ${mode[0]?.toUpperCase()}${mode.slice(1)}`;
817
- const Icon = mode === "light" ? Sun : mode === "dark" ? Moon : Monitor;
818
- return /* @__PURE__ */ jsxs3(
819
- "button",
820
- {
821
- "aria-label": `${label}. Activate to change.`,
822
- className: "a24-icon-button a24-appearance",
823
- onClick: () => {
824
- const next = MODES[(MODES.indexOf(mode) + 1) % MODES.length] || "system";
825
- window.localStorage.setItem(storageKey, next);
826
- setMode(next);
827
- },
828
- title: label,
829
- type: "button",
830
- children: [
831
- /* @__PURE__ */ jsx4(Icon, { "aria-hidden": "true" }),
832
- /* @__PURE__ */ jsx4("span", { className: "a24-sr-only", children: label })
833
- ]
834
- }
835
- );
836
- }
837
- export {
838
- AgentChatApp,
839
- AgentChatAppearanceControl,
840
- AgentChatLayout,
841
- AgentChatShell,
842
- AgentChatStatePanel,
843
- buildAgentChatOutlineItems,
844
- getAgentChatShellScrollState
845
- };
846
- //# sourceMappingURL=index.js.map