@cntyclub/agent-react 0.7.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { Button, cn, ChatMessage, ChatMessageBubble, Markdown, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, ChatTypingIndicator, useMediaQuery, TooltipProvider, Fab, DataTablePaged } from '@cntyclub/ui-react';
1
+ import { Button, cn, ChatMessage, ChatMessageBubble, Markdown, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Tooltip, TooltipTrigger, TooltipContent, Chat, ChatMessages, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, Fab, DataTablePaged } from '@cntyclub/ui-react';
2
2
  import { FileTextIcon, XIcon, PaperclipIcon, ArrowUpIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, CheckIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Trash2Icon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
3
- import * as React5 from 'react';
3
+ import * as React6 from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
 
6
6
  // src/api-client.ts
@@ -75,6 +75,98 @@ var AgentApiClient = class {
75
75
  method: "POST"
76
76
  });
77
77
  }
78
+ // ------------------------------------------------------------- streaming
79
+ /** POST a chat turn and yield parsed SSE events as they stream in. */
80
+ async streamMessage(message, conversationId, files, onEvent, signal) {
81
+ let body;
82
+ const headers = await this.streamHeaders();
83
+ if (files && files.length > 0) {
84
+ const form = new FormData();
85
+ form.append("client_id", this.config.clientId);
86
+ form.append("message", message);
87
+ if (conversationId) form.append("conversation_id", conversationId);
88
+ for (const file of files) form.append("files", file, file.name);
89
+ body = form;
90
+ } else {
91
+ headers["Content-Type"] = "application/json";
92
+ body = JSON.stringify({
93
+ client_id: this.config.clientId,
94
+ conversation_id: conversationId ?? null,
95
+ message
96
+ });
97
+ }
98
+ await this.consumeStream("/agent-mode/chat/stream/", { body, headers, signal }, onEvent);
99
+ }
100
+ /** POST an approval decision and yield the continued reply as SSE events. */
101
+ async streamResolveApproval(callId, approve, onEvent, signal) {
102
+ const headers = await this.streamHeaders();
103
+ headers["Content-Type"] = "application/json";
104
+ await this.consumeStream(
105
+ `/agent-mode/approvals/${callId}/resolve/stream/`,
106
+ {
107
+ body: JSON.stringify({ approve, client_id: this.config.clientId }),
108
+ headers,
109
+ signal
110
+ },
111
+ onEvent
112
+ );
113
+ }
114
+ async streamHeaders() {
115
+ const token = await this.config.getAccessToken();
116
+ if (!token) {
117
+ throw new AgentApiError("You need to be signed in to use the assistant.", 401);
118
+ }
119
+ return {
120
+ // Include application/json so DRF content-negotiation always finds a match.
121
+ Accept: "text/event-stream, application/json",
122
+ Authorization: `Bearer ${token}`,
123
+ "X-Agent-Client-Id": this.config.clientId
124
+ };
125
+ }
126
+ async consumeStream(path, init, onEvent) {
127
+ const base = this.config.apiBaseUrl.replace(/\/$/, "");
128
+ const separator = path.includes("?") ? "&" : "?";
129
+ const url = `${base}${path}${separator}client_id=${encodeURIComponent(this.config.clientId)}`;
130
+ const response = await fetch(url, {
131
+ body: init.body,
132
+ headers: init.headers,
133
+ method: "POST",
134
+ signal: init.signal
135
+ });
136
+ if (!response.ok || !response.body) {
137
+ let detail = `Request failed (${response.status})`;
138
+ try {
139
+ const payload = await response.json();
140
+ detail = payload.detail || payload.message || detail;
141
+ } catch {
142
+ }
143
+ throw new AgentApiError(detail, response.status || 0);
144
+ }
145
+ const reader = response.body.getReader();
146
+ const decoder = new TextDecoder();
147
+ let buffer = "";
148
+ const flushFrame = (frame) => {
149
+ const dataLines = frame.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
150
+ if (dataLines.length === 0) return;
151
+ const raw = dataLines.join("\n");
152
+ try {
153
+ onEvent(JSON.parse(raw));
154
+ } catch {
155
+ }
156
+ };
157
+ for (; ; ) {
158
+ const { done, value } = await reader.read();
159
+ if (done) break;
160
+ buffer += decoder.decode(value, { stream: true });
161
+ let boundary = buffer.indexOf("\n\n");
162
+ while (boundary !== -1) {
163
+ flushFrame(buffer.slice(0, boundary));
164
+ buffer = buffer.slice(boundary + 2);
165
+ boundary = buffer.indexOf("\n\n");
166
+ }
167
+ }
168
+ if (buffer.trim()) flushFrame(buffer);
169
+ }
78
170
  listConversations() {
79
171
  return this.request("/agent-mode/conversations/");
80
172
  }
@@ -137,14 +229,14 @@ function AgentComposer({
137
229
  placeholder = "Ask anything\u2026",
138
230
  value
139
231
  }) {
140
- const textareaRef = React5.useRef(null);
141
- const inputRef = React5.useRef(null);
232
+ const textareaRef = React6.useRef(null);
233
+ const inputRef = React6.useRef(null);
142
234
  const canSubmit = !disabled && (value.trim().length > 0 || files.length > 0);
143
- const submit = React5.useCallback(() => {
235
+ const submit = React6.useCallback(() => {
144
236
  if (!canSubmit) return;
145
237
  onSubmit(value.trim());
146
238
  }, [canSubmit, onSubmit, value]);
147
- React5.useEffect(() => {
239
+ React6.useEffect(() => {
148
240
  const textarea = textareaRef.current;
149
241
  if (!textarea) return;
150
242
  textarea.style.height = "auto";
@@ -349,6 +441,55 @@ function MessageItem({ message }) {
349
441
  }
350
442
  return null;
351
443
  }
444
+ var DEFAULT_PHRASES = [
445
+ "Thinking",
446
+ "Working on it",
447
+ "Looking that up",
448
+ "Reading the data",
449
+ "Putting it together",
450
+ "Almost there"
451
+ ];
452
+ function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
453
+ const words = phrases && phrases.length ? phrases : DEFAULT_PHRASES;
454
+ const [index, setIndex] = React6.useState(0);
455
+ React6.useEffect(() => {
456
+ setIndex(0);
457
+ const timer = setInterval(() => {
458
+ setIndex((current) => (current + 1) % words.length);
459
+ }, intervalMs);
460
+ return () => clearInterval(timer);
461
+ }, [intervalMs, words]);
462
+ return /* @__PURE__ */ jsxs(
463
+ "div",
464
+ {
465
+ "aria-label": "Assistant is working",
466
+ className: cn(
467
+ "fade-in-0 flex w-fit animate-in items-center gap-2 rounded-2xl rounded-bl-md bg-muted px-3.5 py-2.5 duration-200",
468
+ className
469
+ ),
470
+ "data-slot": "thinking-indicator",
471
+ role: "status",
472
+ children: [
473
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
474
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:0ms]" }),
475
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:150ms]" }),
476
+ /* @__PURE__ */ jsx("span", { className: "size-1.5 animate-bounce rounded-full bg-muted-foreground/60 [animation-delay:300ms]" })
477
+ ] }),
478
+ /* @__PURE__ */ jsxs(
479
+ "span",
480
+ {
481
+ className: "fade-in-0 animate-in text-muted-foreground text-xs duration-300",
482
+ children: [
483
+ words[index],
484
+ "\u2026"
485
+ ]
486
+ },
487
+ index
488
+ )
489
+ ]
490
+ }
491
+ );
492
+ }
352
493
  var NOISE_KEYS = /* @__PURE__ */ new Set([
353
494
  "id",
354
495
  "pk",
@@ -573,7 +714,7 @@ function DownloadButton({ block, size = "sm" }) {
573
714
  );
574
715
  }
575
716
  function DocumentViewer({ block, onClose }) {
576
- React5.useEffect(() => {
717
+ React6.useEffect(() => {
577
718
  const onKey = (event) => event.key === "Escape" && onClose();
578
719
  window.addEventListener("keydown", onKey);
579
720
  return () => window.removeEventListener("keydown", onKey);
@@ -598,7 +739,7 @@ function DocumentViewer({ block, onClose }) {
598
739
  ] });
599
740
  }
600
741
  function DocumentCard({ block }) {
601
- const [open, setOpen] = React5.useState(false);
742
+ const [open, setOpen] = React6.useState(false);
602
743
  const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
603
744
  return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
604
745
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
@@ -720,7 +861,7 @@ function StepDetail({
720
861
  ] });
721
862
  }
722
863
  function ToolActivityGroup({ describeToolCall, steps }) {
723
- const [open, setOpen] = React5.useState(false);
864
+ const [open, setOpen] = React6.useState(false);
724
865
  if (steps.length === 0) return null;
725
866
  const last = steps[steps.length - 1];
726
867
  const anyRunning = steps.some((step) => step.status === "running");
@@ -823,19 +964,19 @@ function AgentPanel({
823
964
  onClose,
824
965
  onToggleAgentMode
825
966
  }) {
826
- const [draft, setDraft] = React5.useState("");
827
- const [view, setView] = React5.useState("chat");
828
- const [files, setFiles] = React5.useState([]);
829
- const [uploadError, setUploadError] = React5.useState(null);
830
- const [dragging, setDragging] = React5.useState(false);
831
- const dragDepth = React5.useRef(0);
967
+ const [draft, setDraft] = React6.useState("");
968
+ const [view, setView] = React6.useState("chat");
969
+ const [files, setFiles] = React6.useState([]);
970
+ const [uploadError, setUploadError] = React6.useState(null);
971
+ const [dragging, setDragging] = React6.useState(false);
972
+ const dragDepth = React6.useRef(0);
832
973
  const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
833
974
  const uploadEnabled = fileUploadEnabled(config);
834
975
  const suggestions = config.suggestions ?? [
835
976
  "What can you help me with?",
836
977
  "Give me a quick summary of my data."
837
978
  ];
838
- const addFiles = React5.useCallback(
979
+ const addFiles = React6.useCallback(
839
980
  (incoming) => {
840
981
  const accepted = [];
841
982
  let rejection = null;
@@ -852,10 +993,10 @@ function AgentPanel({
852
993
  },
853
994
  [config]
854
995
  );
855
- const removeFile = React5.useCallback((index) => {
996
+ const removeFile = React6.useCallback((index) => {
856
997
  setFiles((current) => current.filter((_, i) => i !== index));
857
998
  }, []);
858
- const handleSubmit = React5.useCallback(
999
+ const handleSubmit = React6.useCallback(
859
1000
  (text) => {
860
1001
  const staged = files;
861
1002
  setDraft("");
@@ -865,7 +1006,7 @@ function AgentPanel({
865
1006
  },
866
1007
  [chat, files]
867
1008
  );
868
- const onDragEnter = React5.useCallback(
1009
+ const onDragEnter = React6.useCallback(
869
1010
  (event) => {
870
1011
  if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
871
1012
  dragDepth.current += 1;
@@ -873,12 +1014,12 @@ function AgentPanel({
873
1014
  },
874
1015
  [uploadEnabled]
875
1016
  );
876
- const onDragLeave = React5.useCallback((event) => {
1017
+ const onDragLeave = React6.useCallback((event) => {
877
1018
  if (!event.dataTransfer?.types?.includes("Files")) return;
878
1019
  dragDepth.current = Math.max(0, dragDepth.current - 1);
879
1020
  if (dragDepth.current === 0) setDragging(false);
880
1021
  }, []);
881
- const onDrop = React5.useCallback(
1022
+ const onDrop = React6.useCallback(
882
1023
  (event) => {
883
1024
  if (!uploadEnabled) return;
884
1025
  event.preventDefault();
@@ -889,11 +1030,14 @@ function AgentPanel({
889
1030
  },
890
1031
  [addFiles, uploadEnabled]
891
1032
  );
892
- const openHistory = React5.useCallback(() => {
1033
+ const openHistory = React6.useCallback(() => {
893
1034
  setView("history");
894
1035
  void chat.refreshConversations();
895
1036
  }, [chat]);
896
1037
  const busy = chat.sending || chat.resolvingApprovalId !== null;
1038
+ const lastMessage = chat.messages[chat.messages.length - 1];
1039
+ const replyStreaming = lastMessage?.role === "assistant" && Boolean(lastMessage.content);
1040
+ const showThinking = busy && !replyStreaming;
897
1041
  return /* @__PURE__ */ jsxs(
898
1042
  "div",
899
1043
  {
@@ -1050,7 +1194,7 @@ function AgentPanel({
1050
1194
  },
1051
1195
  approval.id
1052
1196
  )),
1053
- busy ? /* @__PURE__ */ jsx(ChatTypingIndicator, {}) : null
1197
+ showThinking ? /* @__PURE__ */ jsx(ThinkingIndicator, {}) : null
1054
1198
  ] }) }),
1055
1199
  chat.error || uploadError ? /* @__PURE__ */ jsxs("div", { className: "mx-3 mb-1 flex items-center justify-between gap-2 rounded-lg bg-destructive/10 px-3 py-2 text-destructive text-xs", children: [
1056
1200
  /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate", children: chat.error ?? uploadError }),
@@ -1087,17 +1231,37 @@ function AgentPanel({
1087
1231
  }
1088
1232
  );
1089
1233
  }
1234
+ var OPTIMISTIC_ID_PREFIX = "optimistic-";
1235
+ var STREAMING_ID_PREFIX = "streaming-";
1236
+ var optimisticCounter = 0;
1237
+ var streamingCounter = 0;
1238
+ function buildOptimisticUserMessage(text, files) {
1239
+ optimisticCounter += 1;
1240
+ const attachments = (files ?? []).map((file, index) => ({
1241
+ id: `${OPTIMISTIC_ID_PREFIX}att-${optimisticCounter}-${index}`,
1242
+ filename: file.name,
1243
+ format: file.name.split(".").pop()?.toLowerCase() ?? "file",
1244
+ size_bytes: file.size
1245
+ }));
1246
+ return {
1247
+ id: `${OPTIMISTIC_ID_PREFIX}${optimisticCounter}`,
1248
+ role: "user",
1249
+ content: text,
1250
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
1251
+ ...attachments.length ? { attachments } : {}
1252
+ };
1253
+ }
1090
1254
  function useAgentChat(config) {
1091
- const client = React5.useMemo(() => new AgentApiClient(config), [config]);
1092
- const [agentInfo, setAgentInfo] = React5.useState(null);
1093
- const [conversationId, setConversationId] = React5.useState(null);
1094
- const [conversations, setConversations] = React5.useState([]);
1095
- const [messages, setMessages] = React5.useState([]);
1096
- const [pendingApprovals, setPendingApprovals] = React5.useState([]);
1097
- const [sending, setSending] = React5.useState(false);
1098
- const [resolvingApprovalId, setResolvingApprovalId] = React5.useState(null);
1099
- const [error, setError] = React5.useState(null);
1100
- React5.useEffect(() => {
1255
+ const client = React6.useMemo(() => new AgentApiClient(config), [config]);
1256
+ const [agentInfo, setAgentInfo] = React6.useState(null);
1257
+ const [conversationId, setConversationId] = React6.useState(null);
1258
+ const [conversations, setConversations] = React6.useState([]);
1259
+ const [messages, setMessages] = React6.useState([]);
1260
+ const [pendingApprovals, setPendingApprovals] = React6.useState([]);
1261
+ const [sending, setSending] = React6.useState(false);
1262
+ const [resolvingApprovalId, setResolvingApprovalId] = React6.useState(null);
1263
+ const [error, setError] = React6.useState(null);
1264
+ React6.useEffect(() => {
1101
1265
  let cancelled = false;
1102
1266
  client.getConfig().then((info) => {
1103
1267
  if (!cancelled) setAgentInfo(info);
@@ -1107,11 +1271,11 @@ function useAgentChat(config) {
1107
1271
  cancelled = true;
1108
1272
  };
1109
1273
  }, [client]);
1110
- const handleFailure = React5.useCallback((err) => {
1274
+ const handleFailure = React6.useCallback((err) => {
1111
1275
  const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
1112
1276
  setError(message);
1113
1277
  }, []);
1114
- const applyResponse = React5.useCallback(
1278
+ const applyResponse = React6.useCallback(
1115
1279
  (response) => {
1116
1280
  if (response.status === "error" && !response.messages?.length) {
1117
1281
  setError(response.message ?? "The assistant could not answer.");
@@ -1120,9 +1284,10 @@ function useAgentChat(config) {
1120
1284
  if (response.conversation_id) setConversationId(response.conversation_id);
1121
1285
  if (response.messages?.length) {
1122
1286
  setMessages((current) => {
1123
- const seen = new Set(current.map((message) => message.id));
1287
+ const settled = current.filter((message) => !message.id.startsWith(OPTIMISTIC_ID_PREFIX));
1288
+ const seen = new Set(settled.map((message) => message.id));
1124
1289
  const fresh = response.messages?.filter((message) => !seen.has(message.id)) ?? [];
1125
- return [...current, ...fresh];
1290
+ return [...settled, ...fresh];
1126
1291
  });
1127
1292
  }
1128
1293
  setPendingApprovals(response.pending_approvals ?? []);
@@ -1132,13 +1297,92 @@ function useAgentChat(config) {
1132
1297
  },
1133
1298
  []
1134
1299
  );
1135
- const send = React5.useCallback(
1300
+ const handleStreamEvent = React6.useCallback(
1301
+ (event, ctx) => {
1302
+ switch (event.type) {
1303
+ case "conversation":
1304
+ setConversationId(event.conversation_id);
1305
+ break;
1306
+ case "user_message":
1307
+ setMessages((current) => {
1308
+ const settled = current.filter((message) => !message.id.startsWith(OPTIMISTIC_ID_PREFIX));
1309
+ const seen = new Set(settled.map((message) => message.id));
1310
+ return seen.has(event.message.id) ? settled : [...settled, event.message];
1311
+ });
1312
+ break;
1313
+ case "text_delta": {
1314
+ if (!ctx.streamingId) {
1315
+ streamingCounter += 1;
1316
+ ctx.streamingId = `${STREAMING_ID_PREFIX}${streamingCounter}`;
1317
+ const bubble = {
1318
+ id: ctx.streamingId,
1319
+ role: "assistant",
1320
+ content: event.delta,
1321
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1322
+ };
1323
+ setMessages((current) => [...current, bubble]);
1324
+ } else {
1325
+ const id = ctx.streamingId;
1326
+ setMessages(
1327
+ (current) => current.map(
1328
+ (message) => message.id === id ? { ...message, content: (message.content ?? "") + event.delta } : message
1329
+ )
1330
+ );
1331
+ }
1332
+ break;
1333
+ }
1334
+ case "assistant_message": {
1335
+ const streamingId = ctx.streamingId;
1336
+ ctx.streamingId = null;
1337
+ setMessages((current) => {
1338
+ const withoutTransient = streamingId ? current.filter((message) => message.id !== streamingId) : current;
1339
+ const seen = new Set(withoutTransient.map((message) => message.id));
1340
+ return seen.has(event.message.id) ? withoutTransient : [...withoutTransient, event.message];
1341
+ });
1342
+ break;
1343
+ }
1344
+ case "tool_result":
1345
+ setMessages((current) => {
1346
+ const seen = new Set(current.map((message) => message.id));
1347
+ return seen.has(event.message.id) ? current : [...current, event.message];
1348
+ });
1349
+ break;
1350
+ case "pending_approvals":
1351
+ setPendingApprovals(event.approvals ?? []);
1352
+ break;
1353
+ case "error":
1354
+ setError(event.message);
1355
+ break;
1356
+ case "done":
1357
+ if (event.conversation_id) setConversationId(event.conversation_id);
1358
+ if (event.pending_approvals) setPendingApprovals(event.pending_approvals);
1359
+ break;
1360
+ }
1361
+ },
1362
+ []
1363
+ );
1364
+ const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
1365
+ const send = React6.useCallback(
1136
1366
  async (text, files) => {
1137
1367
  if (sending) return;
1138
1368
  if (!text.trim() && !(files && files.length)) return;
1139
1369
  setSending(true);
1140
1370
  setError(null);
1371
+ setMessages((current) => [...current, buildOptimisticUserMessage(text, files)]);
1141
1372
  try {
1373
+ if (streamingEnabled) {
1374
+ const ctx = { streamingId: null };
1375
+ let started = false;
1376
+ try {
1377
+ await client.streamMessage(text, conversationId, files, (event) => {
1378
+ started = true;
1379
+ handleStreamEvent(event, ctx);
1380
+ });
1381
+ return;
1382
+ } catch (err) {
1383
+ if (started) throw err;
1384
+ }
1385
+ }
1142
1386
  const response = await client.sendMessage(text, conversationId, files);
1143
1387
  applyResponse(response);
1144
1388
  } catch (err) {
@@ -1147,14 +1391,27 @@ function useAgentChat(config) {
1147
1391
  setSending(false);
1148
1392
  }
1149
1393
  },
1150
- [applyResponse, client, conversationId, handleFailure, sending]
1394
+ [applyResponse, client, conversationId, handleFailure, handleStreamEvent, sending, streamingEnabled]
1151
1395
  );
1152
- const resolveApproval = React5.useCallback(
1396
+ const resolveApproval = React6.useCallback(
1153
1397
  async (callId, approve) => {
1154
1398
  if (resolvingApprovalId) return;
1155
1399
  setResolvingApprovalId(callId);
1156
1400
  setError(null);
1157
1401
  try {
1402
+ if (streamingEnabled) {
1403
+ const ctx = { streamingId: null };
1404
+ let started = false;
1405
+ try {
1406
+ await client.streamResolveApproval(callId, approve, (event) => {
1407
+ started = true;
1408
+ handleStreamEvent(event, ctx);
1409
+ });
1410
+ return;
1411
+ } catch (err) {
1412
+ if (started) throw err;
1413
+ }
1414
+ }
1158
1415
  const response = await client.resolveApproval(callId, approve);
1159
1416
  applyResponse(response);
1160
1417
  } catch (err) {
@@ -1163,15 +1420,15 @@ function useAgentChat(config) {
1163
1420
  setResolvingApprovalId(null);
1164
1421
  }
1165
1422
  },
1166
- [applyResponse, client, handleFailure, resolvingApprovalId]
1423
+ [applyResponse, client, handleFailure, handleStreamEvent, resolvingApprovalId, streamingEnabled]
1167
1424
  );
1168
- const newChat = React5.useCallback(() => {
1425
+ const newChat = React6.useCallback(() => {
1169
1426
  setConversationId(null);
1170
1427
  setMessages([]);
1171
1428
  setPendingApprovals([]);
1172
1429
  setError(null);
1173
1430
  }, []);
1174
- const refreshConversations = React5.useCallback(async () => {
1431
+ const refreshConversations = React6.useCallback(async () => {
1175
1432
  try {
1176
1433
  const { conversations: list } = await client.listConversations();
1177
1434
  setConversations(list);
@@ -1179,7 +1436,7 @@ function useAgentChat(config) {
1179
1436
  handleFailure(err);
1180
1437
  }
1181
1438
  }, [client, handleFailure]);
1182
- const openConversation = React5.useCallback(
1439
+ const openConversation = React6.useCallback(
1183
1440
  async (id) => {
1184
1441
  setError(null);
1185
1442
  try {
@@ -1193,7 +1450,7 @@ function useAgentChat(config) {
1193
1450
  },
1194
1451
  [client, handleFailure]
1195
1452
  );
1196
- const deleteConversation = React5.useCallback(
1453
+ const deleteConversation = React6.useCallback(
1197
1454
  async (id) => {
1198
1455
  try {
1199
1456
  await client.deleteConversation(id);
@@ -1205,7 +1462,7 @@ function useAgentChat(config) {
1205
1462
  },
1206
1463
  [client, conversationId, handleFailure, newChat]
1207
1464
  );
1208
- const clearError = React5.useCallback(() => setError(null), []);
1465
+ const clearError = React6.useCallback(() => setError(null), []);
1209
1466
  return {
1210
1467
  agentInfo,
1211
1468
  clearError,
@@ -1240,11 +1497,11 @@ function readStored(clientId) {
1240
1497
  }
1241
1498
  }
1242
1499
  function useAlwaysApprove(clientId) {
1243
- const [approved, setApproved] = React5.useState(() => /* @__PURE__ */ new Set());
1244
- React5.useEffect(() => {
1500
+ const [approved, setApproved] = React6.useState(() => /* @__PURE__ */ new Set());
1501
+ React6.useEffect(() => {
1245
1502
  setApproved(readStored(clientId));
1246
1503
  }, [clientId]);
1247
- const persist = React5.useCallback(
1504
+ const persist = React6.useCallback(
1248
1505
  (next) => {
1249
1506
  setApproved(next);
1250
1507
  if (typeof window === "undefined") return;
@@ -1255,8 +1512,8 @@ function useAlwaysApprove(clientId) {
1255
1512
  },
1256
1513
  [clientId]
1257
1514
  );
1258
- const isAlwaysApproved = React5.useCallback((toolName) => approved.has(toolName), [approved]);
1259
- const setAlwaysApprove = React5.useCallback(
1515
+ const isAlwaysApproved = React6.useCallback((toolName) => approved.has(toolName), [approved]);
1516
+ const setAlwaysApprove = React6.useCallback(
1260
1517
  (toolName) => {
1261
1518
  if (approved.has(toolName)) return;
1262
1519
  const next = new Set(approved);
@@ -1265,7 +1522,7 @@ function useAlwaysApprove(clientId) {
1265
1522
  },
1266
1523
  [approved, persist]
1267
1524
  );
1268
- const clearAlwaysApprove = React5.useCallback(
1525
+ const clearAlwaysApprove = React6.useCallback(
1269
1526
  (toolName) => {
1270
1527
  if (!approved.has(toolName)) return;
1271
1528
  const next = new Set(approved);
@@ -1277,16 +1534,16 @@ function useAlwaysApprove(clientId) {
1277
1534
  return { clearAlwaysApprove, isAlwaysApproved, setAlwaysApprove };
1278
1535
  }
1279
1536
  function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1280
- const [uncontrolledOpen, setUncontrolledOpen] = React5.useState(false);
1537
+ const [uncontrolledOpen, setUncontrolledOpen] = React6.useState(false);
1281
1538
  const open = controlledOpen ?? uncontrolledOpen;
1282
- const setOpen = React5.useCallback(
1539
+ const setOpen = React6.useCallback(
1283
1540
  (next) => {
1284
1541
  setUncontrolledOpen(next);
1285
1542
  onOpenChange?.(next);
1286
1543
  },
1287
1544
  [onOpenChange]
1288
1545
  );
1289
- const [agentMode, setAgentMode] = React5.useState(false);
1546
+ const [agentMode, setAgentMode] = React6.useState(false);
1290
1547
  const isDesktop = useMediaQuery("(min-width: 640px)", {
1291
1548
  defaultSSRValue: true,
1292
1549
  ssr: true
@@ -1296,14 +1553,14 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1296
1553
  const alwaysApprove = useAlwaysApprove(config.clientId);
1297
1554
  const { pendingApprovals, resolveApproval, resolvingApprovalId } = chat;
1298
1555
  const { isAlwaysApproved } = alwaysApprove;
1299
- React5.useEffect(() => {
1556
+ React6.useEffect(() => {
1300
1557
  if (!open || resolvingApprovalId) return;
1301
1558
  const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
1302
1559
  if (target) void resolveApproval(target.id, true);
1303
1560
  }, [open, pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
1304
- const firedWriteIds = React5.useRef(/* @__PURE__ */ new Set());
1561
+ const firedWriteIds = React6.useRef(/* @__PURE__ */ new Set());
1305
1562
  const { onWriteSuccess } = config;
1306
- React5.useEffect(() => {
1563
+ React6.useEffect(() => {
1307
1564
  if (!onWriteSuccess) return;
1308
1565
  for (const message of chat.messages) {
1309
1566
  if (message.role !== "tool" || !message.is_write || message.tool_status !== "success") continue;
@@ -1313,9 +1570,9 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1313
1570
  onWriteSuccess({ arguments: args, toolName: message.tool_name ?? "" });
1314
1571
  }
1315
1572
  }, [chat.messages, onWriteSuccess]);
1316
- const lastNavigatedMessageId = React5.useRef(null);
1573
+ const lastNavigatedMessageId = React6.useRef(null);
1317
1574
  const { messages } = chat;
1318
- React5.useEffect(() => {
1575
+ React6.useEffect(() => {
1319
1576
  if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
1320
1577
  const toolMessages = messages.filter((message) => message.role === "tool");
1321
1578
  const latest = toolMessages[toolMessages.length - 1];
@@ -1327,7 +1584,7 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
1327
1584
  const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
1328
1585
  if (path) config.navigate(path);
1329
1586
  }, [config, fullscreen, messages, open]);
1330
- const closePanel = React5.useCallback(() => {
1587
+ const closePanel = React6.useCallback(() => {
1331
1588
  setOpen(false);
1332
1589
  setAgentMode(false);
1333
1590
  }, [setOpen]);
@@ -1406,6 +1663,6 @@ function defineAgentConfig(config) {
1406
1663
  return config;
1407
1664
  }
1408
1665
 
1409
- export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
1666
+ export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
1410
1667
  //# sourceMappingURL=index.js.map
1411
1668
  //# sourceMappingURL=index.js.map