@camstack/ui-library 0.1.25 → 0.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1273,8 +1273,30 @@ function ProviderBadge({
1273
1273
  ] });
1274
1274
  }
1275
1275
 
1276
+ // src/composites/version-badge.tsx
1277
+ import { jsx as jsx22 } from "react/jsx-runtime";
1278
+ var VARIANT_STYLES = {
1279
+ success: "bg-emerald-400 text-emerald-950",
1280
+ warning: "bg-amber-400 text-amber-950",
1281
+ danger: "bg-red-400 text-red-950",
1282
+ info: "bg-blue-400 text-blue-950",
1283
+ neutral: "bg-foreground-subtle/20 text-foreground"
1284
+ };
1285
+ function SemanticBadge({ children, variant = "neutral", mono, className }) {
1286
+ return /* @__PURE__ */ jsx22("span", { className: cn(
1287
+ "inline-flex items-center rounded-md px-2 py-0.5 text-[11px] font-bold leading-tight",
1288
+ mono && "font-mono",
1289
+ VARIANT_STYLES[variant],
1290
+ className
1291
+ ), children });
1292
+ }
1293
+ function VersionBadge({ version, preRelease, className }) {
1294
+ const isPreRelease = preRelease ?? /-(alpha|beta|rc|dev|canary|next)/i.test(version);
1295
+ return /* @__PURE__ */ jsx22(SemanticBadge, { variant: isPreRelease ? "warning" : "success", mono: true, className, children: version });
1296
+ }
1297
+
1276
1298
  // src/composites/form-field.tsx
1277
- import { jsx as jsx22, jsxs as jsxs8 } from "react/jsx-runtime";
1299
+ import { jsx as jsx23, jsxs as jsxs8 } from "react/jsx-runtime";
1278
1300
  function FormField({
1279
1301
  label,
1280
1302
  description,
@@ -1297,31 +1319,31 @@ function FormField({
1297
1319
  /* @__PURE__ */ jsxs8("div", { className: cn(isHorizontal ? "flex-1" : ""), children: [
1298
1320
  /* @__PURE__ */ jsxs8(Label, { children: [
1299
1321
  label,
1300
- required && /* @__PURE__ */ jsx22("span", { className: "text-danger ml-0.5", children: "*" })
1322
+ required && /* @__PURE__ */ jsx23("span", { className: "text-danger ml-0.5", children: "*" })
1301
1323
  ] }),
1302
- description && /* @__PURE__ */ jsx22("p", { className: "text-foreground-subtle text-xs mt-0.5", children: description })
1324
+ description && /* @__PURE__ */ jsx23("p", { className: "text-foreground-subtle text-xs mt-0.5", children: description })
1303
1325
  ] }),
1304
- /* @__PURE__ */ jsx22("div", { className: cn(isHorizontal ? "shrink-0" : ""), children }),
1305
- error && /* @__PURE__ */ jsx22("p", { className: "text-danger text-xs", children: error })
1326
+ /* @__PURE__ */ jsx23("div", { className: cn(isHorizontal ? "shrink-0" : ""), children }),
1327
+ error && /* @__PURE__ */ jsx23("p", { className: "text-danger text-xs", children: error })
1306
1328
  ]
1307
1329
  }
1308
1330
  );
1309
1331
  }
1310
1332
 
1311
1333
  // src/composites/page-header.tsx
1312
- import { jsx as jsx23, jsxs as jsxs9 } from "react/jsx-runtime";
1334
+ import { jsx as jsx24, jsxs as jsxs9 } from "react/jsx-runtime";
1313
1335
  function PageHeader({ title, subtitle, actions, className }) {
1314
1336
  return /* @__PURE__ */ jsxs9("div", { className: cn("flex items-center justify-between mb-3", className), children: [
1315
1337
  /* @__PURE__ */ jsxs9("div", { children: [
1316
- /* @__PURE__ */ jsx23("h1", { className: "text-sm font-semibold text-foreground", children: title }),
1317
- subtitle && /* @__PURE__ */ jsx23("p", { className: "text-foreground-subtle text-xs", children: subtitle })
1338
+ /* @__PURE__ */ jsx24("h1", { className: "text-sm font-semibold text-foreground", children: title }),
1339
+ subtitle && /* @__PURE__ */ jsx24("p", { className: "text-foreground-subtle text-xs", children: subtitle })
1318
1340
  ] }),
1319
- actions && /* @__PURE__ */ jsx23("div", { className: "flex items-center gap-2", children: actions })
1341
+ actions && /* @__PURE__ */ jsx24("div", { className: "flex items-center gap-2", children: actions })
1320
1342
  ] });
1321
1343
  }
1322
1344
 
1323
1345
  // src/composites/empty-state.tsx
1324
- import { jsx as jsx24, jsxs as jsxs10 } from "react/jsx-runtime";
1346
+ import { jsx as jsx25, jsxs as jsxs10 } from "react/jsx-runtime";
1325
1347
  function EmptyState({
1326
1348
  icon: Icon,
1327
1349
  title,
@@ -1330,17 +1352,17 @@ function EmptyState({
1330
1352
  className
1331
1353
  }) {
1332
1354
  return /* @__PURE__ */ jsxs10("div", { className: cn("flex flex-col items-center justify-center gap-3 py-12", className), children: [
1333
- Icon && /* @__PURE__ */ jsx24(Icon, { className: "h-12 w-12 text-foreground-subtle", "aria-hidden": "true" }),
1355
+ Icon && /* @__PURE__ */ jsx25(Icon, { className: "h-12 w-12 text-foreground-subtle", "aria-hidden": "true" }),
1334
1356
  /* @__PURE__ */ jsxs10("div", { className: "flex flex-col items-center gap-1 text-center", children: [
1335
- /* @__PURE__ */ jsx24("p", { className: "text-foreground-muted text-sm font-medium", children: title }),
1336
- description && /* @__PURE__ */ jsx24("p", { className: "text-foreground-subtle text-xs max-w-xs", children: description })
1357
+ /* @__PURE__ */ jsx25("p", { className: "text-foreground-muted text-sm font-medium", children: title }),
1358
+ description && /* @__PURE__ */ jsx25("p", { className: "text-foreground-subtle text-xs max-w-xs", children: description })
1337
1359
  ] }),
1338
- action && /* @__PURE__ */ jsx24("div", { className: "mt-1", children: action })
1360
+ action && /* @__PURE__ */ jsx25("div", { className: "mt-1", children: action })
1339
1361
  ] });
1340
1362
  }
1341
1363
 
1342
1364
  // src/composites/confirm-dialog.tsx
1343
- import { jsx as jsx25, jsxs as jsxs11 } from "react/jsx-runtime";
1365
+ import { jsx as jsx26, jsxs as jsxs11 } from "react/jsx-runtime";
1344
1366
  function ConfirmDialog({
1345
1367
  title,
1346
1368
  message,
@@ -1352,14 +1374,14 @@ function ConfirmDialog({
1352
1374
  open,
1353
1375
  onOpenChange
1354
1376
  }) {
1355
- return /* @__PURE__ */ jsx25(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs11(DialogContent, { children: [
1377
+ return /* @__PURE__ */ jsx26(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs11(DialogContent, { children: [
1356
1378
  /* @__PURE__ */ jsxs11(DialogHeader, { children: [
1357
- /* @__PURE__ */ jsx25(DialogTitle, { children: title }),
1358
- /* @__PURE__ */ jsx25(DialogDescription, { children: message })
1379
+ /* @__PURE__ */ jsx26(DialogTitle, { children: title }),
1380
+ /* @__PURE__ */ jsx26(DialogDescription, { children: message })
1359
1381
  ] }),
1360
1382
  /* @__PURE__ */ jsxs11(DialogFooter, { children: [
1361
- /* @__PURE__ */ jsx25(Button, { variant: "ghost", onClick: onCancel, children: cancelLabel }),
1362
- /* @__PURE__ */ jsx25(
1383
+ /* @__PURE__ */ jsx26(Button, { variant: "ghost", onClick: onCancel, children: cancelLabel }),
1384
+ /* @__PURE__ */ jsx26(
1363
1385
  Button,
1364
1386
  {
1365
1387
  variant: variant === "danger" ? "danger" : "primary",
@@ -1373,11 +1395,11 @@ function ConfirmDialog({
1373
1395
 
1374
1396
  // src/composites/stat-card.tsx
1375
1397
  import { TrendingUp, TrendingDown } from "lucide-react";
1376
- import { jsx as jsx26, jsxs as jsxs12 } from "react/jsx-runtime";
1398
+ import { jsx as jsx27, jsxs as jsxs12 } from "react/jsx-runtime";
1377
1399
  function StatCard({ value, label, trend, className }) {
1378
1400
  return /* @__PURE__ */ jsxs12(Card, { className: cn("flex flex-col gap-1", className), children: [
1379
1401
  /* @__PURE__ */ jsxs12("div", { className: "flex items-baseline gap-2", children: [
1380
- /* @__PURE__ */ jsx26("span", { className: "text-2xl font-semibold text-foreground", children: value }),
1402
+ /* @__PURE__ */ jsx27("span", { className: "text-2xl font-semibold text-foreground", children: value }),
1381
1403
  trend && /* @__PURE__ */ jsxs12(
1382
1404
  "span",
1383
1405
  {
@@ -1386,27 +1408,27 @@ function StatCard({ value, label, trend, className }) {
1386
1408
  trend.direction === "up" ? "text-success" : "text-danger"
1387
1409
  ),
1388
1410
  children: [
1389
- trend.direction === "up" ? /* @__PURE__ */ jsx26(TrendingUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx26(TrendingDown, { className: "h-3 w-3" }),
1411
+ trend.direction === "up" ? /* @__PURE__ */ jsx27(TrendingUp, { className: "h-3 w-3" }) : /* @__PURE__ */ jsx27(TrendingDown, { className: "h-3 w-3" }),
1390
1412
  trend.value,
1391
1413
  "%"
1392
1414
  ]
1393
1415
  }
1394
1416
  )
1395
1417
  ] }),
1396
- /* @__PURE__ */ jsx26("span", { className: "text-xs text-foreground-muted", children: label })
1418
+ /* @__PURE__ */ jsx27("span", { className: "text-xs text-foreground-muted", children: label })
1397
1419
  ] });
1398
1420
  }
1399
1421
 
1400
1422
  // src/composites/key-value-list.tsx
1401
- import { jsx as jsx27, jsxs as jsxs13 } from "react/jsx-runtime";
1423
+ import { jsx as jsx28, jsxs as jsxs13 } from "react/jsx-runtime";
1402
1424
  function KeyValueList({ items, className }) {
1403
- return /* @__PURE__ */ jsx27("dl", { className: cn("flex flex-col", className), children: items.map((item) => /* @__PURE__ */ jsxs13(
1425
+ return /* @__PURE__ */ jsx28("dl", { className: cn("flex flex-col", className), children: items.map((item) => /* @__PURE__ */ jsxs13(
1404
1426
  "div",
1405
1427
  {
1406
1428
  className: "flex items-center h-7",
1407
1429
  children: [
1408
- /* @__PURE__ */ jsx27("dt", { className: "text-foreground-subtle text-xs w-1/3 shrink-0", children: item.key }),
1409
- /* @__PURE__ */ jsx27("dd", { className: "text-foreground text-xs", children: item.value })
1430
+ /* @__PURE__ */ jsx28("dt", { className: "text-foreground-subtle text-xs w-1/3 shrink-0", children: item.key }),
1431
+ /* @__PURE__ */ jsx28("dd", { className: "text-foreground text-xs", children: item.value })
1410
1432
  ]
1411
1433
  },
1412
1434
  item.key
@@ -1416,7 +1438,7 @@ function KeyValueList({ items, className }) {
1416
1438
  // src/composites/code-block.tsx
1417
1439
  import { useCallback as useCallback7, useState as useState8 } from "react";
1418
1440
  import { Copy, Check } from "lucide-react";
1419
- import { jsx as jsx28, jsxs as jsxs14 } from "react/jsx-runtime";
1441
+ import { jsx as jsx29, jsxs as jsxs14 } from "react/jsx-runtime";
1420
1442
  function CodeBlock({ children, maxHeight = 300, className }) {
1421
1443
  const [copied, setCopied] = useState8(false);
1422
1444
  const handleCopy = useCallback7(() => {
@@ -1426,8 +1448,8 @@ function CodeBlock({ children, maxHeight = 300, className }) {
1426
1448
  });
1427
1449
  }, [children]);
1428
1450
  return /* @__PURE__ */ jsxs14("div", { className: cn("relative group", className), children: [
1429
- /* @__PURE__ */ jsx28(ScrollArea, { style: { maxHeight }, children: /* @__PURE__ */ jsx28("pre", { className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle", children: /* @__PURE__ */ jsx28("code", { children }) }) }),
1430
- /* @__PURE__ */ jsx28("div", { className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsx28(
1451
+ /* @__PURE__ */ jsx29(ScrollArea, { style: { maxHeight }, children: /* @__PURE__ */ jsx29("pre", { className: "font-mono text-xs bg-surface p-3 rounded-md border border-border-subtle", children: /* @__PURE__ */ jsx29("code", { children }) }) }),
1452
+ /* @__PURE__ */ jsx29("div", { className: "absolute top-2 right-2 opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsx29(
1431
1453
  IconButton,
1432
1454
  {
1433
1455
  icon: copied ? Check : Copy,
@@ -1442,27 +1464,27 @@ function CodeBlock({ children, maxHeight = 300, className }) {
1442
1464
 
1443
1465
  // src/composites/filter-bar.tsx
1444
1466
  import { Search } from "lucide-react";
1445
- import { jsx as jsx29 } from "react/jsx-runtime";
1467
+ import { jsx as jsx30 } from "react/jsx-runtime";
1446
1468
  function FilterBar({ filters, values, onChange, className }) {
1447
1469
  const handleChange = (key, value) => {
1448
1470
  onChange({ ...values, [key]: value });
1449
1471
  };
1450
- return /* @__PURE__ */ jsx29("div", { className: cn("flex items-center gap-2 flex-wrap", className), children: filters.map((filter) => {
1472
+ return /* @__PURE__ */ jsx30("div", { className: cn("flex items-center gap-2 flex-wrap", className), children: filters.map((filter) => {
1451
1473
  switch (filter.type) {
1452
1474
  case "search":
1453
- return /* @__PURE__ */ jsx29(
1475
+ return /* @__PURE__ */ jsx30(
1454
1476
  Input,
1455
1477
  {
1456
1478
  placeholder: filter.placeholder ?? "Search...",
1457
1479
  value: values[filter.key] ?? "",
1458
1480
  onChange: (e) => handleChange(filter.key, e.target.value),
1459
- leftSlot: /* @__PURE__ */ jsx29(Search, { className: "h-3 w-3 text-foreground-subtle" }),
1481
+ leftSlot: /* @__PURE__ */ jsx30(Search, { className: "h-3 w-3 text-foreground-subtle" }),
1460
1482
  className: "w-48"
1461
1483
  },
1462
1484
  filter.key
1463
1485
  );
1464
1486
  case "select":
1465
- return /* @__PURE__ */ jsx29(
1487
+ return /* @__PURE__ */ jsx30(
1466
1488
  Select,
1467
1489
  {
1468
1490
  options: filter.options,
@@ -1473,10 +1495,10 @@ function FilterBar({ filters, values, onChange, className }) {
1473
1495
  filter.key
1474
1496
  );
1475
1497
  case "badge-toggle":
1476
- return /* @__PURE__ */ jsx29("div", { className: "flex items-center gap-1", children: filter.options.map((option) => {
1498
+ return /* @__PURE__ */ jsx30("div", { className: "flex items-center gap-1", children: filter.options.map((option) => {
1477
1499
  const currentValue = values[filter.key];
1478
1500
  const isActive = currentValue === option.value;
1479
- return /* @__PURE__ */ jsx29(
1501
+ return /* @__PURE__ */ jsx30(
1480
1502
  "button",
1481
1503
  {
1482
1504
  type: "button",
@@ -1484,7 +1506,7 @@ function FilterBar({ filters, values, onChange, className }) {
1484
1506
  filter.key,
1485
1507
  isActive ? void 0 : option.value
1486
1508
  ),
1487
- children: /* @__PURE__ */ jsx29(
1509
+ children: /* @__PURE__ */ jsx30(
1488
1510
  Badge,
1489
1511
  {
1490
1512
  variant: isActive ? "info" : "default",
@@ -1503,7 +1525,7 @@ function FilterBar({ filters, values, onChange, className }) {
1503
1525
  }
1504
1526
 
1505
1527
  // src/composites/app-shell/sidebar-item.tsx
1506
- import { jsx as jsx30, jsxs as jsxs15 } from "react/jsx-runtime";
1528
+ import { jsx as jsx31, jsxs as jsxs15 } from "react/jsx-runtime";
1507
1529
  function SidebarItem({
1508
1530
  label,
1509
1531
  icon: Icon,
@@ -1522,16 +1544,16 @@ function SidebarItem({
1522
1544
  className
1523
1545
  ),
1524
1546
  children: [
1525
- /* @__PURE__ */ jsx30(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
1526
- /* @__PURE__ */ jsx30("span", { className: "truncate flex-1", children: label }),
1527
- badge !== void 0 && /* @__PURE__ */ jsx30(Badge, { className: "ml-auto text-[10px] px-1.5 py-0", children: badge })
1547
+ /* @__PURE__ */ jsx31(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
1548
+ /* @__PURE__ */ jsx31("span", { className: "truncate flex-1", children: label }),
1549
+ badge !== void 0 && /* @__PURE__ */ jsx31(Badge, { className: "ml-auto text-[10px] px-1.5 py-0", children: badge })
1528
1550
  ]
1529
1551
  }
1530
1552
  );
1531
1553
  }
1532
1554
 
1533
1555
  // src/composites/app-shell/sidebar.tsx
1534
- import { jsx as jsx31, jsxs as jsxs16 } from "react/jsx-runtime";
1556
+ import { jsx as jsx32, jsxs as jsxs16 } from "react/jsx-runtime";
1535
1557
  function Sidebar({ logo, sections, footer, className }) {
1536
1558
  return /* @__PURE__ */ jsxs16(
1537
1559
  "nav",
@@ -1541,14 +1563,14 @@ function Sidebar({ logo, sections, footer, className }) {
1541
1563
  className
1542
1564
  ),
1543
1565
  children: [
1544
- logo && /* @__PURE__ */ jsx31("div", { className: "px-3 py-2 shrink-0", children: logo }),
1545
- /* @__PURE__ */ jsx31("div", { className: "flex-1 overflow-auto px-1 py-1", children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs16("div", { className: cn(sectionIndex > 0 ? "mt-3" : ""), children: [
1546
- section.label && /* @__PURE__ */ jsx31("span", { className: "text-[10px] text-foreground-disabled uppercase tracking-wider px-2 mb-1 block", children: section.label }),
1547
- /* @__PURE__ */ jsx31("div", { className: "flex flex-col gap-0.5", children: section.items.map((item) => /* @__PURE__ */ jsx31(SidebarItem, { ...item }, item.href)) })
1566
+ logo && /* @__PURE__ */ jsx32("div", { className: "px-3 py-2 shrink-0", children: logo }),
1567
+ /* @__PURE__ */ jsx32("div", { className: "flex-1 overflow-auto px-1 py-1", children: sections.map((section, sectionIndex) => /* @__PURE__ */ jsxs16("div", { className: cn(sectionIndex > 0 ? "mt-3" : ""), children: [
1568
+ section.label && /* @__PURE__ */ jsx32("span", { className: "text-[10px] text-foreground-disabled uppercase tracking-wider px-2 mb-1 block", children: section.label }),
1569
+ /* @__PURE__ */ jsx32("div", { className: "flex flex-col gap-0.5", children: section.items.map((item) => /* @__PURE__ */ jsx32(SidebarItem, { ...item }, item.href)) })
1548
1570
  ] }, sectionIndex)) }),
1549
1571
  footer && footer.length > 0 && /* @__PURE__ */ jsxs16("div", { className: "shrink-0 px-1 pb-1", children: [
1550
- /* @__PURE__ */ jsx31(Separator, { className: "mb-1" }),
1551
- /* @__PURE__ */ jsx31("div", { className: "flex flex-col gap-0.5", children: footer.map((item) => /* @__PURE__ */ jsx31(SidebarItem, { ...item }, item.href)) })
1572
+ /* @__PURE__ */ jsx32(Separator, { className: "mb-1" }),
1573
+ /* @__PURE__ */ jsx32("div", { className: "flex flex-col gap-0.5", children: footer.map((item) => /* @__PURE__ */ jsx32(SidebarItem, { ...item }, item.href)) })
1552
1574
  ] })
1553
1575
  ]
1554
1576
  }
@@ -1557,29 +1579,29 @@ function Sidebar({ logo, sections, footer, className }) {
1557
1579
 
1558
1580
  // src/composites/app-shell/app-shell.tsx
1559
1581
  import { ChevronRight } from "lucide-react";
1560
- import { jsx as jsx32, jsxs as jsxs17 } from "react/jsx-runtime";
1582
+ import { jsx as jsx33, jsxs as jsxs17 } from "react/jsx-runtime";
1561
1583
  function AppShell({ sidebar, header, children, className }) {
1562
1584
  return /* @__PURE__ */ jsxs17("div", { className: cn("flex h-screen", className), children: [
1563
- /* @__PURE__ */ jsx32(Sidebar, { ...sidebar }),
1585
+ /* @__PURE__ */ jsx33(Sidebar, { ...sidebar }),
1564
1586
  /* @__PURE__ */ jsxs17("div", { className: "flex flex-1 flex-col min-w-0", children: [
1565
1587
  header && /* @__PURE__ */ jsxs17("header", { className: "flex items-center h-10 border-b border-border px-4 shrink-0", children: [
1566
- header.breadcrumbs && header.breadcrumbs.length > 0 && /* @__PURE__ */ jsx32("nav", { className: "flex items-center gap-1 text-xs flex-1 min-w-0", children: header.breadcrumbs.map((crumb, index) => {
1588
+ header.breadcrumbs && header.breadcrumbs.length > 0 && /* @__PURE__ */ jsx33("nav", { className: "flex items-center gap-1 text-xs flex-1 min-w-0", children: header.breadcrumbs.map((crumb, index) => {
1567
1589
  const isLast = index === header.breadcrumbs.length - 1;
1568
1590
  return /* @__PURE__ */ jsxs17("span", { className: "flex items-center gap-1", children: [
1569
- index > 0 && /* @__PURE__ */ jsx32(ChevronRight, { className: "h-3 w-3 text-foreground-subtle shrink-0" }),
1570
- crumb.href && !isLast ? /* @__PURE__ */ jsx32(
1591
+ index > 0 && /* @__PURE__ */ jsx33(ChevronRight, { className: "h-3 w-3 text-foreground-subtle shrink-0" }),
1592
+ crumb.href && !isLast ? /* @__PURE__ */ jsx33(
1571
1593
  "a",
1572
1594
  {
1573
1595
  href: crumb.href,
1574
1596
  className: "text-foreground-subtle hover:text-foreground transition-colors truncate",
1575
1597
  children: crumb.label
1576
1598
  }
1577
- ) : /* @__PURE__ */ jsx32("span", { className: "text-foreground truncate", children: crumb.label })
1599
+ ) : /* @__PURE__ */ jsx33("span", { className: "text-foreground truncate", children: crumb.label })
1578
1600
  ] }, index);
1579
1601
  }) }),
1580
- header.actions && /* @__PURE__ */ jsx32("div", { className: "flex items-center gap-2 ml-auto shrink-0", children: header.actions })
1602
+ header.actions && /* @__PURE__ */ jsx33("div", { className: "flex items-center gap-2 ml-auto shrink-0", children: header.actions })
1581
1603
  ] }),
1582
- /* @__PURE__ */ jsx32("main", { className: "flex-1 overflow-auto p-4", children })
1604
+ /* @__PURE__ */ jsx33("main", { className: "flex-1 overflow-auto p-4", children })
1583
1605
  ] })
1584
1606
  ] });
1585
1607
  }
@@ -1597,20 +1619,20 @@ import {
1597
1619
 
1598
1620
  // src/composites/data-table/data-table-header.tsx
1599
1621
  import { ArrowUpDown, ArrowUp, ArrowDown } from "lucide-react";
1600
- import { jsx as jsx33, jsxs as jsxs18 } from "react/jsx-runtime";
1622
+ import { jsx as jsx34, jsxs as jsxs18 } from "react/jsx-runtime";
1601
1623
  function DataTableHeader({
1602
1624
  headerGroups,
1603
1625
  onSortingChange,
1604
1626
  stickyHeader,
1605
1627
  flexRender: render
1606
1628
  }) {
1607
- return /* @__PURE__ */ jsx33(
1629
+ return /* @__PURE__ */ jsx34(
1608
1630
  "thead",
1609
1631
  {
1610
1632
  className: cn(
1611
1633
  stickyHeader && "sticky top-0 z-10 bg-background"
1612
1634
  ),
1613
- children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx33("tr", { className: "h-6", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx33(
1635
+ children: headerGroups.map((headerGroup) => /* @__PURE__ */ jsx34("tr", { className: "h-6", children: headerGroup.headers.map((header) => /* @__PURE__ */ jsx34(
1614
1636
  HeaderCell,
1615
1637
  {
1616
1638
  header,
@@ -1625,7 +1647,7 @@ function DataTableHeader({
1625
1647
  function HeaderCell({ header, sortable, flexRender: render }) {
1626
1648
  const sorted = header.column.getIsSorted();
1627
1649
  const SortIcon = sorted === "asc" ? ArrowUp : sorted === "desc" ? ArrowDown : ArrowUpDown;
1628
- return /* @__PURE__ */ jsx33(
1650
+ return /* @__PURE__ */ jsx34(
1629
1651
  "th",
1630
1652
  {
1631
1653
  className: cn(
@@ -1635,7 +1657,7 @@ function HeaderCell({ header, sortable, flexRender: render }) {
1635
1657
  onClick: sortable ? header.column.getToggleSortingHandler() : void 0,
1636
1658
  children: /* @__PURE__ */ jsxs18("span", { className: "inline-flex items-center gap-1", children: [
1637
1659
  header.isPlaceholder ? null : render(header.column.columnDef.header, header.getContext()),
1638
- sortable && /* @__PURE__ */ jsx33(SortIcon, { className: "h-3 w-3" })
1660
+ sortable && /* @__PURE__ */ jsx34(SortIcon, { className: "h-3 w-3" })
1639
1661
  ] })
1640
1662
  }
1641
1663
  );
@@ -1643,7 +1665,7 @@ function HeaderCell({ header, sortable, flexRender: render }) {
1643
1665
 
1644
1666
  // src/composites/data-table/data-table-row.tsx
1645
1667
  import { MoreHorizontal } from "lucide-react";
1646
- import { jsx as jsx34, jsxs as jsxs19 } from "react/jsx-runtime";
1668
+ import { jsx as jsx35, jsxs as jsxs19 } from "react/jsx-runtime";
1647
1669
  function DataTableRow({
1648
1670
  row,
1649
1671
  onRowClick,
@@ -1661,17 +1683,17 @@ function DataTableRow({
1661
1683
  ),
1662
1684
  onClick: onRowClick ? () => onRowClick(row.original) : void 0,
1663
1685
  children: [
1664
- row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx34(DataTableCell, { cell, flexRender: render }, cell.id)),
1665
- actions.length > 0 && /* @__PURE__ */ jsx34("td", { className: "px-2 py-1.5 w-8", children: /* @__PURE__ */ jsxs19(Dropdown, { children: [
1666
- /* @__PURE__ */ jsx34(
1686
+ row.getVisibleCells().map((cell) => /* @__PURE__ */ jsx35(DataTableCell, { cell, flexRender: render }, cell.id)),
1687
+ actions.length > 0 && /* @__PURE__ */ jsx35("td", { className: "px-2 py-1.5 w-8", children: /* @__PURE__ */ jsxs19(Dropdown, { children: [
1688
+ /* @__PURE__ */ jsx35(
1667
1689
  DropdownTrigger,
1668
1690
  {
1669
1691
  className: "p-0.5 rounded hover:bg-surface-hover",
1670
1692
  onClick: (e) => e.stopPropagation(),
1671
- children: /* @__PURE__ */ jsx34(MoreHorizontal, { className: "h-3.5 w-3.5 text-foreground-muted" })
1693
+ children: /* @__PURE__ */ jsx35(MoreHorizontal, { className: "h-3.5 w-3.5 text-foreground-muted" })
1672
1694
  }
1673
1695
  ),
1674
- /* @__PURE__ */ jsx34(DropdownContent, { className: "right-0 left-auto", children: actions.map((action) => /* @__PURE__ */ jsx34(
1696
+ /* @__PURE__ */ jsx35(DropdownContent, { className: "right-0 left-auto", children: actions.map((action) => /* @__PURE__ */ jsx35(
1675
1697
  DropdownItem,
1676
1698
  {
1677
1699
  icon: action.icon,
@@ -1690,12 +1712,12 @@ function DataTableRow({
1690
1712
  );
1691
1713
  }
1692
1714
  function DataTableCell({ cell, flexRender: render }) {
1693
- return /* @__PURE__ */ jsx34("td", { className: "px-2 py-1.5 text-xs text-foreground", children: render(cell.column.columnDef.cell, cell.getContext()) });
1715
+ return /* @__PURE__ */ jsx35("td", { className: "px-2 py-1.5 text-xs text-foreground", children: render(cell.column.columnDef.cell, cell.getContext()) });
1694
1716
  }
1695
1717
 
1696
1718
  // src/composites/data-table/data-table-pagination.tsx
1697
1719
  import { ChevronLeft, ChevronRight as ChevronRight2 } from "lucide-react";
1698
- import { jsx as jsx35, jsxs as jsxs20 } from "react/jsx-runtime";
1720
+ import { jsx as jsx36, jsxs as jsxs20 } from "react/jsx-runtime";
1699
1721
  var PAGE_SIZE_OPTIONS = [
1700
1722
  { value: "10", label: "10" },
1701
1723
  { value: "25", label: "25" },
@@ -1712,8 +1734,8 @@ function DataTablePagination({
1712
1734
  const currentPage = page + 1;
1713
1735
  return /* @__PURE__ */ jsxs20("div", { className: "flex items-center justify-between px-2 py-2 text-xs text-foreground-muted", children: [
1714
1736
  /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
1715
- /* @__PURE__ */ jsx35("span", { children: "Rows per page" }),
1716
- /* @__PURE__ */ jsx35("div", { className: "w-16", children: /* @__PURE__ */ jsx35(
1737
+ /* @__PURE__ */ jsx36("span", { children: "Rows per page" }),
1738
+ /* @__PURE__ */ jsx36("div", { className: "w-16", children: /* @__PURE__ */ jsx36(
1717
1739
  Select,
1718
1740
  {
1719
1741
  options: PAGE_SIZE_OPTIONS,
@@ -1732,7 +1754,7 @@ function DataTablePagination({
1732
1754
  " of ",
1733
1755
  totalPages
1734
1756
  ] }),
1735
- /* @__PURE__ */ jsx35(
1757
+ /* @__PURE__ */ jsx36(
1736
1758
  IconButton,
1737
1759
  {
1738
1760
  icon: ChevronLeft,
@@ -1743,7 +1765,7 @@ function DataTablePagination({
1743
1765
  onClick: () => onPaginationChange?.({ pageIndex: page - 1, pageSize })
1744
1766
  }
1745
1767
  ),
1746
- /* @__PURE__ */ jsx35(
1768
+ /* @__PURE__ */ jsx36(
1747
1769
  IconButton,
1748
1770
  {
1749
1771
  icon: ChevronRight2,
@@ -1759,7 +1781,7 @@ function DataTablePagination({
1759
1781
  }
1760
1782
 
1761
1783
  // src/composites/data-table/data-table.tsx
1762
- import { Fragment, jsx as jsx36, jsxs as jsxs21 } from "react/jsx-runtime";
1784
+ import { Fragment, jsx as jsx37, jsxs as jsxs21 } from "react/jsx-runtime";
1763
1785
  function DataTable({
1764
1786
  data,
1765
1787
  columns: userColumns,
@@ -1782,7 +1804,7 @@ function DataTable({
1782
1804
  if (!selectable) return userColumns;
1783
1805
  const selectColumn = {
1784
1806
  id: "__select",
1785
- header: ({ table: table2 }) => /* @__PURE__ */ jsx36(
1807
+ header: ({ table: table2 }) => /* @__PURE__ */ jsx37(
1786
1808
  Checkbox,
1787
1809
  {
1788
1810
  checked: table2.getIsAllPageRowsSelected(),
@@ -1790,7 +1812,7 @@ function DataTable({
1790
1812
  "aria-label": "Select all"
1791
1813
  }
1792
1814
  ),
1793
- cell: ({ row }) => /* @__PURE__ */ jsx36(
1815
+ cell: ({ row }) => /* @__PURE__ */ jsx37(
1794
1816
  Checkbox,
1795
1817
  {
1796
1818
  checked: row.getIsSelected(),
@@ -1830,7 +1852,7 @@ function DataTable({
1830
1852
  const hasActions = !!rowActions;
1831
1853
  return /* @__PURE__ */ jsxs21("div", { className: cn("overflow-auto", className), children: [
1832
1854
  /* @__PURE__ */ jsxs21("table", { className: "w-full border-collapse", children: [
1833
- /* @__PURE__ */ jsx36(
1855
+ /* @__PURE__ */ jsx37(
1834
1856
  DataTableHeader,
1835
1857
  {
1836
1858
  headerGroups: table.getHeaderGroups(),
@@ -1839,14 +1861,14 @@ function DataTable({
1839
1861
  flexRender
1840
1862
  }
1841
1863
  ),
1842
- /* @__PURE__ */ jsx36("tbody", { children: loading ? /* @__PURE__ */ jsx36(LoadingRows, { colSpan: columns.length + (hasActions ? 1 : 0), compact }) : table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx36("tr", { children: /* @__PURE__ */ jsx36(
1864
+ /* @__PURE__ */ jsx37("tbody", { children: loading ? /* @__PURE__ */ jsx37(LoadingRows, { colSpan: columns.length + (hasActions ? 1 : 0), compact }) : table.getRowModel().rows.length === 0 ? /* @__PURE__ */ jsx37("tr", { children: /* @__PURE__ */ jsx37(
1843
1865
  "td",
1844
1866
  {
1845
1867
  colSpan: columns.length + (hasActions ? 1 : 0),
1846
1868
  className: "text-center py-8 text-xs text-foreground-muted",
1847
1869
  children: emptyState ?? "No data"
1848
1870
  }
1849
- ) }) : table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx36(
1871
+ ) }) : table.getRowModel().rows.map((row) => /* @__PURE__ */ jsx37(
1850
1872
  DataTableRow,
1851
1873
  {
1852
1874
  row,
@@ -1857,7 +1879,7 @@ function DataTable({
1857
1879
  row.id
1858
1880
  )) })
1859
1881
  ] }),
1860
- pagination && /* @__PURE__ */ jsx36(
1882
+ pagination && /* @__PURE__ */ jsx37(
1861
1883
  DataTablePagination,
1862
1884
  {
1863
1885
  page: pagination.page,
@@ -1869,11 +1891,11 @@ function DataTable({
1869
1891
  ] });
1870
1892
  }
1871
1893
  function LoadingRows({ colSpan, compact }) {
1872
- return /* @__PURE__ */ jsx36(Fragment, { children: Array.from({ length: 5 }).map((_, rowIdx) => /* @__PURE__ */ jsx36("tr", { className: compact ? "h-7" : "h-9", children: Array.from({ length: colSpan }).map((_2, colIdx) => /* @__PURE__ */ jsx36("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsx36(Skeleton, { className: "h-3 w-full" }) }, colIdx)) }, rowIdx)) });
1894
+ return /* @__PURE__ */ jsx37(Fragment, { children: Array.from({ length: 5 }).map((_, rowIdx) => /* @__PURE__ */ jsx37("tr", { className: compact ? "h-7" : "h-9", children: Array.from({ length: colSpan }).map((_2, colIdx) => /* @__PURE__ */ jsx37("td", { className: "px-2 py-1.5", children: /* @__PURE__ */ jsx37(Skeleton, { className: "h-3 w-full" }) }, colIdx)) }, rowIdx)) });
1873
1895
  }
1874
1896
 
1875
1897
  // src/composites/device-card.tsx
1876
- import { jsx as jsx37, jsxs as jsxs22 } from "react/jsx-runtime";
1898
+ import { jsx as jsx38, jsxs as jsxs22 } from "react/jsx-runtime";
1877
1899
  var STATUS_COLORS = {
1878
1900
  online: "bg-success",
1879
1901
  offline: "bg-danger",
@@ -1905,11 +1927,11 @@ function DeviceCard({
1905
1927
  ),
1906
1928
  children: [
1907
1929
  /* @__PURE__ */ jsxs22("div", { className: "flex items-center justify-between mb-2", children: [
1908
- /* @__PURE__ */ jsx37("span", { className: "text-sm font-medium truncate", children: title }),
1909
- status && /* @__PURE__ */ jsx37("span", { className: cn("h-2 w-2 rounded-full shrink-0", STATUS_COLORS[status]) })
1930
+ /* @__PURE__ */ jsx38("span", { className: "text-sm font-medium truncate", children: title }),
1931
+ status && /* @__PURE__ */ jsx38("span", { className: cn("h-2 w-2 rounded-full shrink-0", STATUS_COLORS[status]) })
1910
1932
  ] }),
1911
- subtitle && /* @__PURE__ */ jsx37("div", { className: "text-[11px] text-foreground-muted", children: subtitle }),
1912
- badges && badges.length > 0 && /* @__PURE__ */ jsx37("div", { className: "flex flex-wrap gap-1 mt-2", children: badges.map((badge, i) => {
1933
+ subtitle && /* @__PURE__ */ jsx38("div", { className: "text-[11px] text-foreground-muted", children: subtitle }),
1934
+ badges && badges.length > 0 && /* @__PURE__ */ jsx38("div", { className: "flex flex-wrap gap-1 mt-2", children: badges.map((badge, i) => {
1913
1935
  const cls = cn(
1914
1936
  "rounded px-1.5 py-0.5 text-[10px] flex items-center gap-0.5",
1915
1937
  selected ? "bg-primary/20" : "bg-surface-hover",
@@ -1934,7 +1956,7 @@ function DeviceCard({
1934
1956
  badge.label
1935
1957
  ] }, i);
1936
1958
  }) }),
1937
- !isOffline && actions && actions.length > 0 && /* @__PURE__ */ jsx37("div", { className: "flex items-center gap-0.5 mt-2 -mb-1", children: actions.map((action, i) => /* @__PURE__ */ jsx37(
1959
+ !isOffline && actions && actions.length > 0 && /* @__PURE__ */ jsx38("div", { className: "flex items-center gap-0.5 mt-2 -mb-1", children: actions.map((action, i) => /* @__PURE__ */ jsx38(
1938
1960
  "button",
1939
1961
  {
1940
1962
  onClick: (e) => {
@@ -1948,21 +1970,21 @@ function DeviceCard({
1948
1970
  },
1949
1971
  i
1950
1972
  )) }),
1951
- isOffline && offlineAction && /* @__PURE__ */ jsx37("div", { className: "mt-2", onClick: (e) => e.stopPropagation(), children: offlineAction })
1973
+ isOffline && offlineAction && /* @__PURE__ */ jsx38("div", { className: "mt-2", onClick: (e) => e.stopPropagation(), children: offlineAction })
1952
1974
  ]
1953
1975
  }
1954
1976
  );
1955
1977
  }
1956
1978
 
1957
1979
  // src/composites/device-grid.tsx
1958
- import { jsx as jsx38 } from "react/jsx-runtime";
1980
+ import { jsx as jsx39 } from "react/jsx-runtime";
1959
1981
  function DeviceGrid({
1960
1982
  children,
1961
1983
  minCardWidth = 220,
1962
1984
  gap = 3,
1963
1985
  className
1964
1986
  }) {
1965
- return /* @__PURE__ */ jsx38(
1987
+ return /* @__PURE__ */ jsx39(
1966
1988
  "div",
1967
1989
  {
1968
1990
  className: cn(
@@ -1978,15 +2000,1715 @@ function DeviceGrid({
1978
2000
  }
1979
2001
  );
1980
2002
  }
2003
+
2004
+ // src/composites/pipeline-step.tsx
2005
+ import { useState as useState9 } from "react";
2006
+ import { ChevronRight as ChevronRight3, ChevronDown as ChevronDown2 } from "lucide-react";
2007
+ import { jsx as jsx40, jsxs as jsxs23 } from "react/jsx-runtime";
2008
+ var ADDON_COLORS = {
2009
+ "object-detection": "border-l-blue-500",
2010
+ "motion-detection": "border-l-amber-500",
2011
+ "face-detection": "border-l-purple-500",
2012
+ "face-recognition": "border-l-violet-500",
2013
+ "plate-detection": "border-l-pink-500",
2014
+ "plate-recognition": "border-l-rose-500",
2015
+ "animal-classifier": "border-l-lime-500",
2016
+ "bird-nabirds-classifier": "border-l-emerald-500",
2017
+ "bird-global-classifier": "border-l-teal-500",
2018
+ "audio-classification": "border-l-green-500",
2019
+ "segmentation-refiner": "border-l-cyan-500"
2020
+ };
2021
+ var SLOT_FALLBACK = {
2022
+ detector: "border-l-blue-500",
2023
+ cropper: "border-l-purple-500",
2024
+ classifier: "border-l-lime-500",
2025
+ refiner: "border-l-cyan-500"
2026
+ };
2027
+ function borderColor(addonId, slot) {
2028
+ return ADDON_COLORS[addonId] ?? SLOT_FALLBACK[slot] ?? "border-l-primary";
2029
+ }
2030
+ var BACKEND_FORMAT = {
2031
+ cpu: "onnx",
2032
+ coreml: "coreml",
2033
+ openvino: "openvino",
2034
+ cuda: "onnx",
2035
+ tensorrt: "onnx",
2036
+ "onnx-py": "onnx",
2037
+ pytorch: "pt"
2038
+ };
2039
+ function backendsForRuntime(runtime, caps, schema) {
2040
+ const allBackends = runtime === "node" ? caps.runtimes.node.backends : caps.runtimes.python.backends;
2041
+ if (!schema) return allBackends;
2042
+ const availableFormats = /* @__PURE__ */ new Set();
2043
+ for (const m of schema.models) {
2044
+ for (const fmt of Object.keys(m.formats)) {
2045
+ availableFormats.add(fmt);
2046
+ }
2047
+ }
2048
+ if (runtime === "node") availableFormats.add("onnx");
2049
+ return allBackends.map((b) => {
2050
+ const neededFormat = BACKEND_FORMAT[b.id] ?? "onnx";
2051
+ const hasFormat = availableFormats.has(neededFormat);
2052
+ return { ...b, available: b.available && hasFormat };
2053
+ });
2054
+ }
2055
+ function modelsForStep(schema, runtime, backend, caps) {
2056
+ if (!schema) return [];
2057
+ const fmt = runtime === "node" ? "onnx" : caps.runtimes.python.backends.find((b) => b.id === backend)?.modelFormat ?? "onnx";
2058
+ return schema.models.filter((m) => m.formats[fmt]).map((m) => ({
2059
+ id: m.id,
2060
+ name: m.name,
2061
+ downloaded: m.formats[fmt]?.downloaded ?? false
2062
+ }));
2063
+ }
2064
+ function runtimeOptions(caps) {
2065
+ const opts = [
2066
+ { id: "node", label: "Node.js (ONNX)", available: true }
2067
+ ];
2068
+ if (caps.runtimes.python.available && caps.runtimes.python.backends.some((b) => b.available)) {
2069
+ opts.unshift({ id: "python", label: "Python", available: true });
2070
+ }
2071
+ return opts;
2072
+ }
2073
+ function PipelineStep({
2074
+ step,
2075
+ schema,
2076
+ allSchemas,
2077
+ capabilities,
2078
+ depth = 0,
2079
+ onChange,
2080
+ onDelete,
2081
+ readOnly = false
2082
+ }) {
2083
+ const [expanded, setExpanded] = useState9(false);
2084
+ const color = borderColor(step.addonId, step.slot);
2085
+ const backends = backendsForRuntime(step.runtime, capabilities, schema);
2086
+ const rtOptions = runtimeOptions(capabilities);
2087
+ const currentBackendAvailable = backends.find((b) => b.id === step.backend)?.available ?? false;
2088
+ if (!currentBackendAvailable && !readOnly) {
2089
+ const firstAvailable = backends.find((b) => b.available);
2090
+ if (firstAvailable && firstAvailable.id !== step.backend) {
2091
+ queueMicrotask(() => onChange({ ...step, backend: firstAvailable.id }));
2092
+ }
2093
+ }
2094
+ const models = modelsForStep(schema, step.runtime, step.backend, capabilities);
2095
+ if (models.length > 0 && !models.some((m) => m.id === step.modelId) && !readOnly) {
2096
+ const defaultModel = schema?.defaultModelId ? models.find((m) => m.id === schema.defaultModelId) : null;
2097
+ const fallback = defaultModel ?? models[0];
2098
+ if (fallback && fallback.id !== step.modelId) {
2099
+ queueMicrotask(() => onChange({ ...step, modelId: fallback.id }));
2100
+ }
2101
+ }
2102
+ function handleClick(e) {
2103
+ if (e.target.closest(".step-config")) return;
2104
+ setExpanded((v) => !v);
2105
+ }
2106
+ return /* @__PURE__ */ jsx40("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs23(
2107
+ "div",
2108
+ {
2109
+ className: cn(
2110
+ "rounded-xl border border-border bg-surface overflow-hidden border-l-4 shadow-sm",
2111
+ color,
2112
+ !step.enabled && "opacity-[0.45]"
2113
+ ),
2114
+ children: [
2115
+ /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2.5 px-3 py-2.5 cursor-pointer select-none", onClick: handleClick, children: [
2116
+ /* @__PURE__ */ jsx40("span", { className: "text-foreground-subtle", children: expanded ? /* @__PURE__ */ jsx40(ChevronDown2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx40(ChevronRight3, { className: "h-4 w-4" }) }),
2117
+ /* @__PURE__ */ jsxs23("div", { className: "flex-1 min-w-0", children: [
2118
+ /* @__PURE__ */ jsx40("span", { className: "text-[10px] uppercase tracking-wider font-medium text-foreground-subtle/60 block leading-none", children: step.slot }),
2119
+ /* @__PURE__ */ jsx40("span", { className: "text-sm font-semibold text-foreground truncate block leading-tight", children: step.addonName }),
2120
+ /* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1 mt-0.5 flex-wrap", children: [
2121
+ step.inputClasses.map((c) => /* @__PURE__ */ jsx40("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-blue-500/12 text-blue-400", children: c }, c)),
2122
+ step.inputClasses.length > 0 && step.outputClasses.length > 0 && /* @__PURE__ */ jsx40("span", { className: "text-foreground-subtle/40 text-[10px]", children: "\u2192" }),
2123
+ step.outputClasses.map((c) => /* @__PURE__ */ jsx40("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-green-500/12 text-green-400", children: c }, c))
2124
+ ] })
2125
+ ] }),
2126
+ /* @__PURE__ */ jsx40(
2127
+ "button",
2128
+ {
2129
+ onClick: (e) => {
2130
+ e.stopPropagation();
2131
+ onChange({ ...step, enabled: !step.enabled });
2132
+ },
2133
+ className: cn(
2134
+ "relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors",
2135
+ step.enabled ? "bg-success" : "bg-foreground-subtle/30"
2136
+ ),
2137
+ children: /* @__PURE__ */ jsx40("span", { className: cn(
2138
+ "inline-block h-4 w-4 rounded-full bg-white shadow transition-transform",
2139
+ step.enabled ? "translate-x-6" : "translate-x-1"
2140
+ ) })
2141
+ }
2142
+ )
2143
+ ] }),
2144
+ expanded && /* @__PURE__ */ jsxs23("div", { className: "step-config border-t border-border bg-background px-4 py-4 space-y-3", children: [
2145
+ /* @__PURE__ */ jsxs23("div", { className: "grid grid-cols-2 gap-3", children: [
2146
+ /* @__PURE__ */ jsx40(
2147
+ ConfigSelect,
2148
+ {
2149
+ label: "Agent",
2150
+ value: step.agentId,
2151
+ disabled: readOnly,
2152
+ options: [{ value: step.agentId || "hub", label: step.agentId || "Hub (local)" }],
2153
+ onChange: (v) => onChange({ ...step, agentId: v })
2154
+ }
2155
+ ),
2156
+ /* @__PURE__ */ jsx40(
2157
+ ConfigSelect,
2158
+ {
2159
+ label: "Runtime",
2160
+ value: step.runtime,
2161
+ disabled: readOnly,
2162
+ options: rtOptions.map((r) => ({ value: r.id, label: r.label, disabled: !r.available })),
2163
+ onChange: (v) => onChange({ ...step, runtime: v })
2164
+ }
2165
+ ),
2166
+ /* @__PURE__ */ jsx40(
2167
+ ConfigSelect,
2168
+ {
2169
+ label: "Backend",
2170
+ value: step.backend,
2171
+ disabled: readOnly,
2172
+ options: backends.map((b) => ({ value: b.id, label: b.label, disabled: !b.available })),
2173
+ onChange: (v) => onChange({ ...step, backend: v })
2174
+ }
2175
+ ),
2176
+ /* @__PURE__ */ jsx40(
2177
+ ConfigSelect,
2178
+ {
2179
+ label: "Model",
2180
+ value: step.modelId,
2181
+ disabled: readOnly,
2182
+ options: models.map((m) => ({ value: m.id, label: `${m.name}${m.downloaded ? " \u2713" : ""}` })),
2183
+ onChange: (v) => onChange({ ...step, modelId: v })
2184
+ }
2185
+ )
2186
+ ] }),
2187
+ /* @__PURE__ */ jsxs23("div", { children: [
2188
+ /* @__PURE__ */ jsxs23("div", { className: "flex items-center justify-between mb-1", children: [
2189
+ /* @__PURE__ */ jsx40("span", { className: "text-[10px] font-medium text-foreground-subtle uppercase tracking-wide", children: "Confidence" }),
2190
+ /* @__PURE__ */ jsxs23("span", { className: "text-xs font-medium text-foreground tabular-nums", children: [
2191
+ (step.confidence * 100).toFixed(0),
2192
+ "%"
2193
+ ] })
2194
+ ] }),
2195
+ /* @__PURE__ */ jsx40(
2196
+ "input",
2197
+ {
2198
+ type: "range",
2199
+ min: 0,
2200
+ max: 1,
2201
+ step: 0.01,
2202
+ value: step.confidence,
2203
+ disabled: readOnly,
2204
+ onChange: (e) => onChange({ ...step, confidence: Number(e.target.value) }),
2205
+ className: "w-full accent-primary h-1.5"
2206
+ }
2207
+ )
2208
+ ] })
2209
+ ] })
2210
+ ]
2211
+ }
2212
+ ) });
2213
+ }
2214
+ function ConfigSelect({ label, value, options, disabled, onChange }) {
2215
+ return /* @__PURE__ */ jsxs23("div", { children: [
2216
+ /* @__PURE__ */ jsx40("label", { className: "block text-[10px] font-medium text-foreground-subtle uppercase tracking-wide mb-1.5", children: label }),
2217
+ /* @__PURE__ */ jsxs23(
2218
+ "select",
2219
+ {
2220
+ value,
2221
+ onChange: (e) => onChange(e.target.value),
2222
+ disabled,
2223
+ className: "w-full rounded-lg border border-border bg-surface px-3 py-2 text-xs text-foreground focus:outline-none focus:border-primary/50",
2224
+ children: [
2225
+ options.length === 0 && /* @__PURE__ */ jsx40("option", { value, children: value || "default" }),
2226
+ options.map((o) => /* @__PURE__ */ jsx40("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
2227
+ ]
2228
+ }
2229
+ )
2230
+ ] });
2231
+ }
2232
+
2233
+ // src/composites/pipeline-runtime-selector.tsx
2234
+ import { Cpu, Star } from "lucide-react";
2235
+ import { jsx as jsx41, jsxs as jsxs24 } from "react/jsx-runtime";
2236
+ function PipelineRuntimeSelector({ options, value, onChange }) {
2237
+ return /* @__PURE__ */ jsx41("div", { className: "flex flex-wrap gap-2", children: options.map((opt) => {
2238
+ const active = opt.id === value;
2239
+ return /* @__PURE__ */ jsxs24(
2240
+ "button",
2241
+ {
2242
+ onClick: () => opt.available && onChange(opt.id),
2243
+ disabled: !opt.available,
2244
+ className: `flex items-center gap-2 rounded-lg border px-3 py-2 text-xs font-medium transition-all ${active ? "border-primary/40 bg-primary/10 text-primary" : opt.available ? "border-border bg-surface text-foreground-subtle hover:bg-surface-hover hover:text-foreground" : "border-border/40 bg-surface/40 text-foreground-subtle/40 cursor-not-allowed"}`,
2245
+ children: [
2246
+ /* @__PURE__ */ jsx41(Cpu, { className: "h-3.5 w-3.5 shrink-0" }),
2247
+ opt.label,
2248
+ opt.isBest && /* @__PURE__ */ jsxs24("span", { className: "inline-flex items-center gap-0.5 rounded-full bg-amber-500/15 px-1.5 py-0.5 text-[10px] font-semibold text-amber-400", children: [
2249
+ /* @__PURE__ */ jsx41(Star, { className: "h-2.5 w-2.5" }),
2250
+ "Best"
2251
+ ] }),
2252
+ opt.platformScore != null && /* @__PURE__ */ jsxs24("span", { className: "text-[10px] text-foreground-subtle/60", children: [
2253
+ "(",
2254
+ opt.platformScore,
2255
+ ")"
2256
+ ] }),
2257
+ /* @__PURE__ */ jsx41(
2258
+ "span",
2259
+ {
2260
+ className: `h-1.5 w-1.5 rounded-full ${opt.available ? "bg-success" : "bg-danger"}`
2261
+ }
2262
+ )
2263
+ ]
2264
+ },
2265
+ opt.id
2266
+ );
2267
+ }) });
2268
+ }
2269
+
2270
+ // src/composites/pipeline-builder.tsx
2271
+ import { useMemo as useMemo3, useState as useState10 } from "react";
2272
+ import { Save, CopyPlus, Trash2, PlusCircle, X as X2 } from "lucide-react";
2273
+
2274
+ // src/lib/validate-template.ts
2275
+ function validateTemplate(steps, schema) {
2276
+ const availableAddonIds = new Set(
2277
+ schema.slots.flatMap((s) => s.addons.map((a) => a.id))
2278
+ );
2279
+ const warnings = [];
2280
+ function validateStep(step) {
2281
+ if (!availableAddonIds.has(step.addonId)) {
2282
+ warnings.push(`Addon "${step.addonId}" is no longer available \u2014 step removed`);
2283
+ return null;
2284
+ }
2285
+ const addon = schema.slots.flatMap((s) => s.addons).find((a) => a.id === step.addonId);
2286
+ let modelId = step.modelId;
2287
+ if (addon && !addon.models.some((m) => m.id === modelId)) {
2288
+ const fallback = addon.defaultModelId;
2289
+ warnings.push(`Model "${modelId}" not available for ${step.addonId} \u2014 using "${fallback}"`);
2290
+ modelId = fallback;
2291
+ }
2292
+ const validChildren = step.children.map((c) => validateStep(c)).filter((c) => c !== null);
2293
+ return { ...step, modelId, children: validChildren };
2294
+ }
2295
+ const validSteps = steps.map((s) => validateStep(s)).filter((s) => s !== null);
2296
+ return {
2297
+ valid: warnings.length === 0,
2298
+ steps: validSteps,
2299
+ warnings
2300
+ };
2301
+ }
2302
+
2303
+ // src/composites/pipeline-builder.tsx
2304
+ import { jsx as jsx42, jsxs as jsxs25 } from "react/jsx-runtime";
2305
+ function buildSchemaMap(schema) {
2306
+ const map = /* @__PURE__ */ new Map();
2307
+ for (const slot of schema.slots) {
2308
+ for (const addon of slot.addons) {
2309
+ map.set(addon.id, addon);
2310
+ }
2311
+ }
2312
+ return map;
2313
+ }
2314
+ function createDefaultStep(addon, fallbackRuntime, fallbackBackend) {
2315
+ return {
2316
+ addonId: addon.id,
2317
+ addonName: addon.name,
2318
+ slot: addon.slot,
2319
+ inputClasses: [...addon.inputClasses],
2320
+ outputClasses: [...addon.outputClasses],
2321
+ enabled: true,
2322
+ agentId: "hub",
2323
+ // Use per-addon defaults from backend PlatformScorer, fallback to provided
2324
+ runtime: addon.defaultRuntime ?? fallbackRuntime ?? "node",
2325
+ backend: addon.defaultBackend ?? fallbackBackend ?? "cpu",
2326
+ modelId: addon.defaultModelId,
2327
+ confidence: addon.defaultConfidence,
2328
+ classFilters: [...addon.inputClasses],
2329
+ children: []
2330
+ };
2331
+ }
2332
+ function PlaceholderStep({ addon, onClick }) {
2333
+ return /* @__PURE__ */ jsx42(
2334
+ "button",
2335
+ {
2336
+ type: "button",
2337
+ onClick,
2338
+ className: "w-full rounded-xl border-2 border-dashed border-border/60 px-4 py-3 text-left transition-all hover:border-primary/30 hover:bg-surface/60 group",
2339
+ children: /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-3", children: [
2340
+ /* @__PURE__ */ jsx42(PlusCircle, { className: "h-[18px] w-[18px] text-foreground-subtle/30 group-hover:text-primary/60 shrink-0" }),
2341
+ /* @__PURE__ */ jsxs25("div", { className: "flex-1 min-w-0", children: [
2342
+ /* @__PURE__ */ jsx42("span", { className: "text-[13px] font-medium text-foreground-subtle/50 group-hover:text-foreground-subtle block truncate", children: addon.name }),
2343
+ /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-1 mt-0.5 flex-wrap", children: [
2344
+ addon.inputClasses.map((c) => /* @__PURE__ */ jsx42("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-blue-500/8 text-blue-400/50", children: c }, c)),
2345
+ addon.inputClasses.length > 0 && addon.outputClasses.length > 0 && /* @__PURE__ */ jsx42("span", { className: "text-foreground-subtle/25 text-[10px]", children: "\u2192" }),
2346
+ addon.outputClasses.map((c) => /* @__PURE__ */ jsx42("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-green-500/8 text-green-400/50", children: c }, c))
2347
+ ] })
2348
+ ] })
2349
+ ] })
2350
+ }
2351
+ );
2352
+ }
2353
+ function PipelineBuilder({
2354
+ schema,
2355
+ capabilities,
2356
+ steps,
2357
+ onChange,
2358
+ templates,
2359
+ selectedTemplateId,
2360
+ onSelectTemplate,
2361
+ onSaveTemplate,
2362
+ onUpdateTemplate,
2363
+ onDeleteTemplate,
2364
+ readOnly = false,
2365
+ excludeAddons = []
2366
+ }) {
2367
+ const excluded = useMemo3(() => new Set(excludeAddons), [excludeAddons]);
2368
+ const schemaMap = useMemo3(() => buildSchemaMap(schema), [schema]);
2369
+ const [warnings, setWarnings] = useState10([]);
2370
+ const bestPlatformScore = capabilities.platformScores?.find((s) => s.available);
2371
+ const hasPython = capabilities.runtimes.python.available && capabilities.runtimes.python.backends.some((b) => b.available);
2372
+ const defaultRuntime = bestPlatformScore?.runtime ?? (hasPython ? "python" : "node");
2373
+ const defaultBackend = bestPlatformScore?.backend ?? (hasPython ? capabilities.runtimes.python.backends.find((b) => b.available)?.id ?? "cpu" : capabilities.runtimes.node.backends.find((b) => b.available && b.id !== "cpu")?.id ?? "cpu");
2374
+ const dirty = selectedTemplateId ? JSON.stringify(steps) !== JSON.stringify(templates.find((t) => t.id === selectedTemplateId)?.steps) : false;
2375
+ function handleSelectTemplate(e) {
2376
+ const id = e.target.value || null;
2377
+ if (id) {
2378
+ const tpl = templates.find((t) => t.id === id);
2379
+ if (tpl) {
2380
+ const result = validateTemplate(tpl.steps, schema);
2381
+ setWarnings([...result.warnings]);
2382
+ }
2383
+ } else {
2384
+ setWarnings([]);
2385
+ }
2386
+ onSelectTemplate(id);
2387
+ }
2388
+ function handleSave() {
2389
+ if (selectedTemplateId) onUpdateTemplate(selectedTemplateId, steps);
2390
+ }
2391
+ function handleSaveAs() {
2392
+ const name = window.prompt("Template name:");
2393
+ if (name?.trim()) onSaveTemplate(name.trim(), steps);
2394
+ }
2395
+ function handleDelete() {
2396
+ if (!selectedTemplateId) return;
2397
+ const tpl = templates.find((t) => t.id === selectedTemplateId);
2398
+ if (tpl && window.confirm(`Delete template "${tpl.name}"?`)) {
2399
+ onDeleteTemplate(selectedTemplateId);
2400
+ }
2401
+ }
2402
+ function handleStepChange(updated) {
2403
+ onChange(steps.map((s) => {
2404
+ if (s.addonId !== updated.addonId) return s;
2405
+ return autoEnableAncestors(updated);
2406
+ }));
2407
+ }
2408
+ function autoEnableAncestors(step) {
2409
+ const hasEnabledChild = step.children.some((c) => c.enabled || c.children.some((gc) => gc.enabled));
2410
+ return {
2411
+ ...step,
2412
+ enabled: step.enabled || hasEnabledChild,
2413
+ children: step.children.map((c) => {
2414
+ const hasEnabledGrandchild = c.children.some((gc) => gc.enabled);
2415
+ return {
2416
+ ...c,
2417
+ enabled: c.enabled || hasEnabledGrandchild
2418
+ };
2419
+ })
2420
+ };
2421
+ }
2422
+ function handleAddChildToStep(parentAddonId, addon) {
2423
+ const child = createDefaultStep(addon, defaultRuntime, defaultBackend);
2424
+ onChange(steps.map((s) => {
2425
+ if (s.addonId !== parentAddonId) return s;
2426
+ return { ...s, children: [...s.children, child] };
2427
+ }));
2428
+ }
2429
+ function collectIds(list) {
2430
+ const ids = /* @__PURE__ */ new Set();
2431
+ for (const s of list) {
2432
+ ids.add(s.addonId);
2433
+ for (const c of s.children) ids.add(c.addonId);
2434
+ const childIds = collectIds(s.children);
2435
+ for (const id of childIds) ids.add(id);
2436
+ }
2437
+ return ids;
2438
+ }
2439
+ const existingIds = collectIds(steps);
2440
+ function getChildPlaceholders(step) {
2441
+ const stepSchema = schemaMap.get(step.addonId);
2442
+ if (!stepSchema) return [];
2443
+ const childSlotIds = stepSchema.childSlots;
2444
+ const childIds = new Set(step.children.map((c) => c.addonId));
2445
+ const placeholders = [];
2446
+ for (const slot of schema.slots) {
2447
+ if (!childSlotIds.includes(slot.id)) continue;
2448
+ for (const addon of slot.addons) {
2449
+ if (childIds.has(addon.id) || excluded.has(addon.id)) continue;
2450
+ const compatible = addon.inputClasses.some((ic) => step.outputClasses.includes(ic));
2451
+ if (compatible) placeholders.push(addon);
2452
+ }
2453
+ }
2454
+ return placeholders;
2455
+ }
2456
+ function renderStep(step) {
2457
+ const childPlaceholders = getChildPlaceholders(step);
2458
+ return /* @__PURE__ */ jsxs25("div", { className: "space-y-1.5", children: [
2459
+ /* @__PURE__ */ jsx42(
2460
+ PipelineStep,
2461
+ {
2462
+ step,
2463
+ schema: schemaMap.get(step.addonId) ?? null,
2464
+ allSchemas: schemaMap,
2465
+ capabilities,
2466
+ onChange: handleStepChange,
2467
+ onDelete: readOnly ? void 0 : (id) => onChange(steps.filter((s) => s.addonId !== id)),
2468
+ readOnly
2469
+ }
2470
+ ),
2471
+ (step.children.length > 0 || childPlaceholders.length > 0) && /* @__PURE__ */ jsxs25("div", { className: "ml-6 pl-4 border-l-2 border-dashed border-border/40 space-y-1.5", children: [
2472
+ /* @__PURE__ */ jsx42("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-foreground-subtle/40", children: "Slot: Cropper / Classifier" }),
2473
+ step.children.map((child) => {
2474
+ const childChildPlaceholders = getChildPlaceholders(child);
2475
+ return /* @__PURE__ */ jsxs25("div", { className: "space-y-1.5", children: [
2476
+ /* @__PURE__ */ jsx42(
2477
+ PipelineStep,
2478
+ {
2479
+ step: child,
2480
+ schema: schemaMap.get(child.addonId) ?? null,
2481
+ allSchemas: schemaMap,
2482
+ capabilities,
2483
+ depth: 1,
2484
+ onChange: (updated) => {
2485
+ handleStepChange({
2486
+ ...step,
2487
+ children: step.children.map((c) => c.addonId === updated.addonId ? updated : c)
2488
+ });
2489
+ },
2490
+ onDelete: readOnly ? void 0 : (id) => {
2491
+ handleStepChange({
2492
+ ...step,
2493
+ children: step.children.filter((c) => c.addonId !== id)
2494
+ });
2495
+ },
2496
+ readOnly
2497
+ }
2498
+ ),
2499
+ (child.children.length > 0 || childChildPlaceholders.length > 0) && /* @__PURE__ */ jsxs25("div", { className: "ml-6 pl-4 border-l-2 border-dashed border-border/30 space-y-1.5", children: [
2500
+ /* @__PURE__ */ jsx42("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-foreground-subtle/30", children: "Slot: Recognizer" }),
2501
+ child.children.map((grandchild) => /* @__PURE__ */ jsx42(
2502
+ PipelineStep,
2503
+ {
2504
+ step: grandchild,
2505
+ schema: schemaMap.get(grandchild.addonId) ?? null,
2506
+ allSchemas: schemaMap,
2507
+ capabilities,
2508
+ depth: 2,
2509
+ onChange: (updatedGrandchild) => {
2510
+ handleStepChange({
2511
+ ...step,
2512
+ children: step.children.map(
2513
+ (c) => c.addonId === child.addonId ? { ...c, children: c.children.map((gc) => gc.addonId === updatedGrandchild.addonId ? updatedGrandchild : gc) } : c
2514
+ )
2515
+ });
2516
+ },
2517
+ onDelete: readOnly ? void 0 : (id) => {
2518
+ handleStepChange({
2519
+ ...step,
2520
+ children: step.children.map(
2521
+ (c) => c.addonId === child.addonId ? { ...c, children: c.children.filter((gc) => gc.addonId !== id) } : c
2522
+ )
2523
+ });
2524
+ },
2525
+ readOnly
2526
+ },
2527
+ grandchild.addonId
2528
+ )),
2529
+ !readOnly && childChildPlaceholders.map((addon) => /* @__PURE__ */ jsx42(
2530
+ PlaceholderStep,
2531
+ {
2532
+ addon,
2533
+ onClick: () => {
2534
+ const newChild = createDefaultStep(addon, defaultRuntime, defaultBackend);
2535
+ handleStepChange({
2536
+ ...step,
2537
+ children: step.children.map(
2538
+ (c) => c.addonId === child.addonId ? { ...c, children: [...c.children, newChild] } : c
2539
+ )
2540
+ });
2541
+ }
2542
+ },
2543
+ addon.id
2544
+ ))
2545
+ ] })
2546
+ ] }, child.addonId);
2547
+ }),
2548
+ !readOnly && childPlaceholders.map((addon) => /* @__PURE__ */ jsx42(
2549
+ PlaceholderStep,
2550
+ {
2551
+ addon,
2552
+ onClick: () => handleAddChildToStep(step.addonId, addon)
2553
+ },
2554
+ addon.id
2555
+ ))
2556
+ ] })
2557
+ ] }, step.addonId);
2558
+ }
2559
+ const rootSlots = schema.slots.filter((s) => s.parentSlot === null).sort((a, b) => a.priority - b.priority);
2560
+ return /* @__PURE__ */ jsxs25("div", { className: "space-y-4", children: [
2561
+ /* @__PURE__ */ jsx42("div", { className: "rounded-xl border border-border bg-surface p-3", children: /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
2562
+ /* @__PURE__ */ jsx42("div", { className: "relative flex-1 min-w-0", children: /* @__PURE__ */ jsxs25(
2563
+ "select",
2564
+ {
2565
+ value: selectedTemplateId ?? "",
2566
+ onChange: handleSelectTemplate,
2567
+ className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary/50",
2568
+ children: [
2569
+ /* @__PURE__ */ jsx42("option", { value: "", children: "No template" }),
2570
+ templates.map((t) => /* @__PURE__ */ jsx42("option", { value: t.id, children: t.name }, t.id))
2571
+ ]
2572
+ }
2573
+ ) }),
2574
+ dirty && /* @__PURE__ */ jsx42("span", { className: "h-1.5 w-1.5 rounded-full bg-amber-500 shrink-0" }),
2575
+ /* @__PURE__ */ jsx42(
2576
+ "button",
2577
+ {
2578
+ onClick: handleSave,
2579
+ disabled: !selectedTemplateId || readOnly,
2580
+ title: "Save",
2581
+ className: cn(
2582
+ "p-2 rounded-lg border border-border transition-colors",
2583
+ selectedTemplateId && !readOnly ? "text-foreground-subtle hover:bg-surface-hover" : "text-foreground-subtle/30 cursor-not-allowed"
2584
+ ),
2585
+ children: /* @__PURE__ */ jsx42(Save, { className: "h-4 w-4" })
2586
+ }
2587
+ ),
2588
+ /* @__PURE__ */ jsx42(
2589
+ "button",
2590
+ {
2591
+ onClick: handleSaveAs,
2592
+ disabled: readOnly,
2593
+ title: "Save As",
2594
+ className: cn(
2595
+ "p-2 rounded-lg border border-border transition-colors",
2596
+ !readOnly ? "text-foreground-subtle hover:bg-surface-hover" : "text-foreground-subtle/30 cursor-not-allowed"
2597
+ ),
2598
+ children: /* @__PURE__ */ jsx42(CopyPlus, { className: "h-4 w-4" })
2599
+ }
2600
+ ),
2601
+ /* @__PURE__ */ jsx42(
2602
+ "button",
2603
+ {
2604
+ onClick: handleDelete,
2605
+ disabled: !selectedTemplateId || readOnly,
2606
+ title: "Delete",
2607
+ className: cn(
2608
+ "p-2 rounded-lg border border-border transition-colors",
2609
+ selectedTemplateId && !readOnly ? "text-foreground-subtle hover:text-danger" : "text-foreground-subtle/30 cursor-not-allowed"
2610
+ ),
2611
+ children: /* @__PURE__ */ jsx42(Trash2, { className: "h-4 w-4" })
2612
+ }
2613
+ )
2614
+ ] }) }),
2615
+ warnings.length > 0 && /* @__PURE__ */ jsxs25("div", { className: "rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-xs text-amber-400 space-y-1", children: [
2616
+ /* @__PURE__ */ jsxs25("div", { className: "flex items-center justify-between", children: [
2617
+ /* @__PURE__ */ jsx42("span", { className: "font-medium", children: "Template loaded with warnings:" }),
2618
+ /* @__PURE__ */ jsx42("button", { onClick: () => setWarnings([]), className: "text-amber-400/60 hover:text-amber-400", children: /* @__PURE__ */ jsx42(X2, { className: "h-3.5 w-3.5" }) })
2619
+ ] }),
2620
+ warnings.map((w, i) => /* @__PURE__ */ jsxs25("div", { children: [
2621
+ "\u2022 ",
2622
+ w
2623
+ ] }, i))
2624
+ ] }),
2625
+ rootSlots.map((slot) => {
2626
+ const slotSteps = steps.filter((s) => s.slot === slot.id && !excluded.has(s.addonId));
2627
+ const missingRootAddons = slot.addons.filter((a) => !existingIds.has(a.id) && !excluded.has(a.id));
2628
+ return /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
2629
+ /* @__PURE__ */ jsxs25("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-foreground-subtle/50", children: [
2630
+ "Slot: ",
2631
+ slot.label
2632
+ ] }),
2633
+ slotSteps.map((step) => renderStep(step)),
2634
+ !readOnly && missingRootAddons.map((addon) => /* @__PURE__ */ jsx42(PlaceholderStep, { addon, onClick: () => {
2635
+ onChange([...steps, createDefaultStep(addon, defaultRuntime, defaultBackend)]);
2636
+ } }, addon.id))
2637
+ ] }, slot.id);
2638
+ })
2639
+ ] });
2640
+ }
2641
+
2642
+ // src/composites/detection-colors.ts
2643
+ var CLASS_COLORS = {
2644
+ // Primary detection classes
2645
+ person: "#3b82f6",
2646
+ // blue-500
2647
+ vehicle: "#f59e0b",
2648
+ // amber-500
2649
+ animal: "#22c55e",
2650
+ // green-500
2651
+ // Sub-detection classes
2652
+ face: "#a855f7",
2653
+ // purple-500
2654
+ plate: "#ec4899",
2655
+ // pink-500
2656
+ // Specific animal types
2657
+ bird: "#14b8a6",
2658
+ // teal-500
2659
+ dog: "#84cc16",
2660
+ // lime-500
2661
+ cat: "#f97316",
2662
+ // orange-500
2663
+ // Specific vehicle types
2664
+ car: "#f59e0b",
2665
+ // amber-500
2666
+ truck: "#d97706",
2667
+ // amber-600
2668
+ bus: "#b45309",
2669
+ // amber-700
2670
+ motorcycle: "#eab308",
2671
+ // yellow-500
2672
+ bicycle: "#ca8a04",
2673
+ // yellow-600
2674
+ // Other
2675
+ motion: "#facc15"
2676
+ // yellow-400
2677
+ };
2678
+ var FALLBACK_PALETTE = [
2679
+ "#ef4444",
2680
+ // red-500
2681
+ "#8b5cf6",
2682
+ // violet-500
2683
+ "#06b6d4",
2684
+ // cyan-500
2685
+ "#f97316",
2686
+ // orange-500
2687
+ "#10b981",
2688
+ // emerald-500
2689
+ "#6366f1",
2690
+ // indigo-500
2691
+ "#e11d48",
2692
+ // rose-600
2693
+ "#0891b2",
2694
+ // cyan-600
2695
+ "#7c3aed",
2696
+ // violet-600
2697
+ "#059669"
2698
+ // emerald-600
2699
+ ];
2700
+ var DEFAULT_COLOR = "#f59e42";
2701
+ function getClassColor(className, customColors) {
2702
+ if (customColors?.[className]) return customColors[className];
2703
+ if (customColors?.[className.toLowerCase()]) return customColors[className.toLowerCase()];
2704
+ if (CLASS_COLORS[className]) return CLASS_COLORS[className];
2705
+ if (CLASS_COLORS[className.toLowerCase()]) return CLASS_COLORS[className.toLowerCase()];
2706
+ let hash = 0;
2707
+ for (let i = 0; i < className.length; i++) {
2708
+ hash = hash * 31 + (className.codePointAt(i) ?? 0) >>> 0;
2709
+ }
2710
+ return FALLBACK_PALETTE[hash % FALLBACK_PALETTE.length] ?? DEFAULT_COLOR;
2711
+ }
2712
+
2713
+ // src/composites/detection-canvas.tsx
2714
+ import { useRef as useRef6, useEffect as useEffect6 } from "react";
2715
+ import { Fragment as Fragment2, jsx as jsx43, jsxs as jsxs26 } from "react/jsx-runtime";
2716
+ var DEFAULT_CLASS_COLORS = CLASS_COLORS;
2717
+ function DetectionCanvas({
2718
+ src,
2719
+ imageWidth,
2720
+ imageHeight,
2721
+ detections = [],
2722
+ classColors,
2723
+ aspectRatio,
2724
+ className,
2725
+ placeholder,
2726
+ showConfidence = true,
2727
+ minConfidence = 0,
2728
+ borderWidth = 2
2729
+ }) {
2730
+ function getColor(className2) {
2731
+ return getClassColor(className2, classColors);
2732
+ }
2733
+ const ratio = aspectRatio ?? (imageWidth && imageHeight ? `${imageWidth}/${imageHeight}` : "16/9");
2734
+ const filteredDetections = detections.filter((d) => d.confidence >= minConfidence);
2735
+ return /* @__PURE__ */ jsx43(
2736
+ "div",
2737
+ {
2738
+ className: cn(
2739
+ "rounded-lg border border-border bg-surface overflow-hidden relative",
2740
+ className
2741
+ ),
2742
+ style: { aspectRatio: ratio },
2743
+ children: src ? /* @__PURE__ */ jsxs26(Fragment2, { children: [
2744
+ /* @__PURE__ */ jsx43("img", { src, className: "absolute inset-0 w-full h-full object-fill", alt: "" }),
2745
+ filteredDetections.map(
2746
+ (d, i) => d.mask && d.maskWidth && d.maskHeight ? /* @__PURE__ */ jsx43(
2747
+ MaskOverlay,
2748
+ {
2749
+ mask: d.mask,
2750
+ maskWidth: d.maskWidth,
2751
+ maskHeight: d.maskHeight,
2752
+ bbox: d.bbox,
2753
+ imageWidth,
2754
+ imageHeight,
2755
+ color: getColor(d.className)
2756
+ },
2757
+ `mask-${i}`
2758
+ ) : null
2759
+ ),
2760
+ filteredDetections.map((d, i) => /* @__PURE__ */ jsx43(
2761
+ BoundingBox,
2762
+ {
2763
+ detection: d,
2764
+ imageWidth,
2765
+ imageHeight,
2766
+ color: getColor(d.className),
2767
+ showConfidence,
2768
+ borderWidth: d.mask ? 1 : borderWidth,
2769
+ children: d.children?.filter((c) => {
2770
+ if (c.confidence < minConfidence) return false;
2771
+ const [cx1, cy1, cx2, cy2] = c.bbox;
2772
+ const cw = cx2 - cx1;
2773
+ const ch = cy2 - cy1;
2774
+ if (cw <= 0 || ch <= 0) return false;
2775
+ const [px1, py1, px2, py2] = d.bbox;
2776
+ const pw = px2 - px1;
2777
+ const ph = py2 - py1;
2778
+ if (pw > 0 && ph > 0 && cw * ch / (pw * ph) > 0.8) return false;
2779
+ return true;
2780
+ }).map((child, j) => /* @__PURE__ */ jsx43(
2781
+ ChildBoundingBox,
2782
+ {
2783
+ child,
2784
+ parentBbox: d.bbox,
2785
+ color: getColor(child.className),
2786
+ showConfidence
2787
+ },
2788
+ `child-${j}`
2789
+ ))
2790
+ },
2791
+ `det-${i}`
2792
+ ))
2793
+ ] }) : /* @__PURE__ */ jsx43("div", { className: "w-full h-full flex items-center justify-center text-foreground-subtle text-sm", children: placeholder ?? "No image loaded" })
2794
+ }
2795
+ );
2796
+ }
2797
+ function BoundingBox({
2798
+ detection,
2799
+ imageWidth,
2800
+ imageHeight,
2801
+ color,
2802
+ showConfidence,
2803
+ borderWidth,
2804
+ children
2805
+ }) {
2806
+ const [x1, y1, x2, y2] = detection.bbox;
2807
+ const labelCount = 1 + (detection.labelsData?.length ?? 0);
2808
+ const labelHeightPx = labelCount * 16 + 4;
2809
+ const topPct = y1 / imageHeight * 100;
2810
+ const containerRef = useRef6(null);
2811
+ const showBelow = topPct < labelHeightPx / imageHeight * 100 * 1.5;
2812
+ const labelsElement = /* @__PURE__ */ jsxs26(
2813
+ "div",
2814
+ {
2815
+ className: `absolute left-0 flex flex-col items-start gap-px ${showBelow ? "" : ""}`,
2816
+ style: showBelow ? { top: "100%", marginTop: "2px" } : { bottom: "100%", marginBottom: "2px" },
2817
+ children: [
2818
+ /* @__PURE__ */ jsxs26(
2819
+ "span",
2820
+ {
2821
+ className: "text-[10px] px-1 rounded-sm whitespace-nowrap text-white",
2822
+ style: { backgroundColor: color },
2823
+ children: [
2824
+ detection.className,
2825
+ showConfidence && ` ${(detection.confidence * 100).toFixed(0)}%`
2826
+ ]
2827
+ }
2828
+ ),
2829
+ detection.labelsData?.map((l, k) => /* @__PURE__ */ jsxs26(
2830
+ "span",
2831
+ {
2832
+ className: "text-[9px] font-semibold px-1 rounded-sm whitespace-nowrap text-white",
2833
+ style: { backgroundColor: getClassColor(l.addonId ?? l.label) },
2834
+ children: [
2835
+ l.label,
2836
+ " ",
2837
+ (l.score * 100).toFixed(0),
2838
+ "%"
2839
+ ]
2840
+ },
2841
+ k
2842
+ ))
2843
+ ]
2844
+ }
2845
+ );
2846
+ return /* @__PURE__ */ jsxs26(
2847
+ "div",
2848
+ {
2849
+ ref: containerRef,
2850
+ className: "absolute rounded-sm",
2851
+ style: {
2852
+ left: `${x1 / imageWidth * 100}%`,
2853
+ top: `${y1 / imageHeight * 100}%`,
2854
+ width: `${(x2 - x1) / imageWidth * 100}%`,
2855
+ height: `${(y2 - y1) / imageHeight * 100}%`,
2856
+ borderWidth: `${borderWidth}px`,
2857
+ borderStyle: "solid",
2858
+ borderColor: color
2859
+ },
2860
+ children: [
2861
+ labelsElement,
2862
+ children
2863
+ ]
2864
+ }
2865
+ );
2866
+ }
2867
+ function MaskOverlay({
2868
+ mask,
2869
+ maskWidth,
2870
+ maskHeight,
2871
+ bbox,
2872
+ imageWidth,
2873
+ imageHeight,
2874
+ color
2875
+ }) {
2876
+ const canvasRef = useRef6(null);
2877
+ useEffect6(() => {
2878
+ const canvas = canvasRef.current;
2879
+ if (!canvas) return;
2880
+ const ctx = canvas.getContext("2d");
2881
+ if (!ctx) return;
2882
+ const binary = atob(mask);
2883
+ const bytes = new Uint8Array(binary.length);
2884
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
2885
+ const r = parseInt(color.slice(1, 3), 16);
2886
+ const g = parseInt(color.slice(3, 5), 16);
2887
+ const b = parseInt(color.slice(5, 7), 16);
2888
+ canvas.width = maskWidth;
2889
+ canvas.height = maskHeight;
2890
+ const imageData = ctx.createImageData(maskWidth, maskHeight);
2891
+ const totalPixels = maskWidth * maskHeight;
2892
+ for (let i = 0; i < totalPixels; i++) {
2893
+ const val = i < bytes.length ? bytes[i] : 0;
2894
+ const px = i * 4;
2895
+ if (val > 0) {
2896
+ imageData.data[px] = r;
2897
+ imageData.data[px + 1] = g;
2898
+ imageData.data[px + 2] = b;
2899
+ imageData.data[px + 3] = 120;
2900
+ }
2901
+ }
2902
+ ctx.putImageData(imageData, 0, 0);
2903
+ }, [mask, maskWidth, maskHeight, color]);
2904
+ const [x1, y1, x2, y2] = bbox;
2905
+ return /* @__PURE__ */ jsx43(
2906
+ "canvas",
2907
+ {
2908
+ ref: canvasRef,
2909
+ className: "absolute pointer-events-none",
2910
+ style: {
2911
+ left: `${x1 / imageWidth * 100}%`,
2912
+ top: `${y1 / imageHeight * 100}%`,
2913
+ width: `${(x2 - x1) / imageWidth * 100}%`,
2914
+ height: `${(y2 - y1) / imageHeight * 100}%`,
2915
+ imageRendering: "pixelated"
2916
+ }
2917
+ }
2918
+ );
2919
+ }
2920
+ function ChildBoundingBox({
2921
+ child,
2922
+ parentBbox,
2923
+ color,
2924
+ showConfidence
2925
+ }) {
2926
+ const [px1, py1, px2, py2] = parentBbox;
2927
+ const [cx1, cy1, cx2, cy2] = child.bbox;
2928
+ const pw = px2 - px1;
2929
+ const ph = py2 - py1;
2930
+ if (pw <= 0 || ph <= 0) return null;
2931
+ return /* @__PURE__ */ jsx43(
2932
+ "div",
2933
+ {
2934
+ className: "absolute rounded-sm",
2935
+ style: {
2936
+ left: `${Math.max(0, (cx1 - px1) / pw * 100)}%`,
2937
+ top: `${Math.max(0, (cy1 - py1) / ph * 100)}%`,
2938
+ width: `${Math.min(100, (cx2 - cx1) / pw * 100)}%`,
2939
+ height: `${Math.min(100, (cy2 - cy1) / ph * 100)}%`,
2940
+ borderWidth: "1px",
2941
+ borderStyle: "solid",
2942
+ borderColor: color
2943
+ },
2944
+ children: (() => {
2945
+ const labelCount = 1 + (child.labelsData?.length ?? 0);
2946
+ const relTop = (cy1 - py1) / ph * 100;
2947
+ const showBelow = relTop < labelCount * 6;
2948
+ return /* @__PURE__ */ jsxs26(
2949
+ "div",
2950
+ {
2951
+ className: "absolute left-0 flex flex-col items-start gap-px",
2952
+ style: showBelow ? { top: "100%", marginTop: "1px" } : { bottom: "100%", marginBottom: "1px" },
2953
+ children: [
2954
+ /* @__PURE__ */ jsxs26(
2955
+ "span",
2956
+ {
2957
+ className: "text-[9px] px-0.5 rounded-sm whitespace-nowrap text-white",
2958
+ style: { backgroundColor: color },
2959
+ children: [
2960
+ child.className,
2961
+ showConfidence && ` ${(child.confidence * 100).toFixed(0)}%`
2962
+ ]
2963
+ }
2964
+ ),
2965
+ child.labelsData?.map((l, k) => /* @__PURE__ */ jsxs26(
2966
+ "span",
2967
+ {
2968
+ className: "text-[8px] font-semibold px-0.5 rounded-sm whitespace-nowrap text-white",
2969
+ style: { backgroundColor: getClassColor(l.addonId ?? l.label) },
2970
+ children: [
2971
+ l.label,
2972
+ " ",
2973
+ (l.score * 100).toFixed(0),
2974
+ "%"
2975
+ ]
2976
+ },
2977
+ k
2978
+ ))
2979
+ ]
2980
+ }
2981
+ );
2982
+ })()
2983
+ }
2984
+ );
2985
+ }
2986
+
2987
+ // src/composites/detection-result-tree.tsx
2988
+ import { jsx as jsx44, jsxs as jsxs27 } from "react/jsx-runtime";
2989
+ function DetectionResultTree({
2990
+ detections,
2991
+ classColors,
2992
+ className,
2993
+ hiddenKeys,
2994
+ onToggleVisibility
2995
+ }) {
2996
+ const colors = classColors;
2997
+ if (detections.length === 0) {
2998
+ return /* @__PURE__ */ jsx44("div", { className: "text-sm text-foreground-subtle italic text-center py-4", children: "No detections" });
2999
+ }
3000
+ return /* @__PURE__ */ jsxs27("div", { className, children: [
3001
+ /* @__PURE__ */ jsxs27("div", { className: "text-xs font-medium text-foreground-subtle uppercase tracking-wide mb-2", children: [
3002
+ "Detections (",
3003
+ detections.length,
3004
+ ")"
3005
+ ] }),
3006
+ /* @__PURE__ */ jsx44("div", { className: "space-y-2", children: detections.map((d, i) => /* @__PURE__ */ jsx44(
3007
+ DetectionNode,
3008
+ {
3009
+ detection: d,
3010
+ path: String(i),
3011
+ colors,
3012
+ hiddenKeys,
3013
+ onToggleVisibility
3014
+ },
3015
+ i
3016
+ )) })
3017
+ ] });
3018
+ }
3019
+ function DetectionNode({
3020
+ detection,
3021
+ path,
3022
+ colors,
3023
+ hiddenKeys,
3024
+ onToggleVisibility
3025
+ }) {
3026
+ const color = getClassColor(detection.className, colors);
3027
+ const isVisible = !hiddenKeys?.has(path);
3028
+ return /* @__PURE__ */ jsxs27("div", { className: `rounded-md border border-border bg-surface p-3 space-y-1 ${isVisible ? "" : "opacity-40"}`, children: [
3029
+ /* @__PURE__ */ jsxs27("div", { className: "flex justify-between items-center", children: [
3030
+ /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2", children: [
3031
+ onToggleVisibility && /* @__PURE__ */ jsx44(
3032
+ "input",
3033
+ {
3034
+ type: "checkbox",
3035
+ checked: isVisible,
3036
+ onChange: () => onToggleVisibility(path, !isVisible),
3037
+ className: "h-3.5 w-3.5 rounded border-border accent-primary cursor-pointer shrink-0"
3038
+ }
3039
+ ),
3040
+ /* @__PURE__ */ jsx44(
3041
+ "span",
3042
+ {
3043
+ className: "h-2.5 w-2.5 rounded-full shrink-0",
3044
+ style: { backgroundColor: color }
3045
+ }
3046
+ ),
3047
+ /* @__PURE__ */ jsx44("span", { className: "text-sm font-medium text-foreground", children: detection.className }),
3048
+ detection.mask && detection.maskWidth && detection.maskHeight && /* @__PURE__ */ jsxs27("span", { className: "text-[9px] font-mono px-1 py-0.5 rounded bg-primary/10 text-primary", children: [
3049
+ "mask ",
3050
+ detection.maskWidth,
3051
+ "x",
3052
+ detection.maskHeight
3053
+ ] })
3054
+ ] }),
3055
+ /* @__PURE__ */ jsx44(ConfidenceBadge, { confidence: detection.confidence })
3056
+ ] }),
3057
+ /* @__PURE__ */ jsxs27("div", { className: "text-[10px] text-foreground-subtle font-mono", children: [
3058
+ "bbox: [",
3059
+ detection.bbox.map((v) => Math.round(v)).join(", "),
3060
+ "]"
3061
+ ] }),
3062
+ detection.labelsData && detection.labelsData.length > 0 && /* @__PURE__ */ jsx44("div", { className: "flex flex-wrap gap-1 mt-1", children: detection.labelsData.map((l, k) => /* @__PURE__ */ jsxs27(
3063
+ "span",
3064
+ {
3065
+ className: "inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full",
3066
+ style: { backgroundColor: getClassColor(l.addonId ?? l.label, colors) + "20", color: getClassColor(l.addonId ?? l.label, colors) },
3067
+ children: [
3068
+ l.label,
3069
+ /* @__PURE__ */ jsxs27("span", { className: "opacity-60", children: [
3070
+ (l.score * 100).toFixed(0),
3071
+ "%"
3072
+ ] }),
3073
+ l.addonId && /* @__PURE__ */ jsx44("span", { className: "opacity-40 text-[8px]", children: l.addonId })
3074
+ ]
3075
+ },
3076
+ k
3077
+ )) }),
3078
+ detection.children && detection.children.length > 0 && /* @__PURE__ */ jsx44(
3079
+ ChildrenTree,
3080
+ {
3081
+ children: detection.children,
3082
+ parentPath: path,
3083
+ colors,
3084
+ hiddenKeys,
3085
+ onToggleVisibility
3086
+ }
3087
+ )
3088
+ ] });
3089
+ }
3090
+ function ChildrenTree({
3091
+ children,
3092
+ parentPath,
3093
+ colors,
3094
+ hiddenKeys,
3095
+ onToggleVisibility
3096
+ }) {
3097
+ return /* @__PURE__ */ jsx44("div", { className: "ml-4 mt-1.5 space-y-1.5 border-l-2 border-border pl-3", children: children.map((child, j) => {
3098
+ const childPath = `${parentPath}.${j}`;
3099
+ const childColor = getClassColor(child.className, colors);
3100
+ const isVisible = !hiddenKeys?.has(childPath);
3101
+ return /* @__PURE__ */ jsxs27("div", { className: `text-xs space-y-0.5 ${isVisible ? "" : "opacity-40"}`, children: [
3102
+ /* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-1.5", children: [
3103
+ onToggleVisibility && /* @__PURE__ */ jsx44(
3104
+ "input",
3105
+ {
3106
+ type: "checkbox",
3107
+ checked: isVisible,
3108
+ onChange: () => onToggleVisibility(childPath, !isVisible),
3109
+ className: "h-3 w-3 rounded border-border accent-primary cursor-pointer shrink-0"
3110
+ }
3111
+ ),
3112
+ /* @__PURE__ */ jsx44(
3113
+ "span",
3114
+ {
3115
+ className: "h-1.5 w-1.5 rounded-full shrink-0",
3116
+ style: { backgroundColor: childColor }
3117
+ }
3118
+ ),
3119
+ /* @__PURE__ */ jsx44("span", { className: "font-medium", style: { color: childColor }, children: child.className }),
3120
+ /* @__PURE__ */ jsxs27("span", { className: "text-foreground-subtle", children: [
3121
+ (child.confidence * 100).toFixed(0),
3122
+ "%"
3123
+ ] }),
3124
+ child.mask && child.maskWidth && child.maskHeight && /* @__PURE__ */ jsxs27("span", { className: "text-[9px] font-mono px-1 py-0.5 rounded bg-primary/10 text-primary", children: [
3125
+ "mask ",
3126
+ child.maskWidth,
3127
+ "x",
3128
+ child.maskHeight
3129
+ ] })
3130
+ ] }),
3131
+ child.labelsData && child.labelsData.length > 0 && /* @__PURE__ */ jsx44("div", { className: "flex flex-wrap gap-1 ml-5 mt-0.5", children: child.labelsData.map((l, k) => /* @__PURE__ */ jsxs27(
3132
+ "span",
3133
+ {
3134
+ className: "inline-flex items-center gap-0.5 text-[9px] font-medium px-1 py-0.5 rounded-full",
3135
+ style: { backgroundColor: getClassColor(l.addonId ?? l.label, colors) + "20", color: getClassColor(l.addonId ?? l.label, colors) },
3136
+ children: [
3137
+ l.label,
3138
+ " ",
3139
+ /* @__PURE__ */ jsxs27("span", { className: "opacity-60", children: [
3140
+ (l.score * 100).toFixed(0),
3141
+ "%"
3142
+ ] })
3143
+ ]
3144
+ },
3145
+ k
3146
+ )) }),
3147
+ child.children && child.children.length > 0 && /* @__PURE__ */ jsx44(
3148
+ ChildrenTree,
3149
+ {
3150
+ children: child.children,
3151
+ parentPath: childPath,
3152
+ colors,
3153
+ hiddenKeys,
3154
+ onToggleVisibility
3155
+ }
3156
+ )
3157
+ ] }, j);
3158
+ }) });
3159
+ }
3160
+ function ConfidenceBadge({ confidence }) {
3161
+ const level = confidence >= 0.8 ? "bg-success/10 text-success" : confidence >= 0.5 ? "bg-warning/10 text-warning" : "bg-danger/10 text-danger";
3162
+ return /* @__PURE__ */ jsxs27("span", { className: `text-xs font-medium px-2 py-0.5 rounded-full ${level}`, children: [
3163
+ (confidence * 100).toFixed(1),
3164
+ "%"
3165
+ ] });
3166
+ }
3167
+
3168
+ // src/composites/step-timings.tsx
3169
+ import { jsx as jsx45, jsxs as jsxs28 } from "react/jsx-runtime";
3170
+ function StepTimings({ timings, totalMs, className }) {
3171
+ const entries = Object.entries(timings);
3172
+ if (entries.length === 0 && totalMs === void 0) return null;
3173
+ return /* @__PURE__ */ jsxs28("div", { className: `rounded-lg border border-border bg-surface p-3 space-y-2 ${className ?? ""}`, children: [
3174
+ /* @__PURE__ */ jsx45("div", { className: "text-xs font-medium text-foreground-subtle uppercase tracking-wide", children: "Timings" }),
3175
+ /* @__PURE__ */ jsxs28("div", { className: "space-y-1 text-xs", children: [
3176
+ entries.map(([step, ms]) => /* @__PURE__ */ jsxs28("div", { className: "flex justify-between", children: [
3177
+ /* @__PURE__ */ jsx45("span", { className: "text-foreground-subtle", children: step }),
3178
+ /* @__PURE__ */ jsxs28("span", { className: "font-mono text-foreground", children: [
3179
+ ms.toFixed(1),
3180
+ "ms"
3181
+ ] })
3182
+ ] }, step)),
3183
+ totalMs !== void 0 && /* @__PURE__ */ jsxs28("div", { className: "flex justify-between pt-1 border-t border-border font-medium text-foreground", children: [
3184
+ /* @__PURE__ */ jsx45("span", { children: "Total" }),
3185
+ /* @__PURE__ */ jsxs28("span", { className: "font-mono", children: [
3186
+ totalMs.toFixed(1),
3187
+ "ms"
3188
+ ] })
3189
+ ] })
3190
+ ] })
3191
+ ] });
3192
+ }
3193
+
3194
+ // src/composites/image-selector.tsx
3195
+ import { jsx as jsx46, jsxs as jsxs29 } from "react/jsx-runtime";
3196
+ function ImageSelector({
3197
+ images,
3198
+ selectedFilename,
3199
+ uploadedName,
3200
+ onSelect,
3201
+ onUpload,
3202
+ className
3203
+ }) {
3204
+ const handleUploadClick = () => {
3205
+ const input = document.createElement("input");
3206
+ input.type = "file";
3207
+ input.accept = "image/*";
3208
+ input.onchange = (ev) => {
3209
+ const file = ev.target.files?.[0];
3210
+ if (!file) return;
3211
+ const reader = new FileReader();
3212
+ reader.onload = (e) => {
3213
+ const dataUrl = e.target?.result;
3214
+ const b64 = dataUrl.split(",")[1];
3215
+ if (b64) onUpload(b64, file.name, dataUrl);
3216
+ };
3217
+ reader.readAsDataURL(file);
3218
+ };
3219
+ input.click();
3220
+ };
3221
+ return /* @__PURE__ */ jsxs29("div", { className: `flex flex-wrap items-center gap-2 ${className ?? ""}`, children: [
3222
+ images.map((img) => /* @__PURE__ */ jsx46(
3223
+ "button",
3224
+ {
3225
+ onClick: () => onSelect(img.filename),
3226
+ className: `px-3 py-1.5 text-xs rounded-md border transition-colors ${selectedFilename === img.filename ? "bg-primary text-primary-foreground border-primary" : "bg-surface border-border text-foreground hover:border-primary/50"}`,
3227
+ children: img.id
3228
+ },
3229
+ img.filename
3230
+ )),
3231
+ /* @__PURE__ */ jsx46(
3232
+ "button",
3233
+ {
3234
+ onClick: handleUploadClick,
3235
+ className: "px-3 py-1.5 text-xs rounded-md border border-border bg-surface text-foreground hover:bg-surface-hover transition-colors",
3236
+ children: "Upload..."
3237
+ }
3238
+ ),
3239
+ uploadedName && /* @__PURE__ */ jsx46("span", { className: "text-xs text-foreground-subtle", children: uploadedName })
3240
+ ] });
3241
+ }
3242
+
3243
+ // src/composites/inference-config-selector.tsx
3244
+ import { jsx as jsx47, jsxs as jsxs30 } from "react/jsx-runtime";
3245
+ var SELECT_CLASS = "w-full px-3 py-2 text-sm rounded-md border border-border bg-surface text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50";
3246
+ function InferenceConfigSelector({
3247
+ runtime,
3248
+ backend,
3249
+ modelId,
3250
+ agentId = "hub",
3251
+ runtimes,
3252
+ backends,
3253
+ models,
3254
+ agents = [],
3255
+ onRuntimeChange,
3256
+ onBackendChange,
3257
+ onModelChange,
3258
+ onAgentChange,
3259
+ layout = "grid",
3260
+ className,
3261
+ showAgent = false
3262
+ }) {
3263
+ const containerClass = layout === "grid" ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4" : layout === "horizontal" ? "flex flex-wrap items-end gap-4" : "space-y-3";
3264
+ return /* @__PURE__ */ jsxs30("div", { className: `${containerClass} ${className ?? ""}`, children: [
3265
+ showAgent && agents.length > 0 && /* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
3266
+ /* @__PURE__ */ jsx47("span", { className: "text-xs font-medium text-foreground-subtle", children: "Agent" }),
3267
+ /* @__PURE__ */ jsx47(
3268
+ "select",
3269
+ {
3270
+ value: agentId,
3271
+ onChange: (e) => onAgentChange?.(e.target.value),
3272
+ className: SELECT_CLASS,
3273
+ children: agents.map((a) => /* @__PURE__ */ jsxs30("option", { value: a.id, children: [
3274
+ a.name,
3275
+ " (",
3276
+ a.status,
3277
+ ")"
3278
+ ] }, a.id))
3279
+ }
3280
+ )
3281
+ ] }),
3282
+ /* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
3283
+ /* @__PURE__ */ jsx47("span", { className: "text-xs font-medium text-foreground-subtle", children: "Runtime" }),
3284
+ /* @__PURE__ */ jsx47(
3285
+ "select",
3286
+ {
3287
+ value: runtime,
3288
+ onChange: (e) => onRuntimeChange(e.target.value),
3289
+ className: SELECT_CLASS,
3290
+ children: runtimes.map((r) => /* @__PURE__ */ jsxs30("option", { value: r.value, disabled: !r.available, children: [
3291
+ r.label,
3292
+ !r.available ? " (unavailable)" : ""
3293
+ ] }, r.value))
3294
+ }
3295
+ )
3296
+ ] }),
3297
+ /* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
3298
+ /* @__PURE__ */ jsx47("span", { className: "text-xs font-medium text-foreground-subtle", children: "Backend" }),
3299
+ /* @__PURE__ */ jsx47(
3300
+ "select",
3301
+ {
3302
+ value: backend,
3303
+ onChange: (e) => onBackendChange(e.target.value),
3304
+ className: SELECT_CLASS,
3305
+ children: backends.map((b) => /* @__PURE__ */ jsxs30("option", { value: b.id, disabled: !b.available, children: [
3306
+ b.label,
3307
+ !b.available ? " (unavailable)" : ""
3308
+ ] }, b.id))
3309
+ }
3310
+ )
3311
+ ] }),
3312
+ /* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
3313
+ /* @__PURE__ */ jsx47("span", { className: "text-xs font-medium text-foreground-subtle", children: "Model" }),
3314
+ /* @__PURE__ */ jsx47(
3315
+ "select",
3316
+ {
3317
+ value: modelId,
3318
+ onChange: (e) => onModelChange(e.target.value),
3319
+ className: SELECT_CLASS,
3320
+ children: models.length === 0 ? /* @__PURE__ */ jsx47("option", { value: "", children: "No compatible models" }) : models.map((m) => /* @__PURE__ */ jsxs30("option", { value: m.id, children: [
3321
+ m.name,
3322
+ m.downloaded ? " \u2713" : ""
3323
+ ] }, m.id))
3324
+ }
3325
+ )
3326
+ ] })
3327
+ ] });
3328
+ }
3329
+
3330
+ // src/composites/mount-addon-page.tsx
3331
+ import { createElement } from "react";
3332
+ import { createRoot } from "react-dom/client";
3333
+
3334
+ // src/composites/dev-shell.tsx
3335
+ import { createContext as createContext7, useCallback as useCallback8, useContext as useContext7, useMemo as useMemo4, useState as useState12 } from "react";
3336
+ import { createTRPCClient, createWSClient, wsLink, httpLink, splitLink } from "@trpc/client";
3337
+ import superjson from "superjson";
3338
+
3339
+ // src/composites/login-form.tsx
3340
+ import { useState as useState11 } from "react";
3341
+ import { jsx as jsx48, jsxs as jsxs31 } from "react/jsx-runtime";
3342
+ function EyeIcon({ className }) {
3343
+ return /* @__PURE__ */ jsxs31(
3344
+ "svg",
3345
+ {
3346
+ xmlns: "http://www.w3.org/2000/svg",
3347
+ viewBox: "0 0 24 24",
3348
+ fill: "none",
3349
+ stroke: "currentColor",
3350
+ strokeWidth: "2",
3351
+ strokeLinecap: "round",
3352
+ strokeLinejoin: "round",
3353
+ className,
3354
+ children: [
3355
+ /* @__PURE__ */ jsx48("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }),
3356
+ /* @__PURE__ */ jsx48("circle", { cx: "12", cy: "12", r: "3" })
3357
+ ]
3358
+ }
3359
+ );
3360
+ }
3361
+ function EyeOffIcon({ className }) {
3362
+ return /* @__PURE__ */ jsxs31(
3363
+ "svg",
3364
+ {
3365
+ xmlns: "http://www.w3.org/2000/svg",
3366
+ viewBox: "0 0 24 24",
3367
+ fill: "none",
3368
+ stroke: "currentColor",
3369
+ strokeWidth: "2",
3370
+ strokeLinecap: "round",
3371
+ strokeLinejoin: "round",
3372
+ className,
3373
+ children: [
3374
+ /* @__PURE__ */ jsx48("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
3375
+ /* @__PURE__ */ jsx48("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
3376
+ /* @__PURE__ */ jsx48("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
3377
+ /* @__PURE__ */ jsx48("path", { d: "m2 2 20 20" })
3378
+ ]
3379
+ }
3380
+ );
3381
+ }
3382
+ function SpinnerIcon({ className }) {
3383
+ return /* @__PURE__ */ jsx48(
3384
+ "svg",
3385
+ {
3386
+ xmlns: "http://www.w3.org/2000/svg",
3387
+ viewBox: "0 0 24 24",
3388
+ fill: "none",
3389
+ stroke: "currentColor",
3390
+ strokeWidth: "2",
3391
+ strokeLinecap: "round",
3392
+ strokeLinejoin: "round",
3393
+ className,
3394
+ children: /* @__PURE__ */ jsx48("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
3395
+ }
3396
+ );
3397
+ }
3398
+ function LoginForm({
3399
+ onLogin,
3400
+ serverUrl,
3401
+ logoSrc,
3402
+ error: externalError,
3403
+ className
3404
+ }) {
3405
+ const [username, setUsername] = useState11("");
3406
+ const [password, setPassword] = useState11("");
3407
+ const [showPassword, setShowPassword] = useState11(false);
3408
+ const [submitting, setSubmitting] = useState11(false);
3409
+ const [internalError, setInternalError] = useState11(null);
3410
+ const error = externalError ?? internalError;
3411
+ const handleSubmit = async (e) => {
3412
+ e.preventDefault();
3413
+ if (submitting) return;
3414
+ setInternalError(null);
3415
+ setSubmitting(true);
3416
+ try {
3417
+ await onLogin(username, password);
3418
+ } catch (err) {
3419
+ const message = err instanceof Error ? err.message : "Login failed. Please try again.";
3420
+ setInternalError(message);
3421
+ } finally {
3422
+ setSubmitting(false);
3423
+ }
3424
+ };
3425
+ return /* @__PURE__ */ jsx48(
3426
+ "div",
3427
+ {
3428
+ className: cn(
3429
+ "flex min-h-screen items-center justify-center bg-background p-4",
3430
+ className
3431
+ ),
3432
+ children: /* @__PURE__ */ jsxs31("div", { className: "w-full max-w-sm", children: [
3433
+ logoSrc && /* @__PURE__ */ jsx48("div", { className: "flex justify-center mb-8", children: /* @__PURE__ */ jsx48("img", { src: logoSrc, alt: "Logo", className: "h-12" }) }),
3434
+ serverUrl && /* @__PURE__ */ jsx48("p", { className: "mb-4 text-center text-xs text-foreground-subtle truncate", children: serverUrl }),
3435
+ /* @__PURE__ */ jsxs31(
3436
+ "form",
3437
+ {
3438
+ onSubmit: handleSubmit,
3439
+ className: "space-y-4 rounded-xl border border-border bg-surface p-6 shadow-xl shadow-black/10",
3440
+ children: [
3441
+ error && /* @__PURE__ */ jsx48("div", { className: "rounded-md bg-danger/10 border border-danger/20 px-3 py-2 text-xs text-danger", children: error }),
3442
+ /* @__PURE__ */ jsxs31("div", { className: "space-y-1.5", children: [
3443
+ /* @__PURE__ */ jsx48("label", { className: "text-xs font-medium text-foreground-subtle", children: "Username" }),
3444
+ /* @__PURE__ */ jsx48(
3445
+ "input",
3446
+ {
3447
+ type: "text",
3448
+ value: username,
3449
+ onChange: (e) => setUsername(e.target.value),
3450
+ autoComplete: "username",
3451
+ required: true,
3452
+ className: "w-full rounded-lg border border-border bg-background px-3 py-2.5 text-sm text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
3453
+ }
3454
+ )
3455
+ ] }),
3456
+ /* @__PURE__ */ jsxs31("div", { className: "space-y-1.5", children: [
3457
+ /* @__PURE__ */ jsx48("label", { className: "text-xs font-medium text-foreground-subtle", children: "Password" }),
3458
+ /* @__PURE__ */ jsxs31("div", { className: "relative", children: [
3459
+ /* @__PURE__ */ jsx48(
3460
+ "input",
3461
+ {
3462
+ type: showPassword ? "text" : "password",
3463
+ value: password,
3464
+ onChange: (e) => setPassword(e.target.value),
3465
+ autoComplete: "current-password",
3466
+ required: true,
3467
+ className: "w-full rounded-lg border border-border bg-background px-3 py-2.5 pr-10 text-sm text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
3468
+ }
3469
+ ),
3470
+ /* @__PURE__ */ jsx48(
3471
+ "button",
3472
+ {
3473
+ type: "button",
3474
+ onClick: () => setShowPassword((prev) => !prev),
3475
+ className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-foreground-subtle hover:text-foreground",
3476
+ tabIndex: -1,
3477
+ children: showPassword ? /* @__PURE__ */ jsx48(EyeOffIcon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx48(EyeIcon, { className: "h-4 w-4" })
3478
+ }
3479
+ )
3480
+ ] })
3481
+ ] }),
3482
+ /* @__PURE__ */ jsxs31(
3483
+ "button",
3484
+ {
3485
+ type: "submit",
3486
+ disabled: submitting,
3487
+ className: "w-full rounded-lg bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2",
3488
+ children: [
3489
+ submitting && /* @__PURE__ */ jsx48(SpinnerIcon, { className: "h-4 w-4 animate-spin" }),
3490
+ submitting ? "Logging in..." : "Log in"
3491
+ ]
3492
+ }
3493
+ )
3494
+ ]
3495
+ }
3496
+ )
3497
+ ] })
3498
+ }
3499
+ );
3500
+ }
3501
+
3502
+ // src/composites/dev-shell.tsx
3503
+ import { jsx as jsx49, jsxs as jsxs32 } from "react/jsx-runtime";
3504
+ var STORAGE_KEY = "camstack_dev_token";
3505
+ var DevShellContext = createContext7(null);
3506
+ function useDevShell() {
3507
+ const ctx = useContext7(DevShellContext);
3508
+ if (!ctx) {
3509
+ throw new Error("useDevShell must be used within a DevShell");
3510
+ }
3511
+ return ctx;
3512
+ }
3513
+ function getStoredToken() {
3514
+ if (typeof window === "undefined") return null;
3515
+ return localStorage.getItem(STORAGE_KEY);
3516
+ }
3517
+ function SunIcon({ className }) {
3518
+ return /* @__PURE__ */ jsxs32(
3519
+ "svg",
3520
+ {
3521
+ xmlns: "http://www.w3.org/2000/svg",
3522
+ viewBox: "0 0 24 24",
3523
+ fill: "none",
3524
+ stroke: "currentColor",
3525
+ strokeWidth: "2",
3526
+ strokeLinecap: "round",
3527
+ strokeLinejoin: "round",
3528
+ className,
3529
+ children: [
3530
+ /* @__PURE__ */ jsx49("circle", { cx: "12", cy: "12", r: "4" }),
3531
+ /* @__PURE__ */ jsx49("path", { d: "M12 2v2" }),
3532
+ /* @__PURE__ */ jsx49("path", { d: "M12 20v2" }),
3533
+ /* @__PURE__ */ jsx49("path", { d: "m4.93 4.93 1.41 1.41" }),
3534
+ /* @__PURE__ */ jsx49("path", { d: "m17.66 17.66 1.41 1.41" }),
3535
+ /* @__PURE__ */ jsx49("path", { d: "M2 12h2" }),
3536
+ /* @__PURE__ */ jsx49("path", { d: "M20 12h2" }),
3537
+ /* @__PURE__ */ jsx49("path", { d: "m6.34 17.66-1.41 1.41" }),
3538
+ /* @__PURE__ */ jsx49("path", { d: "m19.07 4.93-1.41 1.41" })
3539
+ ]
3540
+ }
3541
+ );
3542
+ }
3543
+ function MoonIcon({ className }) {
3544
+ return /* @__PURE__ */ jsx49(
3545
+ "svg",
3546
+ {
3547
+ xmlns: "http://www.w3.org/2000/svg",
3548
+ viewBox: "0 0 24 24",
3549
+ fill: "none",
3550
+ stroke: "currentColor",
3551
+ strokeWidth: "2",
3552
+ strokeLinecap: "round",
3553
+ strokeLinejoin: "round",
3554
+ className,
3555
+ children: /* @__PURE__ */ jsx49("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" })
3556
+ }
3557
+ );
3558
+ }
3559
+ function DevShellInner({
3560
+ children,
3561
+ serverUrl,
3562
+ title,
3563
+ token,
3564
+ onLogout
3565
+ }) {
3566
+ const theme = useThemeMode();
3567
+ const trpc = useMemo4(
3568
+ () => {
3569
+ const wsUrl = serverUrl.replace(/^http/, "ws") + "/trpc";
3570
+ const wsClient = createWSClient({
3571
+ url: wsUrl,
3572
+ connectionParams: () => ({ token })
3573
+ });
3574
+ return createTRPCClient({
3575
+ links: [
3576
+ splitLink({
3577
+ condition: (op) => op.type === "subscription",
3578
+ true: wsLink({ client: wsClient, transformer: superjson }),
3579
+ false: httpLink({
3580
+ url: `${serverUrl}/trpc`,
3581
+ transformer: superjson,
3582
+ headers: () => ({ authorization: `Bearer ${token}` })
3583
+ })
3584
+ })
3585
+ ]
3586
+ });
3587
+ },
3588
+ [serverUrl, token]
3589
+ );
3590
+ const contextValue = useMemo4(
3591
+ () => ({ trpc, token, logout: onLogout }),
3592
+ [trpc, token, onLogout]
3593
+ );
3594
+ return /* @__PURE__ */ jsx49(DevShellContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs32("div", { className: "min-h-screen bg-background text-foreground", children: [
3595
+ /* @__PURE__ */ jsxs32("div", { className: "flex items-center justify-between border-b border-border bg-surface px-4 py-2", children: [
3596
+ /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-2", children: [
3597
+ /* @__PURE__ */ jsx49("span", { className: "rounded bg-warning/20 px-2 py-0.5 text-xs font-bold text-warning", children: "DEV MODE" }),
3598
+ title && /* @__PURE__ */ jsx49("span", { className: "text-sm font-medium text-foreground", children: title }),
3599
+ /* @__PURE__ */ jsx49("span", { className: "text-xs text-foreground-subtle", children: serverUrl })
3600
+ ] }),
3601
+ /* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-2", children: [
3602
+ /* @__PURE__ */ jsxs32(
3603
+ "button",
3604
+ {
3605
+ type: "button",
3606
+ onClick: theme.toggleMode,
3607
+ className: "flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors",
3608
+ title: `Theme: ${theme.mode}`,
3609
+ children: [
3610
+ theme.resolvedMode === "dark" ? /* @__PURE__ */ jsx49(SunIcon, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx49(MoonIcon, { className: "h-3.5 w-3.5" }),
3611
+ theme.mode === "dark" ? "Dark" : theme.mode === "light" ? "Light" : "System"
3612
+ ]
3613
+ }
3614
+ ),
3615
+ /* @__PURE__ */ jsx49(
3616
+ "button",
3617
+ {
3618
+ type: "button",
3619
+ onClick: onLogout,
3620
+ className: "rounded-md px-2.5 py-1 text-xs font-medium text-danger hover:bg-danger/10 transition-colors",
3621
+ children: "Logout"
3622
+ }
3623
+ )
3624
+ ] })
3625
+ ] }),
3626
+ /* @__PURE__ */ jsx49("div", { className: "p-4", children: children({ trpc, theme }) })
3627
+ ] }) });
3628
+ }
3629
+ function DevShell({
3630
+ children,
3631
+ serverUrl = "https://localhost:4443",
3632
+ title
3633
+ }) {
3634
+ const [token, setToken] = useState12(getStoredToken);
3635
+ const handleLogin = useCallback8(
3636
+ async (username, password) => {
3637
+ const anonClient = createTRPCClient({
3638
+ links: [
3639
+ httpLink({
3640
+ url: `${serverUrl}/trpc`,
3641
+ transformer: superjson
3642
+ })
3643
+ ]
3644
+ });
3645
+ const res = await anonClient.auth.login.mutate({ username, password });
3646
+ if (!res?.token) throw new Error("No token returned");
3647
+ localStorage.setItem(STORAGE_KEY, res.token);
3648
+ setToken(res.token);
3649
+ },
3650
+ [serverUrl]
3651
+ );
3652
+ const handleLogout = useCallback8(() => {
3653
+ localStorage.removeItem(STORAGE_KEY);
3654
+ setToken(null);
3655
+ }, []);
3656
+ if (!token) {
3657
+ return /* @__PURE__ */ jsx49(ThemeProvider, { children: /* @__PURE__ */ jsx49(LoginForm, { onLogin: handleLogin, serverUrl }) });
3658
+ }
3659
+ return /* @__PURE__ */ jsx49(ThemeProvider, { children: /* @__PURE__ */ jsx49(
3660
+ DevShellInner,
3661
+ {
3662
+ serverUrl,
3663
+ title,
3664
+ token,
3665
+ onLogout: handleLogout,
3666
+ children
3667
+ }
3668
+ ) });
3669
+ }
3670
+
3671
+ // src/composites/mount-addon-page.tsx
3672
+ function mountAddonPage(PageComponent, options = {}) {
3673
+ const {
3674
+ serverUrl = "https://localhost:4443",
3675
+ title,
3676
+ rootId = "root"
3677
+ } = options;
3678
+ const root = document.getElementById(rootId);
3679
+ if (!root) {
3680
+ console.error(`[mountAddonPage] Element #${rootId} not found`);
3681
+ return;
3682
+ }
3683
+ createRoot(root).render(
3684
+ createElement(DevShell, {
3685
+ serverUrl,
3686
+ title,
3687
+ children: ({ trpc, theme }) => createElement(PageComponent, {
3688
+ trpc,
3689
+ theme: { isDark: theme.resolvedMode === "dark" },
3690
+ navigate: (path) => {
3691
+ console.log("[dev] navigate:", path);
3692
+ }
3693
+ })
3694
+ })
3695
+ );
3696
+ }
1981
3697
  export {
1982
3698
  AppShell,
1983
3699
  Badge,
1984
3700
  Button,
3701
+ CLASS_COLORS,
1985
3702
  Card,
1986
3703
  Checkbox,
1987
3704
  CodeBlock,
1988
3705
  ConfirmDialog,
3706
+ DEFAULT_CLASS_COLORS,
3707
+ DEFAULT_COLOR,
1989
3708
  DataTable,
3709
+ DetectionCanvas,
3710
+ DetectionResultTree,
3711
+ DevShell,
1990
3712
  DeviceCard,
1991
3713
  DeviceGrid,
1992
3714
  Dialog,
@@ -2005,22 +3727,30 @@ export {
2005
3727
  FloatingPanel,
2006
3728
  FormField,
2007
3729
  IconButton,
3730
+ ImageSelector,
3731
+ InferenceConfigSelector,
2008
3732
  Input,
2009
3733
  KeyValueList,
2010
3734
  Label,
3735
+ LoginForm,
2011
3736
  PageHeader,
3737
+ PipelineBuilder,
3738
+ PipelineRuntimeSelector,
3739
+ PipelineStep,
2012
3740
  Popover,
2013
3741
  PopoverContent,
2014
3742
  PopoverTrigger,
2015
3743
  ProviderBadge,
2016
3744
  ScrollArea,
2017
3745
  Select,
3746
+ SemanticBadge,
2018
3747
  Separator,
2019
3748
  Sidebar,
2020
3749
  SidebarItem,
2021
3750
  Skeleton,
2022
3751
  StatCard,
2023
3752
  StatusBadge,
3753
+ StepTimings,
2024
3754
  Switch,
2025
3755
  Tabs,
2026
3756
  TabsContent,
@@ -2030,14 +3760,18 @@ export {
2030
3760
  Tooltip,
2031
3761
  TooltipContent,
2032
3762
  TooltipTrigger,
3763
+ VersionBadge,
2033
3764
  cn,
2034
3765
  createTheme,
2035
3766
  darkColors,
2036
3767
  defaultTheme,
3768
+ getClassColor,
2037
3769
  lightColors,
3770
+ mountAddonPage,
2038
3771
  providerIcons,
2039
3772
  statusIcons,
2040
3773
  themeToCss,
3774
+ useDevShell,
2041
3775
  useThemeMode
2042
3776
  };
2043
3777
  //# sourceMappingURL=index.js.map