@cntyclub/agent-react 0.9.1 → 0.10.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/components/agent-panel.d.ts +3 -1
- package/dist/components/agent-panel.d.ts.map +1 -1
- package/dist/components/agent-widget.d.ts +8 -8
- package/dist/components/agent-widget.d.ts.map +1 -1
- package/dist/components/copy-button.d.ts +9 -0
- package/dist/components/copy-button.d.ts.map +1 -0
- package/dist/components/message-item.d.ts.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +337 -122
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-agent-chat.d.ts +1 -1
- package/dist/use-agent-chat.d.ts.map +1 -1
- package/package.json +28 -17
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Button, cn, ChatMessage, ChatMessageBubble, Markdown, Spinner, Checkbox, Collapsible, CollapsibleTrigger, ChatToolChip, CollapsiblePanel, ChatToolCard, ChatToolCardHeader, ChatToolCardActions, Group, Menu, MenuTrigger, MenuPopup, MenuItem, ChatHeader, ChatHeaderAvatar, ChatHeaderTitle, ChatHeaderActions,
|
|
2
|
-
import { FileTextIcon, XIcon, PaperclipIcon, ArrowUpIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon,
|
|
3
|
-
import * as
|
|
1
|
+
import { Button, cn, Tooltip, TooltipTrigger, TooltipContent, ChatMessage, ChatMessageBubble, Markdown, 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 { FileTextIcon, XIcon, PaperclipIcon, ArrowUpIcon, CheckIcon, CopyIcon, Maximize2Icon, ListChecksIcon, ChevronDownIcon, ShieldAlertIcon, CheckCheckIcon, UploadCloudIcon, HistoryIcon, SquarePenIcon, Minimize2Icon, Trash2Icon, SparklesIcon, DownloadIcon, CodeIcon } from 'lucide-react';
|
|
3
|
+
import * as React8 from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import { motion, AnimatePresence } from 'motion/react';
|
|
5
6
|
|
|
6
7
|
// src/api-client.ts
|
|
7
8
|
var AgentApiError = class extends Error {
|
|
@@ -230,18 +231,18 @@ function AgentComposer({
|
|
|
230
231
|
placeholder = "Ask anything\u2026",
|
|
231
232
|
value
|
|
232
233
|
}) {
|
|
233
|
-
const textareaRef =
|
|
234
|
-
const inputRef =
|
|
234
|
+
const textareaRef = React8.useRef(null);
|
|
235
|
+
const inputRef = React8.useRef(null);
|
|
235
236
|
const canSubmit = !disabled && !busy && (value.trim().length > 0 || files.length > 0);
|
|
236
|
-
const submit =
|
|
237
|
+
const submit = React8.useCallback(() => {
|
|
237
238
|
if (!canSubmit) return;
|
|
238
239
|
onSubmit(value.trim());
|
|
239
240
|
textareaRef.current?.focus();
|
|
240
241
|
}, [canSubmit, onSubmit, value]);
|
|
241
|
-
|
|
242
|
+
React8.useEffect(() => {
|
|
242
243
|
if (!disabled) textareaRef.current?.focus();
|
|
243
244
|
}, [disabled]);
|
|
244
|
-
|
|
245
|
+
React8.useEffect(() => {
|
|
245
246
|
const textarea = textareaRef.current;
|
|
246
247
|
if (!textarea) return;
|
|
247
248
|
textarea.style.height = "auto";
|
|
@@ -423,15 +424,15 @@ function groupMessages(messages) {
|
|
|
423
424
|
return items;
|
|
424
425
|
}
|
|
425
426
|
function ChatScroller({ children, className, ...props }) {
|
|
426
|
-
const viewportRef =
|
|
427
|
-
const pinnedRef =
|
|
428
|
-
const handleScroll =
|
|
427
|
+
const viewportRef = React8.useRef(null);
|
|
428
|
+
const pinnedRef = React8.useRef(true);
|
|
429
|
+
const handleScroll = React8.useCallback(() => {
|
|
429
430
|
const viewport = viewportRef.current;
|
|
430
431
|
if (!viewport) return;
|
|
431
432
|
const distanceFromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
|
|
432
433
|
pinnedRef.current = distanceFromBottom < 64;
|
|
433
434
|
}, []);
|
|
434
|
-
|
|
435
|
+
React8.useEffect(() => {
|
|
435
436
|
const viewport = viewportRef.current;
|
|
436
437
|
if (!viewport || !pinnedRef.current) return;
|
|
437
438
|
viewport.scrollTop = viewport.scrollHeight;
|
|
@@ -448,6 +449,45 @@ function ChatScroller({ children, className, ...props }) {
|
|
|
448
449
|
}
|
|
449
450
|
);
|
|
450
451
|
}
|
|
452
|
+
function CopyButton({ className, text }) {
|
|
453
|
+
const [copied, setCopied] = React8.useState(false);
|
|
454
|
+
const timerRef = React8.useRef(null);
|
|
455
|
+
React8.useEffect(() => {
|
|
456
|
+
return () => {
|
|
457
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
458
|
+
};
|
|
459
|
+
}, []);
|
|
460
|
+
const copy = React8.useCallback(async () => {
|
|
461
|
+
try {
|
|
462
|
+
await navigator.clipboard.writeText(text);
|
|
463
|
+
} catch {
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
setCopied(true);
|
|
467
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
468
|
+
timerRef.current = setTimeout(() => setCopied(false), 1500);
|
|
469
|
+
}, [text]);
|
|
470
|
+
return /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
471
|
+
/* @__PURE__ */ jsx(
|
|
472
|
+
TooltipTrigger,
|
|
473
|
+
{
|
|
474
|
+
render: /* @__PURE__ */ jsx(
|
|
475
|
+
Button,
|
|
476
|
+
{
|
|
477
|
+
"aria-label": copied ? "Copied" : "Copy message",
|
|
478
|
+
className: cn("text-muted-foreground", className),
|
|
479
|
+
onClick: copy,
|
|
480
|
+
size: "icon-xs",
|
|
481
|
+
type: "button",
|
|
482
|
+
variant: "ghost",
|
|
483
|
+
children: copied ? /* @__PURE__ */ jsx(CheckIcon, { className: "text-success" }) : /* @__PURE__ */ jsx(CopyIcon, {})
|
|
484
|
+
}
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
),
|
|
488
|
+
/* @__PURE__ */ jsx(TooltipContent, { children: copied ? "Copied!" : "Copy" })
|
|
489
|
+
] });
|
|
490
|
+
}
|
|
451
491
|
function MessageItem({ message }) {
|
|
452
492
|
if (message.role === "user") {
|
|
453
493
|
const attachments = message.attachments ?? [];
|
|
@@ -468,7 +508,19 @@ function MessageItem({ message }) {
|
|
|
468
508
|
}
|
|
469
509
|
if (message.role === "assistant") {
|
|
470
510
|
if (!message.content || !message.content.trim()) return null;
|
|
471
|
-
return /* @__PURE__ */
|
|
511
|
+
return /* @__PURE__ */ jsxs(ChatMessage, { className: "group", from: "assistant", children: [
|
|
512
|
+
/* @__PURE__ */ jsx(ChatMessageBubble, { from: "assistant", children: /* @__PURE__ */ jsx(Markdown, { children: message.content }) }),
|
|
513
|
+
/* @__PURE__ */ jsx(
|
|
514
|
+
"div",
|
|
515
|
+
{
|
|
516
|
+
className: cn(
|
|
517
|
+
"mt-0.5 flex opacity-0 transition-opacity",
|
|
518
|
+
"group-hover:opacity-100 focus-within:opacity-100"
|
|
519
|
+
),
|
|
520
|
+
children: /* @__PURE__ */ jsx(CopyButton, { text: message.content })
|
|
521
|
+
}
|
|
522
|
+
)
|
|
523
|
+
] });
|
|
472
524
|
}
|
|
473
525
|
return null;
|
|
474
526
|
}
|
|
@@ -482,8 +534,8 @@ var DEFAULT_PHRASES = [
|
|
|
482
534
|
];
|
|
483
535
|
function ThinkingIndicator({ className, intervalMs = 1800, phrases }) {
|
|
484
536
|
const words = phrases && phrases.length ? phrases : DEFAULT_PHRASES;
|
|
485
|
-
const [index, setIndex] =
|
|
486
|
-
|
|
537
|
+
const [index, setIndex] = React8.useState(0);
|
|
538
|
+
React8.useEffect(() => {
|
|
487
539
|
setIndex(0);
|
|
488
540
|
const timer = setInterval(() => {
|
|
489
541
|
setIndex((current) => (current + 1) % words.length);
|
|
@@ -745,7 +797,7 @@ function DownloadButton({ block, size = "sm" }) {
|
|
|
745
797
|
);
|
|
746
798
|
}
|
|
747
799
|
function DocumentViewer({ block, onClose }) {
|
|
748
|
-
|
|
800
|
+
React8.useEffect(() => {
|
|
749
801
|
const onKey = (event) => event.key === "Escape" && onClose();
|
|
750
802
|
window.addEventListener("keydown", onKey);
|
|
751
803
|
return () => window.removeEventListener("keydown", onKey);
|
|
@@ -770,7 +822,7 @@ function DocumentViewer({ block, onClose }) {
|
|
|
770
822
|
] });
|
|
771
823
|
}
|
|
772
824
|
function DocumentCard({ block }) {
|
|
773
|
-
const [open, setOpen] =
|
|
825
|
+
const [open, setOpen] = React8.useState(false);
|
|
774
826
|
const previewLines = (block.preview || "").split("\n").slice(0, 6).join("\n");
|
|
775
827
|
return /* @__PURE__ */ jsxs("div", { className: "w-full max-w-full overflow-hidden rounded-xl border border-border bg-card shadow-xs", children: [
|
|
776
828
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 border-border/72 border-b px-3 py-2", children: [
|
|
@@ -892,7 +944,7 @@ function StepDetail({
|
|
|
892
944
|
] });
|
|
893
945
|
}
|
|
894
946
|
function ToolActivityGroup({ describeToolCall, steps }) {
|
|
895
|
-
const [open, setOpen] =
|
|
947
|
+
const [open, setOpen] = React8.useState(false);
|
|
896
948
|
if (steps.length === 0) return null;
|
|
897
949
|
const last = steps[steps.length - 1];
|
|
898
950
|
const anyRunning = steps.some((step) => step.status === "running");
|
|
@@ -991,23 +1043,24 @@ function AgentPanel({
|
|
|
991
1043
|
chat,
|
|
992
1044
|
className,
|
|
993
1045
|
config,
|
|
1046
|
+
newMessageAnchorId,
|
|
994
1047
|
onAlwaysApprove,
|
|
995
1048
|
onClose,
|
|
996
1049
|
onToggleAgentMode
|
|
997
1050
|
}) {
|
|
998
|
-
const [draft, setDraft] =
|
|
999
|
-
const [view, setView] =
|
|
1000
|
-
const [files, setFiles] =
|
|
1001
|
-
const [uploadError, setUploadError] =
|
|
1002
|
-
const [dragging, setDragging] =
|
|
1003
|
-
const dragDepth =
|
|
1051
|
+
const [draft, setDraft] = React8.useState("");
|
|
1052
|
+
const [view, setView] = React8.useState("chat");
|
|
1053
|
+
const [files, setFiles] = React8.useState([]);
|
|
1054
|
+
const [uploadError, setUploadError] = React8.useState(null);
|
|
1055
|
+
const [dragging, setDragging] = React8.useState(false);
|
|
1056
|
+
const dragDepth = React8.useRef(0);
|
|
1004
1057
|
const agentName = config.agentName ?? chat.agentInfo?.name ?? "Assistant";
|
|
1005
1058
|
const uploadEnabled = fileUploadEnabled(config);
|
|
1006
1059
|
const suggestions = config.suggestions ?? [
|
|
1007
1060
|
"What can you help me with?",
|
|
1008
1061
|
"Give me a quick summary of my data."
|
|
1009
1062
|
];
|
|
1010
|
-
const addFiles =
|
|
1063
|
+
const addFiles = React8.useCallback(
|
|
1011
1064
|
(incoming) => {
|
|
1012
1065
|
const accepted = [];
|
|
1013
1066
|
let rejection = null;
|
|
@@ -1024,10 +1077,10 @@ function AgentPanel({
|
|
|
1024
1077
|
},
|
|
1025
1078
|
[config]
|
|
1026
1079
|
);
|
|
1027
|
-
const removeFile =
|
|
1080
|
+
const removeFile = React8.useCallback((index) => {
|
|
1028
1081
|
setFiles((current) => current.filter((_, i) => i !== index));
|
|
1029
1082
|
}, []);
|
|
1030
|
-
const handleSubmit =
|
|
1083
|
+
const handleSubmit = React8.useCallback(
|
|
1031
1084
|
(text) => {
|
|
1032
1085
|
const staged = files;
|
|
1033
1086
|
setDraft("");
|
|
@@ -1037,7 +1090,7 @@ function AgentPanel({
|
|
|
1037
1090
|
},
|
|
1038
1091
|
[chat, files]
|
|
1039
1092
|
);
|
|
1040
|
-
const onDragEnter =
|
|
1093
|
+
const onDragEnter = React8.useCallback(
|
|
1041
1094
|
(event) => {
|
|
1042
1095
|
if (!uploadEnabled || !event.dataTransfer?.types?.includes("Files")) return;
|
|
1043
1096
|
dragDepth.current += 1;
|
|
@@ -1045,12 +1098,12 @@ function AgentPanel({
|
|
|
1045
1098
|
},
|
|
1046
1099
|
[uploadEnabled]
|
|
1047
1100
|
);
|
|
1048
|
-
const onDragLeave =
|
|
1101
|
+
const onDragLeave = React8.useCallback((event) => {
|
|
1049
1102
|
if (!event.dataTransfer?.types?.includes("Files")) return;
|
|
1050
1103
|
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
1051
1104
|
if (dragDepth.current === 0) setDragging(false);
|
|
1052
1105
|
}, []);
|
|
1053
|
-
const onDrop =
|
|
1106
|
+
const onDrop = React8.useCallback(
|
|
1054
1107
|
(event) => {
|
|
1055
1108
|
if (!uploadEnabled) return;
|
|
1056
1109
|
event.preventDefault();
|
|
@@ -1061,7 +1114,7 @@ function AgentPanel({
|
|
|
1061
1114
|
},
|
|
1062
1115
|
[addFiles, uploadEnabled]
|
|
1063
1116
|
);
|
|
1064
|
-
const openHistory =
|
|
1117
|
+
const openHistory = React8.useCallback(() => {
|
|
1065
1118
|
setView("history");
|
|
1066
1119
|
void chat.refreshConversations();
|
|
1067
1120
|
}, [chat]);
|
|
@@ -1211,9 +1264,30 @@ function AgentPanel({
|
|
|
1211
1264
|
suggestion
|
|
1212
1265
|
)) })
|
|
1213
1266
|
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1267
|
+
(() => {
|
|
1268
|
+
const grouped = groupMessages(chat.messages);
|
|
1269
|
+
const anchorKey = newMessageAnchorId ? grouped.find(
|
|
1270
|
+
(it) => it.kind !== "tool-activity" && it.message.id === newMessageAnchorId
|
|
1271
|
+
)?.key : void 0;
|
|
1272
|
+
return grouped.map((item) => /* @__PURE__ */ jsxs(React8.Fragment, { children: [
|
|
1273
|
+
item.key === anchorKey ? /* @__PURE__ */ jsx(NewMessagesDivider, {}) : null,
|
|
1274
|
+
/* @__PURE__ */ jsx(
|
|
1275
|
+
motion.div,
|
|
1276
|
+
{
|
|
1277
|
+
initial: { opacity: 0, y: 8 },
|
|
1278
|
+
animate: { opacity: 1, y: 0 },
|
|
1279
|
+
transition: { duration: 0.18, ease: "easeOut" },
|
|
1280
|
+
children: item.kind === "tool-activity" ? /* @__PURE__ */ jsx(ChatMessage, { from: "assistant", children: /* @__PURE__ */ jsx(
|
|
1281
|
+
ToolActivityGroup,
|
|
1282
|
+
{
|
|
1283
|
+
describeToolCall: config.describeToolCall,
|
|
1284
|
+
steps: item.steps
|
|
1285
|
+
}
|
|
1286
|
+
) }) : /* @__PURE__ */ jsx(MessageItem, { message: item.message })
|
|
1287
|
+
}
|
|
1288
|
+
)
|
|
1289
|
+
] }, item.key));
|
|
1290
|
+
})(),
|
|
1217
1291
|
chat.pendingApprovals.map((approval) => /* @__PURE__ */ jsx(
|
|
1218
1292
|
ToolApprovalCard,
|
|
1219
1293
|
{
|
|
@@ -1263,6 +1337,13 @@ function AgentPanel({
|
|
|
1263
1337
|
}
|
|
1264
1338
|
);
|
|
1265
1339
|
}
|
|
1340
|
+
function NewMessagesDivider() {
|
|
1341
|
+
return /* @__PURE__ */ jsxs("div", { className: "my-1 flex items-center gap-2", "aria-label": "New messages", children: [
|
|
1342
|
+
/* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" }),
|
|
1343
|
+
/* @__PURE__ */ jsx("span", { className: "text-[11px] font-semibold uppercase tracking-wider text-muted-foreground", children: "New messages" }),
|
|
1344
|
+
/* @__PURE__ */ jsx("span", { className: "h-px flex-1 bg-border" })
|
|
1345
|
+
] });
|
|
1346
|
+
}
|
|
1266
1347
|
var OPTIMISTIC_ID_PREFIX = "optimistic-";
|
|
1267
1348
|
var STREAMING_ID_PREFIX = "streaming-";
|
|
1268
1349
|
var optimisticCounter = 0;
|
|
@@ -1284,16 +1365,16 @@ function buildOptimisticUserMessage(text, files) {
|
|
|
1284
1365
|
};
|
|
1285
1366
|
}
|
|
1286
1367
|
function useAgentChat(config) {
|
|
1287
|
-
const client =
|
|
1288
|
-
const [agentInfo, setAgentInfo] =
|
|
1289
|
-
const [conversationId, setConversationId] =
|
|
1290
|
-
const [conversations, setConversations] =
|
|
1291
|
-
const [messages, setMessages] =
|
|
1292
|
-
const [pendingApprovals, setPendingApprovals] =
|
|
1293
|
-
const [sending, setSending] =
|
|
1294
|
-
const [resolvingApprovalId, setResolvingApprovalId] =
|
|
1295
|
-
const [error, setError] =
|
|
1296
|
-
|
|
1368
|
+
const client = React8.useMemo(() => new AgentApiClient(config), [config]);
|
|
1369
|
+
const [agentInfo, setAgentInfo] = React8.useState(null);
|
|
1370
|
+
const [conversationId, setConversationId] = React8.useState(null);
|
|
1371
|
+
const [conversations, setConversations] = React8.useState([]);
|
|
1372
|
+
const [messages, setMessages] = React8.useState([]);
|
|
1373
|
+
const [pendingApprovals, setPendingApprovals] = React8.useState([]);
|
|
1374
|
+
const [sending, setSending] = React8.useState(false);
|
|
1375
|
+
const [resolvingApprovalId, setResolvingApprovalId] = React8.useState(null);
|
|
1376
|
+
const [error, setError] = React8.useState(null);
|
|
1377
|
+
React8.useEffect(() => {
|
|
1297
1378
|
let cancelled = false;
|
|
1298
1379
|
client.getConfig().then((info) => {
|
|
1299
1380
|
if (!cancelled) setAgentInfo(info);
|
|
@@ -1303,11 +1384,11 @@ function useAgentChat(config) {
|
|
|
1303
1384
|
cancelled = true;
|
|
1304
1385
|
};
|
|
1305
1386
|
}, [client]);
|
|
1306
|
-
const handleFailure =
|
|
1387
|
+
const handleFailure = React8.useCallback((err) => {
|
|
1307
1388
|
const message = err instanceof AgentApiError ? err.message : "Something went wrong. Please try again.";
|
|
1308
1389
|
setError(message);
|
|
1309
1390
|
}, []);
|
|
1310
|
-
const applyResponse =
|
|
1391
|
+
const applyResponse = React8.useCallback(
|
|
1311
1392
|
(response) => {
|
|
1312
1393
|
if (response.status === "error" && !response.messages?.length) {
|
|
1313
1394
|
setError(response.message ?? "The assistant could not answer.");
|
|
@@ -1329,7 +1410,7 @@ function useAgentChat(config) {
|
|
|
1329
1410
|
},
|
|
1330
1411
|
[]
|
|
1331
1412
|
);
|
|
1332
|
-
const handleStreamEvent =
|
|
1413
|
+
const handleStreamEvent = React8.useCallback(
|
|
1333
1414
|
(event, ctx) => {
|
|
1334
1415
|
switch (event.type) {
|
|
1335
1416
|
case "conversation":
|
|
@@ -1394,7 +1475,7 @@ function useAgentChat(config) {
|
|
|
1394
1475
|
[]
|
|
1395
1476
|
);
|
|
1396
1477
|
const streamingEnabled = config.streaming !== false && typeof ReadableStream !== "undefined";
|
|
1397
|
-
const send =
|
|
1478
|
+
const send = React8.useCallback(
|
|
1398
1479
|
async (text, files) => {
|
|
1399
1480
|
if (sending) return;
|
|
1400
1481
|
if (!text.trim() && !(files && files.length)) return;
|
|
@@ -1425,7 +1506,7 @@ function useAgentChat(config) {
|
|
|
1425
1506
|
},
|
|
1426
1507
|
[applyResponse, client, conversationId, handleFailure, handleStreamEvent, sending, streamingEnabled]
|
|
1427
1508
|
);
|
|
1428
|
-
const resolveApproval =
|
|
1509
|
+
const resolveApproval = React8.useCallback(
|
|
1429
1510
|
async (callId, approve) => {
|
|
1430
1511
|
if (resolvingApprovalId) return;
|
|
1431
1512
|
setResolvingApprovalId(callId);
|
|
@@ -1454,21 +1535,23 @@ function useAgentChat(config) {
|
|
|
1454
1535
|
},
|
|
1455
1536
|
[applyResponse, client, handleFailure, handleStreamEvent, resolvingApprovalId, streamingEnabled]
|
|
1456
1537
|
);
|
|
1457
|
-
const newChat =
|
|
1538
|
+
const newChat = React8.useCallback(() => {
|
|
1458
1539
|
setConversationId(null);
|
|
1459
1540
|
setMessages([]);
|
|
1460
1541
|
setPendingApprovals([]);
|
|
1461
1542
|
setError(null);
|
|
1462
1543
|
}, []);
|
|
1463
|
-
const refreshConversations =
|
|
1544
|
+
const refreshConversations = React8.useCallback(async () => {
|
|
1464
1545
|
try {
|
|
1465
1546
|
const { conversations: list } = await client.listConversations();
|
|
1466
1547
|
setConversations(list);
|
|
1548
|
+
return list;
|
|
1467
1549
|
} catch (err) {
|
|
1468
1550
|
handleFailure(err);
|
|
1551
|
+
return [];
|
|
1469
1552
|
}
|
|
1470
1553
|
}, [client, handleFailure]);
|
|
1471
|
-
const openConversation =
|
|
1554
|
+
const openConversation = React8.useCallback(
|
|
1472
1555
|
async (id) => {
|
|
1473
1556
|
setError(null);
|
|
1474
1557
|
try {
|
|
@@ -1482,7 +1565,7 @@ function useAgentChat(config) {
|
|
|
1482
1565
|
},
|
|
1483
1566
|
[client, handleFailure]
|
|
1484
1567
|
);
|
|
1485
|
-
const deleteConversation =
|
|
1568
|
+
const deleteConversation = React8.useCallback(
|
|
1486
1569
|
async (id) => {
|
|
1487
1570
|
try {
|
|
1488
1571
|
await client.deleteConversation(id);
|
|
@@ -1494,7 +1577,7 @@ function useAgentChat(config) {
|
|
|
1494
1577
|
},
|
|
1495
1578
|
[client, conversationId, handleFailure, newChat]
|
|
1496
1579
|
);
|
|
1497
|
-
const clearError =
|
|
1580
|
+
const clearError = React8.useCallback(() => setError(null), []);
|
|
1498
1581
|
return {
|
|
1499
1582
|
agentInfo,
|
|
1500
1583
|
clearError,
|
|
@@ -1529,11 +1612,11 @@ function readStored(clientId) {
|
|
|
1529
1612
|
}
|
|
1530
1613
|
}
|
|
1531
1614
|
function useAlwaysApprove(clientId) {
|
|
1532
|
-
const [approved, setApproved] =
|
|
1533
|
-
|
|
1615
|
+
const [approved, setApproved] = React8.useState(() => /* @__PURE__ */ new Set());
|
|
1616
|
+
React8.useEffect(() => {
|
|
1534
1617
|
setApproved(readStored(clientId));
|
|
1535
1618
|
}, [clientId]);
|
|
1536
|
-
const persist =
|
|
1619
|
+
const persist = React8.useCallback(
|
|
1537
1620
|
(next) => {
|
|
1538
1621
|
setApproved(next);
|
|
1539
1622
|
if (typeof window === "undefined") return;
|
|
@@ -1544,8 +1627,8 @@ function useAlwaysApprove(clientId) {
|
|
|
1544
1627
|
},
|
|
1545
1628
|
[clientId]
|
|
1546
1629
|
);
|
|
1547
|
-
const isAlwaysApproved =
|
|
1548
|
-
const setAlwaysApprove =
|
|
1630
|
+
const isAlwaysApproved = React8.useCallback((toolName) => approved.has(toolName), [approved]);
|
|
1631
|
+
const setAlwaysApprove = React8.useCallback(
|
|
1549
1632
|
(toolName) => {
|
|
1550
1633
|
if (approved.has(toolName)) return;
|
|
1551
1634
|
const next = new Set(approved);
|
|
@@ -1554,7 +1637,7 @@ function useAlwaysApprove(clientId) {
|
|
|
1554
1637
|
},
|
|
1555
1638
|
[approved, persist]
|
|
1556
1639
|
);
|
|
1557
|
-
const clearAlwaysApprove =
|
|
1640
|
+
const clearAlwaysApprove = React8.useCallback(
|
|
1558
1641
|
(toolName) => {
|
|
1559
1642
|
if (!approved.has(toolName)) return;
|
|
1560
1643
|
const next = new Set(approved);
|
|
@@ -1565,46 +1648,110 @@ function useAlwaysApprove(clientId) {
|
|
|
1565
1648
|
);
|
|
1566
1649
|
return { clearAlwaysApprove, isAlwaysApproved, setAlwaysApprove };
|
|
1567
1650
|
}
|
|
1651
|
+
function isUnreadable(message) {
|
|
1652
|
+
if (message.id.startsWith("streaming-")) return false;
|
|
1653
|
+
return message.role === "assistant" || message.role === "tool";
|
|
1654
|
+
}
|
|
1568
1655
|
function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
1569
|
-
const
|
|
1656
|
+
const behavior = config.behavior ?? {};
|
|
1657
|
+
const position = config.appearance?.position ?? "bottom-right";
|
|
1658
|
+
const isControlled = controlledOpen !== void 0;
|
|
1659
|
+
const storageKey2 = `cc-agent-open:${config.clientId}`;
|
|
1660
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React8.useState(false);
|
|
1570
1661
|
const open = controlledOpen ?? uncontrolledOpen;
|
|
1571
|
-
const
|
|
1662
|
+
const persist = React8.useCallback(
|
|
1663
|
+
(next) => {
|
|
1664
|
+
if (isControlled || !behavior.persistOpenState) return;
|
|
1665
|
+
try {
|
|
1666
|
+
window.localStorage.setItem(storageKey2, next ? "1" : "0");
|
|
1667
|
+
} catch {
|
|
1668
|
+
}
|
|
1669
|
+
},
|
|
1670
|
+
[behavior.persistOpenState, isControlled, storageKey2]
|
|
1671
|
+
);
|
|
1672
|
+
const setOpen = React8.useCallback(
|
|
1572
1673
|
(next) => {
|
|
1573
1674
|
setUncontrolledOpen(next);
|
|
1675
|
+
persist(next);
|
|
1574
1676
|
onOpenChange?.(next);
|
|
1575
1677
|
},
|
|
1576
|
-
[onOpenChange]
|
|
1678
|
+
[onOpenChange, persist]
|
|
1577
1679
|
);
|
|
1578
|
-
const [agentMode, setAgentMode] =
|
|
1579
|
-
const isDesktop = useMediaQuery("(min-width: 640px)", {
|
|
1580
|
-
defaultSSRValue: true,
|
|
1581
|
-
ssr: true
|
|
1582
|
-
});
|
|
1680
|
+
const [agentMode, setAgentMode] = React8.useState(false);
|
|
1681
|
+
const isDesktop = useMediaQuery("(min-width: 640px)", { defaultSSRValue: true, ssr: true });
|
|
1583
1682
|
const fullscreen = !isDesktop || agentMode;
|
|
1584
1683
|
const chat = useAgentChat(config);
|
|
1585
1684
|
const alwaysApprove = useAlwaysApprove(config.clientId);
|
|
1586
|
-
const { pendingApprovals, resolveApproval, resolvingApprovalId } = chat;
|
|
1685
|
+
const { messages, pendingApprovals, resolveApproval, resolvingApprovalId, sending } = chat;
|
|
1686
|
+
const bootRef = React8.useRef(false);
|
|
1687
|
+
React8.useEffect(() => {
|
|
1688
|
+
if (isControlled || bootRef.current) return;
|
|
1689
|
+
bootRef.current = true;
|
|
1690
|
+
let cancelled = false;
|
|
1691
|
+
void (async () => {
|
|
1692
|
+
if (behavior.openLastConversation !== false) {
|
|
1693
|
+
const list = await chat.refreshConversations();
|
|
1694
|
+
if (cancelled) return;
|
|
1695
|
+
if (list.length) await chat.openConversation(list[0].id);
|
|
1696
|
+
if (cancelled) return;
|
|
1697
|
+
}
|
|
1698
|
+
let shouldOpen = behavior.initialOpen ?? false;
|
|
1699
|
+
if (behavior.persistOpenState) {
|
|
1700
|
+
try {
|
|
1701
|
+
const stored = window.localStorage.getItem(storageKey2);
|
|
1702
|
+
if (stored !== null) shouldOpen = stored === "1";
|
|
1703
|
+
} catch {
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
if (shouldOpen) {
|
|
1707
|
+
setUncontrolledOpen(true);
|
|
1708
|
+
onOpenChange?.(true);
|
|
1709
|
+
}
|
|
1710
|
+
})();
|
|
1711
|
+
return () => {
|
|
1712
|
+
cancelled = true;
|
|
1713
|
+
};
|
|
1714
|
+
}, []);
|
|
1715
|
+
const seenCountRef = React8.useRef(0);
|
|
1716
|
+
const [anchorId, setAnchorId] = React8.useState(null);
|
|
1717
|
+
React8.useEffect(() => {
|
|
1718
|
+
if (open) {
|
|
1719
|
+
if (messages.length > seenCountRef.current) {
|
|
1720
|
+
const firstUnread = messages[seenCountRef.current];
|
|
1721
|
+
setAnchorId(firstUnread && isUnreadable(firstUnread) ? firstUnread.id : null);
|
|
1722
|
+
}
|
|
1723
|
+
seenCountRef.current = messages.length;
|
|
1724
|
+
}
|
|
1725
|
+
}, [open, messages]);
|
|
1726
|
+
const unreadCount = open ? 0 : messages.slice(seenCountRef.current).filter(isUnreadable).length;
|
|
1727
|
+
const workLabel = React8.useMemo(() => {
|
|
1728
|
+
if (!sending) return null;
|
|
1729
|
+
const last = messages[messages.length - 1];
|
|
1730
|
+
if (last?.role === "tool") return "Calling tools\u2026";
|
|
1731
|
+
if (last?.role === "assistant" && (last.tool_calls?.length ?? 0) > 0) return "Calling tools\u2026";
|
|
1732
|
+
return "Thinking\u2026";
|
|
1733
|
+
}, [sending, messages]);
|
|
1734
|
+
const approvalWaiting = pendingApprovals.length > 0;
|
|
1587
1735
|
const { isAlwaysApproved } = alwaysApprove;
|
|
1588
|
-
|
|
1589
|
-
if (
|
|
1736
|
+
React8.useEffect(() => {
|
|
1737
|
+
if (resolvingApprovalId) return;
|
|
1590
1738
|
const target = pendingApprovals.find((approval) => isAlwaysApproved(approval.tool_name));
|
|
1591
1739
|
if (target) void resolveApproval(target.id, true);
|
|
1592
|
-
}, [
|
|
1593
|
-
const firedWriteIds =
|
|
1740
|
+
}, [pendingApprovals, resolvingApprovalId, isAlwaysApproved, resolveApproval]);
|
|
1741
|
+
const firedWriteIds = React8.useRef(/* @__PURE__ */ new Set());
|
|
1594
1742
|
const { onWriteSuccess } = config;
|
|
1595
|
-
|
|
1743
|
+
React8.useEffect(() => {
|
|
1596
1744
|
if (!onWriteSuccess) return;
|
|
1597
|
-
for (const message of
|
|
1745
|
+
for (const message of messages) {
|
|
1598
1746
|
if (message.role !== "tool" || !message.is_write || message.tool_status !== "success") continue;
|
|
1599
1747
|
if (firedWriteIds.current.has(message.id)) continue;
|
|
1600
1748
|
firedWriteIds.current.add(message.id);
|
|
1601
|
-
const args = findToolArguments(
|
|
1749
|
+
const args = findToolArguments(messages, message);
|
|
1602
1750
|
onWriteSuccess({ arguments: args, toolName: message.tool_name ?? "" });
|
|
1603
1751
|
}
|
|
1604
|
-
}, [
|
|
1605
|
-
const lastNavigatedMessageId =
|
|
1606
|
-
|
|
1607
|
-
React7.useEffect(() => {
|
|
1752
|
+
}, [messages, onWriteSuccess]);
|
|
1753
|
+
const lastNavigatedMessageId = React8.useRef(null);
|
|
1754
|
+
React8.useEffect(() => {
|
|
1608
1755
|
if (!open || fullscreen || !config.navigate || !config.pages?.length) return;
|
|
1609
1756
|
const toolMessages = messages.filter((message) => message.role === "tool");
|
|
1610
1757
|
const latest = toolMessages[toolMessages.length - 1];
|
|
@@ -1616,69 +1763,137 @@ function AgentWidget({ config, onOpenChange, open: controlledOpen }) {
|
|
|
1616
1763
|
const path = mapping.buildRoute ? mapping.buildRoute(args) : mapping.route;
|
|
1617
1764
|
if (path) config.navigate(path);
|
|
1618
1765
|
}, [config, fullscreen, messages, open]);
|
|
1619
|
-
const closePanel =
|
|
1766
|
+
const closePanel = React8.useCallback(() => {
|
|
1620
1767
|
setOpen(false);
|
|
1621
1768
|
setAgentMode(false);
|
|
1622
1769
|
}, [setOpen]);
|
|
1770
|
+
const transformOrigin = position === "bottom-left" ? "bottom left" : "bottom right";
|
|
1771
|
+
const launcherHidden = open || config.appearance?.hideLauncher;
|
|
1623
1772
|
return /* @__PURE__ */ jsxs(TooltipProvider, { children: [
|
|
1624
|
-
|
|
1625
|
-
|
|
1773
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: !launcherHidden ? /* @__PURE__ */ jsxs(
|
|
1774
|
+
motion.div,
|
|
1626
1775
|
{
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1776
|
+
initial: { opacity: 0, scale: 0.6 },
|
|
1777
|
+
animate: { opacity: 1, scale: 1 },
|
|
1778
|
+
exit: { opacity: 0, scale: 0.6 },
|
|
1779
|
+
transition: { type: "spring", stiffness: 300, damping: 24 },
|
|
1780
|
+
style: { transformOrigin },
|
|
1781
|
+
className: cn(
|
|
1782
|
+
"fixed z-50 flex flex-col items-end gap-2",
|
|
1783
|
+
position === "bottom-left" ? "bottom-4 left-4 items-start sm:bottom-6 sm:left-6" : "right-4 bottom-4 sm:right-6 sm:bottom-6"
|
|
1784
|
+
),
|
|
1785
|
+
children: [
|
|
1786
|
+
/* @__PURE__ */ jsx(AnimatePresence, { mode: "popLayout", children: approvalWaiting ? /* @__PURE__ */ jsx(StatusChip, { tone: "warning", pulse: true, children: "Approval needed" }, "approval") : workLabel ? /* @__PURE__ */ jsx(StatusChip, { tone: "muted", children: workLabel }, workLabel) : null }),
|
|
1787
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
1788
|
+
/* @__PURE__ */ jsx(
|
|
1789
|
+
"button",
|
|
1790
|
+
{
|
|
1791
|
+
type: "button",
|
|
1792
|
+
"aria-label": "Open assistant",
|
|
1793
|
+
onClick: () => setOpen(true),
|
|
1794
|
+
className: "flex size-14 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-lg outline-none transition-transform hover:scale-105 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
1795
|
+
children: /* @__PURE__ */ jsx(SparklesIcon, { className: "size-6" })
|
|
1796
|
+
}
|
|
1797
|
+
),
|
|
1798
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: unreadCount > 0 ? /* @__PURE__ */ jsx(
|
|
1799
|
+
motion.span,
|
|
1800
|
+
{
|
|
1801
|
+
initial: { scale: 0 },
|
|
1802
|
+
animate: { scale: 1 },
|
|
1803
|
+
exit: { scale: 0 },
|
|
1804
|
+
transition: { type: "spring", stiffness: 500, damping: 20 },
|
|
1805
|
+
className: "absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-destructive px-1 text-xs font-semibold text-destructive-foreground shadow",
|
|
1806
|
+
"aria-label": `${unreadCount} new message${unreadCount === 1 ? "" : "s"}`,
|
|
1807
|
+
children: unreadCount > 9 ? "9+" : unreadCount
|
|
1808
|
+
},
|
|
1809
|
+
"badge"
|
|
1810
|
+
) : null })
|
|
1811
|
+
] })
|
|
1812
|
+
]
|
|
1813
|
+
},
|
|
1814
|
+
"agent-launcher"
|
|
1815
|
+
) : null }),
|
|
1816
|
+
/* @__PURE__ */ jsx(AnimatePresence, { children: open ? /* @__PURE__ */ jsx(
|
|
1817
|
+
motion.div,
|
|
1635
1818
|
{
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
config,
|
|
1647
|
-
onAlwaysApprove: alwaysApprove.setAlwaysApprove,
|
|
1648
|
-
onClose: closePanel,
|
|
1649
|
-
onToggleAgentMode: () => setAgentMode((value) => !value)
|
|
1819
|
+
layout: true,
|
|
1820
|
+
initial: { opacity: 0, scale: 0.9 },
|
|
1821
|
+
animate: { opacity: 1, scale: 1 },
|
|
1822
|
+
exit: { opacity: 0, scale: 0.9 },
|
|
1823
|
+
transition: { type: "spring", stiffness: 260, damping: 28 },
|
|
1824
|
+
style: {
|
|
1825
|
+
transformOrigin,
|
|
1826
|
+
...fullscreen ? {} : {
|
|
1827
|
+
height: `min(${config.appearance?.panelHeight ?? 640}px, calc(100dvh - 3rem))`,
|
|
1828
|
+
width: `min(${config.appearance?.panelWidth ?? 400}px, calc(100vw - 2rem))`
|
|
1650
1829
|
}
|
|
1651
|
-
|
|
1652
|
-
}
|
|
1653
|
-
) : /* @__PURE__ */ jsx(
|
|
1654
|
-
"div",
|
|
1655
|
-
{
|
|
1830
|
+
},
|
|
1656
1831
|
className: cn(
|
|
1657
|
-
"fixed z-50 flex flex-col overflow-hidden
|
|
1658
|
-
|
|
1832
|
+
"fixed z-50 flex flex-col overflow-hidden bg-background",
|
|
1833
|
+
fullscreen ? "inset-0" : cn(
|
|
1834
|
+
"rounded-2xl border border-border shadow-2xl",
|
|
1835
|
+
position === "bottom-left" ? "bottom-4 left-4 sm:bottom-6 sm:left-6" : "right-4 bottom-4 sm:right-6 sm:bottom-6"
|
|
1836
|
+
)
|
|
1659
1837
|
),
|
|
1660
1838
|
role: "dialog",
|
|
1661
|
-
|
|
1662
|
-
height: `min(${config.appearance?.panelHeight ?? 640}px, calc(100dvh - 3rem))`,
|
|
1663
|
-
width: `min(${config.appearance?.panelWidth ?? 400}px, calc(100vw - 2rem))`
|
|
1664
|
-
},
|
|
1839
|
+
"aria-modal": fullscreen ? "true" : void 0,
|
|
1665
1840
|
children: /* @__PURE__ */ jsx(
|
|
1666
1841
|
AgentPanel,
|
|
1667
1842
|
{
|
|
1668
|
-
agentMode:
|
|
1669
|
-
canToggleAgentMode:
|
|
1843
|
+
agentMode: fullscreen && agentMode,
|
|
1844
|
+
canToggleAgentMode: isDesktop,
|
|
1670
1845
|
chat,
|
|
1671
1846
|
className: "flex-1",
|
|
1672
1847
|
config,
|
|
1848
|
+
newMessageAnchorId: anchorId,
|
|
1673
1849
|
onAlwaysApprove: alwaysApprove.setAlwaysApprove,
|
|
1674
1850
|
onClose: closePanel,
|
|
1675
|
-
onToggleAgentMode: () => setAgentMode(
|
|
1851
|
+
onToggleAgentMode: () => setAgentMode((value) => !value)
|
|
1676
1852
|
}
|
|
1677
1853
|
)
|
|
1678
|
-
}
|
|
1679
|
-
|
|
1854
|
+
},
|
|
1855
|
+
"agent-panel"
|
|
1856
|
+
) : null })
|
|
1680
1857
|
] });
|
|
1681
1858
|
}
|
|
1859
|
+
function StatusChip({
|
|
1860
|
+
children,
|
|
1861
|
+
tone,
|
|
1862
|
+
pulse
|
|
1863
|
+
}) {
|
|
1864
|
+
return /* @__PURE__ */ jsxs(
|
|
1865
|
+
motion.div,
|
|
1866
|
+
{
|
|
1867
|
+
layout: true,
|
|
1868
|
+
initial: { opacity: 0, y: 6, scale: 0.9 },
|
|
1869
|
+
animate: { opacity: 1, y: 0, scale: 1 },
|
|
1870
|
+
exit: { opacity: 0, y: 6, scale: 0.9 },
|
|
1871
|
+
transition: { duration: 0.18 },
|
|
1872
|
+
className: cn(
|
|
1873
|
+
"flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium shadow-sm",
|
|
1874
|
+
tone === "warning" ? "border-warning bg-warning-surface text-warning-foreground" : "border-border bg-card text-secondary-foreground"
|
|
1875
|
+
),
|
|
1876
|
+
children: [
|
|
1877
|
+
pulse ? /* @__PURE__ */ jsx("span", { className: "size-1.5 rounded-full bg-warning-foreground" }) : /* @__PURE__ */ jsxs("span", { className: "flex gap-0.5", children: [
|
|
1878
|
+
/* @__PURE__ */ jsx(Dot, { delay: 0 }),
|
|
1879
|
+
/* @__PURE__ */ jsx(Dot, { delay: 0.15 }),
|
|
1880
|
+
/* @__PURE__ */ jsx(Dot, { delay: 0.3 })
|
|
1881
|
+
] }),
|
|
1882
|
+
children
|
|
1883
|
+
]
|
|
1884
|
+
}
|
|
1885
|
+
);
|
|
1886
|
+
}
|
|
1887
|
+
function Dot({ delay }) {
|
|
1888
|
+
return /* @__PURE__ */ jsx(
|
|
1889
|
+
motion.span,
|
|
1890
|
+
{
|
|
1891
|
+
className: "size-1.5 rounded-full bg-current opacity-60",
|
|
1892
|
+
animate: { opacity: [0.2, 1, 0.2] },
|
|
1893
|
+
transition: { duration: 1, repeat: Infinity, delay }
|
|
1894
|
+
}
|
|
1895
|
+
);
|
|
1896
|
+
}
|
|
1682
1897
|
function findToolArguments(messages, toolMessage) {
|
|
1683
1898
|
if (!toolMessage.tool_call_id) return {};
|
|
1684
1899
|
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -1695,6 +1910,6 @@ function defineAgentConfig(config) {
|
|
|
1695
1910
|
return config;
|
|
1696
1911
|
}
|
|
1697
1912
|
|
|
1698
|
-
export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, ChatScroller, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
|
|
1913
|
+
export { AgentApiClient, AgentApiError, AgentComposer, AgentPanel, AgentWidget, ChatScroller, CopyButton, DEFAULT_ACCEPT, DocumentCard, MessageItem, TaskChecklist, ThinkingIndicator, ToolActivityGroup, ToolApprovalCard, acceptAttribute, acceptedExtensions, defineAgentConfig, deriveDetails, fileUploadEnabled, formatBytes, groupMessages, humanizeToolName, resolveToolPresentation, useAgentChat, useAlwaysApprove, validateFile };
|
|
1699
1914
|
//# sourceMappingURL=index.js.map
|
|
1700
1915
|
//# sourceMappingURL=index.js.map
|