@cntyclub/agent-react 0.17.0 → 0.19.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/api-client.d.ts +67 -9
- package/dist/api-client.d.ts.map +1 -1
- package/dist/components/agent-panel.d.ts.map +1 -1
- package/dist/components/agent-visibility-control.d.ts +18 -0
- package/dist/components/agent-visibility-control.d.ts.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +724 -131
- package/dist/index.js.map +1 -1
- package/dist/realtime-client.d.ts +41 -0
- package/dist/realtime-client.d.ts.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-agent-chat.d.ts +34 -1
- package/dist/use-agent-chat.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
import { cn, Button, ThinkingOrb, Tooltip, TooltipTrigger, TooltipContent, ChatMessage, ChatMessageBubble, Markdown, AuroraText, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions, Chat, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, DataTablePaged } from '@cntyclub/ui-react';
|
|
2
|
-
import { XIcon, Trash2Icon, SquareIcon, PauseIcon, PlayIcon, CheckIcon, FileTextIcon, PaperclipIcon, MicIcon, ArrowUpIcon, CopyIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, ChevronsRightIcon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
|
|
3
|
-
import * as
|
|
1
|
+
import { cn, Button, ThinkingOrb, Tooltip, TooltipTrigger, TooltipContent, Popover, PopoverTrigger, PopoverPopup, ChatMessage, ChatMessageBubble, Markdown, AuroraText, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, AvatarGroup, ChatHeaderActions, ContextMeter, Chat, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, DataTablePaged } from '@cntyclub/ui-react';
|
|
2
|
+
import { XIcon, Trash2Icon, SquareIcon, PauseIcon, PlayIcon, CheckIcon, FileTextIcon, PaperclipIcon, MicIcon, ArrowUpIcon, UsersIcon, LockIcon, GlobeIcon, CopyIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, ChevronsRightIcon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
|
|
3
|
+
import * as React13 from 'react';
|
|
4
4
|
import { AnimatePresence, motion } from 'motion/react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
// src/api-client.ts
|
|
9
|
+
function collabBody(options) {
|
|
10
|
+
if (!options) return {};
|
|
11
|
+
const body = {};
|
|
12
|
+
if (options.idempotencyKey) body.idempotency_key = options.idempotencyKey;
|
|
13
|
+
if (options.replyToId) body.reply_to_id = options.replyToId;
|
|
14
|
+
if (options.threadRootId) body.thread_root_id = options.threadRootId;
|
|
15
|
+
if (options.mentionUserIds?.length) body.mention_user_ids = options.mentionUserIds;
|
|
16
|
+
return body;
|
|
17
|
+
}
|
|
18
|
+
function appendCollab(form, options) {
|
|
19
|
+
if (!options) return;
|
|
20
|
+
if (options.idempotencyKey) form.append("idempotency_key", options.idempotencyKey);
|
|
21
|
+
if (options.replyToId) form.append("reply_to_id", options.replyToId);
|
|
22
|
+
if (options.threadRootId) form.append("thread_root_id", options.threadRootId);
|
|
23
|
+
for (const id of options.mentionUserIds ?? []) form.append("mention_user_ids", id);
|
|
24
|
+
}
|
|
9
25
|
var AgentApiError = class extends Error {
|
|
10
26
|
status;
|
|
11
27
|
constructor(message, status) {
|
|
@@ -65,20 +81,22 @@ var AgentApiClient = class {
|
|
|
65
81
|
method: "POST"
|
|
66
82
|
});
|
|
67
83
|
}
|
|
68
|
-
sendMessage(message, conversationId, files) {
|
|
84
|
+
sendMessage(message, conversationId, files, options) {
|
|
69
85
|
if (files && files.length > 0) {
|
|
70
86
|
const form = new FormData();
|
|
71
87
|
form.append("client_id", this.config.clientId);
|
|
72
88
|
form.append("message", message);
|
|
73
89
|
if (conversationId) form.append("conversation_id", conversationId);
|
|
74
90
|
for (const file of files) form.append("files", file, file.name);
|
|
91
|
+
appendCollab(form, options);
|
|
75
92
|
return this.request("/agent-mode/chat/", { body: form, method: "POST" });
|
|
76
93
|
}
|
|
77
94
|
return this.request("/agent-mode/chat/", {
|
|
78
95
|
body: {
|
|
79
96
|
client_id: this.config.clientId,
|
|
80
97
|
conversation_id: conversationId ?? null,
|
|
81
|
-
message
|
|
98
|
+
message,
|
|
99
|
+
...collabBody(options)
|
|
82
100
|
},
|
|
83
101
|
method: "POST"
|
|
84
102
|
});
|
|
@@ -91,7 +109,7 @@ var AgentApiClient = class {
|
|
|
91
109
|
}
|
|
92
110
|
// ------------------------------------------------------------- streaming
|
|
93
111
|
/** POST a chat turn and yield parsed SSE events as they stream in. */
|
|
94
|
-
async streamMessage(message, conversationId, files, onEvent, signal) {
|
|
112
|
+
async streamMessage(message, conversationId, files, onEvent, signal, options) {
|
|
95
113
|
let body;
|
|
96
114
|
const headers = await this.streamHeaders();
|
|
97
115
|
if (files && files.length > 0) {
|
|
@@ -100,13 +118,15 @@ var AgentApiClient = class {
|
|
|
100
118
|
form.append("message", message);
|
|
101
119
|
if (conversationId) form.append("conversation_id", conversationId);
|
|
102
120
|
for (const file of files) form.append("files", file, file.name);
|
|
121
|
+
appendCollab(form, options);
|
|
103
122
|
body = form;
|
|
104
123
|
} else {
|
|
105
124
|
headers["Content-Type"] = "application/json";
|
|
106
125
|
body = JSON.stringify({
|
|
107
126
|
client_id: this.config.clientId,
|
|
108
127
|
conversation_id: conversationId ?? null,
|
|
109
|
-
message
|
|
128
|
+
message,
|
|
129
|
+
...collabBody(options)
|
|
110
130
|
});
|
|
111
131
|
}
|
|
112
132
|
await this.consumeStream("/agent-mode/chat/stream/", { body, headers, signal }, onEvent);
|
|
@@ -184,14 +204,219 @@ var AgentApiClient = class {
|
|
|
184
204
|
listConversations() {
|
|
185
205
|
return this.request("/agent-mode/conversations/");
|
|
186
206
|
}
|
|
187
|
-
getConversation(conversationId) {
|
|
188
|
-
|
|
207
|
+
getConversation(conversationId, options) {
|
|
208
|
+
const qs = new URLSearchParams();
|
|
209
|
+
if (options?.beforeSeq != null) qs.set("before_seq", String(options.beforeSeq));
|
|
210
|
+
if (options?.limit != null) qs.set("limit", String(options.limit));
|
|
211
|
+
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
212
|
+
return this.request(`/agent-mode/conversations/${conversationId}/${suffix}`);
|
|
189
213
|
}
|
|
214
|
+
/** Soft-delete (creator/admin) or leave (member). Never a hard delete. */
|
|
190
215
|
deleteConversation(conversationId) {
|
|
191
216
|
return this.request(`/agent-mode/conversations/${conversationId}/`, {
|
|
192
217
|
method: "DELETE"
|
|
193
218
|
});
|
|
194
219
|
}
|
|
220
|
+
// ------------------------------------------------------------- collaboration
|
|
221
|
+
setVisibility(conversationId, visibility, scope) {
|
|
222
|
+
return this.request(`/agent-mode/conversations/${conversationId}/visibility/`, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
body: {
|
|
225
|
+
client_id: this.config.clientId,
|
|
226
|
+
visibility,
|
|
227
|
+
scope_type: scope?.scopeType ?? "none",
|
|
228
|
+
scope_event_id: scope?.scopeEventId
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
listParticipants(conversationId) {
|
|
233
|
+
return this.request(`/agent-mode/conversations/${conversationId}/participants/`);
|
|
234
|
+
}
|
|
235
|
+
markRead(conversationId, seq) {
|
|
236
|
+
return this.request(`/agent-mode/conversations/${conversationId}/read/`, {
|
|
237
|
+
method: "POST",
|
|
238
|
+
body: { client_id: this.config.clientId, seq }
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
saveDraft(conversationId, text) {
|
|
242
|
+
return this.request(`/agent-mode/conversations/${conversationId}/draft/`, {
|
|
243
|
+
method: "POST",
|
|
244
|
+
body: { client_id: this.config.clientId, text }
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
setFlags(conversationId, flags) {
|
|
248
|
+
return this.request(`/agent-mode/conversations/${conversationId}/flags/`, {
|
|
249
|
+
method: "POST",
|
|
250
|
+
body: { client_id: this.config.clientId, ...flags }
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
listFiles(conversationId) {
|
|
254
|
+
return this.request(`/agent-mode/conversations/${conversationId}/files/`);
|
|
255
|
+
}
|
|
256
|
+
summarize(conversationId) {
|
|
257
|
+
return this.request(`/agent-mode/conversations/${conversationId}/summarize/`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
body: { client_id: this.config.clientId }
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
listContext(conversationId) {
|
|
263
|
+
return this.request(`/agent-mode/conversations/${conversationId}/context/`);
|
|
264
|
+
}
|
|
265
|
+
pinContext(conversationId, item) {
|
|
266
|
+
return this.request(`/agent-mode/conversations/${conversationId}/context/`, {
|
|
267
|
+
method: "POST",
|
|
268
|
+
body: {
|
|
269
|
+
client_id: this.config.clientId,
|
|
270
|
+
kind: item.kind,
|
|
271
|
+
label: item.label,
|
|
272
|
+
ref_type: item.refType,
|
|
273
|
+
ref_id: item.refId
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
editMessage(conversationId, messageId, message) {
|
|
278
|
+
return this.request(`/agent-mode/conversations/${conversationId}/messages/${messageId}/`, {
|
|
279
|
+
method: "PATCH",
|
|
280
|
+
body: { client_id: this.config.clientId, message }
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
deleteMessage(conversationId, messageId) {
|
|
284
|
+
return this.request(`/agent-mode/conversations/${conversationId}/messages/${messageId}/`, {
|
|
285
|
+
method: "DELETE"
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
search(query, limit = 20) {
|
|
289
|
+
const qs = new URLSearchParams({ query, limit: String(limit) });
|
|
290
|
+
return this.request(`/agent-mode/search/?${qs.toString()}`);
|
|
291
|
+
}
|
|
292
|
+
getToolTrust() {
|
|
293
|
+
return this.request("/agent-mode/tool-trust/");
|
|
294
|
+
}
|
|
295
|
+
setToolTrust(toolName, trusted) {
|
|
296
|
+
return this.request("/agent-mode/tool-trust/", {
|
|
297
|
+
method: "POST",
|
|
298
|
+
body: { client_id: this.config.clientId, tool_name: toolName, trusted }
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
// src/realtime-client.ts
|
|
304
|
+
var AgentRealtimeClient = class {
|
|
305
|
+
config;
|
|
306
|
+
conversationId;
|
|
307
|
+
handlers;
|
|
308
|
+
socket = null;
|
|
309
|
+
lastSeq = 0;
|
|
310
|
+
closed = false;
|
|
311
|
+
retries = 0;
|
|
312
|
+
reconnectTimer = null;
|
|
313
|
+
constructor(config, conversationId, handlers, lastSeq = 0) {
|
|
314
|
+
this.config = config;
|
|
315
|
+
this.conversationId = conversationId;
|
|
316
|
+
this.handlers = handlers;
|
|
317
|
+
this.lastSeq = lastSeq;
|
|
318
|
+
}
|
|
319
|
+
async connect() {
|
|
320
|
+
if (this.closed || typeof WebSocket === "undefined") return;
|
|
321
|
+
const url = await this.buildUrl();
|
|
322
|
+
if (!url) return;
|
|
323
|
+
this.handlers.onStatus?.("connecting");
|
|
324
|
+
let socket;
|
|
325
|
+
try {
|
|
326
|
+
socket = new WebSocket(url);
|
|
327
|
+
} catch {
|
|
328
|
+
this.scheduleReconnect();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
this.socket = socket;
|
|
332
|
+
socket.onopen = () => {
|
|
333
|
+
this.retries = 0;
|
|
334
|
+
this.handlers.onStatus?.("open");
|
|
335
|
+
this.send({ action: "sync", last_seq: this.lastSeq });
|
|
336
|
+
};
|
|
337
|
+
socket.onmessage = (raw) => {
|
|
338
|
+
let event;
|
|
339
|
+
try {
|
|
340
|
+
event = JSON.parse(raw.data);
|
|
341
|
+
} catch {
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
this.trackSeq(event);
|
|
345
|
+
this.handlers.onEvent(event);
|
|
346
|
+
};
|
|
347
|
+
socket.onclose = () => {
|
|
348
|
+
this.handlers.onStatus?.("closed");
|
|
349
|
+
if (!this.closed) this.scheduleReconnect();
|
|
350
|
+
};
|
|
351
|
+
socket.onerror = () => {
|
|
352
|
+
try {
|
|
353
|
+
socket.close();
|
|
354
|
+
} catch {
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
/** Advance the local high-water mark so a reconnect replays only the true gap. */
|
|
359
|
+
trackSeq(event) {
|
|
360
|
+
if (event.type === "sync") {
|
|
361
|
+
if (typeof event.last_seq === "number") this.lastSeq = Math.max(this.lastSeq, event.last_seq);
|
|
362
|
+
} else if (event.type === "message" || event.type === "message_edited") {
|
|
363
|
+
const seq = event.message?.seq;
|
|
364
|
+
if (typeof seq === "number") this.lastSeq = Math.max(this.lastSeq, seq);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
setLastSeq(seq) {
|
|
368
|
+
if (seq > this.lastSeq) this.lastSeq = seq;
|
|
369
|
+
}
|
|
370
|
+
sendTyping(isTyping) {
|
|
371
|
+
this.send({ action: "typing", is_typing: isTyping });
|
|
372
|
+
}
|
|
373
|
+
markRead(seq) {
|
|
374
|
+
this.setLastSeq(seq);
|
|
375
|
+
this.send({ action: "read", seq });
|
|
376
|
+
}
|
|
377
|
+
requestSync() {
|
|
378
|
+
this.send({ action: "sync", last_seq: this.lastSeq });
|
|
379
|
+
}
|
|
380
|
+
close() {
|
|
381
|
+
this.closed = true;
|
|
382
|
+
if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
|
|
383
|
+
this.reconnectTimer = null;
|
|
384
|
+
if (this.socket) {
|
|
385
|
+
try {
|
|
386
|
+
this.socket.close();
|
|
387
|
+
} catch {
|
|
388
|
+
}
|
|
389
|
+
this.socket = null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
send(payload) {
|
|
393
|
+
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
|
|
394
|
+
try {
|
|
395
|
+
this.socket.send(JSON.stringify(payload));
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
scheduleReconnect() {
|
|
401
|
+
if (this.closed || this.reconnectTimer) return;
|
|
402
|
+
this.retries += 1;
|
|
403
|
+
const delay = Math.min(3e4, 1e3 * 2 ** Math.min(this.retries, 5));
|
|
404
|
+
this.reconnectTimer = setTimeout(() => {
|
|
405
|
+
this.reconnectTimer = null;
|
|
406
|
+
void this.connect();
|
|
407
|
+
}, delay);
|
|
408
|
+
}
|
|
409
|
+
async buildUrl() {
|
|
410
|
+
const token = await this.config.getAccessToken();
|
|
411
|
+
if (!token) return null;
|
|
412
|
+
const base = (this.config.realtime?.baseUrl ?? this.config.apiBaseUrl).replace(/\/$/, "").replace(/^http/, "ws");
|
|
413
|
+
const params = new URLSearchParams({
|
|
414
|
+
client_id: this.config.clientId,
|
|
415
|
+
token,
|
|
416
|
+
last_seq: String(this.lastSeq)
|
|
417
|
+
});
|
|
418
|
+
return `${base}/ws/agent-mode/conversations/${this.conversationId}/?${params.toString()}`;
|
|
419
|
+
}
|
|
195
420
|
};
|
|
196
421
|
|
|
197
422
|
// src/file-support.ts
|
|
@@ -259,7 +484,7 @@ function formatBytes(bytes) {
|
|
|
259
484
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
260
485
|
}
|
|
261
486
|
function AgentImageLightbox({ src, alt, onClose }) {
|
|
262
|
-
|
|
487
|
+
React13.useEffect(() => {
|
|
263
488
|
if (!src) return;
|
|
264
489
|
const onKey = (event) => {
|
|
265
490
|
if (event.key === "Escape") onClose();
|
|
@@ -329,24 +554,24 @@ function pickMimeType() {
|
|
|
329
554
|
return "";
|
|
330
555
|
}
|
|
331
556
|
function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError }) {
|
|
332
|
-
const [phase, setPhase] =
|
|
333
|
-
const [elapsed, setElapsed] =
|
|
334
|
-
const [playing, setPlaying] =
|
|
335
|
-
const recorderRef =
|
|
336
|
-
const streamRef =
|
|
337
|
-
const chunksRef =
|
|
338
|
-
const blobRef =
|
|
339
|
-
const audioUrlRef =
|
|
340
|
-
const audioRef =
|
|
341
|
-
const timerRef =
|
|
342
|
-
const canvasRef =
|
|
343
|
-
const audioContextRef =
|
|
344
|
-
const analyserRef =
|
|
345
|
-
const rafRef =
|
|
346
|
-
const levelsRef =
|
|
347
|
-
const onErrorRef =
|
|
557
|
+
const [phase, setPhase] = React13.useState("recording");
|
|
558
|
+
const [elapsed, setElapsed] = React13.useState(0);
|
|
559
|
+
const [playing, setPlaying] = React13.useState(false);
|
|
560
|
+
const recorderRef = React13.useRef(null);
|
|
561
|
+
const streamRef = React13.useRef(null);
|
|
562
|
+
const chunksRef = React13.useRef([]);
|
|
563
|
+
const blobRef = React13.useRef(null);
|
|
564
|
+
const audioUrlRef = React13.useRef(null);
|
|
565
|
+
const audioRef = React13.useRef(null);
|
|
566
|
+
const timerRef = React13.useRef(null);
|
|
567
|
+
const canvasRef = React13.useRef(null);
|
|
568
|
+
const audioContextRef = React13.useRef(null);
|
|
569
|
+
const analyserRef = React13.useRef(null);
|
|
570
|
+
const rafRef = React13.useRef(null);
|
|
571
|
+
const levelsRef = React13.useRef(new Array(MAX_BARS).fill(0));
|
|
572
|
+
const onErrorRef = React13.useRef(onError);
|
|
348
573
|
onErrorRef.current = onError;
|
|
349
|
-
const stopMeter =
|
|
574
|
+
const stopMeter = React13.useCallback(() => {
|
|
350
575
|
if (rafRef.current != null) {
|
|
351
576
|
cancelAnimationFrame(rafRef.current);
|
|
352
577
|
rafRef.current = null;
|
|
@@ -357,7 +582,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
|
|
|
357
582
|
if (ctx && ctx.state !== "closed") void ctx.close().catch(() => {
|
|
358
583
|
});
|
|
359
584
|
}, []);
|
|
360
|
-
const stopTracks =
|
|
585
|
+
const stopTracks = React13.useCallback(() => {
|
|
361
586
|
streamRef.current?.getTracks().forEach((track) => track.stop());
|
|
362
587
|
streamRef.current = null;
|
|
363
588
|
if (timerRef.current) {
|
|
@@ -366,7 +591,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
|
|
|
366
591
|
}
|
|
367
592
|
stopMeter();
|
|
368
593
|
}, [stopMeter]);
|
|
369
|
-
const startMeter =
|
|
594
|
+
const startMeter = React13.useCallback((stream) => {
|
|
370
595
|
const Ctor = typeof window === "undefined" ? void 0 : window.AudioContext ?? window.webkitAudioContext;
|
|
371
596
|
if (!Ctor) return;
|
|
372
597
|
let context;
|
|
@@ -436,7 +661,7 @@ function AgentVoiceRecorder({ maxSeconds, transcribe, onDone, onCancel, onError
|
|
|
436
661
|
};
|
|
437
662
|
rafRef.current = requestAnimationFrame(draw);
|
|
438
663
|
}, []);
|
|
439
|
-
|
|
664
|
+
React13.useEffect(() => {
|
|
440
665
|
let cancelled = false;
|
|
441
666
|
void (async () => {
|
|
442
667
|
if (typeof navigator === "undefined" || !navigator.mediaDevices?.getUserMedia) {
|
|
@@ -589,40 +814,40 @@ function AgentComposer({
|
|
|
589
814
|
voiceEnabled,
|
|
590
815
|
voiceMaxSeconds = 600
|
|
591
816
|
}) {
|
|
592
|
-
const textareaRef =
|
|
593
|
-
const inputRef =
|
|
594
|
-
const [recording, setRecording] =
|
|
595
|
-
const [lightboxSrc, setLightboxSrc] =
|
|
817
|
+
const textareaRef = React13.useRef(null);
|
|
818
|
+
const inputRef = React13.useRef(null);
|
|
819
|
+
const [recording, setRecording] = React13.useState(false);
|
|
820
|
+
const [lightboxSrc, setLightboxSrc] = React13.useState(null);
|
|
596
821
|
const canSubmit = !disabled && !busy && (value.trim().length > 0 || files.length > 0);
|
|
597
822
|
const voiceAvailable = Boolean(voiceEnabled && onTranscribe);
|
|
598
|
-
const submit =
|
|
823
|
+
const submit = React13.useCallback(() => {
|
|
599
824
|
if (!canSubmit) return;
|
|
600
825
|
onSubmit(value.trim());
|
|
601
826
|
textareaRef.current?.focus();
|
|
602
827
|
}, [canSubmit, onSubmit, value]);
|
|
603
|
-
|
|
828
|
+
React13.useEffect(() => {
|
|
604
829
|
if (!disabled) textareaRef.current?.focus();
|
|
605
830
|
}, [disabled]);
|
|
606
|
-
|
|
831
|
+
React13.useEffect(() => {
|
|
607
832
|
const textarea = textareaRef.current;
|
|
608
833
|
if (!textarea) return;
|
|
609
834
|
textarea.style.height = "auto";
|
|
610
835
|
textarea.style.height = `${Math.min(textarea.scrollHeight, 144)}px`;
|
|
611
836
|
}, [value]);
|
|
612
|
-
const previews =
|
|
837
|
+
const previews = React13.useMemo(
|
|
613
838
|
() => files.map((file, originalIndex) => {
|
|
614
839
|
const image = isImageFile(file);
|
|
615
840
|
return { file, image, originalIndex, url: image ? URL.createObjectURL(file) : null };
|
|
616
841
|
}),
|
|
617
842
|
[files]
|
|
618
843
|
);
|
|
619
|
-
|
|
844
|
+
React13.useEffect(
|
|
620
845
|
() => () => {
|
|
621
846
|
previews.forEach((preview) => preview.url && URL.revokeObjectURL(preview.url));
|
|
622
847
|
},
|
|
623
848
|
[previews]
|
|
624
849
|
);
|
|
625
|
-
const ordered =
|
|
850
|
+
const ordered = React13.useMemo(
|
|
626
851
|
() => [...previews].sort((a, b) => Number(b.image) - Number(a.image)),
|
|
627
852
|
[previews]
|
|
628
853
|
);
|
|
@@ -866,16 +1091,114 @@ function groupMessages(messages) {
|
|
|
866
1091
|
flush();
|
|
867
1092
|
return items;
|
|
868
1093
|
}
|
|
1094
|
+
function audienceLabel(scope) {
|
|
1095
|
+
return scope === "event" ? "this event's organizers" : "your team";
|
|
1096
|
+
}
|
|
1097
|
+
function AgentVisibilityControl({ config, conversation, onSetVisibility }) {
|
|
1098
|
+
const [open, setOpen] = React13.useState(false);
|
|
1099
|
+
const collab = config.collaboration;
|
|
1100
|
+
const scope = collab?.scope;
|
|
1101
|
+
const isPublic = conversation.visibility === "public";
|
|
1102
|
+
if (collab?.enabled === false) return null;
|
|
1103
|
+
if (!conversation.can_toggle_visibility) {
|
|
1104
|
+
if (!isPublic) return null;
|
|
1105
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1106
|
+
/* @__PURE__ */ jsx(
|
|
1107
|
+
TooltipTrigger,
|
|
1108
|
+
{
|
|
1109
|
+
render: /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-primary/10 px-2 py-0.5 text-primary text-xs", children: [
|
|
1110
|
+
/* @__PURE__ */ jsx(UsersIcon, { className: "size-3" }),
|
|
1111
|
+
"Shared"
|
|
1112
|
+
] })
|
|
1113
|
+
}
|
|
1114
|
+
),
|
|
1115
|
+
/* @__PURE__ */ jsxs(TooltipContent, { children: [
|
|
1116
|
+
"Shared with ",
|
|
1117
|
+
audienceLabel(scope)
|
|
1118
|
+
] })
|
|
1119
|
+
] });
|
|
1120
|
+
}
|
|
1121
|
+
const eventId = scope === "event" ? collab?.getEventId?.() ?? null : null;
|
|
1122
|
+
const missingEvent = scope === "event" && !eventId;
|
|
1123
|
+
const makePublic = () => {
|
|
1124
|
+
onSetVisibility("public", eventId ? { scopeEventId: eventId } : void 0);
|
|
1125
|
+
setOpen(false);
|
|
1126
|
+
};
|
|
1127
|
+
const makePrivate = () => {
|
|
1128
|
+
onSetVisibility("private");
|
|
1129
|
+
setOpen(false);
|
|
1130
|
+
};
|
|
1131
|
+
return /* @__PURE__ */ jsxs(Popover, { onOpenChange: setOpen, open, children: [
|
|
1132
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1133
|
+
/* @__PURE__ */ jsx(
|
|
1134
|
+
TooltipTrigger,
|
|
1135
|
+
{
|
|
1136
|
+
render: /* @__PURE__ */ jsx(
|
|
1137
|
+
PopoverTrigger,
|
|
1138
|
+
{
|
|
1139
|
+
render: /* @__PURE__ */ jsx(
|
|
1140
|
+
Button,
|
|
1141
|
+
{
|
|
1142
|
+
"aria-label": isPublic ? "Shared conversation" : "Private conversation",
|
|
1143
|
+
size: "icon-sm",
|
|
1144
|
+
variant: "ghost",
|
|
1145
|
+
children: isPublic ? /* @__PURE__ */ jsx(UsersIcon, { className: "text-primary" }) : /* @__PURE__ */ jsx(LockIcon, {})
|
|
1146
|
+
}
|
|
1147
|
+
)
|
|
1148
|
+
}
|
|
1149
|
+
)
|
|
1150
|
+
}
|
|
1151
|
+
),
|
|
1152
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: isPublic ? "Shared \u2014 manage access" : "Private \u2014 share" })
|
|
1153
|
+
] }),
|
|
1154
|
+
/* @__PURE__ */ jsx(PopoverPopup, { className: "w-72 p-3", children: isPublic ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
1155
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 font-medium text-sm", children: [
|
|
1156
|
+
/* @__PURE__ */ jsx(GlobeIcon, { className: "size-4 text-primary" }),
|
|
1157
|
+
"Shared conversation"
|
|
1158
|
+
] }),
|
|
1159
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-xs", children: [
|
|
1160
|
+
"Everyone in ",
|
|
1161
|
+
audienceLabel(scope),
|
|
1162
|
+
" can see this conversation and everything in it."
|
|
1163
|
+
] }),
|
|
1164
|
+
/* @__PURE__ */ jsx(
|
|
1165
|
+
Button,
|
|
1166
|
+
{
|
|
1167
|
+
className: "mt-1 w-full",
|
|
1168
|
+
disabled: !conversation.can_go_private,
|
|
1169
|
+
onClick: makePrivate,
|
|
1170
|
+
size: "sm",
|
|
1171
|
+
variant: "outline",
|
|
1172
|
+
children: "Make private"
|
|
1173
|
+
}
|
|
1174
|
+
),
|
|
1175
|
+
!conversation.can_go_private ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: "Others have already taken part, so this can no longer be made private." }) : null
|
|
1176
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
1177
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 font-medium text-sm", children: [
|
|
1178
|
+
/* @__PURE__ */ jsx(UsersIcon, { className: "size-4" }),
|
|
1179
|
+
"Share this conversation"
|
|
1180
|
+
] }),
|
|
1181
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-xs", children: [
|
|
1182
|
+
audienceLabel(scope),
|
|
1183
|
+
" will be able to see this conversation",
|
|
1184
|
+
/* @__PURE__ */ jsx("strong", { children: " and its full history" }),
|
|
1185
|
+
", and take part in it."
|
|
1186
|
+
] }),
|
|
1187
|
+
missingEvent ? /* @__PURE__ */ jsx("p", { className: "text-destructive text-xs", children: "Open an event first \u2014 a shared conversation is tied to a specific event." }) : null,
|
|
1188
|
+
/* @__PURE__ */ jsx(Button, { className: "mt-1 w-full", disabled: missingEvent, onClick: makePublic, size: "sm", children: "Make public" })
|
|
1189
|
+
] }) })
|
|
1190
|
+
] });
|
|
1191
|
+
}
|
|
869
1192
|
function ChatScroller({ children, className, ...props }) {
|
|
870
|
-
const viewportRef =
|
|
871
|
-
const pinnedRef =
|
|
872
|
-
const handleScroll =
|
|
1193
|
+
const viewportRef = React13.useRef(null);
|
|
1194
|
+
const pinnedRef = React13.useRef(true);
|
|
1195
|
+
const handleScroll = React13.useCallback(() => {
|
|
873
1196
|
const viewport = viewportRef.current;
|
|
874
1197
|
if (!viewport) return;
|
|
875
1198
|
const distanceFromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
876
1199
|
pinnedRef.current = distanceFromBottom < 64;
|
|
877
1200
|
}, []);
|
|
878
|
-
|
|
1201
|
+
React13.useEffect(() => {
|
|
879
1202
|
const viewport = viewportRef.current;
|
|
880
1203
|
if (!viewport || !pinnedRef.current) return;
|
|
881
1204
|
viewport.scrollTop = viewport.scrollHeight;
|
|
@@ -895,10 +1218,10 @@ function ChatScroller({ children, className, ...props }) {
|
|
|
895
1218
|
var MIN_CHARS_PER_FRAME = 2;
|
|
896
1219
|
var CATCH_UP = 0.22;
|
|
897
1220
|
function usePrefersReducedMotion() {
|
|
898
|
-
const [reduced, setReduced] =
|
|
1221
|
+
const [reduced, setReduced] = React13.useState(
|
|
899
1222
|
() => typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches
|
|
900
1223
|
);
|
|
901
|
-
|
|
1224
|
+
React13.useEffect(() => {
|
|
902
1225
|
if (typeof matchMedia === "undefined") return;
|
|
903
1226
|
const mq = matchMedia("(prefers-reduced-motion: reduce)");
|
|
904
1227
|
const onChange = (event) => setReduced(event.matches);
|
|
@@ -910,12 +1233,12 @@ function usePrefersReducedMotion() {
|
|
|
910
1233
|
function useSmoothText(target, animate) {
|
|
911
1234
|
const prefersReduced = usePrefersReducedMotion();
|
|
912
1235
|
const enabled = animate && !prefersReduced;
|
|
913
|
-
const [count, setCount] =
|
|
914
|
-
const countRef =
|
|
1236
|
+
const [count, setCount] = React13.useState(() => enabled ? 0 : target.length);
|
|
1237
|
+
const countRef = React13.useRef(count);
|
|
915
1238
|
countRef.current = count;
|
|
916
|
-
const everEnabled =
|
|
1239
|
+
const everEnabled = React13.useRef(enabled);
|
|
917
1240
|
if (enabled) everEnabled.current = true;
|
|
918
|
-
|
|
1241
|
+
React13.useEffect(() => {
|
|
919
1242
|
if (!everEnabled.current) {
|
|
920
1243
|
if (countRef.current !== target.length) setCount(target.length);
|
|
921
1244
|
return;
|
|
@@ -944,14 +1267,14 @@ function useSmoothText(target, animate) {
|
|
|
944
1267
|
return everEnabled.current ? target.slice(0, Math.floor(count)) : target;
|
|
945
1268
|
}
|
|
946
1269
|
function CopyButton({ className, text }) {
|
|
947
|
-
const [copied, setCopied] =
|
|
948
|
-
const timerRef =
|
|
949
|
-
|
|
1270
|
+
const [copied, setCopied] = React13.useState(false);
|
|
1271
|
+
const timerRef = React13.useRef(null);
|
|
1272
|
+
React13.useEffect(() => {
|
|
950
1273
|
return () => {
|
|
951
1274
|
if (timerRef.current) clearTimeout(timerRef.current);
|
|
952
1275
|
};
|
|
953
1276
|
}, []);
|
|
954
|
-
const copy =
|
|
1277
|
+
const copy = React13.useCallback(async () => {
|
|
955
1278
|
try {
|
|
956
1279
|
await navigator.clipboard.writeText(text);
|
|
957
1280
|
} catch {
|
|
@@ -984,7 +1307,7 @@ function CopyButton({ className, text }) {
|
|
|
984
1307
|
}
|
|
985
1308
|
var isImageAttachment = (format) => IMAGE_ACCEPT.includes(`.${format.toLowerCase()}`);
|
|
986
1309
|
function MessageItem({ animate = false, message }) {
|
|
987
|
-
const [lightboxSrc, setLightboxSrc] =
|
|
1310
|
+
const [lightboxSrc, setLightboxSrc] = React13.useState(null);
|
|
988
1311
|
const displayed = useSmoothText(message.content ?? "", animate && message.role === "assistant");
|
|
989
1312
|
if (message.role === "user") {
|
|
990
1313
|
const attachments = message.attachments ?? [];
|
|
@@ -1069,8 +1392,8 @@ var DEFAULT_PHRASES = [
|
|
|
1069
1392
|
];
|
|
1070
1393
|
function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
|
|
1071
1394
|
const words = phrases && phrases.length ? phrases : DEFAULT_PHRASES;
|
|
1072
|
-
const [index, setIndex] =
|
|
1073
|
-
|
|
1395
|
+
const [index, setIndex] = React13.useState(0);
|
|
1396
|
+
React13.useEffect(() => {
|
|
1074
1397
|
setIndex(0);
|
|
1075
1398
|
const timer = setInterval(() => {
|
|
1076
1399
|
setIndex((current) => (current + 1) % words.length);
|
|
@@ -1318,7 +1641,7 @@ function DownloadButton({ block, size = "sm" }) {
|
|
|
1318
1641
|
);
|
|
1319
1642
|
}
|
|
1320
1643
|
function DocumentViewer({ block, onClose }) {
|
|
1321
|
-
|
|
1644
|
+
React13.useEffect(() => {
|
|
1322
1645
|
const onKey = (event) => event.key === "Escape" && onClose();
|
|
1323
1646
|
window.addEventListener("keydown", onKey);
|
|
1324
1647
|
return () => window.removeEventListener("keydown", onKey);
|
|
@@ -1343,7 +1666,7 @@ function DocumentViewer({ block, onClose }) {
|
|
|
1343
1666
|
] });
|
|
1344
1667
|
}
|
|
1345
1668
|
function DocumentCard({ block }) {
|
|
1346
|
-
const [open, setOpen] =
|
|
1669
|
+
const [open, setOpen] = React13.useState(false);
|
|
1347
1670
|
const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
|
|
1348
1671
|
return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
|
|
1349
1672
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
|
|
@@ -1465,7 +1788,7 @@ function StepDetail({
|
|
|
1465
1788
|
] });
|
|
1466
1789
|
}
|
|
1467
1790
|
function ToolActivityGroup({ describeToolCall, steps }) {
|
|
1468
|
-
const [open, setOpen] =
|
|
1791
|
+
const [open, setOpen] = React13.useState(false);
|
|
1469
1792
|
if (steps.length === 0) return null;
|
|
1470
1793
|
const last = steps[steps.length - 1];
|
|
1471
1794
|
const anyRunning = steps.some((step) => step.status === "running");
|
|
@@ -1558,6 +1881,12 @@ function ToolApprovalCard({
|
|
|
1558
1881
|
] })
|
|
1559
1882
|
] });
|
|
1560
1883
|
}
|
|
1884
|
+
function contextMeterLabel(meter) {
|
|
1885
|
+
const pct = Math.round(meter.pct * 100);
|
|
1886
|
+
if (meter.state === "full") return `Context full (${pct}%) \u2014 start a new chat to continue`;
|
|
1887
|
+
if (meter.state === "summarizing") return `Context ${pct}% \u2014 summarizing older messages`;
|
|
1888
|
+
return `Context ${pct}% used`;
|
|
1889
|
+
}
|
|
1561
1890
|
function AgentPanel({
|
|
1562
1891
|
agentMode,
|
|
1563
1892
|
canToggleAgentMode,
|
|
@@ -1570,12 +1899,12 @@ function AgentPanel({
|
|
|
1570
1899
|
onClose,
|
|
1571
1900
|
onToggleAgentMode
|
|
1572
1901
|
}) {
|
|
1573
|
-
const [draft, setDraft] =
|
|
1574
|
-
const [view, setView] =
|
|
1575
|
-
const [files, setFiles] =
|
|
1576
|
-
const [uploadError, setUploadError] =
|
|
1577
|
-
const [dragging, setDragging] =
|
|
1578
|
-
const dragDepth =
|
|
1902
|
+
const [draft, setDraft] = React13.useState("");
|
|
1903
|
+
const [view, setView] = React13.useState("chat");
|
|
1904
|
+
const [files, setFiles] = React13.useState([]);
|
|
1905
|
+
const [uploadError, setUploadError] = React13.useState(null);
|
|
1906
|
+
const [dragging, setDragging] = React13.useState(false);
|
|
1907
|
+
const dragDepth = React13.useRef(0);
|
|
1579
1908
|
const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
|
|
1580
1909
|
const caps = {
|
|
1581
1910
|
visionEnabled: chat.agentInfo?.vision_enabled,
|
|
@@ -1586,7 +1915,25 @@ function AgentPanel({
|
|
|
1586
1915
|
"What can you help me with?",
|
|
1587
1916
|
"Give me a quick summary of my data."
|
|
1588
1917
|
];
|
|
1589
|
-
const
|
|
1918
|
+
const typingLabel = React13.useMemo(() => {
|
|
1919
|
+
const names = Object.values(chat.typingUsers);
|
|
1920
|
+
if (!names.length) return null;
|
|
1921
|
+
if (names.length === 1) return `${names[0]} is typing\u2026`;
|
|
1922
|
+
if (names.length === 2) return `${names[0]} and ${names[1]} are typing\u2026`;
|
|
1923
|
+
return `${names.length} people are typing\u2026`;
|
|
1924
|
+
}, [chat.typingUsers]);
|
|
1925
|
+
const draftEpochRef = React13.useRef(-1);
|
|
1926
|
+
React13.useEffect(() => {
|
|
1927
|
+
if (draftEpochRef.current === chat.conversationEpoch) return;
|
|
1928
|
+
draftEpochRef.current = chat.conversationEpoch;
|
|
1929
|
+
setDraft(chat.myState?.draft_text ?? "");
|
|
1930
|
+
}, [chat.conversationEpoch, chat.myState?.draft_text]);
|
|
1931
|
+
React13.useEffect(() => {
|
|
1932
|
+
if (!chat.conversationId) return;
|
|
1933
|
+
const handle = setTimeout(() => chat.saveDraft(draft), 600);
|
|
1934
|
+
return () => clearTimeout(handle);
|
|
1935
|
+
}, [draft, chat.conversationId, chat.saveDraft]);
|
|
1936
|
+
const addFiles = React13.useCallback(
|
|
1590
1937
|
(incoming) => {
|
|
1591
1938
|
const accepted = [];
|
|
1592
1939
|
let rejection = null;
|
|
@@ -1603,10 +1950,10 @@ function AgentPanel({
|
|
|
1603
1950
|
},
|
|
1604
1951
|
[config, caps.visionEnabled, caps.visionMaxMb]
|
|
1605
1952
|
);
|
|
1606
|
-
const removeFile =
|
|
1953
|
+
const removeFile = React13.useCallback((index) => {
|
|
1607
1954
|
setFiles((current) => current.filter((_, i) => i !== index));
|
|
1608
1955
|
}, []);
|
|
1609
|
-
const handleSubmit =
|
|
1956
|
+
const handleSubmit = React13.useCallback(
|
|
1610
1957
|
(text) => {
|
|
1611
1958
|
const staged = files;
|
|
1612
1959
|
setDraft("");
|
|
@@ -1616,7 +1963,7 @@ function AgentPanel({
|
|
|
1616
1963
|
},
|
|
1617
1964
|
[chat, files]
|
|
1618
1965
|
);
|
|
1619
|
-
const onDragEnter =
|
|
1966
|
+
const onDragEnter = React13.useCallback(
|
|
1620
1967
|
(event) => {
|
|
1621
1968
|
if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
|
|
1622
1969
|
dragDepth.current += 1;
|
|
@@ -1624,12 +1971,12 @@ function AgentPanel({
|
|
|
1624
1971
|
},
|
|
1625
1972
|
[uploadEnabled]
|
|
1626
1973
|
);
|
|
1627
|
-
const onDragLeave =
|
|
1974
|
+
const onDragLeave = React13.useCallback((event) => {
|
|
1628
1975
|
if (!event.dataTransfer?.types?.includes("Files")) return;
|
|
1629
1976
|
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
1630
1977
|
if (dragDepth.current === 0) setDragging(false);
|
|
1631
1978
|
}, []);
|
|
1632
|
-
const onDrop =
|
|
1979
|
+
const onDrop = React13.useCallback(
|
|
1633
1980
|
(event) => {
|
|
1634
1981
|
if (!uploadEnabled) return;
|
|
1635
1982
|
event.preventDefault();
|
|
@@ -1640,7 +1987,7 @@ function AgentPanel({
|
|
|
1640
1987
|
},
|
|
1641
1988
|
[addFiles, uploadEnabled]
|
|
1642
1989
|
);
|
|
1643
|
-
const openHistory =
|
|
1990
|
+
const openHistory = React13.useCallback(() => {
|
|
1644
1991
|
setView("history");
|
|
1645
1992
|
void chat.refreshConversations();
|
|
1646
1993
|
}, [chat]);
|
|
@@ -1671,10 +2018,59 @@ function AgentPanel({
|
|
|
1671
2018
|
/* @__PURE__ */ jsx(ChatHeaderAvatar, {}),
|
|
1672
2019
|
/* @__PURE__ */ jsxs(ChatHeaderTitle, { children: [
|
|
1673
2020
|
/* @__PURE__ */ jsx("div", { className: "truncate font-medium text-sm", children: agentName }),
|
|
1674
|
-
config.tagline ? /* @__PURE__ */ jsx("div", { className: "truncate text-muted-foreground text-xs", children: config.tagline }) : null
|
|
1675
|
-
] })
|
|
2021
|
+
typingLabel ? /* @__PURE__ */ jsx("div", { className: "truncate text-primary text-xs", children: typingLabel }) : config.tagline ? /* @__PURE__ */ jsx("div", { className: "truncate text-muted-foreground text-xs", children: config.tagline }) : null
|
|
2022
|
+
] }),
|
|
2023
|
+
chat.participants.length > 1 ? /* @__PURE__ */ jsx(
|
|
2024
|
+
AvatarGroup,
|
|
2025
|
+
{
|
|
2026
|
+
className: "ml-0.5",
|
|
2027
|
+
items: chat.participants.map((participant) => ({
|
|
2028
|
+
alt: participant.name,
|
|
2029
|
+
name: participant.name,
|
|
2030
|
+
src: participant.avatar_url ?? void 0
|
|
2031
|
+
})),
|
|
2032
|
+
max: 4,
|
|
2033
|
+
showNameOnHover: true,
|
|
2034
|
+
size: "sm"
|
|
2035
|
+
}
|
|
2036
|
+
) : null
|
|
1676
2037
|
] }),
|
|
1677
2038
|
/* @__PURE__ */ jsxs(ChatHeaderActions, { children: [
|
|
2039
|
+
chat.currentConversation ? /* @__PURE__ */ jsx(
|
|
2040
|
+
AgentVisibilityControl,
|
|
2041
|
+
{
|
|
2042
|
+
config,
|
|
2043
|
+
conversation: chat.currentConversation,
|
|
2044
|
+
onSetVisibility: chat.setVisibility
|
|
2045
|
+
}
|
|
2046
|
+
) : null,
|
|
2047
|
+
chat.contextMeter && chat.contextMeter.pct > 0 ? /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
2048
|
+
/* @__PURE__ */ jsx(
|
|
2049
|
+
TooltipTrigger,
|
|
2050
|
+
{
|
|
2051
|
+
render: /* @__PURE__ */ jsx(
|
|
2052
|
+
"button",
|
|
2053
|
+
{
|
|
2054
|
+
"aria-label": contextMeterLabel(chat.contextMeter),
|
|
2055
|
+
className: "mr-0.5 inline-flex cursor-default items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
2056
|
+
type: "button",
|
|
2057
|
+
children: /* @__PURE__ */ jsx(
|
|
2058
|
+
ContextMeter,
|
|
2059
|
+
{
|
|
2060
|
+
lockAt: chat.contextMeter.lock_at,
|
|
2061
|
+
pct: chat.contextMeter.pct,
|
|
2062
|
+
size: 18,
|
|
2063
|
+
state: chat.contextMeter.state,
|
|
2064
|
+
summarizeAt: chat.contextMeter.summarize_at,
|
|
2065
|
+
warnAt: chat.contextMeter.warn_at
|
|
2066
|
+
}
|
|
2067
|
+
)
|
|
2068
|
+
}
|
|
2069
|
+
)
|
|
2070
|
+
}
|
|
2071
|
+
),
|
|
2072
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: contextMeterLabel(chat.contextMeter) })
|
|
2073
|
+
] }) : null,
|
|
1678
2074
|
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1679
2075
|
/* @__PURE__ */ jsx(
|
|
1680
2076
|
TooltipTrigger,
|
|
@@ -1813,7 +2209,7 @@ function AgentPanel({
|
|
|
1813
2209
|
}
|
|
1814
2210
|
}
|
|
1815
2211
|
}
|
|
1816
|
-
return grouped.map((item) => /* @__PURE__ */ jsxs(
|
|
2212
|
+
return grouped.map((item) => /* @__PURE__ */ jsxs(React13.Fragment, { children: [
|
|
1817
2213
|
item.key === anchorKey ? /* @__PURE__ */ jsx(NewMessagesDivider, {}) : null,
|
|
1818
2214
|
/* @__PURE__ */ jsx(
|
|
1819
2215
|
motion.div,
|
|
@@ -1896,6 +2292,43 @@ var OPTIMISTIC_ID_PREFIX = "optimistic-";
|
|
|
1896
2292
|
var STREAMING_ID_PREFIX = "streaming-";
|
|
1897
2293
|
var optimisticCounter = 0;
|
|
1898
2294
|
var streamingCounter = 0;
|
|
2295
|
+
function newIdempotencyKey() {
|
|
2296
|
+
try {
|
|
2297
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
2298
|
+
} catch {
|
|
2299
|
+
}
|
|
2300
|
+
return `idem-${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
|
|
2301
|
+
}
|
|
2302
|
+
function mergeMessages(current, incoming) {
|
|
2303
|
+
if (!incoming?.length) return current;
|
|
2304
|
+
const settledIds = new Set(
|
|
2305
|
+
current.filter((m) => !m.id.startsWith(OPTIMISTIC_ID_PREFIX)).map((m) => m.id)
|
|
2306
|
+
);
|
|
2307
|
+
const additions = incoming.filter((m) => m.id && !settledIds.has(m.id));
|
|
2308
|
+
if (!additions.length) return current;
|
|
2309
|
+
let base = current;
|
|
2310
|
+
const optimistic = current.filter((m) => m.id.startsWith(OPTIMISTIC_ID_PREFIX));
|
|
2311
|
+
const placeholder = optimistic[optimistic.length - 1];
|
|
2312
|
+
if (placeholder) {
|
|
2313
|
+
const echoIndex = additions.findIndex(
|
|
2314
|
+
(m) => m.role === "user" && (m.content ?? "") === (placeholder.content ?? "")
|
|
2315
|
+
);
|
|
2316
|
+
if (echoIndex !== -1) {
|
|
2317
|
+
base = current.filter((m) => m.id !== placeholder.id);
|
|
2318
|
+
additions[echoIndex] = {
|
|
2319
|
+
...additions[echoIndex],
|
|
2320
|
+
client_key: placeholder.client_key ?? placeholder.id
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
const merged = [...base, ...additions];
|
|
2325
|
+
merged.sort((a, b) => {
|
|
2326
|
+
const sa = typeof a.seq === "number" ? a.seq : Number.MAX_SAFE_INTEGER;
|
|
2327
|
+
const sb = typeof b.seq === "number" ? b.seq : Number.MAX_SAFE_INTEGER;
|
|
2328
|
+
return sa - sb;
|
|
2329
|
+
});
|
|
2330
|
+
return merged;
|
|
2331
|
+
}
|
|
1899
2332
|
function buildOptimisticUserMessage(text, files) {
|
|
1900
2333
|
optimisticCounter += 1;
|
|
1901
2334
|
const attachments = (files ?? []).map((file, index) => ({
|
|
@@ -1913,17 +2346,25 @@ function buildOptimisticUserMessage(text, files) {
|
|
|
1913
2346
|
};
|
|
1914
2347
|
}
|
|
1915
2348
|
function useAgentChat(config) {
|
|
1916
|
-
const client =
|
|
1917
|
-
const
|
|
1918
|
-
|
|
1919
|
-
const [
|
|
1920
|
-
const [
|
|
1921
|
-
const [
|
|
1922
|
-
const [
|
|
1923
|
-
const [
|
|
1924
|
-
const [
|
|
1925
|
-
const [
|
|
1926
|
-
|
|
2349
|
+
const client = React13.useMemo(() => new AgentApiClient(config), [config]);
|
|
2350
|
+
const configRef = React13.useRef(config);
|
|
2351
|
+
configRef.current = config;
|
|
2352
|
+
const [agentInfo, setAgentInfo] = React13.useState(null);
|
|
2353
|
+
const [conversationId, setConversationId] = React13.useState(null);
|
|
2354
|
+
const [conversationEpoch, setConversationEpoch] = React13.useState(0);
|
|
2355
|
+
const [conversations, setConversations] = React13.useState([]);
|
|
2356
|
+
const [messages, setMessages] = React13.useState([]);
|
|
2357
|
+
const [pendingApprovals, setPendingApprovals] = React13.useState([]);
|
|
2358
|
+
const [sending, setSending] = React13.useState(false);
|
|
2359
|
+
const [resolvingApprovalId, setResolvingApprovalId] = React13.useState(null);
|
|
2360
|
+
const [error, setError] = React13.useState(null);
|
|
2361
|
+
const [currentConversation, setCurrentConversation] = React13.useState(null);
|
|
2362
|
+
const [contextMeter, setContextMeter] = React13.useState(null);
|
|
2363
|
+
const [participants, setParticipants] = React13.useState([]);
|
|
2364
|
+
const [myState, setMyState] = React13.useState(null);
|
|
2365
|
+
const [typingUsers, setTypingUsers] = React13.useState({});
|
|
2366
|
+
const [realtimeStatus, setRealtimeStatus] = React13.useState("idle");
|
|
2367
|
+
React13.useEffect(() => {
|
|
1927
2368
|
let cancelled = false;
|
|
1928
2369
|
client.getConfig().then((info) => {
|
|
1929
2370
|
if (!cancelled) setAgentInfo(info);
|
|
@@ -1933,11 +2374,11 @@ function useAgentChat(config) {
|
|
|
1933
2374
|
cancelled = true;
|
|
1934
2375
|
};
|
|
1935
2376
|
}, [client]);
|
|
1936
|
-
const handleFailure =
|
|
2377
|
+
const handleFailure = React13.useCallback((err) => {
|
|
1937
2378
|
const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
|
|
1938
2379
|
setError(message);
|
|
1939
2380
|
}, []);
|
|
1940
|
-
const applyResponse =
|
|
2381
|
+
const applyResponse = React13.useCallback(
|
|
1941
2382
|
(response) => {
|
|
1942
2383
|
if (response.status === "error" && !response.messages?.length) {
|
|
1943
2384
|
setError(response.message ?? "The assistant could not answer.");
|
|
@@ -1967,7 +2408,7 @@ function useAgentChat(config) {
|
|
|
1967
2408
|
},
|
|
1968
2409
|
[]
|
|
1969
2410
|
);
|
|
1970
|
-
const handleStreamEvent =
|
|
2411
|
+
const handleStreamEvent = React13.useCallback(
|
|
1971
2412
|
(event, ctx) => {
|
|
1972
2413
|
switch (event.type) {
|
|
1973
2414
|
case "conversation":
|
|
@@ -2045,29 +2486,93 @@ function useAgentChat(config) {
|
|
|
2045
2486
|
},
|
|
2046
2487
|
[]
|
|
2047
2488
|
);
|
|
2489
|
+
const applyRealtime = React13.useCallback((event) => {
|
|
2490
|
+
switch (event.type) {
|
|
2491
|
+
case "sync":
|
|
2492
|
+
setMessages((current) => mergeMessages(current, event.messages ?? []));
|
|
2493
|
+
break;
|
|
2494
|
+
case "message":
|
|
2495
|
+
setMessages((current) => mergeMessages(current, [event.message]));
|
|
2496
|
+
break;
|
|
2497
|
+
case "message_edited":
|
|
2498
|
+
setMessages(
|
|
2499
|
+
(current) => current.map(
|
|
2500
|
+
(message) => message.id === event.message.id ? { ...event.message, client_key: message.client_key ?? message.id } : message
|
|
2501
|
+
)
|
|
2502
|
+
);
|
|
2503
|
+
break;
|
|
2504
|
+
case "message_deleted":
|
|
2505
|
+
setMessages(
|
|
2506
|
+
(current) => current.map(
|
|
2507
|
+
(message) => message.id === event.message_id ? { ...message, deleted: true, content: "" } : message
|
|
2508
|
+
)
|
|
2509
|
+
);
|
|
2510
|
+
break;
|
|
2511
|
+
case "typing":
|
|
2512
|
+
setTypingUsers((current) => {
|
|
2513
|
+
const next = { ...current };
|
|
2514
|
+
if (event.is_typing) next[event.user_id] = event.name;
|
|
2515
|
+
else delete next[event.user_id];
|
|
2516
|
+
return next;
|
|
2517
|
+
});
|
|
2518
|
+
break;
|
|
2519
|
+
case "visibility_changed":
|
|
2520
|
+
setCurrentConversation(
|
|
2521
|
+
(current) => current ? { ...current, visibility: event.visibility } : current
|
|
2522
|
+
);
|
|
2523
|
+
break;
|
|
2524
|
+
}
|
|
2525
|
+
}, []);
|
|
2526
|
+
const realtimeEnabled = config.realtime?.enabled !== false;
|
|
2527
|
+
const isPublic = currentConversation?.visibility === "public";
|
|
2528
|
+
const baselineSeq = currentConversation?.last_seq ?? 0;
|
|
2529
|
+
React13.useEffect(() => {
|
|
2530
|
+
if (!realtimeEnabled || !conversationId || !isPublic) {
|
|
2531
|
+
setRealtimeStatus("idle");
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const rt = new AgentRealtimeClient(
|
|
2535
|
+
configRef.current,
|
|
2536
|
+
conversationId,
|
|
2537
|
+
{ onEvent: applyRealtime, onStatus: setRealtimeStatus },
|
|
2538
|
+
baselineSeq
|
|
2539
|
+
);
|
|
2540
|
+
void rt.connect();
|
|
2541
|
+
return () => {
|
|
2542
|
+
rt.close();
|
|
2543
|
+
};
|
|
2544
|
+
}, [applyRealtime, baselineSeq, conversationId, isPublic, realtimeEnabled]);
|
|
2048
2545
|
const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
|
|
2049
|
-
const send =
|
|
2546
|
+
const send = React13.useCallback(
|
|
2050
2547
|
async (text, files) => {
|
|
2051
2548
|
if (sending) return;
|
|
2052
2549
|
if (!text.trim() && !(files && files.length)) return;
|
|
2053
2550
|
setSending(true);
|
|
2054
2551
|
setError(null);
|
|
2055
2552
|
setMessages((current) => [...current, buildOptimisticUserMessage(text, files)]);
|
|
2553
|
+
const idempotencyKey = newIdempotencyKey();
|
|
2056
2554
|
try {
|
|
2057
2555
|
if (streamingEnabled) {
|
|
2058
2556
|
const ctx = { streamingId: null };
|
|
2059
2557
|
let started = false;
|
|
2060
2558
|
try {
|
|
2061
|
-
await client.streamMessage(
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2559
|
+
await client.streamMessage(
|
|
2560
|
+
text,
|
|
2561
|
+
conversationId,
|
|
2562
|
+
files,
|
|
2563
|
+
(event) => {
|
|
2564
|
+
started = true;
|
|
2565
|
+
handleStreamEvent(event, ctx);
|
|
2566
|
+
},
|
|
2567
|
+
void 0,
|
|
2568
|
+
{ idempotencyKey }
|
|
2569
|
+
);
|
|
2065
2570
|
return;
|
|
2066
2571
|
} catch (err) {
|
|
2067
2572
|
if (started) throw err;
|
|
2068
2573
|
}
|
|
2069
2574
|
}
|
|
2070
|
-
const response = await client.sendMessage(text, conversationId, files);
|
|
2575
|
+
const response = await client.sendMessage(text, conversationId, files, { idempotencyKey });
|
|
2071
2576
|
applyResponse(response);
|
|
2072
2577
|
} catch (err) {
|
|
2073
2578
|
handleFailure(err);
|
|
@@ -2077,14 +2582,14 @@ function useAgentChat(config) {
|
|
|
2077
2582
|
},
|
|
2078
2583
|
[applyResponse, client, conversationId, handleFailure, handleStreamEvent, sending, streamingEnabled]
|
|
2079
2584
|
);
|
|
2080
|
-
const transcribe =
|
|
2585
|
+
const transcribe = React13.useCallback(
|
|
2081
2586
|
async (audio) => {
|
|
2082
2587
|
const { text } = await client.transcribe(audio);
|
|
2083
2588
|
return text;
|
|
2084
2589
|
},
|
|
2085
2590
|
[client]
|
|
2086
2591
|
);
|
|
2087
|
-
const resolveApproval =
|
|
2592
|
+
const resolveApproval = React13.useCallback(
|
|
2088
2593
|
async (callId, approve) => {
|
|
2089
2594
|
if (resolvingApprovalId) return;
|
|
2090
2595
|
setResolvingApprovalId(callId);
|
|
@@ -2113,14 +2618,19 @@ function useAgentChat(config) {
|
|
|
2113
2618
|
},
|
|
2114
2619
|
[applyResponse, client, handleFailure, handleStreamEvent, resolvingApprovalId, streamingEnabled]
|
|
2115
2620
|
);
|
|
2116
|
-
const newChat =
|
|
2621
|
+
const newChat = React13.useCallback(() => {
|
|
2117
2622
|
setConversationId(null);
|
|
2118
2623
|
setMessages([]);
|
|
2119
2624
|
setPendingApprovals([]);
|
|
2120
2625
|
setError(null);
|
|
2626
|
+
setCurrentConversation(null);
|
|
2627
|
+
setContextMeter(null);
|
|
2628
|
+
setParticipants([]);
|
|
2629
|
+
setMyState(null);
|
|
2630
|
+
setTypingUsers({});
|
|
2121
2631
|
setConversationEpoch((e) => e + 1);
|
|
2122
2632
|
}, []);
|
|
2123
|
-
const refreshConversations =
|
|
2633
|
+
const refreshConversations = React13.useCallback(async () => {
|
|
2124
2634
|
try {
|
|
2125
2635
|
const { conversations: list } = await client.listConversations();
|
|
2126
2636
|
setConversations(list);
|
|
@@ -2130,7 +2640,7 @@ function useAgentChat(config) {
|
|
|
2130
2640
|
return [];
|
|
2131
2641
|
}
|
|
2132
2642
|
}, [client, handleFailure]);
|
|
2133
|
-
const openConversation =
|
|
2643
|
+
const openConversation = React13.useCallback(
|
|
2134
2644
|
async (id) => {
|
|
2135
2645
|
setError(null);
|
|
2136
2646
|
try {
|
|
@@ -2138,6 +2648,23 @@ function useAgentChat(config) {
|
|
|
2138
2648
|
setConversationId(id);
|
|
2139
2649
|
setMessages(payload.messages ?? []);
|
|
2140
2650
|
setPendingApprovals(payload.pending_approvals ?? []);
|
|
2651
|
+
const convo = payload.conversation;
|
|
2652
|
+
setCurrentConversation(
|
|
2653
|
+
convo ? {
|
|
2654
|
+
id: convo.id,
|
|
2655
|
+
title: convo.title,
|
|
2656
|
+
visibility: convo.visibility,
|
|
2657
|
+
scope_type: convo.scope_type,
|
|
2658
|
+
last_seq: convo.last_seq,
|
|
2659
|
+
is_creator: convo.is_creator,
|
|
2660
|
+
can_toggle_visibility: convo.can_toggle_visibility,
|
|
2661
|
+
can_go_private: convo.can_go_private
|
|
2662
|
+
} : null
|
|
2663
|
+
);
|
|
2664
|
+
setContextMeter(payload.context_meter ?? null);
|
|
2665
|
+
setParticipants(payload.participants ?? []);
|
|
2666
|
+
setMyState(payload.my_state ?? null);
|
|
2667
|
+
setTypingUsers({});
|
|
2141
2668
|
setConversationEpoch((e) => e + 1);
|
|
2142
2669
|
} catch (err) {
|
|
2143
2670
|
handleFailure(err);
|
|
@@ -2145,7 +2672,7 @@ function useAgentChat(config) {
|
|
|
2145
2672
|
},
|
|
2146
2673
|
[client, handleFailure]
|
|
2147
2674
|
);
|
|
2148
|
-
const deleteConversation =
|
|
2675
|
+
const deleteConversation = React13.useCallback(
|
|
2149
2676
|
async (id) => {
|
|
2150
2677
|
try {
|
|
2151
2678
|
await client.deleteConversation(id);
|
|
@@ -2157,25 +2684,91 @@ function useAgentChat(config) {
|
|
|
2157
2684
|
},
|
|
2158
2685
|
[client, conversationId, handleFailure, newChat]
|
|
2159
2686
|
);
|
|
2160
|
-
const
|
|
2687
|
+
const setVisibility = React13.useCallback(
|
|
2688
|
+
async (visibility, opts) => {
|
|
2689
|
+
if (!conversationId) return;
|
|
2690
|
+
setError(null);
|
|
2691
|
+
try {
|
|
2692
|
+
const scope = configRef.current.collaboration?.scope;
|
|
2693
|
+
const res = await client.setVisibility(
|
|
2694
|
+
conversationId,
|
|
2695
|
+
visibility,
|
|
2696
|
+
visibility === "public" ? { scopeType: scope, scopeEventId: opts?.scopeEventId } : void 0
|
|
2697
|
+
);
|
|
2698
|
+
if (res.status === "error") {
|
|
2699
|
+
setError(res.message ?? "Could not change the conversation's visibility.");
|
|
2700
|
+
return;
|
|
2701
|
+
}
|
|
2702
|
+
setCurrentConversation(
|
|
2703
|
+
(current) => current ? { ...current, visibility, can_go_private: Boolean(res.can_go_private) } : current
|
|
2704
|
+
);
|
|
2705
|
+
void client.listParticipants(conversationId).then((p) => setParticipants(p.participants ?? [])).catch(() => {
|
|
2706
|
+
});
|
|
2707
|
+
} catch (err) {
|
|
2708
|
+
handleFailure(err);
|
|
2709
|
+
}
|
|
2710
|
+
},
|
|
2711
|
+
[client, conversationId, handleFailure]
|
|
2712
|
+
);
|
|
2713
|
+
const markRead = React13.useCallback(
|
|
2714
|
+
(seq) => {
|
|
2715
|
+
if (!conversationId || seq <= 0) return;
|
|
2716
|
+
setMyState(
|
|
2717
|
+
(current) => current && seq > current.last_read_seq ? { ...current, last_read_seq: seq } : current
|
|
2718
|
+
);
|
|
2719
|
+
void client.markRead(conversationId, seq).catch(() => {
|
|
2720
|
+
});
|
|
2721
|
+
},
|
|
2722
|
+
[client, conversationId]
|
|
2723
|
+
);
|
|
2724
|
+
const saveDraft = React13.useCallback(
|
|
2725
|
+
(text) => {
|
|
2726
|
+
if (!conversationId) return;
|
|
2727
|
+
void client.saveDraft(conversationId, text).catch(() => {
|
|
2728
|
+
});
|
|
2729
|
+
},
|
|
2730
|
+
[client, conversationId]
|
|
2731
|
+
);
|
|
2732
|
+
const summarize = React13.useCallback(async () => {
|
|
2733
|
+
if (!conversationId) return;
|
|
2734
|
+
setError(null);
|
|
2735
|
+
try {
|
|
2736
|
+
await client.summarize(conversationId);
|
|
2737
|
+
const detail = await client.getConversation(conversationId);
|
|
2738
|
+
setContextMeter(detail.context_meter ?? null);
|
|
2739
|
+
} catch (err) {
|
|
2740
|
+
handleFailure(err);
|
|
2741
|
+
}
|
|
2742
|
+
}, [client, conversationId, handleFailure]);
|
|
2743
|
+
const clearError = React13.useCallback(() => setError(null), []);
|
|
2161
2744
|
return {
|
|
2162
2745
|
agentInfo,
|
|
2163
2746
|
clearError,
|
|
2747
|
+
contextMeter,
|
|
2164
2748
|
conversationId,
|
|
2165
2749
|
conversationEpoch,
|
|
2166
2750
|
conversations,
|
|
2751
|
+
currentConversation,
|
|
2167
2752
|
deleteConversation,
|
|
2168
2753
|
error,
|
|
2754
|
+
markRead,
|
|
2169
2755
|
messages,
|
|
2756
|
+
myState,
|
|
2170
2757
|
newChat,
|
|
2171
2758
|
openConversation,
|
|
2759
|
+
participants,
|
|
2172
2760
|
pendingApprovals,
|
|
2761
|
+
realtimeStatus,
|
|
2173
2762
|
refreshConversations,
|
|
2174
2763
|
resolveApproval,
|
|
2175
2764
|
resolvingApprovalId,
|
|
2765
|
+
saveDraft,
|
|
2176
2766
|
send,
|
|
2177
2767
|
sending,
|
|
2178
|
-
|
|
2768
|
+
setVisibility,
|
|
2769
|
+
summarize,
|
|
2770
|
+
transcribe,
|
|
2771
|
+
typingUsers
|
|
2179
2772
|
};
|
|
2180
2773
|
}
|
|
2181
2774
|
var STORAGE_PREFIX = "cc-agent-always-approve:";
|
|
@@ -2194,11 +2787,11 @@ function readStored(clientId) {
|
|
|
2194
2787
|
}
|
|
2195
2788
|
}
|
|
2196
2789
|
function useAlwaysApprove(clientId) {
|
|
2197
|
-
const [approved, setApproved] =
|
|
2198
|
-
|
|
2790
|
+
const [approved, setApproved] = React13.useState(() => /* @__PURE__ */ new Set());
|
|
2791
|
+
React13.useEffect(() => {
|
|
2199
2792
|
setApproved(readStored(clientId));
|
|
2200
2793
|
}, [clientId]);
|
|
2201
|
-
const persist =
|
|
2794
|
+
const persist = React13.useCallback(
|
|
2202
2795
|
(next) => {
|
|
2203
2796
|
setApproved(next);
|
|
2204
2797
|
if (typeof window === "undefined") return;
|
|
@@ -2209,8 +2802,8 @@ function useAlwaysApprove(clientId) {
|
|
|
2209
2802
|
},
|
|
2210
2803
|
[clientId]
|
|
2211
2804
|
);
|
|
2212
|
-
const isAlwaysApproved =
|
|
2213
|
-
const setAlwaysApprove =
|
|
2805
|
+
const isAlwaysApproved = React13.useCallback((toolName) => approved.has(toolName), [approved]);
|
|
2806
|
+
const setAlwaysApprove = React13.useCallback(
|
|
2214
2807
|
(toolName) => {
|
|
2215
2808
|
if (approved.has(toolName)) return;
|
|
2216
2809
|
const next = new Set(approved);
|
|
@@ -2219,7 +2812,7 @@ function useAlwaysApprove(clientId) {
|
|
|
2219
2812
|
},
|
|
2220
2813
|
[approved, persist]
|
|
2221
2814
|
);
|
|
2222
|
-
const clearAlwaysApprove =
|
|
2815
|
+
const clearAlwaysApprove = React13.useCallback(
|
|
2223
2816
|
(toolName) => {
|
|
2224
2817
|
if (!approved.has(toolName)) return;
|
|
2225
2818
|
const next = new Set(approved);
|
|
@@ -2245,10 +2838,10 @@ function AgentWidget({
|
|
|
2245
2838
|
const isControlled = controlledOpen !== void 0;
|
|
2246
2839
|
const isDocked = presentation === "docked";
|
|
2247
2840
|
const storageKey2 = `cc-agent-open:${config.clientId}`;
|
|
2248
|
-
const [uncontrolledOpen, setUncontrolledOpen] =
|
|
2841
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React13.useState(false);
|
|
2249
2842
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
2250
|
-
const [bootstrapDone, setBootstrapDone] =
|
|
2251
|
-
const persist =
|
|
2843
|
+
const [bootstrapDone, setBootstrapDone] = React13.useState(isControlled);
|
|
2844
|
+
const persist = React13.useCallback(
|
|
2252
2845
|
(next) => {
|
|
2253
2846
|
if (isControlled || !behavior.persistOpenState) return;
|
|
2254
2847
|
try {
|
|
@@ -2258,7 +2851,7 @@ function AgentWidget({
|
|
|
2258
2851
|
},
|
|
2259
2852
|
[behavior.persistOpenState, isControlled, storageKey2]
|
|
2260
2853
|
);
|
|
2261
|
-
const setOpen =
|
|
2854
|
+
const setOpen = React13.useCallback(
|
|
2262
2855
|
(next) => {
|
|
2263
2856
|
setUncontrolledOpen(next);
|
|
2264
2857
|
persist(next);
|
|
@@ -2266,14 +2859,14 @@ function AgentWidget({
|
|
|
2266
2859
|
},
|
|
2267
2860
|
[onOpenChange, persist]
|
|
2268
2861
|
);
|
|
2269
|
-
const [agentMode, setAgentMode] =
|
|
2862
|
+
const [agentMode, setAgentMode] = React13.useState(false);
|
|
2270
2863
|
const isDesktop = useMediaQuery("(min-width: 640px)", { defaultSSRValue: true, ssr: true });
|
|
2271
2864
|
const fullscreen = !isDesktop || agentMode;
|
|
2272
2865
|
const chat = useAgentChat(config);
|
|
2273
2866
|
const alwaysApprove = useAlwaysApprove(config.clientId);
|
|
2274
2867
|
const { messages, pendingApprovals, resolveApproval, resolvingApprovalId, sending } = chat;
|
|
2275
|
-
const bootRef =
|
|
2276
|
-
|
|
2868
|
+
const bootRef = React13.useRef(false);
|
|
2869
|
+
React13.useEffect(() => {
|
|
2277
2870
|
if (bootRef.current) return;
|
|
2278
2871
|
if (isControlled && !isDocked) return;
|
|
2279
2872
|
bootRef.current = true;
|
|
@@ -2305,15 +2898,15 @@ function AgentWidget({
|
|
|
2305
2898
|
cancelled = true;
|
|
2306
2899
|
};
|
|
2307
2900
|
}, []);
|
|
2308
|
-
const seenCountRef =
|
|
2309
|
-
const [baselined, setBaselined] =
|
|
2310
|
-
const [anchorId, setAnchorId] =
|
|
2311
|
-
|
|
2901
|
+
const seenCountRef = React13.useRef(0);
|
|
2902
|
+
const [baselined, setBaselined] = React13.useState(false);
|
|
2903
|
+
const [anchorId, setAnchorId] = React13.useState(null);
|
|
2904
|
+
React13.useEffect(() => {
|
|
2312
2905
|
if (baselined || !bootstrapDone) return;
|
|
2313
2906
|
seenCountRef.current = messages.length;
|
|
2314
2907
|
setBaselined(true);
|
|
2315
2908
|
}, [baselined, bootstrapDone, messages.length]);
|
|
2316
|
-
|
|
2909
|
+
React13.useEffect(() => {
|
|
2317
2910
|
if (open) {
|
|
2318
2911
|
if (messages.length > seenCountRef.current) {
|
|
2319
2912
|
const firstUnread = messages[seenCountRef.current];
|
|
@@ -2323,7 +2916,7 @@ function AgentWidget({
|
|
|
2323
2916
|
}
|
|
2324
2917
|
}, [open, messages]);
|
|
2325
2918
|
const unreadCount = open || !baselined ? 0 : messages.slice(seenCountRef.current).filter(isUnreadable).length;
|
|
2326
|
-
const workLabel =
|
|
2919
|
+
const workLabel = React13.useMemo(() => {
|
|
2327
2920
|
if (!sending) return null;
|
|
2328
2921
|
const last = messages[messages.length - 1];
|
|
2329
2922
|
if (last?.role === "tool") return "Calling tools\u2026";
|
|
@@ -2332,14 +2925,14 @@ function AgentWidget({
|
|
|
2332
2925
|
}, [sending, messages]);
|
|
2333
2926
|
const approvalWaiting = pendingApprovals.length > 0;
|
|
2334
2927
|
const { isAlwaysApproved } = alwaysApprove;
|
|
2335
|
-
|
|
2928
|
+
React13.useEffect(() => {
|
|
2336
2929
|
if (resolvingApprovalId) return;
|
|
2337
2930
|
const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
|
|
2338
2931
|
if (target) void resolveApproval(target.id, true);
|
|
2339
2932
|
}, [pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
|
|
2340
|
-
const firedWriteIds =
|
|
2933
|
+
const firedWriteIds = React13.useRef(/* @__PURE__ */ new Set());
|
|
2341
2934
|
const { onWriteSuccess } = config;
|
|
2342
|
-
|
|
2935
|
+
React13.useEffect(() => {
|
|
2343
2936
|
if (!onWriteSuccess) return;
|
|
2344
2937
|
for (const message of messages) {
|
|
2345
2938
|
if (message.role !== "tool" || !message.is_write || message.tool_status !== "success") continue;
|
|
@@ -2349,9 +2942,9 @@ function AgentWidget({
|
|
|
2349
2942
|
onWriteSuccess({ arguments: args, toolName: message.tool_name ?? "" });
|
|
2350
2943
|
}
|
|
2351
2944
|
}, [messages, onWriteSuccess]);
|
|
2352
|
-
const lastNavigatedMessageId =
|
|
2353
|
-
const navEpochRef =
|
|
2354
|
-
|
|
2945
|
+
const lastNavigatedMessageId = React13.useRef(null);
|
|
2946
|
+
const navEpochRef = React13.useRef(-1);
|
|
2947
|
+
React13.useEffect(() => {
|
|
2355
2948
|
if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
|
|
2356
2949
|
const toolMessages = messages.filter((message) => message.role === "tool");
|
|
2357
2950
|
const latest = toolMessages[toolMessages.length - 1];
|
|
@@ -2370,7 +2963,7 @@ function AgentWidget({
|
|
|
2370
2963
|
const scrollTo = typeof mapping.scrollTo === "function" ? mapping.scrollTo(args) : mapping.scrollTo;
|
|
2371
2964
|
config.navigate(path, { scrollTo: scrollTo ?? null });
|
|
2372
2965
|
}, [chat.conversationEpoch, config, fullscreen, messages, open]);
|
|
2373
|
-
const closePanel =
|
|
2966
|
+
const closePanel = React13.useCallback(() => {
|
|
2374
2967
|
setOpen(false);
|
|
2375
2968
|
setAgentMode(false);
|
|
2376
2969
|
}, [setOpen]);
|
|
@@ -2543,6 +3136,6 @@ function defineAgentConfig(config) {
|
|
|
2543
3136
|
return config;
|
|
2544
3137
|
}
|
|
2545
3138
|
|
|
2546
|
-
export { AgentApiClient, AgentApiError, AgentComposer, AgentImageLightbox, AgentPanel, AgentVoiceRecorder, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, IMAGE_ACCEPT, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, attachmentsEnabled, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, isImageFile, resolveToolPresentation, textExtensions, useAgentChat, useAlwaysApprove, useSmoothText, validateFile };
|
|
3139
|
+
export { AgentApiClient, AgentApiError, AgentComposer, AgentImageLightbox, AgentPanel, AgentRealtimeClient, AgentVisibilityControl, AgentVoiceRecorder, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, IMAGE_ACCEPT, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, attachmentsEnabled, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, isImageFile, resolveToolPresentation, textExtensions, useAgentChat, useAlwaysApprove, useSmoothText, validateFile };
|
|
2547
3140
|
//# sourceMappingURL=index.js.map
|
|
2548
3141
|
//# sourceMappingURL=index.js.map
|