@cntyclub/agent-react 0.17.0 → 0.18.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/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +409 -14
- 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 +183 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-agent-chat.d.ts +24 -1
- package/dist/use-agent-chat.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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';
|
|
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, ContextMeter, Chat, ChatEmptyState, ChatEmptyStateIcon, ChatEmptyStateTitle, ChatEmptyStateDescription, ChatSuggestions, ChatSuggestion, useMediaQuery, TooltipProvider, DataTablePaged } from '@cntyclub/ui-react';
|
|
2
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
3
|
import * as React12 from 'react';
|
|
4
4
|
import { AnimatePresence, motion } from 'motion/react';
|
|
@@ -6,6 +6,22 @@ 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
|
|
@@ -1558,6 +1783,12 @@ function ToolApprovalCard({
|
|
|
1558
1783
|
] })
|
|
1559
1784
|
] });
|
|
1560
1785
|
}
|
|
1786
|
+
function contextMeterLabel(meter) {
|
|
1787
|
+
const pct = Math.round(meter.pct * 100);
|
|
1788
|
+
if (meter.state === "full") return `Context full (${pct}%) \u2014 start a new chat to continue`;
|
|
1789
|
+
if (meter.state === "summarizing") return `Context ${pct}% \u2014 summarizing older messages`;
|
|
1790
|
+
return `Context ${pct}% used`;
|
|
1791
|
+
}
|
|
1561
1792
|
function AgentPanel({
|
|
1562
1793
|
agentMode,
|
|
1563
1794
|
canToggleAgentMode,
|
|
@@ -1675,6 +1906,33 @@ function AgentPanel({
|
|
|
1675
1906
|
] })
|
|
1676
1907
|
] }),
|
|
1677
1908
|
/* @__PURE__ */ jsxs(ChatHeaderActions, { children: [
|
|
1909
|
+
chat.contextMeter && chat.contextMeter.pct > 0 ? /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1910
|
+
/* @__PURE__ */ jsx(
|
|
1911
|
+
TooltipTrigger,
|
|
1912
|
+
{
|
|
1913
|
+
render: /* @__PURE__ */ jsx(
|
|
1914
|
+
"button",
|
|
1915
|
+
{
|
|
1916
|
+
"aria-label": contextMeterLabel(chat.contextMeter),
|
|
1917
|
+
className: "mr-0.5 inline-flex cursor-default items-center rounded-full outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1918
|
+
type: "button",
|
|
1919
|
+
children: /* @__PURE__ */ jsx(
|
|
1920
|
+
ContextMeter,
|
|
1921
|
+
{
|
|
1922
|
+
lockAt: chat.contextMeter.lock_at,
|
|
1923
|
+
pct: chat.contextMeter.pct,
|
|
1924
|
+
size: 18,
|
|
1925
|
+
state: chat.contextMeter.state,
|
|
1926
|
+
summarizeAt: chat.contextMeter.summarize_at,
|
|
1927
|
+
warnAt: chat.contextMeter.warn_at
|
|
1928
|
+
}
|
|
1929
|
+
)
|
|
1930
|
+
}
|
|
1931
|
+
)
|
|
1932
|
+
}
|
|
1933
|
+
),
|
|
1934
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: contextMeterLabel(chat.contextMeter) })
|
|
1935
|
+
] }) : null,
|
|
1678
1936
|
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
1679
1937
|
/* @__PURE__ */ jsx(
|
|
1680
1938
|
TooltipTrigger,
|
|
@@ -1896,6 +2154,43 @@ var OPTIMISTIC_ID_PREFIX = "optimistic-";
|
|
|
1896
2154
|
var STREAMING_ID_PREFIX = "streaming-";
|
|
1897
2155
|
var optimisticCounter = 0;
|
|
1898
2156
|
var streamingCounter = 0;
|
|
2157
|
+
function newIdempotencyKey() {
|
|
2158
|
+
try {
|
|
2159
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return crypto.randomUUID();
|
|
2160
|
+
} catch {
|
|
2161
|
+
}
|
|
2162
|
+
return `idem-${Date.now()}-${Math.floor(Math.random() * 1e9)}`;
|
|
2163
|
+
}
|
|
2164
|
+
function mergeMessages(current, incoming) {
|
|
2165
|
+
if (!incoming?.length) return current;
|
|
2166
|
+
const settledIds = new Set(
|
|
2167
|
+
current.filter((m) => !m.id.startsWith(OPTIMISTIC_ID_PREFIX)).map((m) => m.id)
|
|
2168
|
+
);
|
|
2169
|
+
const additions = incoming.filter((m) => m.id && !settledIds.has(m.id));
|
|
2170
|
+
if (!additions.length) return current;
|
|
2171
|
+
let base = current;
|
|
2172
|
+
const optimistic = current.filter((m) => m.id.startsWith(OPTIMISTIC_ID_PREFIX));
|
|
2173
|
+
const placeholder = optimistic[optimistic.length - 1];
|
|
2174
|
+
if (placeholder) {
|
|
2175
|
+
const echoIndex = additions.findIndex(
|
|
2176
|
+
(m) => m.role === "user" && (m.content ?? "") === (placeholder.content ?? "")
|
|
2177
|
+
);
|
|
2178
|
+
if (echoIndex !== -1) {
|
|
2179
|
+
base = current.filter((m) => m.id !== placeholder.id);
|
|
2180
|
+
additions[echoIndex] = {
|
|
2181
|
+
...additions[echoIndex],
|
|
2182
|
+
client_key: placeholder.client_key ?? placeholder.id
|
|
2183
|
+
};
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
const merged = [...base, ...additions];
|
|
2187
|
+
merged.sort((a, b) => {
|
|
2188
|
+
const sa = typeof a.seq === "number" ? a.seq : Number.MAX_SAFE_INTEGER;
|
|
2189
|
+
const sb = typeof b.seq === "number" ? b.seq : Number.MAX_SAFE_INTEGER;
|
|
2190
|
+
return sa - sb;
|
|
2191
|
+
});
|
|
2192
|
+
return merged;
|
|
2193
|
+
}
|
|
1899
2194
|
function buildOptimisticUserMessage(text, files) {
|
|
1900
2195
|
optimisticCounter += 1;
|
|
1901
2196
|
const attachments = (files ?? []).map((file, index) => ({
|
|
@@ -1914,6 +2209,8 @@ function buildOptimisticUserMessage(text, files) {
|
|
|
1914
2209
|
}
|
|
1915
2210
|
function useAgentChat(config) {
|
|
1916
2211
|
const client = React12.useMemo(() => new AgentApiClient(config), [config]);
|
|
2212
|
+
const configRef = React12.useRef(config);
|
|
2213
|
+
configRef.current = config;
|
|
1917
2214
|
const [agentInfo, setAgentInfo] = React12.useState(null);
|
|
1918
2215
|
const [conversationId, setConversationId] = React12.useState(null);
|
|
1919
2216
|
const [conversationEpoch, setConversationEpoch] = React12.useState(0);
|
|
@@ -1923,6 +2220,12 @@ function useAgentChat(config) {
|
|
|
1923
2220
|
const [sending, setSending] = React12.useState(false);
|
|
1924
2221
|
const [resolvingApprovalId, setResolvingApprovalId] = React12.useState(null);
|
|
1925
2222
|
const [error, setError] = React12.useState(null);
|
|
2223
|
+
const [currentConversation, setCurrentConversation] = React12.useState(null);
|
|
2224
|
+
const [contextMeter, setContextMeter] = React12.useState(null);
|
|
2225
|
+
const [participants, setParticipants] = React12.useState([]);
|
|
2226
|
+
const [myState, setMyState] = React12.useState(null);
|
|
2227
|
+
const [typingUsers, setTypingUsers] = React12.useState({});
|
|
2228
|
+
const [realtimeStatus, setRealtimeStatus] = React12.useState("idle");
|
|
1926
2229
|
React12.useEffect(() => {
|
|
1927
2230
|
let cancelled = false;
|
|
1928
2231
|
client.getConfig().then((info) => {
|
|
@@ -2045,6 +2348,62 @@ function useAgentChat(config) {
|
|
|
2045
2348
|
},
|
|
2046
2349
|
[]
|
|
2047
2350
|
);
|
|
2351
|
+
const applyRealtime = React12.useCallback((event) => {
|
|
2352
|
+
switch (event.type) {
|
|
2353
|
+
case "sync":
|
|
2354
|
+
setMessages((current) => mergeMessages(current, event.messages ?? []));
|
|
2355
|
+
break;
|
|
2356
|
+
case "message":
|
|
2357
|
+
setMessages((current) => mergeMessages(current, [event.message]));
|
|
2358
|
+
break;
|
|
2359
|
+
case "message_edited":
|
|
2360
|
+
setMessages(
|
|
2361
|
+
(current) => current.map(
|
|
2362
|
+
(message) => message.id === event.message.id ? { ...event.message, client_key: message.client_key ?? message.id } : message
|
|
2363
|
+
)
|
|
2364
|
+
);
|
|
2365
|
+
break;
|
|
2366
|
+
case "message_deleted":
|
|
2367
|
+
setMessages(
|
|
2368
|
+
(current) => current.map(
|
|
2369
|
+
(message) => message.id === event.message_id ? { ...message, deleted: true, content: "" } : message
|
|
2370
|
+
)
|
|
2371
|
+
);
|
|
2372
|
+
break;
|
|
2373
|
+
case "typing":
|
|
2374
|
+
setTypingUsers((current) => {
|
|
2375
|
+
const next = { ...current };
|
|
2376
|
+
if (event.is_typing) next[event.user_id] = event.name;
|
|
2377
|
+
else delete next[event.user_id];
|
|
2378
|
+
return next;
|
|
2379
|
+
});
|
|
2380
|
+
break;
|
|
2381
|
+
case "visibility_changed":
|
|
2382
|
+
setCurrentConversation(
|
|
2383
|
+
(current) => current ? { ...current, visibility: event.visibility } : current
|
|
2384
|
+
);
|
|
2385
|
+
break;
|
|
2386
|
+
}
|
|
2387
|
+
}, []);
|
|
2388
|
+
const realtimeEnabled = config.realtime?.enabled !== false;
|
|
2389
|
+
const isPublic = currentConversation?.visibility === "public";
|
|
2390
|
+
const baselineSeq = currentConversation?.last_seq ?? 0;
|
|
2391
|
+
React12.useEffect(() => {
|
|
2392
|
+
if (!realtimeEnabled || !conversationId || !isPublic) {
|
|
2393
|
+
setRealtimeStatus("idle");
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2396
|
+
const rt = new AgentRealtimeClient(
|
|
2397
|
+
configRef.current,
|
|
2398
|
+
conversationId,
|
|
2399
|
+
{ onEvent: applyRealtime, onStatus: setRealtimeStatus },
|
|
2400
|
+
baselineSeq
|
|
2401
|
+
);
|
|
2402
|
+
void rt.connect();
|
|
2403
|
+
return () => {
|
|
2404
|
+
rt.close();
|
|
2405
|
+
};
|
|
2406
|
+
}, [applyRealtime, baselineSeq, conversationId, isPublic, realtimeEnabled]);
|
|
2048
2407
|
const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
|
|
2049
2408
|
const send = React12.useCallback(
|
|
2050
2409
|
async (text, files) => {
|
|
@@ -2053,21 +2412,29 @@ function useAgentChat(config) {
|
|
|
2053
2412
|
setSending(true);
|
|
2054
2413
|
setError(null);
|
|
2055
2414
|
setMessages((current) => [...current, buildOptimisticUserMessage(text, files)]);
|
|
2415
|
+
const idempotencyKey = newIdempotencyKey();
|
|
2056
2416
|
try {
|
|
2057
2417
|
if (streamingEnabled) {
|
|
2058
2418
|
const ctx = { streamingId: null };
|
|
2059
2419
|
let started = false;
|
|
2060
2420
|
try {
|
|
2061
|
-
await client.streamMessage(
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2421
|
+
await client.streamMessage(
|
|
2422
|
+
text,
|
|
2423
|
+
conversationId,
|
|
2424
|
+
files,
|
|
2425
|
+
(event) => {
|
|
2426
|
+
started = true;
|
|
2427
|
+
handleStreamEvent(event, ctx);
|
|
2428
|
+
},
|
|
2429
|
+
void 0,
|
|
2430
|
+
{ idempotencyKey }
|
|
2431
|
+
);
|
|
2065
2432
|
return;
|
|
2066
2433
|
} catch (err) {
|
|
2067
2434
|
if (started) throw err;
|
|
2068
2435
|
}
|
|
2069
2436
|
}
|
|
2070
|
-
const response = await client.sendMessage(text, conversationId, files);
|
|
2437
|
+
const response = await client.sendMessage(text, conversationId, files, { idempotencyKey });
|
|
2071
2438
|
applyResponse(response);
|
|
2072
2439
|
} catch (err) {
|
|
2073
2440
|
handleFailure(err);
|
|
@@ -2118,6 +2485,11 @@ function useAgentChat(config) {
|
|
|
2118
2485
|
setMessages([]);
|
|
2119
2486
|
setPendingApprovals([]);
|
|
2120
2487
|
setError(null);
|
|
2488
|
+
setCurrentConversation(null);
|
|
2489
|
+
setContextMeter(null);
|
|
2490
|
+
setParticipants([]);
|
|
2491
|
+
setMyState(null);
|
|
2492
|
+
setTypingUsers({});
|
|
2121
2493
|
setConversationEpoch((e) => e + 1);
|
|
2122
2494
|
}, []);
|
|
2123
2495
|
const refreshConversations = React12.useCallback(async () => {
|
|
@@ -2138,6 +2510,23 @@ function useAgentChat(config) {
|
|
|
2138
2510
|
setConversationId(id);
|
|
2139
2511
|
setMessages(payload.messages ?? []);
|
|
2140
2512
|
setPendingApprovals(payload.pending_approvals ?? []);
|
|
2513
|
+
const convo = payload.conversation;
|
|
2514
|
+
setCurrentConversation(
|
|
2515
|
+
convo ? {
|
|
2516
|
+
id: convo.id,
|
|
2517
|
+
title: convo.title,
|
|
2518
|
+
visibility: convo.visibility,
|
|
2519
|
+
scope_type: convo.scope_type,
|
|
2520
|
+
last_seq: convo.last_seq,
|
|
2521
|
+
is_creator: convo.is_creator,
|
|
2522
|
+
can_toggle_visibility: convo.can_toggle_visibility,
|
|
2523
|
+
can_go_private: convo.can_go_private
|
|
2524
|
+
} : null
|
|
2525
|
+
);
|
|
2526
|
+
setContextMeter(payload.context_meter ?? null);
|
|
2527
|
+
setParticipants(payload.participants ?? []);
|
|
2528
|
+
setMyState(payload.my_state ?? null);
|
|
2529
|
+
setTypingUsers({});
|
|
2141
2530
|
setConversationEpoch((e) => e + 1);
|
|
2142
2531
|
} catch (err) {
|
|
2143
2532
|
handleFailure(err);
|
|
@@ -2161,21 +2550,27 @@ function useAgentChat(config) {
|
|
|
2161
2550
|
return {
|
|
2162
2551
|
agentInfo,
|
|
2163
2552
|
clearError,
|
|
2553
|
+
contextMeter,
|
|
2164
2554
|
conversationId,
|
|
2165
2555
|
conversationEpoch,
|
|
2166
2556
|
conversations,
|
|
2557
|
+
currentConversation,
|
|
2167
2558
|
deleteConversation,
|
|
2168
2559
|
error,
|
|
2169
2560
|
messages,
|
|
2561
|
+
myState,
|
|
2170
2562
|
newChat,
|
|
2171
2563
|
openConversation,
|
|
2564
|
+
participants,
|
|
2172
2565
|
pendingApprovals,
|
|
2566
|
+
realtimeStatus,
|
|
2173
2567
|
refreshConversations,
|
|
2174
2568
|
resolveApproval,
|
|
2175
2569
|
resolvingApprovalId,
|
|
2176
2570
|
send,
|
|
2177
2571
|
sending,
|
|
2178
|
-
transcribe
|
|
2572
|
+
transcribe,
|
|
2573
|
+
typingUsers
|
|
2179
2574
|
};
|
|
2180
2575
|
}
|
|
2181
2576
|
var STORAGE_PREFIX = "cc-agent-always-approve:";
|
|
@@ -2543,6 +2938,6 @@ function defineAgentConfig(config) {
|
|
|
2543
2938
|
return config;
|
|
2544
2939
|
}
|
|
2545
2940
|
|
|
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 };
|
|
2941
|
+
export { AgentApiClient, AgentApiError, AgentComposer, AgentImageLightbox, AgentPanel, AgentRealtimeClient, 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
2942
|
//# sourceMappingURL=index.js.map
|
|
2548
2943
|
//# sourceMappingURL=index.js.map
|