@ash-cloud/ash-ui 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -41,27 +41,41 @@ function mapToolToActionType(toolName, input) {
41
41
  command: inputObj.command || "",
42
42
  description: inputObj.description
43
43
  };
44
- case "Read":
44
+ case "Read": {
45
+ const limit = inputObj.limit;
45
46
  return {
46
47
  action: "file_read",
47
48
  path: inputObj.file_path || "",
48
49
  offset: inputObj.offset,
49
- limit: inputObj.limit
50
+ limit,
51
+ linesRead: limit
52
+ // Use limit as approximate lines read if specified
50
53
  };
51
- case "Edit":
54
+ }
55
+ case "Edit": {
56
+ const oldStr = inputObj.old_string;
57
+ const newStr = inputObj.new_string;
58
+ const oldLines = oldStr ? oldStr.split("\n").length : 0;
59
+ const newLines = newStr ? newStr.split("\n").length : 0;
52
60
  return {
53
61
  action: "file_edit",
54
62
  path: inputObj.file_path || "",
55
- oldString: inputObj.old_string,
56
- newString: inputObj.new_string,
57
- replaceAll: inputObj.replace_all
63
+ oldString: oldStr,
64
+ newString: newStr,
65
+ replaceAll: inputObj.replace_all,
66
+ linesAdded: newLines,
67
+ linesRemoved: oldLines
58
68
  };
59
- case "Write":
69
+ }
70
+ case "Write": {
71
+ const content = inputObj.content;
60
72
  return {
61
73
  action: "file_write",
62
74
  path: inputObj.file_path || "",
63
- content: inputObj.content
75
+ content,
76
+ linesWritten: content ? content.split("\n").length : void 0
64
77
  };
78
+ }
65
79
  case "Grep":
66
80
  return {
67
81
  action: "search",
@@ -1310,275 +1324,422 @@ function StreamingText({
1310
1324
  isStreaming && /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicator, { variant: "cursor", size: "sm", className: "inline-block ml-0.5" })
1311
1325
  ] }) });
1312
1326
  }
1313
- function CompactToolStatusLine({
1314
- toolCall,
1315
- previousToolCall,
1316
- animationDuration = 300,
1317
- className
1318
- }) {
1319
- const [isAnimating, setIsAnimating] = react.useState(false);
1320
- const [displayedToolCall, setDisplayedToolCall] = react.useState(toolCall);
1321
- const [exitingToolCall, setExitingToolCall] = react.useState(null);
1322
- const prevToolCallRef = react.useRef(null);
1323
- react.useEffect(() => {
1324
- if (toolCall.id !== prevToolCallRef.current) {
1325
- if (prevToolCallRef.current !== null && previousToolCall) {
1326
- setExitingToolCall(previousToolCall);
1327
- setIsAnimating(true);
1328
- const timer = setTimeout(() => {
1329
- setDisplayedToolCall(toolCall);
1330
- setExitingToolCall(null);
1331
- setIsAnimating(false);
1332
- }, animationDuration);
1333
- prevToolCallRef.current = toolCall.id;
1334
- return () => clearTimeout(timer);
1335
- } else {
1336
- setDisplayedToolCall(toolCall);
1337
- prevToolCallRef.current = toolCall.id;
1327
+ function getFilePath(actionType) {
1328
+ switch (actionType.action) {
1329
+ case "file_read":
1330
+ case "file_edit":
1331
+ case "file_write":
1332
+ return actionType.path;
1333
+ default:
1334
+ return null;
1335
+ }
1336
+ }
1337
+ function getFileName(path) {
1338
+ const parts = path.split("/");
1339
+ return parts[parts.length - 1] || path;
1340
+ }
1341
+ function getFileExtension(path) {
1342
+ const fileName = getFileName(path);
1343
+ const dotIndex = fileName.lastIndexOf(".");
1344
+ if (dotIndex === -1) return null;
1345
+ return fileName.slice(dotIndex + 1).toLowerCase();
1346
+ }
1347
+ function getDiffStats(actionType) {
1348
+ switch (actionType.action) {
1349
+ case "file_edit": {
1350
+ const edit = actionType;
1351
+ if (edit.linesAdded !== void 0 || edit.linesRemoved !== void 0) {
1352
+ return { added: edit.linesAdded, removed: edit.linesRemoved };
1338
1353
  }
1339
- } else {
1340
- setDisplayedToolCall(toolCall);
1354
+ return null;
1341
1355
  }
1342
- return void 0;
1343
- }, [toolCall, previousToolCall, animationDuration]);
1344
- const statusClasses = {
1345
- pending: "border-yellow-500/30",
1346
- success: "border-[var(--ash-accent)]/30",
1347
- failed: "border-red-500/30"
1348
- };
1349
- const renderToolCallContent = (tc, isExiting) => /* @__PURE__ */ jsxRuntime.jsxs(
1356
+ case "file_read": {
1357
+ const read = actionType;
1358
+ if (read.linesRead !== void 0) {
1359
+ return { read: read.linesRead };
1360
+ }
1361
+ return null;
1362
+ }
1363
+ case "file_write": {
1364
+ const write = actionType;
1365
+ if (write.linesWritten !== void 0) {
1366
+ return { written: write.linesWritten };
1367
+ }
1368
+ return null;
1369
+ }
1370
+ default:
1371
+ return null;
1372
+ }
1373
+ }
1374
+ function getFileTypeColor(ext) {
1375
+ switch (ext) {
1376
+ case "ts":
1377
+ case "tsx":
1378
+ return "text-blue-400";
1379
+ case "js":
1380
+ case "jsx":
1381
+ return "text-yellow-400";
1382
+ case "md":
1383
+ return "text-white/60";
1384
+ case "json":
1385
+ return "text-orange-400";
1386
+ case "sh":
1387
+ return "text-green-400";
1388
+ case "css":
1389
+ case "scss":
1390
+ return "text-pink-400";
1391
+ case "py":
1392
+ return "text-blue-300";
1393
+ default:
1394
+ return "text-white/70";
1395
+ }
1396
+ }
1397
+ function CompactToolRow({ toolCall, showFullPath = false, className }) {
1398
+ const { actionType, status, summary } = toolCall;
1399
+ const label = getActionLabel(actionType);
1400
+ const filePath = getFilePath(actionType);
1401
+ const diffStats = getDiffStats(actionType);
1402
+ const displayPath = filePath ? showFullPath ? filePath : getFileName(filePath) : null;
1403
+ const ext = filePath ? getFileExtension(filePath) : null;
1404
+ const fileColor = getFileTypeColor(ext);
1405
+ const showSummary = !filePath && summary;
1406
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1350
1407
  "div",
1351
1408
  {
1352
1409
  className: cn(
1353
- "flex items-center gap-3 px-4 py-2.5",
1354
- isExiting ? "ash-status-line-exit" : isAnimating ? "ash-status-line-enter" : ""
1410
+ "flex items-center gap-2 py-1.5 text-sm min-w-0",
1411
+ className
1355
1412
  ),
1356
- style: {
1357
- animationDuration: `${animationDuration}ms`
1358
- },
1359
1413
  children: [
1360
- /* @__PURE__ */ jsxRuntime.jsx(
1361
- "div",
1362
- {
1363
- className: cn(
1364
- "w-6 h-6 rounded-lg flex items-center justify-center shrink-0",
1365
- tc.status === "pending" ? "bg-yellow-500/20" : tc.status === "failed" ? "bg-red-500/20" : "bg-[var(--ash-accent)]/20"
1366
- ),
1367
- children: /* @__PURE__ */ jsxRuntime.jsx(
1368
- ActionIcon,
1369
- {
1370
- actionType: tc.actionType,
1371
- className: cn(
1372
- "w-3.5 h-3.5",
1373
- tc.status === "pending" ? "text-yellow-400" : tc.status === "failed" ? "text-red-400" : "text-[var(--ash-accent)]"
1374
- )
1375
- }
1376
- )
1377
- }
1378
- ),
1379
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-white shrink-0", children: getActionLabel(tc.actionType) }),
1380
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-sm truncate text-white/60 flex-1 min-w-0", children: tc.summary }),
1381
- /* @__PURE__ */ jsxRuntime.jsx(StatusIndicator, { status: tc.status, size: "sm" })
1414
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 shrink-0", children: [
1415
+ /* @__PURE__ */ jsxRuntime.jsx(
1416
+ ActionIcon,
1417
+ {
1418
+ actionType,
1419
+ className: cn(
1420
+ "w-3.5 h-3.5",
1421
+ status === "pending" ? "text-yellow-400" : status === "failed" ? "text-red-400" : "text-white/50"
1422
+ )
1423
+ }
1424
+ ),
1425
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
1426
+ "font-medium",
1427
+ status === "pending" ? "text-white/90" : status === "failed" ? "text-red-400" : "text-white/60"
1428
+ ), children: label })
1429
+ ] }),
1430
+ displayPath && /* @__PURE__ */ jsxRuntime.jsx("code", { className: cn(
1431
+ "px-1.5 py-0.5 rounded bg-white/5 font-mono text-xs truncate max-w-[200px]",
1432
+ fileColor
1433
+ ), children: displayPath }),
1434
+ diffStats && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-1 text-xs shrink-0 font-mono", children: [
1435
+ diffStats.added !== void 0 && diffStats.added > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-emerald-400", children: [
1436
+ "+",
1437
+ diffStats.added
1438
+ ] }),
1439
+ diffStats.removed !== void 0 && diffStats.removed > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-red-400", children: [
1440
+ "-",
1441
+ diffStats.removed
1442
+ ] }),
1443
+ diffStats.read !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-white/40", children: [
1444
+ diffStats.read,
1445
+ " lines"
1446
+ ] }),
1447
+ diffStats.written !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-emerald-400", children: [
1448
+ "+",
1449
+ diffStats.written
1450
+ ] })
1451
+ ] }),
1452
+ showSummary && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/40 truncate min-w-0 text-xs", children: summary })
1382
1453
  ]
1383
1454
  }
1384
1455
  );
1456
+ }
1457
+ function getFileExtension2(path) {
1458
+ const fileName = path.split("/").pop() || path;
1459
+ const dotIndex = fileName.lastIndexOf(".");
1460
+ if (dotIndex === -1) return null;
1461
+ return fileName.slice(dotIndex + 1).toLowerCase();
1462
+ }
1463
+ function getFileIcon(ext) {
1464
+ switch (ext) {
1465
+ case "ts":
1466
+ case "tsx":
1467
+ return "TS";
1468
+ case "js":
1469
+ case "jsx":
1470
+ return "JS";
1471
+ case "md":
1472
+ return "MD";
1473
+ case "json":
1474
+ return "{}";
1475
+ case "sh":
1476
+ return "$";
1477
+ case "css":
1478
+ case "scss":
1479
+ return "#";
1480
+ case "py":
1481
+ return "PY";
1482
+ default:
1483
+ return "";
1484
+ }
1485
+ }
1486
+ function getFileBgColor(ext) {
1487
+ switch (ext) {
1488
+ case "ts":
1489
+ case "tsx":
1490
+ return "bg-blue-500/20";
1491
+ case "js":
1492
+ case "jsx":
1493
+ return "bg-yellow-500/20";
1494
+ case "md":
1495
+ return "bg-white/10";
1496
+ case "json":
1497
+ return "bg-orange-500/20";
1498
+ case "sh":
1499
+ return "bg-green-500/20";
1500
+ case "css":
1501
+ case "scss":
1502
+ return "bg-pink-500/20";
1503
+ case "py":
1504
+ return "bg-blue-400/20";
1505
+ default:
1506
+ return "bg-white/10";
1507
+ }
1508
+ }
1509
+ function FileBadge({
1510
+ path,
1511
+ linesAdded,
1512
+ linesRemoved,
1513
+ showOnlyFilename = true,
1514
+ className
1515
+ }) {
1516
+ const fileName = showOnlyFilename ? path.split("/").pop() || path : path;
1517
+ const ext = getFileExtension2(path);
1518
+ const icon = getFileIcon(ext);
1519
+ const bgColor = getFileBgColor(ext);
1520
+ const hasDiff = linesAdded !== void 0 && linesAdded > 0 || linesRemoved !== void 0 && linesRemoved > 0;
1385
1521
  return /* @__PURE__ */ jsxRuntime.jsxs(
1386
- "div",
1522
+ "span",
1387
1523
  {
1388
1524
  className: cn(
1389
- "relative rounded-xl border bg-[var(--ash-surface-dark,#0a0a0a)] overflow-hidden",
1390
- statusClasses[displayedToolCall.status],
1391
- displayedToolCall.status === "pending" && "ash-tool-status-pending",
1525
+ "inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md text-xs font-mono",
1526
+ bgColor,
1392
1527
  className
1393
1528
  ),
1394
1529
  children: [
1395
- exitingToolCall && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0", children: renderToolCallContent(exitingToolCall, true) }),
1396
- renderToolCallContent(displayedToolCall, false)
1530
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] opacity-60 font-semibold", children: icon }),
1531
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-white/80 truncate max-w-[120px]", children: fileName }),
1532
+ hasDiff && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex items-center gap-0.5", children: [
1533
+ linesAdded !== void 0 && linesAdded > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-emerald-400", children: [
1534
+ "+",
1535
+ linesAdded
1536
+ ] }),
1537
+ linesRemoved !== void 0 && linesRemoved > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-red-400", children: [
1538
+ "-",
1539
+ linesRemoved
1540
+ ] })
1541
+ ] })
1397
1542
  ]
1398
1543
  }
1399
1544
  );
1400
1545
  }
1401
- function calculateGroupStatus(toolCalls) {
1402
- const hasPending = toolCalls.some((tc) => tc.status === "pending");
1403
- const hasFailed = toolCalls.some((tc) => tc.status === "failed");
1404
- const allSuccess = toolCalls.every((tc) => tc.status === "success");
1405
- if (hasPending) return "pending";
1406
- if (allSuccess) return "success";
1407
- if (hasFailed && !hasPending) return toolCalls.some((tc) => tc.status === "success") ? "partial_failure" : "failed";
1408
- return "pending";
1546
+ function extractFileChanges(toolCalls) {
1547
+ const fileMap = /* @__PURE__ */ new Map();
1548
+ for (const tc of toolCalls) {
1549
+ const { actionType } = tc;
1550
+ if (actionType.action === "file_edit") {
1551
+ const edit = actionType;
1552
+ const existing = fileMap.get(edit.path);
1553
+ if (existing) {
1554
+ existing.linesAdded = (existing.linesAdded || 0) + (edit.linesAdded || 0);
1555
+ existing.linesRemoved = (existing.linesRemoved || 0) + (edit.linesRemoved || 0);
1556
+ } else {
1557
+ fileMap.set(edit.path, {
1558
+ path: edit.path,
1559
+ linesAdded: edit.linesAdded,
1560
+ linesRemoved: edit.linesRemoved
1561
+ });
1562
+ }
1563
+ } else if (actionType.action === "file_write") {
1564
+ const write = actionType;
1565
+ if (!fileMap.has(write.path)) {
1566
+ fileMap.set(write.path, {
1567
+ path: write.path,
1568
+ linesAdded: write.linesWritten
1569
+ });
1570
+ }
1571
+ }
1572
+ }
1573
+ return Array.from(fileMap.values());
1574
+ }
1575
+ function countActionTypes(toolCalls) {
1576
+ const counts = {};
1577
+ for (const tc of toolCalls) {
1578
+ const action = tc.actionType.action;
1579
+ counts[action] = (counts[action] || 0) + 1;
1580
+ }
1581
+ return counts;
1582
+ }
1583
+ function getActionIconComponent(action) {
1584
+ switch (action) {
1585
+ case "file_read":
1586
+ return FileIcon;
1587
+ case "file_edit":
1588
+ case "file_write":
1589
+ return EditIcon;
1590
+ case "command_run":
1591
+ return TerminalIcon;
1592
+ case "search":
1593
+ case "glob":
1594
+ return SearchIcon;
1595
+ default:
1596
+ return null;
1597
+ }
1409
1598
  }
1410
1599
  function ToolExecutionGroup({
1411
1600
  toolCalls,
1412
1601
  defaultExpanded = false,
1413
- animationDuration = 300,
1414
1602
  className
1415
1603
  }) {
1416
1604
  const [expanded, setExpanded] = react.useState(defaultExpanded);
1417
- const activeToolCall = toolCalls[toolCalls.length - 1];
1418
- const previousToolCall = toolCalls.length > 1 ? toolCalls[toolCalls.length - 2] : null;
1419
- const groupStatus = react.useMemo(() => calculateGroupStatus(toolCalls), [toolCalls]);
1420
- const completedCount = toolCalls.filter((tc) => tc.status === "success").length;
1421
- const failedCount = toolCalls.filter((tc) => tc.status === "failed").length;
1605
+ const [expandedCardId, setExpandedCardId] = react.useState(null);
1606
+ const fileChanges = react.useMemo(() => extractFileChanges(toolCalls), [toolCalls]);
1607
+ const actionCounts = react.useMemo(() => countActionTypes(toolCalls), [toolCalls]);
1608
+ const displayActions = react.useMemo(() => {
1609
+ return Object.entries(actionCounts).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([action]) => action);
1610
+ }, [actionCounts]);
1422
1611
  const totalCount = toolCalls.length;
1423
- if (!activeToolCall) {
1612
+ if (toolCalls.length === 0) {
1424
1613
  return null;
1425
1614
  }
1426
- const borderClasses = {
1427
- pending: "border-yellow-500/30",
1428
- success: "border-[var(--ash-accent)]/30",
1429
- partial_failure: "border-orange-500/30",
1430
- failed: "border-red-500/30"
1431
- };
1432
- return /* @__PURE__ */ jsxRuntime.jsxs(
1433
- "div",
1434
- {
1435
- className: cn(
1436
- "rounded-xl border bg-[var(--ash-surface-dark,#0a0a0a)] overflow-hidden ash-animate-fade-in",
1437
- borderClasses[groupStatus],
1438
- groupStatus === "pending" && "ash-tool-status-pending",
1439
- className
1440
- ),
1441
- children: [
1442
- /* @__PURE__ */ jsxRuntime.jsx(
1443
- "button",
1444
- {
1445
- onClick: () => setExpanded(!expanded),
1446
- className: cn(
1447
- "w-full transition-colors hover:bg-white/5 cursor-pointer"
1448
- ),
1449
- children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
1450
- /* @__PURE__ */ jsxRuntime.jsx(
1451
- CompactToolStatusLine,
1452
- {
1453
- toolCall: activeToolCall,
1454
- previousToolCall,
1455
- animationDuration,
1456
- className: "border-0 rounded-none"
1457
- }
1458
- ),
1459
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-2", children: [
1460
- totalCount > 1 && /* @__PURE__ */ jsxRuntime.jsx(
1461
- "div",
1462
- {
1463
- className: cn(
1464
- "flex items-center gap-1.5 px-2 py-0.5 rounded-full text-xs font-medium",
1465
- groupStatus === "pending" ? "bg-yellow-500/20 text-yellow-400" : groupStatus === "success" ? "bg-[var(--ash-accent)]/20 text-[var(--ash-accent)]" : groupStatus === "failed" ? "bg-red-500/20 text-red-400" : "bg-orange-500/20 text-orange-400"
1466
- ),
1467
- children: groupStatus === "pending" ? /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1468
- completedCount,
1469
- "/",
1470
- totalCount
1471
- ] }) : failedCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1472
- completedCount,
1473
- " ok, ",
1474
- failedCount,
1475
- " failed"
1476
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
1477
- totalCount,
1478
- " tools"
1479
- ] })
1480
- }
1481
- ),
1482
- /* @__PURE__ */ jsxRuntime.jsx(
1483
- ChevronDownIcon,
1484
- {
1485
- className: cn(
1486
- "w-4 h-4 text-white/40 transition-transform duration-200",
1487
- expanded && "rotate-180"
1488
- )
1489
- }
1490
- )
1491
- ] })
1492
- ] })
1493
- }
1494
- ),
1495
- expanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-white/5 bg-black/20", children: [
1496
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 space-y-2", children: toolCalls.map((toolCall, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
1497
- index < toolCalls.length - 1 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute left-[19px] top-10 bottom-0 w-px bg-white/10" }),
1498
- /* @__PURE__ */ jsxRuntime.jsx(
1499
- ToolCallCard,
1500
- {
1501
- toolCall,
1502
- defaultExpanded: false,
1503
- className: "relative z-10"
1504
- }
1505
- )
1506
- ] }, toolCall.id)) }),
1507
- groupStatus !== "pending" && /* @__PURE__ */ jsxRuntime.jsx(
1508
- "div",
1615
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ash-animate-fade-in", className), children: [
1616
+ /* @__PURE__ */ jsxRuntime.jsxs(
1617
+ "button",
1618
+ {
1619
+ onClick: () => setExpanded(!expanded),
1620
+ className: "w-full flex items-center gap-2 py-1 text-left group",
1621
+ children: [
1622
+ /* @__PURE__ */ jsxRuntime.jsx(
1623
+ ChevronRightIcon,
1509
1624
  {
1510
1625
  className: cn(
1511
- "px-4 py-3 border-t border-white/5 flex items-center gap-2",
1512
- groupStatus === "success" ? "bg-[var(--ash-accent)]/5" : groupStatus === "failed" ? "bg-red-500/5" : "bg-orange-500/5"
1513
- ),
1514
- children: groupStatus === "success" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1515
- /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { className: "w-4 h-4 text-[var(--ash-accent)]" }),
1516
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-[var(--ash-accent)] font-medium", children: [
1517
- "All ",
1518
- totalCount,
1519
- " tool",
1520
- totalCount !== 1 ? "s" : "",
1521
- " completed successfully"
1522
- ] })
1523
- ] }) : groupStatus === "failed" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1524
- /* @__PURE__ */ jsxRuntime.jsx(StatusIndicator, { status: "failed", size: "sm" }),
1525
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-red-400 font-medium", children: [
1526
- failedCount,
1527
- " tool",
1528
- failedCount !== 1 ? "s" : "",
1529
- " failed"
1530
- ] })
1531
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1532
- /* @__PURE__ */ jsxRuntime.jsx(StatusIndicator, { status: "failed", size: "sm" }),
1533
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-orange-400 font-medium", children: [
1534
- completedCount,
1535
- " succeeded, ",
1536
- failedCount,
1537
- " failed"
1538
- ] })
1539
- ] })
1626
+ "w-3.5 h-3.5 text-white/40 transition-transform duration-200 shrink-0",
1627
+ expanded && "rotate-90"
1628
+ )
1540
1629
  }
1541
- )
1542
- ] })
1543
- ]
1544
- }
1545
- );
1546
- }
1547
- function formatDuration(startMs, endMs) {
1548
- const duration = endMs - startMs;
1549
- if (duration < 1e3) return `${duration}ms`;
1550
- return `${(duration / 1e3).toFixed(1)}s`;
1630
+ ),
1631
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-white/60", children: [
1632
+ totalCount,
1633
+ " tool call",
1634
+ totalCount !== 1 ? "s" : ""
1635
+ ] }),
1636
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: displayActions.map((action) => {
1637
+ const IconComponent = getActionIconComponent(action);
1638
+ if (!IconComponent) return null;
1639
+ return /* @__PURE__ */ jsxRuntime.jsx(
1640
+ IconComponent,
1641
+ {
1642
+ className: "w-3.5 h-3.5 text-white/30"
1643
+ },
1644
+ action
1645
+ );
1646
+ }) }),
1647
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
1648
+ !expanded && fileChanges.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 flex-wrap justify-end", children: [
1649
+ fileChanges.slice(0, 4).map((fc) => /* @__PURE__ */ jsxRuntime.jsx(
1650
+ FileBadge,
1651
+ {
1652
+ path: fc.path,
1653
+ linesAdded: fc.linesAdded,
1654
+ linesRemoved: fc.linesRemoved
1655
+ },
1656
+ fc.path
1657
+ )),
1658
+ fileChanges.length > 4 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-white/40", children: [
1659
+ "+",
1660
+ fileChanges.length - 4,
1661
+ " more"
1662
+ ] })
1663
+ ] })
1664
+ ]
1665
+ }
1666
+ ),
1667
+ expanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pl-5 border-l border-white/10 ml-1.5 mt-1 space-y-0.5", children: toolCalls.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx("div", { children: expandedCardId === toolCall.id ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "py-1", children: [
1668
+ /* @__PURE__ */ jsxRuntime.jsx(
1669
+ ToolCallCard,
1670
+ {
1671
+ toolCall,
1672
+ defaultExpanded: true
1673
+ }
1674
+ ),
1675
+ /* @__PURE__ */ jsxRuntime.jsx(
1676
+ "button",
1677
+ {
1678
+ onClick: () => setExpandedCardId(null),
1679
+ className: "text-xs text-white/40 hover:text-white/60 mt-1 pl-1",
1680
+ children: "Collapse"
1681
+ }
1682
+ )
1683
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(
1684
+ "button",
1685
+ {
1686
+ onClick: () => setExpandedCardId(toolCall.id),
1687
+ className: "w-full text-left hover:bg-white/5 rounded px-1 -mx-1 transition-colors",
1688
+ children: /* @__PURE__ */ jsxRuntime.jsx(CompactToolRow, { toolCall })
1689
+ }
1690
+ ) }, toolCall.id)) })
1691
+ ] });
1551
1692
  }
1552
- function getToolLabel(toolName, summary) {
1553
- if (summary && summary.length > 0 && summary.length < 50) {
1554
- return summary;
1693
+ function extractFileChanges2(toolCalls) {
1694
+ const fileMap = /* @__PURE__ */ new Map();
1695
+ for (const tc of toolCalls) {
1696
+ const { actionType } = tc;
1697
+ if (actionType.action === "file_edit") {
1698
+ const edit = actionType;
1699
+ const existing = fileMap.get(edit.path);
1700
+ if (existing) {
1701
+ existing.linesAdded = (existing.linesAdded || 0) + (edit.linesAdded || 0);
1702
+ existing.linesRemoved = (existing.linesRemoved || 0) + (edit.linesRemoved || 0);
1703
+ } else {
1704
+ fileMap.set(edit.path, {
1705
+ path: edit.path,
1706
+ linesAdded: edit.linesAdded,
1707
+ linesRemoved: edit.linesRemoved
1708
+ });
1709
+ }
1710
+ } else if (actionType.action === "file_write") {
1711
+ if (!fileMap.has(actionType.path)) {
1712
+ fileMap.set(actionType.path, {
1713
+ path: actionType.path,
1714
+ linesAdded: actionType.linesWritten
1715
+ });
1716
+ }
1717
+ }
1555
1718
  }
1556
- const cleaned = toolName.replace(/^mcp__[^_]+__/, "").replace(/Tool$/, "").replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2");
1557
- return cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
1719
+ return Array.from(fileMap.values());
1558
1720
  }
1559
- function toStepStatus(status) {
1560
- switch (status) {
1561
- case "pending":
1562
- return "running";
1563
- case "success":
1564
- return "success";
1565
- case "failed":
1566
- return "error";
1567
- default:
1568
- return "pending";
1721
+ function countActionTypes2(toolCalls) {
1722
+ const counts = {};
1723
+ for (const tc of toolCalls) {
1724
+ const action = tc.actionType.action;
1725
+ counts[action] = (counts[action] || 0) + 1;
1569
1726
  }
1727
+ return counts;
1570
1728
  }
1571
- function StepIcon({ status }) {
1572
- const iconClass = "w-3.5 h-3.5";
1573
- switch (status) {
1574
- case "running":
1575
- return /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, { className: cn(iconClass, "animate-spin text-[var(--ash-accent)]") });
1576
- case "success":
1577
- return /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { className: cn(iconClass, "text-[var(--ash-accent)]") });
1578
- case "error":
1579
- return /* @__PURE__ */ jsxRuntime.jsx(ErrorIcon, { className: cn(iconClass, "text-red-500") });
1729
+ function getActionIconComponent2(action) {
1730
+ switch (action) {
1731
+ case "file_read":
1732
+ return FileIcon;
1733
+ case "file_edit":
1734
+ case "file_write":
1735
+ return EditIcon;
1736
+ case "command_run":
1737
+ return TerminalIcon;
1738
+ case "search":
1739
+ case "glob":
1740
+ return SearchIcon;
1580
1741
  default:
1581
- return /* @__PURE__ */ jsxRuntime.jsx(ToolIcon, { className: cn(iconClass, "text-white/40") });
1742
+ return null;
1582
1743
  }
1583
1744
  }
1584
1745
  function StepAccordion({
@@ -1597,109 +1758,70 @@ function StepAccordion({
1597
1758
  setInternalExpanded((prev) => !prev);
1598
1759
  }
1599
1760
  }, [onToggle]);
1761
+ const fileChanges = react.useMemo(() => extractFileChanges2(toolCalls), [toolCalls]);
1762
+ const actionCounts = react.useMemo(() => countActionTypes2(toolCalls), [toolCalls]);
1763
+ const displayActions = react.useMemo(() => {
1764
+ return Object.entries(actionCounts).sort((a, b) => b[1] - a[1]).slice(0, 3).map(([action]) => action);
1765
+ }, [actionCounts]);
1600
1766
  if (toolCalls.length === 0) {
1601
1767
  return null;
1602
1768
  }
1603
- const completedSteps = toolCalls.filter((tc) => tc.status === "success" || tc.status === "failed").length;
1604
- const runningStep = toolCalls.find((tc) => tc.status === "pending");
1605
- const hasError = toolCalls.some((tc) => tc.status === "failed");
1606
- const allComplete = completedSteps === toolCalls.length;
1607
- return /* @__PURE__ */ jsxRuntime.jsxs(
1608
- "div",
1609
- {
1610
- className: cn(
1611
- "rounded-xl border overflow-hidden",
1612
- hasError ? "border-red-500/30" : allComplete ? "border-[var(--ash-accent)]/30" : "border-yellow-500/30",
1613
- className
1614
- ),
1615
- children: [
1616
- /* @__PURE__ */ jsxRuntime.jsxs(
1617
- "button",
1618
- {
1619
- type: "button",
1620
- onClick: handleToggle,
1621
- className: "w-full flex items-center gap-2 px-3 py-2 bg-white/5 hover:bg-white/10 transition-colors text-left cursor-pointer",
1622
- children: [
1623
- /* @__PURE__ */ jsxRuntime.jsx(
1624
- ChevronDownIcon,
1625
- {
1626
- className: cn(
1627
- "w-4 h-4 text-white/40 transition-transform duration-200 flex-shrink-0",
1628
- !isExpanded && "-rotate-90"
1629
- )
1630
- }
1631
- ),
1632
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 min-w-0 flex items-center gap-2", children: runningStep ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1633
- /* @__PURE__ */ jsxRuntime.jsx(SpinnerIcon, { className: "w-3.5 h-3.5 animate-spin text-[var(--ash-accent)] flex-shrink-0" }),
1634
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-white/80 truncate", children: getToolLabel(runningStep.toolName, runningStep.summary) })
1635
- ] }) : hasError ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1636
- /* @__PURE__ */ jsxRuntime.jsx(ErrorIcon, { className: "w-3.5 h-3.5 text-red-500 flex-shrink-0" }),
1637
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-red-400 truncate", children: "Completed with errors" })
1638
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1639
- /* @__PURE__ */ jsxRuntime.jsx(CheckIcon, { className: "w-3.5 h-3.5 text-[var(--ash-accent)] flex-shrink-0" }),
1640
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-white/70 truncate", children: [
1641
- completedSteps,
1642
- " step",
1643
- completedSteps !== 1 ? "s" : "",
1644
- " completed"
1645
- ] })
1646
- ] }) }),
1647
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs px-1.5 py-0.5 rounded-full bg-white/10 text-white/50 flex-shrink-0", children: [
1648
- completedSteps,
1649
- "/",
1650
- toolCalls.length
1651
- ] })
1652
- ]
1653
- }
1654
- ),
1655
- isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-t border-white/10 ash-accordion-content", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-white/5", children: toolCalls.map((toolCall, index) => {
1656
- const stepStatus = toStepStatus(toolCall.status);
1657
- const startTime = toolCall.startedAt ? new Date(toolCall.startedAt).getTime() : 0;
1658
- const endTime = toolCall.completedAt ? new Date(toolCall.completedAt).getTime() : 0;
1659
- const hasDuration = startTime > 0 && endTime > 0;
1660
- return /* @__PURE__ */ jsxRuntime.jsxs(
1661
- "div",
1769
+ const totalCount = toolCalls.length;
1770
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("ash-animate-fade-in", className), children: [
1771
+ /* @__PURE__ */ jsxRuntime.jsxs(
1772
+ "button",
1773
+ {
1774
+ type: "button",
1775
+ onClick: handleToggle,
1776
+ className: "w-full flex items-center gap-2 py-1 text-left group",
1777
+ children: [
1778
+ /* @__PURE__ */ jsxRuntime.jsx(
1779
+ ChevronRightIcon,
1662
1780
  {
1663
1781
  className: cn(
1664
- "px-3 py-2 flex items-start gap-2",
1665
- stepStatus === "running" && "bg-[var(--ash-accent)]/5",
1666
- stepStatus === "error" && "bg-red-500/5"
1667
- ),
1668
- children: [
1669
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 flex-shrink-0 pt-0.5", children: [
1670
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-white/40 w-4 text-right", children: [
1671
- index + 1,
1672
- "."
1673
- ] }),
1674
- /* @__PURE__ */ jsxRuntime.jsx(StepIcon, { status: stepStatus })
1675
- ] }),
1676
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
1677
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1678
- /* @__PURE__ */ jsxRuntime.jsx(
1679
- "span",
1680
- {
1681
- className: cn(
1682
- "text-sm",
1683
- stepStatus === "running" && "text-[var(--ash-accent)]",
1684
- stepStatus === "success" && "text-white/70",
1685
- stepStatus === "error" && "text-red-400",
1686
- stepStatus === "pending" && "text-white/40"
1687
- ),
1688
- children: getToolLabel(toolCall.toolName, toolCall.summary)
1689
- }
1690
- ),
1691
- hasDuration && (stepStatus === "success" || stepStatus === "error") && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-white/40", children: formatDuration(startTime, endTime) })
1692
- ] }),
1693
- toolCall.isError && toolCall.actionType && "result" in toolCall.actionType && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs mt-1 text-red-400/80 truncate", children: String(toolCall.actionType.result?.value || "Error") })
1694
- ] })
1695
- ]
1696
- },
1697
- toolCall.id
1698
- );
1699
- }) }) })
1700
- ]
1701
- }
1702
- );
1782
+ "w-3.5 h-3.5 text-white/40 transition-transform duration-200 shrink-0",
1783
+ isExpanded && "rotate-90"
1784
+ )
1785
+ }
1786
+ ),
1787
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-white/60", children: [
1788
+ totalCount,
1789
+ " tool call",
1790
+ totalCount !== 1 ? "s" : ""
1791
+ ] }),
1792
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: displayActions.map((action) => {
1793
+ const IconComponent = getActionIconComponent2(action);
1794
+ if (!IconComponent) return null;
1795
+ return /* @__PURE__ */ jsxRuntime.jsx(
1796
+ IconComponent,
1797
+ {
1798
+ className: "w-3.5 h-3.5 text-white/30"
1799
+ },
1800
+ action
1801
+ );
1802
+ }) }),
1803
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1" }),
1804
+ !isExpanded && fileChanges.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 flex-wrap justify-end", children: [
1805
+ fileChanges.slice(0, 4).map((fc) => /* @__PURE__ */ jsxRuntime.jsx(
1806
+ FileBadge,
1807
+ {
1808
+ path: fc.path,
1809
+ linesAdded: fc.linesAdded,
1810
+ linesRemoved: fc.linesRemoved
1811
+ },
1812
+ fc.path
1813
+ )),
1814
+ fileChanges.length > 4 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-white/40", children: [
1815
+ "+",
1816
+ fileChanges.length - 4,
1817
+ " more"
1818
+ ] })
1819
+ ] })
1820
+ ]
1821
+ }
1822
+ ),
1823
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pl-5 border-l border-white/10 ml-1.5 mt-1 space-y-0.5", children: toolCalls.map((toolCall) => /* @__PURE__ */ jsxRuntime.jsx(CompactToolRow, { toolCall }, toolCall.id)) })
1824
+ ] });
1703
1825
  }
1704
1826
 
1705
1827
  // src/types.ts
@@ -1802,10 +1924,18 @@ function MessageList({
1802
1924
  onOptionSelect,
1803
1925
  renderWidget,
1804
1926
  onWidgetAction,
1927
+ autoScroll = true,
1805
1928
  className
1806
1929
  }) {
1807
1930
  const contextConfig = useDisplayConfig();
1808
1931
  const config = displayConfigProp || contextConfig;
1932
+ const containerRef = react.useRef(null);
1933
+ const messagesEndRef = react.useRef(null);
1934
+ react.useEffect(() => {
1935
+ if (autoScroll && messagesEndRef.current && containerRef.current) {
1936
+ messagesEndRef.current.scrollIntoView({ behavior: "smooth", block: "end" });
1937
+ }
1938
+ }, [entries, streamingContent, loading, autoScroll]);
1809
1939
  const createWidgetActionHandler = react.useCallback(
1810
1940
  (entryId, widgetType) => {
1811
1941
  if (!onWidgetAction) return void 0;
@@ -1829,7 +1959,7 @@ function MessageList({
1829
1959
  }
1830
1960
  return groupEntriesForCompactMode(entries, config);
1831
1961
  }, [entries, config]);
1832
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex-1 overflow-y-auto p-4 space-y-4 ash-scrollbar", className), children: [
1962
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { ref: containerRef, className: cn("flex-1 overflow-y-auto p-4 space-y-4 ash-scrollbar", className), children: [
1833
1963
  groupedEntries.map((groupedEntry) => {
1834
1964
  if (groupedEntry.type === "single") {
1835
1965
  const entry = groupedEntry.entry;
@@ -1873,7 +2003,8 @@ function MessageList({
1873
2003
  loading && !streamingContent && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 ash-animate-fade-in", children: [
1874
2004
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-7 h-7 rounded-full bg-[var(--ash-accent)]/20 flex items-center justify-center shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(BotIcon, { className: "w-4 h-4 text-[var(--ash-accent)]" }) }),
1875
2005
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl p-3 bg-white/5", children: /* @__PURE__ */ jsxRuntime.jsx(LoadingIndicator, { variant: "dots" }) })
1876
- ] })
2006
+ ] }),
2007
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: messagesEndRef })
1877
2008
  ] });
1878
2009
  }
1879
2010
  function getLevelIcon(level) {
@@ -2020,6 +2151,94 @@ function LogsPanel({
2020
2151
  }
2021
2152
  );
2022
2153
  }
2154
+ function CompactToolStatusLine({
2155
+ toolCall,
2156
+ previousToolCall,
2157
+ animationDuration = 300,
2158
+ className
2159
+ }) {
2160
+ const [isAnimating, setIsAnimating] = react.useState(false);
2161
+ const [displayedToolCall, setDisplayedToolCall] = react.useState(toolCall);
2162
+ const [exitingToolCall, setExitingToolCall] = react.useState(null);
2163
+ const prevToolCallRef = react.useRef(null);
2164
+ react.useEffect(() => {
2165
+ if (toolCall.id !== prevToolCallRef.current) {
2166
+ if (prevToolCallRef.current !== null && previousToolCall) {
2167
+ setExitingToolCall(previousToolCall);
2168
+ setIsAnimating(true);
2169
+ const timer = setTimeout(() => {
2170
+ setDisplayedToolCall(toolCall);
2171
+ setExitingToolCall(null);
2172
+ setIsAnimating(false);
2173
+ }, animationDuration);
2174
+ prevToolCallRef.current = toolCall.id;
2175
+ return () => clearTimeout(timer);
2176
+ } else {
2177
+ setDisplayedToolCall(toolCall);
2178
+ prevToolCallRef.current = toolCall.id;
2179
+ }
2180
+ } else {
2181
+ setDisplayedToolCall(toolCall);
2182
+ }
2183
+ return void 0;
2184
+ }, [toolCall, previousToolCall, animationDuration]);
2185
+ const statusClasses = {
2186
+ pending: "border-yellow-500/30",
2187
+ success: "border-[var(--ash-accent)]/30",
2188
+ failed: "border-red-500/30"
2189
+ };
2190
+ const renderToolCallContent = (tc, isExiting) => /* @__PURE__ */ jsxRuntime.jsxs(
2191
+ "div",
2192
+ {
2193
+ className: cn(
2194
+ "flex items-center gap-3 px-4 py-2.5",
2195
+ isExiting ? "ash-status-line-exit" : isAnimating ? "ash-status-line-enter" : ""
2196
+ ),
2197
+ style: {
2198
+ animationDuration: `${animationDuration}ms`
2199
+ },
2200
+ children: [
2201
+ /* @__PURE__ */ jsxRuntime.jsx(
2202
+ "div",
2203
+ {
2204
+ className: cn(
2205
+ "w-6 h-6 rounded-lg flex items-center justify-center shrink-0",
2206
+ tc.status === "pending" ? "bg-yellow-500/20" : tc.status === "failed" ? "bg-red-500/20" : "bg-[var(--ash-accent)]/20"
2207
+ ),
2208
+ children: /* @__PURE__ */ jsxRuntime.jsx(
2209
+ ActionIcon,
2210
+ {
2211
+ actionType: tc.actionType,
2212
+ className: cn(
2213
+ "w-3.5 h-3.5",
2214
+ tc.status === "pending" ? "text-yellow-400" : tc.status === "failed" ? "text-red-400" : "text-[var(--ash-accent)]"
2215
+ )
2216
+ }
2217
+ )
2218
+ }
2219
+ ),
2220
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-white shrink-0", children: getActionLabel(tc.actionType) }),
2221
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-sm truncate text-white/60 flex-1 min-w-0", children: tc.summary }),
2222
+ /* @__PURE__ */ jsxRuntime.jsx(StatusIndicator, { status: tc.status, size: "sm" })
2223
+ ]
2224
+ }
2225
+ );
2226
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2227
+ "div",
2228
+ {
2229
+ className: cn(
2230
+ "relative rounded-xl border bg-[var(--ash-surface-dark,#0a0a0a)] overflow-hidden",
2231
+ statusClasses[displayedToolCall.status],
2232
+ displayedToolCall.status === "pending" && "ash-tool-status-pending",
2233
+ className
2234
+ ),
2235
+ children: [
2236
+ exitingToolCall && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0", children: renderToolCallContent(exitingToolCall, true) }),
2237
+ renderToolCallContent(displayedToolCall, false)
2238
+ ]
2239
+ }
2240
+ );
2241
+ }
2023
2242
  function TodoStatusIcon({ status, className = "w-4 h-4" }) {
2024
2243
  switch (status) {
2025
2244
  case "completed":
@@ -2973,6 +3192,284 @@ function useFileUpload({
2973
3192
  openFilePicker
2974
3193
  };
2975
3194
  }
3195
+ function useAgentChat(options) {
3196
+ const {
3197
+ createStream,
3198
+ initialSessionId,
3199
+ initialEntries = [],
3200
+ onSessionStart,
3201
+ onSessionEnd,
3202
+ onError,
3203
+ onSandboxLog
3204
+ } = options;
3205
+ const [historyEntries, setHistoryEntries] = react.useState(initialEntries);
3206
+ const [streamingEntries, setStreamingEntries] = react.useState([]);
3207
+ const [isStreaming, setIsStreaming] = react.useState(false);
3208
+ const [error, setError] = react.useState(null);
3209
+ const [sessionId, setSessionId] = react.useState(initialSessionId || null);
3210
+ const abortControllerRef = react.useRef(null);
3211
+ const currentTextRef = react.useRef("");
3212
+ const currentTextIdRef = react.useRef(null);
3213
+ const pendingToolCallsRef = react.useRef(/* @__PURE__ */ new Map());
3214
+ const hadToolCallSinceTextRef = react.useRef(false);
3215
+ const entries = [...historyEntries, ...streamingEntries];
3216
+ const emitStreamingEntries = react.useCallback((newEntries) => {
3217
+ setStreamingEntries([...newEntries]);
3218
+ }, []);
3219
+ const createTextEntry = react.useCallback((id, content) => ({
3220
+ id,
3221
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3222
+ entryType: { type: "assistant_message" },
3223
+ content
3224
+ }), []);
3225
+ const resetStreamingState = react.useCallback(() => {
3226
+ currentTextRef.current = "";
3227
+ currentTextIdRef.current = null;
3228
+ pendingToolCallsRef.current.clear();
3229
+ hadToolCallSinceTextRef.current = false;
3230
+ setStreamingEntries([]);
3231
+ }, []);
3232
+ const processEvent = react.useCallback((event, streamEntries) => {
3233
+ const newEntries = [...streamEntries];
3234
+ switch (event.type) {
3235
+ case "session_start":
3236
+ if (event.sessionId) {
3237
+ setSessionId(event.sessionId);
3238
+ onSessionStart?.(event.sessionId);
3239
+ }
3240
+ break;
3241
+ case "text_delta":
3242
+ if (event.delta) {
3243
+ if (hadToolCallSinceTextRef.current && currentTextIdRef.current) {
3244
+ currentTextRef.current = "";
3245
+ currentTextIdRef.current = null;
3246
+ hadToolCallSinceTextRef.current = false;
3247
+ }
3248
+ currentTextRef.current += event.delta;
3249
+ if (!currentTextIdRef.current) {
3250
+ currentTextIdRef.current = `text-${Date.now()}-${Math.random().toString(36).slice(2)}`;
3251
+ newEntries.push(createTextEntry(currentTextIdRef.current, currentTextRef.current));
3252
+ } else {
3253
+ const entryIndex = newEntries.findIndex((e) => e.id === currentTextIdRef.current);
3254
+ if (entryIndex !== -1) {
3255
+ newEntries[entryIndex] = createTextEntry(currentTextIdRef.current, currentTextRef.current);
3256
+ }
3257
+ }
3258
+ }
3259
+ break;
3260
+ case "tool_use":
3261
+ if (event.toolId && event.toolName) {
3262
+ currentTextRef.current = "";
3263
+ currentTextIdRef.current = null;
3264
+ hadToolCallSinceTextRef.current = true;
3265
+ const toolCall = createToolCall({
3266
+ id: event.toolId,
3267
+ name: event.toolName,
3268
+ input: event.input
3269
+ });
3270
+ const entry = {
3271
+ id: event.toolId,
3272
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3273
+ entryType: { type: "tool_call", toolCall },
3274
+ content: toolCall.summary
3275
+ };
3276
+ const entryIndex = newEntries.length;
3277
+ newEntries.push(entry);
3278
+ pendingToolCallsRef.current.set(event.toolId, { entryIndex, toolCall });
3279
+ }
3280
+ break;
3281
+ case "tool_result":
3282
+ if (event.toolId) {
3283
+ const pending = pendingToolCallsRef.current.get(event.toolId);
3284
+ if (pending) {
3285
+ const updatedToolCall = updateToolCallWithResult(
3286
+ pending.toolCall,
3287
+ event.toolResult,
3288
+ event.isError
3289
+ );
3290
+ const existingEntry = newEntries[pending.entryIndex];
3291
+ if (existingEntry && existingEntry.entryType.type === "tool_call") {
3292
+ newEntries[pending.entryIndex] = {
3293
+ ...existingEntry,
3294
+ entryType: { type: "tool_call", toolCall: updatedToolCall }
3295
+ };
3296
+ }
3297
+ pendingToolCallsRef.current.delete(event.toolId);
3298
+ }
3299
+ }
3300
+ break;
3301
+ case "sandbox_log":
3302
+ if (event.entry) {
3303
+ onSandboxLog?.(event.entry);
3304
+ }
3305
+ break;
3306
+ case "error":
3307
+ if (event.error) {
3308
+ setError(event.error);
3309
+ onError?.(event.error);
3310
+ newEntries.push({
3311
+ id: `error-${Date.now()}`,
3312
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3313
+ entryType: { type: "error", message: event.error, code: event.code },
3314
+ content: event.error
3315
+ });
3316
+ }
3317
+ break;
3318
+ case "session_end":
3319
+ onSessionEnd?.(event.sessionId || sessionId || "", event.status || "completed");
3320
+ break;
3321
+ case "complete":
3322
+ if (newEntries.length > 0) {
3323
+ setHistoryEntries((prev) => [...prev, ...newEntries]);
3324
+ }
3325
+ resetStreamingState();
3326
+ return [];
3327
+ }
3328
+ return newEntries;
3329
+ }, [sessionId, onSessionStart, onSessionEnd, onError, onSandboxLog, createTextEntry, resetStreamingState]);
3330
+ const send = react.useCallback(async (prompt) => {
3331
+ if (isStreaming) return;
3332
+ setIsStreaming(true);
3333
+ setError(null);
3334
+ const userEntry = {
3335
+ id: `user-${Date.now()}`,
3336
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3337
+ entryType: { type: "user_message" },
3338
+ content: prompt
3339
+ };
3340
+ setHistoryEntries((prev) => [...prev, userEntry]);
3341
+ resetStreamingState();
3342
+ const controller = new AbortController();
3343
+ abortControllerRef.current = controller;
3344
+ let localStreamingEntries = [];
3345
+ try {
3346
+ const stream = createStream(prompt, sessionId || void 0);
3347
+ for await (const event of stream) {
3348
+ if (controller.signal.aborted) break;
3349
+ localStreamingEntries = processEvent(event, localStreamingEntries);
3350
+ emitStreamingEntries(localStreamingEntries);
3351
+ }
3352
+ if (localStreamingEntries.length > 0) {
3353
+ setHistoryEntries((prev) => [...prev, ...localStreamingEntries]);
3354
+ setStreamingEntries([]);
3355
+ }
3356
+ } catch (err) {
3357
+ if (err.name !== "AbortError") {
3358
+ const errorMessage = err instanceof Error ? err.message : "Unknown error";
3359
+ setError(errorMessage);
3360
+ onError?.(errorMessage);
3361
+ }
3362
+ } finally {
3363
+ setIsStreaming(false);
3364
+ abortControllerRef.current = null;
3365
+ resetStreamingState();
3366
+ }
3367
+ }, [isStreaming, sessionId, createStream, processEvent, emitStreamingEntries, resetStreamingState, onError]);
3368
+ const stop = react.useCallback(() => {
3369
+ if (abortControllerRef.current) {
3370
+ abortControllerRef.current.abort();
3371
+ }
3372
+ }, []);
3373
+ const clear = react.useCallback(() => {
3374
+ setHistoryEntries([]);
3375
+ resetStreamingState();
3376
+ setSessionId(initialSessionId || null);
3377
+ setError(null);
3378
+ }, [initialSessionId, resetStreamingState]);
3379
+ const setEntries = react.useCallback((newEntries) => {
3380
+ resetStreamingState();
3381
+ setHistoryEntries(newEntries);
3382
+ }, [resetStreamingState]);
3383
+ react.useEffect(() => {
3384
+ return () => {
3385
+ if (abortControllerRef.current) {
3386
+ abortControllerRef.current.abort();
3387
+ }
3388
+ };
3389
+ }, []);
3390
+ return {
3391
+ entries,
3392
+ isStreaming,
3393
+ error,
3394
+ sessionId,
3395
+ send,
3396
+ stop,
3397
+ clear,
3398
+ setEntries
3399
+ };
3400
+ }
3401
+ function textToBase64(text) {
3402
+ const encoder = new TextEncoder();
3403
+ const bytes = encoder.encode(text);
3404
+ let binary = "";
3405
+ for (let i = 0; i < bytes.length; i++) {
3406
+ const byte = bytes[i];
3407
+ if (byte !== void 0) {
3408
+ binary += String.fromCharCode(byte);
3409
+ }
3410
+ }
3411
+ return btoa(binary);
3412
+ }
3413
+ function generateFilename(template) {
3414
+ const timestamp = Date.now();
3415
+ const date = new Date(timestamp);
3416
+ const dateStr = date.toISOString().split("T")[0] ?? "unknown-date";
3417
+ const timeStr = (date.toTimeString().split(" ")[0] ?? "00-00-00").replace(/:/g, "-");
3418
+ return template.replace("{timestamp}", String(timestamp)).replace("{date}", dateStr).replace("{time}", timeStr);
3419
+ }
3420
+ function useLongTextConversion({
3421
+ threshold = 5e3,
3422
+ filenameTemplate = "pasted-text-{timestamp}.txt",
3423
+ onConversion
3424
+ } = {}) {
3425
+ const [lastConversion, setLastConversion] = react.useState(null);
3426
+ const processText = react.useCallback((text) => {
3427
+ if (text.length <= threshold) {
3428
+ return { text };
3429
+ }
3430
+ const filename = generateFilename(filenameTemplate);
3431
+ const content = textToBase64(text);
3432
+ const encoder = new TextEncoder();
3433
+ const size = encoder.encode(text).length;
3434
+ const lineCount = text.split("\n").length;
3435
+ const attachment = {
3436
+ filename,
3437
+ content,
3438
+ mimeType: "text/plain",
3439
+ size
3440
+ };
3441
+ const preview = text.slice(0, 100) + (text.length > 100 ? "..." : "");
3442
+ const conversionInfo = {
3443
+ originalLength: text.length,
3444
+ filename,
3445
+ preview,
3446
+ lineCount
3447
+ };
3448
+ setLastConversion(conversionInfo);
3449
+ onConversion?.(conversionInfo);
3450
+ return { text: "", attachment };
3451
+ }, [threshold, filenameTemplate, onConversion]);
3452
+ const handlePaste = react.useCallback((event, _currentValue, _setValue, addAttachment) => {
3453
+ const pastedText = event.clipboardData.getData("text/plain");
3454
+ if (pastedText.length > threshold) {
3455
+ event.preventDefault();
3456
+ const result = processText(pastedText);
3457
+ if (result.attachment) {
3458
+ addAttachment(result.attachment);
3459
+ }
3460
+ }
3461
+ }, [threshold, processText]);
3462
+ const clearLastConversion = react.useCallback(() => {
3463
+ setLastConversion(null);
3464
+ }, []);
3465
+ return {
3466
+ processText,
3467
+ handlePaste,
3468
+ lastConversion,
3469
+ clearLastConversion,
3470
+ threshold
3471
+ };
3472
+ }
2976
3473
 
2977
3474
  exports.ActionIcon = ActionIcon;
2978
3475
  exports.AlertCircleIcon = AlertCircleIcon;
@@ -2991,6 +3488,7 @@ exports.CircleIcon = CircleIcon;
2991
3488
  exports.ClipboardListIcon = ClipboardListIcon;
2992
3489
  exports.CodeBlock = CodeBlock;
2993
3490
  exports.CodeIcon = CodeIcon;
3491
+ exports.CompactToolRow = CompactToolRow;
2994
3492
  exports.CompactToolStatusLine = CompactToolStatusLine;
2995
3493
  exports.CopyIcon = CopyIcon;
2996
3494
  exports.DEFAULT_DISPLAY_CONFIG = DEFAULT_DISPLAY_CONFIG;
@@ -3000,6 +3498,7 @@ exports.EditIcon = EditIcon;
3000
3498
  exports.EnvVarsPanel = EnvVarsPanel;
3001
3499
  exports.ErrorIcon = ErrorIcon;
3002
3500
  exports.ErrorMessage = ErrorMessage;
3501
+ exports.FileBadge = FileBadge;
3003
3502
  exports.FileIcon = FileIcon;
3004
3503
  exports.FilePlusIcon = FilePlusIcon;
3005
3504
  exports.FolderSearchIcon = FolderSearchIcon;
@@ -3082,9 +3581,11 @@ exports.transitions = transitions;
3082
3581
  exports.truncate = truncate;
3083
3582
  exports.typography = typography;
3084
3583
  exports.updateToolCallWithResult = updateToolCallWithResult;
3584
+ exports.useAgentChat = useAgentChat;
3085
3585
  exports.useDisplayConfig = useDisplayConfig;
3086
3586
  exports.useDisplayMode = useDisplayMode;
3087
3587
  exports.useFileUpload = useFileUpload;
3588
+ exports.useLongTextConversion = useLongTextConversion;
3088
3589
  exports.useMessageQueue = useMessageQueue;
3089
3590
  exports.useStopExecution = useStopExecution;
3090
3591
  exports.useTheme = useTheme;