Upload 6 files
Browse files- web/static/app.css +303 -8
- web/static/app.js +33 -18
- web/static/progress.js +34 -9
- web/static/runs.js +188 -46
web/static/app.css
CHANGED
|
@@ -900,24 +900,27 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 900 |
.endpoint-params{margin:8px 0 0;color:#667085;font-size:11px;line-height:1.35}
|
| 901 |
.compact-empty{font-size:11px;padding:6px 0;color:#667085}
|
| 902 |
|
| 903 |
-
/*
|
| 904 |
.run-documents-card{background:linear-gradient(180deg,#fff 0%,#fbfdff 100%)}
|
| 905 |
.run-documents-card .card-title-row{align-items:center}
|
| 906 |
.run-documents-card .card-title-row .muted{font-size:10.5px}
|
| 907 |
-
.run-documents-dock{display:
|
| 908 |
-
.run-doc-btn{display:grid;grid-template-
|
| 909 |
.run-doc-btn.ready:hover{border-color:#93c5fd;background:#fff;box-shadow:0 8px 18px rgba(16,24,40,.08);transform:translateY(-1px)}
|
| 910 |
-
.run-doc-btn.pending{opacity:.
|
| 911 |
-
.run-doc-btn
|
| 912 |
-
.run-doc-btn
|
| 913 |
-
.run-doc-btn
|
|
|
|
|
|
|
| 914 |
.run-doc-btn.raw.ready{border-color:#fed7aa;background:linear-gradient(180deg,#fffaf3 0%,#fff 100%)}
|
| 915 |
.run-doc-btn.raw.ready .doc-icon{background:#fff7ed;border-color:#fed7aa}
|
| 916 |
.run-doc-btn.safe.ready{border-color:#c7d2fe;background:linear-gradient(180deg,#f8fbff 0%,#fff 100%)}
|
| 917 |
.run-doc-btn.safe.ready .doc-icon{background:#eef2ff;border-color:#c7d2fe}
|
| 918 |
.run-doc-btn.warn.ready{border-color:#fed7aa;background:linear-gradient(180deg,#fff7ed 0%,#fff 100%)}
|
| 919 |
.run-doc-btn.warn.ready strong{color:#9a3412}
|
| 920 |
-
|
|
|
|
| 921 |
|
| 922 |
/* v102: narrow-safe run explorer, point-based validation progress */
|
| 923 |
.runs-table{min-width:0}
|
|
@@ -1272,6 +1275,11 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 1272 |
.recovery-meta strong{display:block;margin-top:2px;font-size:11.5px;color:#344054;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 1273 |
.recovery-evidence{margin:0;padding-left:18px;color:#475467;font-size:11.5px;line-height:1.4}
|
| 1274 |
.recovery-next{margin:0;border:1px dashed #c7d2fe;background:#f8fbff;color:#3730a3;border-radius:11px;padding:8px 9px;font-size:11.5px;line-height:1.35}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1275 |
.run-doc-btn .doc-icon{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-weight:900}
|
| 1276 |
@media (max-width:720px){.recovery-ladder,.recovery-meta{grid-template-columns:1fr 1fr}}
|
| 1277 |
@media (max-width:480px){.recovery-ladder,.recovery-meta{grid-template-columns:1fr}}
|
|
@@ -1617,3 +1625,290 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 1617 |
.step.trace-post-failure.status-muted:before{
|
| 1618 |
background:var(--red);
|
| 1619 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 900 |
.endpoint-params{margin:8px 0 0;color:#667085;font-size:11px;line-height:1.35}
|
| 901 |
.compact-empty{font-size:11px;padding:6px 0;color:#667085}
|
| 902 |
|
| 903 |
+
/* v159: horizontal source-document dock */
|
| 904 |
.run-documents-card{background:linear-gradient(180deg,#fff 0%,#fbfdff 100%)}
|
| 905 |
.run-documents-card .card-title-row{align-items:center}
|
| 906 |
.run-documents-card .card-title-row .muted{font-size:10.5px}
|
| 907 |
+
.run-documents-dock{display:flex;align-items:stretch;gap:8px;margin-top:8px;overflow-x:auto;overflow-y:hidden;padding:2px 2px 8px;scrollbar-width:thin;scroll-snap-type:x proximity}
|
| 908 |
+
.run-doc-btn{position:relative;flex:0 0 86px;display:grid;grid-template-rows:34px auto auto;justify-items:center;align-content:start;gap:5px;border:1px solid var(--line);border-radius:13px;background:#fff;padding:9px 8px 8px;color:#344054;text-align:center;min-height:92px;text-decoration:none;scroll-snap-align:start;transition:border-color .16s ease,box-shadow .16s ease,background .16s ease,transform .16s ease}
|
| 909 |
.run-doc-btn.ready:hover{border-color:#93c5fd;background:#fff;box-shadow:0 8px 18px rgba(16,24,40,.08);transform:translateY(-1px)}
|
| 910 |
+
.run-doc-btn.pending{opacity:.56;filter:grayscale(.25);cursor:not-allowed;background:#f8fafc;border-style:dashed}
|
| 911 |
+
.run-doc-btn.ready::after{content:"";position:absolute;top:7px;right:7px;width:7px;height:7px;border-radius:999px;background:#22c55e;box-shadow:0 0 0 2px #fff}
|
| 912 |
+
.run-doc-btn.pending::after{content:"";position:absolute;top:7px;right:7px;width:7px;height:7px;border-radius:999px;background:#cbd5e1;box-shadow:0 0 0 2px #fff}
|
| 913 |
+
.run-doc-btn .doc-icon{width:34px;height:34px;border-radius:11px;display:grid;place-items:center;background:#f8fafc;border:1px solid #eef2f7;font-size:16px;line-height:1}
|
| 914 |
+
.run-doc-btn strong{display:block;width:100%;font-size:11.5px;line-height:1.12;color:#101828;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 915 |
+
.run-doc-btn em{display:block;width:100%;font-style:normal;font-size:9.5px;line-height:1.18;color:#667085;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 916 |
.run-doc-btn.raw.ready{border-color:#fed7aa;background:linear-gradient(180deg,#fffaf3 0%,#fff 100%)}
|
| 917 |
.run-doc-btn.raw.ready .doc-icon{background:#fff7ed;border-color:#fed7aa}
|
| 918 |
.run-doc-btn.safe.ready{border-color:#c7d2fe;background:linear-gradient(180deg,#f8fbff 0%,#fff 100%)}
|
| 919 |
.run-doc-btn.safe.ready .doc-icon{background:#eef2ff;border-color:#c7d2fe}
|
| 920 |
.run-doc-btn.warn.ready{border-color:#fed7aa;background:linear-gradient(180deg,#fff7ed 0%,#fff 100%)}
|
| 921 |
.run-doc-btn.warn.ready strong{color:#9a3412}
|
| 922 |
+
.run-documents-dock .empty{min-width:100%}
|
| 923 |
+
@media (max-width:420px){.run-doc-btn{flex-basis:78px;min-height:88px;padding-left:7px;padding-right:7px}.run-doc-btn strong{font-size:11px}.run-doc-btn em{font-size:9px}}
|
| 924 |
|
| 925 |
/* v102: narrow-safe run explorer, point-based validation progress */
|
| 926 |
.runs-table{min-width:0}
|
|
|
|
| 1275 |
.recovery-meta strong{display:block;margin-top:2px;font-size:11.5px;color:#344054;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 1276 |
.recovery-evidence{margin:0;padding-left:18px;color:#475467;font-size:11.5px;line-height:1.4}
|
| 1277 |
.recovery-next{margin:0;border:1px dashed #c7d2fe;background:#f8fbff;color:#3730a3;border-radius:11px;padding:8px 9px;font-size:11.5px;line-height:1.35}
|
| 1278 |
+
.recovery-docs{display:flex;align-items:center;gap:7px;flex-wrap:wrap;border:1px solid #e5e7eb;background:#fff;border-radius:11px;padding:8px 9px}
|
| 1279 |
+
.recovery-docs span{font-size:9.5px;text-transform:uppercase;letter-spacing:.06em;color:#94a3b8;font-weight:900;margin-right:2px}
|
| 1280 |
+
.recovery-docs a{font-size:11px;font-weight:850;text-decoration:none;border:1px solid #dbeafe;background:#eff6ff;color:#1d4ed8;border-radius:999px;padding:4px 8px}
|
| 1281 |
+
.recovery-docs a.warn{border-color:#fed7aa;background:#fff7ed;color:#c2410c}
|
| 1282 |
+
.recovery-docs a.raw{border-color:#fecdd3;background:#fff1f2;color:#be123c}
|
| 1283 |
.run-doc-btn .doc-icon{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-weight:900}
|
| 1284 |
@media (max-width:720px){.recovery-ladder,.recovery-meta{grid-template-columns:1fr 1fr}}
|
| 1285 |
@media (max-width:480px){.recovery-ladder,.recovery-meta{grid-template-columns:1fr}}
|
|
|
|
| 1625 |
.step.trace-post-failure.status-muted:before{
|
| 1626 |
background:var(--red);
|
| 1627 |
}
|
| 1628 |
+
|
| 1629 |
+
/* v153 — visual stability and premium polish */
|
| 1630 |
+
.loading-shimmer{
|
| 1631 |
+
position:relative;
|
| 1632 |
+
overflow:hidden;
|
| 1633 |
+
}
|
| 1634 |
+
.loading-shimmer::after{
|
| 1635 |
+
content:"";
|
| 1636 |
+
position:absolute;
|
| 1637 |
+
inset:0;
|
| 1638 |
+
transform:translateX(-100%);
|
| 1639 |
+
background:linear-gradient(90deg,transparent,rgba(255,255,255,.65),transparent);
|
| 1640 |
+
animation:shimmer-pass 1.3s ease-in-out infinite;
|
| 1641 |
+
}
|
| 1642 |
+
@keyframes shimmer-pass{100%{transform:translateX(100%)}}
|
| 1643 |
+
#activeLatencyCard.is-loading{
|
| 1644 |
+
opacity:.78;
|
| 1645 |
+
}
|
| 1646 |
+
.timeline.trace-failed .step.trace-breakpoint{
|
| 1647 |
+
transform:none;
|
| 1648 |
+
}
|
| 1649 |
+
.timeline.trace-failed .step.trace-breakpoint .step-dot,
|
| 1650 |
+
.timeline.trace-recovered .step.trace-recovered .step-dot{
|
| 1651 |
+
width:28px;
|
| 1652 |
+
height:28px;
|
| 1653 |
+
min-width:28px;
|
| 1654 |
+
}
|
| 1655 |
+
.timeline.trace-failed .step.trace-breakpoint:after,
|
| 1656 |
+
.timeline.trace-recovered .step.trace-recovered:after{
|
| 1657 |
+
top:2px;
|
| 1658 |
+
height:40px;
|
| 1659 |
+
}
|
| 1660 |
+
.endpoint-meta{
|
| 1661 |
+
display:flex;
|
| 1662 |
+
gap:8px;
|
| 1663 |
+
flex-wrap:wrap;
|
| 1664 |
+
margin-bottom:8px;
|
| 1665 |
+
}
|
| 1666 |
+
.endpoint-meta span{
|
| 1667 |
+
border:1px solid var(--rule);
|
| 1668 |
+
border-radius:999px;
|
| 1669 |
+
padding:3px 8px;
|
| 1670 |
+
background:#fff;
|
| 1671 |
+
color:var(--muted);
|
| 1672 |
+
font-size:11px;
|
| 1673 |
+
font-weight:800;
|
| 1674 |
+
}
|
| 1675 |
+
.endpoint-param span{
|
| 1676 |
+
display:block;
|
| 1677 |
+
opacity:.7;
|
| 1678 |
+
margin-top:2px;
|
| 1679 |
+
}
|
| 1680 |
+
.prescan-meta{
|
| 1681 |
+
display:grid!important;
|
| 1682 |
+
grid-template-columns:repeat(3,minmax(0,1fr));
|
| 1683 |
+
gap:8px!important;
|
| 1684 |
+
margin-bottom:8px;
|
| 1685 |
+
}
|
| 1686 |
+
.prescan-meta span{
|
| 1687 |
+
border:1px solid var(--rule);
|
| 1688 |
+
border-radius:12px;
|
| 1689 |
+
padding:8px 10px;
|
| 1690 |
+
background:#fff;
|
| 1691 |
+
}
|
| 1692 |
+
.prescan-meta em{
|
| 1693 |
+
display:block;
|
| 1694 |
+
font-size:10px;
|
| 1695 |
+
text-transform:uppercase;
|
| 1696 |
+
letter-spacing:.06em;
|
| 1697 |
+
color:var(--muted);
|
| 1698 |
+
font-style:normal;
|
| 1699 |
+
font-weight:900;
|
| 1700 |
+
}
|
| 1701 |
+
.prescan-meta strong{
|
| 1702 |
+
display:block;
|
| 1703 |
+
margin-top:3px;
|
| 1704 |
+
overflow:hidden;
|
| 1705 |
+
text-overflow:ellipsis;
|
| 1706 |
+
white-space:nowrap;
|
| 1707 |
+
}
|
| 1708 |
+
|
| 1709 |
+
/* v160 premium destructive action modal */
|
| 1710 |
+
.delete-run-modal[hidden]{display:none!important}
|
| 1711 |
+
.delete-run-modal{position:fixed;inset:0;z-index:1000;display:grid;place-items:center;padding:24px;opacity:0;pointer-events:none;transition:opacity .18s ease}
|
| 1712 |
+
.delete-run-modal.is-open{opacity:1;pointer-events:auto}
|
| 1713 |
+
.delete-run-backdrop{position:absolute;inset:0;background:rgba(15,23,42,.42);backdrop-filter:blur(10px)}
|
| 1714 |
+
.delete-run-card{position:relative;width:min(520px,calc(100vw - 32px));display:grid;gap:16px;padding:22px;border-radius:28px;border:1px solid rgba(255,255,255,.78);background:linear-gradient(180deg,rgba(255,255,255,.98),rgba(248,250,252,.96));box-shadow:0 30px 90px rgba(15,23,42,.25),0 1px 0 rgba(255,255,255,.8) inset;transform:translateY(8px) scale(.98);transition:transform .18s ease}
|
| 1715 |
+
.delete-run-modal.is-open .delete-run-card{transform:translateY(0) scale(1)}
|
| 1716 |
+
.delete-run-close{position:absolute;top:14px;right:14px;width:34px;height:34px;border-radius:999px;border:1px solid #e5e7eb;background:#fff;color:#667085;font-size:20px;line-height:1;display:grid;place-items:center;cursor:pointer}
|
| 1717 |
+
.delete-run-close:hover{background:#fff1f2;border-color:#fecdd3;color:#be123c}
|
| 1718 |
+
.delete-run-icon{width:52px;height:52px;border-radius:18px;display:grid;place-items:center;background:linear-gradient(180deg,#fff1f2,#ffe4e6);border:1px solid #fecdd3;color:#be123c;font-size:26px;box-shadow:0 10px 24px rgba(190,18,60,.12)}
|
| 1719 |
+
.delete-run-copy{display:grid;gap:6px;padding-right:28px}.delete-run-kicker{font-size:11px;font-weight:900;text-transform:uppercase;letter-spacing:.08em;color:#be123c}.delete-run-copy h3{margin:0;font-size:22px;line-height:1.15;color:#101828;letter-spacing:-.03em}.delete-run-copy p{margin:0;color:#475467;font-size:13px;line-height:1.45}
|
| 1720 |
+
.delete-run-meta{display:grid;gap:8px;padding:12px;border:1px solid #e5e7eb;border-radius:18px;background:#fff}.delete-run-meta span{display:grid;grid-template-columns:64px minmax(0,1fr);gap:10px;align-items:center;font-size:12px;color:#344054}.delete-run-meta strong{font-size:10px;text-transform:uppercase;letter-spacing:.06em;color:#98a2b3}.delete-run-meta code{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:11.5px;color:#101828;background:#f8fafc;border:1px solid #eef2f7;border-radius:9px;padding:5px 7px}
|
| 1721 |
+
.delete-run-warning{border:1px solid #fed7aa;background:#fff7ed;color:#9a3412;border-radius:16px;padding:10px 12px;font-size:12px;line-height:1.4}.delete-run-warning[hidden]{display:none!important}
|
| 1722 |
+
.delete-run-actions{display:flex;justify-content:flex-end;gap:10px}.delete-run-actions .primary.danger{background:linear-gradient(180deg,#e11d48,#be123c);border-color:#be123c;color:#fff;box-shadow:0 12px 24px rgba(190,18,60,.22)}.delete-run-actions .primary.danger:hover{background:linear-gradient(180deg,#be123c,#9f1239)}
|
| 1723 |
+
@media (max-width:520px){.delete-run-modal{padding:14px}.delete-run-card{border-radius:22px;padding:18px}.delete-run-actions{display:grid;grid-template-columns:1fr}.delete-run-actions button{width:100%}.delete-run-meta span{grid-template-columns:1fr;gap:4px}}
|
| 1724 |
+
|
| 1725 |
+
/* v162: Latest events gets enough vertical room for live runs */
|
| 1726 |
+
.latest-events-card .activity-feed,
|
| 1727 |
+
.latest-events-card .activity-feed.show-all{
|
| 1728 |
+
max-height:354px;
|
| 1729 |
+
overflow-y:auto;
|
| 1730 |
+
overscroll-behavior:contain;
|
| 1731 |
+
}
|
| 1732 |
+
|
| 1733 |
+
/* v164 — premium agent timeline: precise milestones + clockwise grouped progress */
|
| 1734 |
+
.timeline{
|
| 1735 |
+
--tl-line:#d8dee9;
|
| 1736 |
+
--tl-muted:#98a2b3;
|
| 1737 |
+
--tl-ring-bg:#e5e7eb;
|
| 1738 |
+
display:grid!important;
|
| 1739 |
+
grid-auto-flow:column!important;
|
| 1740 |
+
grid-auto-columns:minmax(98px,1fr)!important;
|
| 1741 |
+
gap:0!important;
|
| 1742 |
+
align-items:start!important;
|
| 1743 |
+
padding:8px 2px 10px!important;
|
| 1744 |
+
overflow-x:auto!important;
|
| 1745 |
+
overflow-y:hidden!important;
|
| 1746 |
+
scroll-snap-type:x proximity;
|
| 1747 |
+
}
|
| 1748 |
+
.timeline .step{
|
| 1749 |
+
position:relative;
|
| 1750 |
+
display:grid;
|
| 1751 |
+
justify-items:center;
|
| 1752 |
+
gap:6px;
|
| 1753 |
+
min-width:98px!important;
|
| 1754 |
+
max-width:136px!important;
|
| 1755 |
+
padding:7px 8px 2px!important;
|
| 1756 |
+
scroll-snap-align:center;
|
| 1757 |
+
text-align:center;
|
| 1758 |
+
transition:opacity .18s ease, transform .18s ease, filter .18s ease;
|
| 1759 |
+
}
|
| 1760 |
+
.timeline .step:before{
|
| 1761 |
+
content:"";
|
| 1762 |
+
position:absolute;
|
| 1763 |
+
left:0;
|
| 1764 |
+
right:0;
|
| 1765 |
+
top:24px;
|
| 1766 |
+
height:2px;
|
| 1767 |
+
background:var(--tl-line);
|
| 1768 |
+
z-index:0;
|
| 1769 |
+
transition:background .22s ease;
|
| 1770 |
+
}
|
| 1771 |
+
.timeline .step:first-child:before{left:50%}
|
| 1772 |
+
.timeline .step:last-child:before{right:50%}
|
| 1773 |
+
.timeline .step-dot{
|
| 1774 |
+
position:relative;
|
| 1775 |
+
z-index:1;
|
| 1776 |
+
width:34px!important;
|
| 1777 |
+
height:34px!important;
|
| 1778 |
+
min-width:34px!important;
|
| 1779 |
+
border:0!important;
|
| 1780 |
+
border-radius:999px;
|
| 1781 |
+
display:grid;
|
| 1782 |
+
place-items:center;
|
| 1783 |
+
padding:3px!important;
|
| 1784 |
+
color:var(--timeline-fg,#475467)!important;
|
| 1785 |
+
background:
|
| 1786 |
+
conic-gradient(from -90deg,var(--timeline-ring,var(--blue)) 0 var(--step-progress,0%),var(--tl-ring-bg) var(--step-progress,0%) 100%)!important;
|
| 1787 |
+
box-shadow:0 1px 2px rgba(15,23,42,.08),0 0 0 1px rgba(15,23,42,.06) inset!important;
|
| 1788 |
+
transition:background .28s ease, transform .18s ease, box-shadow .18s ease, color .18s ease;
|
| 1789 |
+
}
|
| 1790 |
+
.timeline .step-dot span{
|
| 1791 |
+
position:relative;
|
| 1792 |
+
z-index:2;
|
| 1793 |
+
width:24px!important;
|
| 1794 |
+
height:24px!important;
|
| 1795 |
+
border-radius:999px;
|
| 1796 |
+
display:grid;
|
| 1797 |
+
place-items:center;
|
| 1798 |
+
margin:auto;
|
| 1799 |
+
background:#fff!important;
|
| 1800 |
+
color:inherit!important;
|
| 1801 |
+
font-size:11px;
|
| 1802 |
+
font-weight:900;
|
| 1803 |
+
line-height:1;
|
| 1804 |
+
box-shadow:0 0 0 1px rgba(15,23,42,.06) inset;
|
| 1805 |
+
}
|
| 1806 |
+
.timeline .step.is-grouped .step-dot::before{
|
| 1807 |
+
content:"";
|
| 1808 |
+
position:absolute;
|
| 1809 |
+
inset:-3px;
|
| 1810 |
+
border-radius:999px;
|
| 1811 |
+
border:1px solid rgba(15,23,42,.08);
|
| 1812 |
+
pointer-events:none;
|
| 1813 |
+
}
|
| 1814 |
+
.timeline .step-count{
|
| 1815 |
+
position:absolute;
|
| 1816 |
+
right:-8px;
|
| 1817 |
+
bottom:-5px;
|
| 1818 |
+
z-index:3;
|
| 1819 |
+
min-width:23px;
|
| 1820 |
+
height:15px;
|
| 1821 |
+
padding:0 5px;
|
| 1822 |
+
display:grid;
|
| 1823 |
+
place-items:center;
|
| 1824 |
+
border-radius:999px;
|
| 1825 |
+
border:1px solid rgba(15,23,42,.08);
|
| 1826 |
+
background:#fff;
|
| 1827 |
+
color:#667085;
|
| 1828 |
+
font-size:8.5px;
|
| 1829 |
+
font-style:normal;
|
| 1830 |
+
font-weight:900;
|
| 1831 |
+
letter-spacing:-.02em;
|
| 1832 |
+
box-shadow:0 2px 5px rgba(15,23,42,.08);
|
| 1833 |
+
}
|
| 1834 |
+
.timeline .step strong{
|
| 1835 |
+
display:block!important;
|
| 1836 |
+
max-width:100%;
|
| 1837 |
+
overflow:hidden;
|
| 1838 |
+
text-overflow:ellipsis;
|
| 1839 |
+
white-space:nowrap;
|
| 1840 |
+
color:#344054;
|
| 1841 |
+
font-size:10.75px!important;
|
| 1842 |
+
line-height:1.12;
|
| 1843 |
+
letter-spacing:-.01em;
|
| 1844 |
+
}
|
| 1845 |
+
.timeline .step small{
|
| 1846 |
+
display:block!important;
|
| 1847 |
+
max-width:100%;
|
| 1848 |
+
margin-top:2px;
|
| 1849 |
+
overflow:hidden;
|
| 1850 |
+
text-overflow:ellipsis;
|
| 1851 |
+
white-space:nowrap;
|
| 1852 |
+
color:#98a2b3;
|
| 1853 |
+
font-size:9px!important;
|
| 1854 |
+
line-height:1.1;
|
| 1855 |
+
}
|
| 1856 |
+
.timeline .step.status-done{--timeline-ring:var(--green);--timeline-fg:#047857}
|
| 1857 |
+
.timeline .step.status-running,.timeline .step.status-waiting{--timeline-ring:var(--blue);--timeline-fg:#2563eb}
|
| 1858 |
+
.timeline .step.status-fallback,.timeline .step.status-warn{--timeline-ring:var(--amber);--timeline-fg:#92400e;--tl-line:#fde68a}
|
| 1859 |
+
.timeline .step.status-failed{--timeline-ring:var(--red);--timeline-fg:#be123c;--tl-line:#fecdd3}
|
| 1860 |
+
.timeline .step.status-stopped,.timeline .step.status-after-fail{--timeline-ring:#64748b;--timeline-fg:#475569;--tl-line:#cbd5e1}
|
| 1861 |
+
.timeline .step.status-pending,.timeline .step.status-muted{--timeline-ring:#cbd5e1;--timeline-fg:#98a2b3;opacity:.72}
|
| 1862 |
+
.timeline .step.status-done:before{background:var(--green)!important}
|
| 1863 |
+
.timeline .step.status-running:before,.timeline .step.status-waiting:before{background:linear-gradient(90deg,var(--green) 0 50%,var(--tl-line) 50%)!important}
|
| 1864 |
+
.timeline .step.status-fallback:before,.timeline .step.status-warn:before{background:linear-gradient(90deg,var(--green) 0 50%,var(--amber) 50%)!important}
|
| 1865 |
+
.timeline .step.status-failed:before{background:linear-gradient(90deg,var(--green) 0 50%,var(--red) 50%)!important}
|
| 1866 |
+
.timeline .step.status-after-fail:before{background:var(--red)!important}
|
| 1867 |
+
.timeline .step.status-running .step-dot,
|
| 1868 |
+
.timeline .step.status-waiting .step-dot{
|
| 1869 |
+
animation:timeline-active-breathe 1.45s ease-in-out infinite!important;
|
| 1870 |
+
}
|
| 1871 |
+
.timeline .step.status-running .step-dot::after,
|
| 1872 |
+
.timeline .step.status-waiting .step-dot::after,
|
| 1873 |
+
.timeline .step.running .step-dot::after{
|
| 1874 |
+
inset:-8px!important;
|
| 1875 |
+
border-color:rgba(37,99,235,.2)!important;
|
| 1876 |
+
animation:timeline-soft-wave 1.45s ease-out infinite!important;
|
| 1877 |
+
}
|
| 1878 |
+
.timeline .step.status-changed .step-dot,
|
| 1879 |
+
.timeline .step .step-dot.dot-pop{
|
| 1880 |
+
animation:timeline-dot-pop .56s cubic-bezier(.2,.9,.2,1.2)!important;
|
| 1881 |
+
}
|
| 1882 |
+
.timeline.trace-failed .step.trace-breakpoint .step-dot,
|
| 1883 |
+
.timeline.trace-recovered .step.trace-recovered .step-dot{
|
| 1884 |
+
width:34px!important;
|
| 1885 |
+
height:34px!important;
|
| 1886 |
+
min-width:34px!important;
|
| 1887 |
+
}
|
| 1888 |
+
.timeline.trace-failed .step.trace-breakpoint:after,
|
| 1889 |
+
.timeline.trace-recovered .step.trace-recovered:after{
|
| 1890 |
+
display:none!important;
|
| 1891 |
+
}
|
| 1892 |
+
@keyframes timeline-active-breathe{
|
| 1893 |
+
0%,100%{transform:translateY(0) scale(1);box-shadow:0 1px 2px rgba(15,23,42,.08),0 0 0 1px rgba(37,99,235,.08) inset}
|
| 1894 |
+
50%{transform:translateY(-1px) scale(1.04);box-shadow:0 8px 18px rgba(37,99,235,.13),0 0 0 1px rgba(37,99,235,.12) inset}
|
| 1895 |
+
}
|
| 1896 |
+
@keyframes timeline-soft-wave{
|
| 1897 |
+
0%{transform:scale(.78);opacity:.85}
|
| 1898 |
+
72%{transform:scale(1.28);opacity:0}
|
| 1899 |
+
100%{transform:scale(1.28);opacity:0}
|
| 1900 |
+
}
|
| 1901 |
+
@keyframes timeline-dot-pop{
|
| 1902 |
+
0%{transform:scale(.82)}
|
| 1903 |
+
60%{transform:scale(1.12)}
|
| 1904 |
+
100%{transform:scale(1)}
|
| 1905 |
+
}
|
| 1906 |
+
@media (prefers-reduced-motion:reduce){
|
| 1907 |
+
.timeline,.timeline *{scroll-behavior:auto!important;animation:none!important;transition:none!important}
|
| 1908 |
+
}
|
| 1909 |
+
@media (max-width:720px){
|
| 1910 |
+
.timeline{grid-auto-flow:row!important;grid-auto-columns:auto!important;grid-template-columns:1fr!important;overflow-x:hidden!important;gap:6px!important}
|
| 1911 |
+
.timeline .step{grid-template-columns:34px minmax(0,1fr);justify-items:start;text-align:left;max-width:none!important;width:100%!important;min-width:0!important}
|
| 1912 |
+
.timeline .step:before{left:24px!important;right:auto!important;top:0;bottom:0;width:2px;height:auto}
|
| 1913 |
+
.timeline .step strong,.timeline .step small{text-align:left}
|
| 1914 |
+
}
|
web/static/app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
const state={runId:null,validationRunId:null,validationPoll:null,centerTab:'active',validationPollErrors:0,validationDetail:null,spaceLinksUnlockedFor:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},activeRunProgress:null,selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage',modelScan:null,runDetailCache:{},runDetailCacheTtlMs:300000};
|
| 2 |
const TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 3 |
const DEFAULT_TIMELINE=[{label:'Bootstrap',status:'pending'},{label:'Dependencies',status:'pending'},{label:'Auth',status:'pending'},{label:'Model analysis',status:'pending'},{label:'Workspace',status:'pending'},{label:'Node/npm',status:'pending'},{label:'Pi install',status:'pending'},{label:'Pi config',status:'pending'},{label:'Pi run',status:'pending'},{label:'Pi verification',status:'pending'},{label:'Metadata sanitize',status:'pending'},{label:'Requirements sanitize',status:'pending'},{label:'Hardware strategy',status:'pending'},{label:'Create with hardware',status:'pending'},{label:'Create Space',status:'pending'},{label:'Repair pass',status:'pending'},{label:'Upload files',status:'pending'},{label:'Space runtime',status:'pending'},{label:'Space logs',status:'pending'},{label:'API validation',status:'pending'},{label:'Live wait',status:'pending'},{label:'Generation smoke',status:'pending'},{label:'Inference gate',status:'pending'},{label:'Report',status:'pending'},{label:'Done',status:'pending'},{label:'Failure',status:'pending'}];
|
| 4 |
|
|
@@ -168,8 +168,12 @@ function endpointDetails(detail={},endpointName=''){
|
|
| 168 |
function renderEndpointParamChips(items=[]){
|
| 169 |
if(!items.length)return '<span class="muted">No parameters discovered yet.</span>';
|
| 170 |
return items.slice(0,10).map(item=>{
|
| 171 |
-
const
|
| 172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
}).join('')+(items.length>10?'<em><strong>…</strong></em>':'');
|
| 174 |
}
|
| 175 |
function renderValidationEndpoints(detail={}){
|
|
@@ -186,7 +190,8 @@ function renderValidationEndpoints(detail={}){
|
|
| 186 |
root.dataset.signature=signature;
|
| 187 |
const inputLine=`<div class="endpoint-io"><strong>Inputs</strong><span>${renderEndpointParamChips(details.inputs)}</span></div>`;
|
| 188 |
const outputLine=details.outputs.length?`<div class="endpoint-io"><strong>Outputs</strong><span>${renderEndpointParamChips(details.outputs)}</span></div>`:'';
|
| 189 |
-
|
|
|
|
| 190 |
}
|
| 191 |
function setGeneratedSpaceLinksEnabled(links={},enabled=false){
|
| 192 |
const space=enabled?links.target_space_url:'';
|
|
@@ -199,7 +204,7 @@ let operationTimer=null;function toastDuration(type='info'){if(type==='loading')
|
|
| 199 |
function setOperation(message,type='info',opts={}){const el=$('operationStatus');if(!el)return;if(operationTimer){clearTimeout(operationTimer);operationTimer=null}if(!message){el.hidden=true;el.textContent='';el.className='operation-status floating-toast';return}el.hidden=false;el.className='operation-status floating-toast '+type;el.textContent=message;const duration=opts.duration??toastDuration(type);if(duration>0)operationTimer=setTimeout(()=>{setOperation('')},duration)}
|
| 200 |
function showMessage(msg,isError=false){const type=isError===true?'error':(typeof isError==='string'?isError:'info');setOperation(msg,type);const el=$('eventsPanel');if(el&&!state.runId)el.textContent=(isError?'Error: ':'')+msg}
|
| 201 |
function setBusy(isBusy,label='Working…'){state.busy=Boolean(isBusy);if(!state.busy&&$('operationStatus')?.className.includes('loading'))setOperation('');document.querySelectorAll('[data-action-button]').forEach(btn=>{btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady});if($('launchBuild'))setBuildLaunchEnabled();if($('launchValidate'))$('launchValidate').disabled=state.busy;if(state.busy)setOperation(label,'loading')}
|
| 202 |
-
function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){btn.dataset.oldText=btn.textContent;btn.textContent=busyLabel||'Working…';btn.disabled=true;btn.classList.add('is-busy')}else{if(btn.dataset.oldText)btn.textContent=btn.dataset.oldText;if(id==='launchBuild')setBuildLaunchEnabled();else btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}}
|
| 203 |
function setBucketFeedback(mode='checking',message='Checking private bucket access…'){
|
| 204 |
const orb=$('bucketInlineOrb');
|
| 205 |
if(orb)orb.className='bucket-status-orb '+mode;
|
|
@@ -253,8 +258,9 @@ function renderModelPreScan(scan=null){
|
|
| 253 |
const recs=[...(scan.recommendations||[]).slice(0,2)];
|
| 254 |
if(outputType)recs.unshift(`Expected output: ${outputType}`);
|
| 255 |
if(signals){
|
|
|
|
| 256 |
signals.hidden=false;
|
| 257 |
-
signals.innerHTML=`${good.length?`<div><strong>Good</strong>${good.map(x=>`<span class="good">${escapeHtml(x)}</span>`).join('')}</div>`:''}${risk.length?`<div><strong>Watch</strong>${risk.map(x=>`<span class="risk">${escapeHtml(x)}</span>`).join('')}</div>`:''}${recs.length?`<div><strong>Recommendation</strong>${recs.map(x=>`<span>${escapeHtml(x)}</span>`).join('')}</div>`:''}`;
|
| 258 |
}
|
| 259 |
if(ackWrap)ackWrap.hidden=!(verdict==='risky');
|
| 260 |
}
|
|
@@ -290,11 +296,12 @@ function resetRunDetails(){
|
|
| 290 |
if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;if(typeof resetProgressStability==='function')resetProgressStability(null);
|
| 291 |
if(typeof activityFeedState!=='undefined'){activityFeedState.lastItems=[];activityFeedState.runId=null}
|
| 292 |
for(const [id,value] of [
|
| 293 |
-
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTraces','—'],
|
| 294 |
['homeRunDetailStatus','No run'],['selectedModelId','—'],['progressTitle','No run selected'],['progressStatus','Idle'],
|
| 295 |
['currentStep','—'],['lastEvent','Select a run from the explorer or launch a new build.'],['elapsed','—'],['lastPolled','—'],
|
| 296 |
['progressPercent','0%']
|
| 297 |
])setText(id,value);
|
|
|
|
| 298 |
const status=$('progressStatus');if(status)status.className='badge neutral';
|
| 299 |
const fill=$('progressFill');if(fill){fill.style.width='0%';fill.classList.remove('is-running','progress-bump')}
|
| 300 |
setQuickLinks({});
|
|
@@ -310,8 +317,8 @@ function resetRunDetails(){
|
|
| 310 |
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
|
| 311 |
for(const id of ['deleteActiveRun','activePrefillSpaceTest','deleteValidationRun']){const btn=$(id);if(btn)btn.disabled=true}
|
| 312 |
renderTimeline(DEFAULT_TIMELINE,'idle');
|
| 313 |
-
} function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=null;state.selectedRunDetail=null;state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();renderTimeline(DEFAULT_TIMELINE);setText('progressPercent','0%');const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','—');if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;setText('lastPolled','—');setText('hardwarePlan',hardwarePlanLabel());renderMetrics({});showMessage('New build mode ready.','info')}
|
| 314 |
-
function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetRunDetails();state.runId=result.run_id;state.spaceLinksUnlockedFor=null;if(Array.isArray(state.runsCache)&&typeof sortRunsNewestFirst==='function'){state.runsCache=sortRunsNewestFirst([{...result,run_id:result.run_id,status:result.status||'running',created_at:result.created_at||new Date().toISOString()},...state.runsCache.filter(r=>r.run_id!==result.run_id)]);renderRunsFromCache();}if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'—');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'—');renderActiveRunMeta({summary:result,state:result,launch:result});renderProgress({run_id:result.run_id,status:result.status||'running',progress:1,current_step_label:'Launching build Job…',events:[{step:'launch',status:'started',message:'Build Job launched; waiting for first worker event.'}],summary:result,state:result});setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username,target_space_url:result.target_space_url},result);saveActiveRun();markPendingActiveFields();startPolling(result.run_id)}
|
| 315 |
function startPolling(runId,options={}){
|
| 316 |
if(!runId)return;
|
| 317 |
const sameRun=state.runId===runId;
|
|
@@ -429,7 +436,7 @@ function renderValidationStatus(data={}){
|
|
| 429 |
if(card)card.classList.toggle('is-validating',isRunning);
|
| 430 |
const runId=data.run_id||summary.run_id||state.validationRunId||'—';
|
| 431 |
setText('spaceTestOutput',runId);
|
| 432 |
-
setText('
|
| 433 |
const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,data.latency_seconds,data.observed_latency_seconds);
|
| 434 |
setText('spaceTestLatency',latency?formatLatencySeconds(latency):'—');
|
| 435 |
setText('spaceTestVerdict',badgeLabel(status));
|
|
@@ -511,7 +518,7 @@ function prepareValidationFromActiveRun(){
|
|
| 511 |
function prepareValidationFromSelectedRun(){
|
| 512 |
applyValidationPrefill(state.selectedRunDetail||{},'selected Space');
|
| 513 |
}
|
| 514 |
-
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Job…');setButtonBusy('launchBuild',true,'Launching…');try{setText('hardwarePlan',hardwarePlanLabel());const payload={bucket_name:state.bucketName,
|
| 515 |
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:normalizeApiName(formValue('validateApi')||'/generate'),expected_output_type:formValue('expectedOutput')||'any',test_args_json:prettyJson(args),test_kwargs_json:prettyJson(kwargs),live_timeout_seconds:Number(formValue('validateTimeout')||1800)};const result=await apiPost('/api/validate',payload);renderValidationLaunchResult(result);startValidationPolling(result.run_id);showMessage(`Validation launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchValidate',false)}}
|
| 516 |
async function refreshRunReportPreview(runId){
|
| 517 |
if(!runId)return;
|
|
@@ -561,19 +568,29 @@ function readRunLatencyPayload(p={}){
|
|
| 561 |
function renderActiveLatency(p={}){
|
| 562 |
const card=$('activeLatencyCard');
|
| 563 |
if(!card)return;
|
|
|
|
|
|
|
| 564 |
const {latency,duration,source}=readRunLatencyPayload(p);
|
| 565 |
const hasLatency=Number.isFinite(latency)&&latency>0;
|
| 566 |
const hasDuration=Number.isFinite(duration)&&duration>0;
|
| 567 |
-
|
| 568 |
-
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
const note=$('activeLatencyNote');
|
| 571 |
if(note){
|
| 572 |
-
note.textContent=
|
| 573 |
? 'This measured latency is normally used to tune the `@spaces.GPU(duration=...)` decorator for ZeroGPU when the generated Space uses ZeroGPU.'
|
| 574 |
: 'When available, the live smoke-test latency is used to recommend the `@spaces.GPU(duration=...)` value for ZeroGPU apps.';
|
| 575 |
}
|
| 576 |
-
card.classList.toggle('has-latency',
|
|
|
|
| 577 |
}
|
| 578 |
function runHardwareMeta(p={}){
|
| 579 |
const summary=p.summary||{};
|
|
@@ -653,8 +670,6 @@ function bindActions(){
|
|
| 653 |
refreshRuns:()=>loadRuns(true),
|
| 654 |
refreshRunsHome:()=>loadRuns(true),
|
| 655 |
cancelRun:cancelActiveRun,
|
| 656 |
-
deleteActiveRun:()=>deleteRunById(state.runId,{kind:'active',detail:state.selectedRunDetail||state.activeRunProgress||{}}),
|
| 657 |
-
deleteValidationRun:()=>deleteRunById(state.validationRunId,{kind:'validation',detail:state.validationDetail||{}}),
|
| 658 |
};
|
| 659 |
Object.entries(actions).forEach(([id,handler])=>bindOnce($(id),'click',`action${id}`,handler));
|
| 660 |
bindOnce($('modelId'),'input','modelPreScanReset',resetModelPreScan);
|
|
|
|
| 1 |
+
const state={runId:null,validationRunId:null,validationPoll:null,centerTab:'active',validationPollErrors:0,validationDetail:null,spaceLinksUnlockedFor:null,bucketName:'space-factory-runs',bucketSource:null,bucketReady:false,poll:null,activeLinks:{},activeRunProgress:null,selectedRunDetail:null,runStatusFilter:'all',runSearch:'',runPage:0,runsPerPage:5,runsCache:[],runsLastLoaded:0,lastRunsRefreshFromPoll:0,lastJobLogPoll:0,pollErrors:0,busy:false,user:null,currentView:'onepage',modelScan:null,runDetailCache:{},runDetailCacheTtlMs:300000,deleteModalPromise:null,deleteModalRunId:null,deletingRuns:{}};
|
| 2 |
const TERMINAL_STATUSES=new Set(['done','success','failed','full_inference_success','full_inference_candidate_health_passed','manual_hardware_required','generated_needs_manual_hardware','technical_blocker','health_only','repair_success','repair_failed','succeeded','waiting_manual_action','blocked','stale','cancelled','canceled']);
|
| 3 |
const DEFAULT_TIMELINE=[{label:'Bootstrap',status:'pending'},{label:'Dependencies',status:'pending'},{label:'Auth',status:'pending'},{label:'Model analysis',status:'pending'},{label:'Workspace',status:'pending'},{label:'Node/npm',status:'pending'},{label:'Pi install',status:'pending'},{label:'Pi config',status:'pending'},{label:'Pi run',status:'pending'},{label:'Pi verification',status:'pending'},{label:'Metadata sanitize',status:'pending'},{label:'Requirements sanitize',status:'pending'},{label:'Hardware strategy',status:'pending'},{label:'Create with hardware',status:'pending'},{label:'Create Space',status:'pending'},{label:'Repair pass',status:'pending'},{label:'Upload files',status:'pending'},{label:'Space runtime',status:'pending'},{label:'Space logs',status:'pending'},{label:'API validation',status:'pending'},{label:'Live wait',status:'pending'},{label:'Generation smoke',status:'pending'},{label:'Inference gate',status:'pending'},{label:'Report',status:'pending'},{label:'Done',status:'pending'},{label:'Failure',status:'pending'}];
|
| 4 |
|
|
|
|
| 168 |
function renderEndpointParamChips(items=[]){
|
| 169 |
if(!items.length)return '<span class="muted">No parameters discovered yet.</span>';
|
| 170 |
return items.slice(0,10).map(item=>{
|
| 171 |
+
const name=item.name||item.label||item.id||'param';
|
| 172 |
+
const type=item.type||item.datatype||'';
|
| 173 |
+
const component=item.component||item.component_type||item.rendered_component||'';
|
| 174 |
+
const required=item.optional?'optional':'required';
|
| 175 |
+
const detail=[type,component,required].filter(Boolean).join(' · ');
|
| 176 |
+
return `<em class="endpoint-param" title="${escapeHtml(detail||name)}"><strong>${escapeHtml(name)}</strong>${detail?`<span>${escapeHtml(detail)}</span>`:''}</em>`;
|
| 177 |
}).join('')+(items.length>10?'<em><strong>…</strong></em>':'');
|
| 178 |
}
|
| 179 |
function renderValidationEndpoints(detail={}){
|
|
|
|
| 190 |
root.dataset.signature=signature;
|
| 191 |
const inputLine=`<div class="endpoint-io"><strong>Inputs</strong><span>${renderEndpointParamChips(details.inputs)}</span></div>`;
|
| 192 |
const outputLine=details.outputs.length?`<div class="endpoint-io"><strong>Outputs</strong><span>${renderEndpointParamChips(details.outputs)}</span></div>`:'';
|
| 193 |
+
const meta=`<div class="endpoint-meta"><span>${escapeHtml(selected)}</span><span>${details.inputs.length} input${details.inputs.length===1?'':'s'}</span><span>${details.outputs.length} output${details.outputs.length===1?'':'s'}</span></div>`;
|
| 194 |
+
root.innerHTML=`<div class="endpoint-chip-list">${chips}</div><div class="endpoint-info-card">${meta}${inputLine}${outputLine}${details.description?`<p>${escapeHtml(details.description)}</p>`:''}</div>`;
|
| 195 |
}
|
| 196 |
function setGeneratedSpaceLinksEnabled(links={},enabled=false){
|
| 197 |
const space=enabled?links.target_space_url:'';
|
|
|
|
| 204 |
function setOperation(message,type='info',opts={}){const el=$('operationStatus');if(!el)return;if(operationTimer){clearTimeout(operationTimer);operationTimer=null}if(!message){el.hidden=true;el.textContent='';el.className='operation-status floating-toast';return}el.hidden=false;el.className='operation-status floating-toast '+type;el.textContent=message;const duration=opts.duration??toastDuration(type);if(duration>0)operationTimer=setTimeout(()=>{setOperation('')},duration)}
|
| 205 |
function showMessage(msg,isError=false){const type=isError===true?'error':(typeof isError==='string'?isError:'info');setOperation(msg,type);const el=$('eventsPanel');if(el&&!state.runId)el.textContent=(isError?'Error: ':'')+msg}
|
| 206 |
function setBusy(isBusy,label='Working…'){state.busy=Boolean(isBusy);if(!state.busy&&$('operationStatus')?.className.includes('loading'))setOperation('');document.querySelectorAll('[data-action-button]').forEach(btn=>{btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady});if($('launchBuild'))setBuildLaunchEnabled();if($('launchValidate'))$('launchValidate').disabled=state.busy;if(state.busy)setOperation(label,'loading')}
|
| 207 |
+
function setButtonBusy(id,isBusy,busyLabel){const btn=$(id);if(!btn)return;if(isBusy){if(!btn.classList.contains('is-busy')&&!btn.dataset.oldText)btn.dataset.oldText=btn.textContent;btn.textContent=busyLabel||'Working…';btn.disabled=true;btn.classList.add('is-busy')}else{if(btn.dataset.oldText){btn.textContent=btn.dataset.oldText;delete btn.dataset.oldText}if(id==='launchBuild')setBuildLaunchEnabled();else btn.disabled=state.busy||btn.dataset.requiresBucket==='true'&&!state.bucketReady;btn.classList.remove('is-busy')}}
|
| 208 |
function setBucketFeedback(mode='checking',message='Checking private bucket access…'){
|
| 209 |
const orb=$('bucketInlineOrb');
|
| 210 |
if(orb)orb.className='bucket-status-orb '+mode;
|
|
|
|
| 258 |
const recs=[...(scan.recommendations||[]).slice(0,2)];
|
| 259 |
if(outputType)recs.unshift(`Expected output: ${outputType}`);
|
| 260 |
if(signals){
|
| 261 |
+
const modelMeta=[scan.pipeline_tag?['Pipeline',scan.pipeline_tag]:null,scan.library_name?['Library',scan.library_name]:null,outputType?['Output',outputType]:null].filter(Boolean);
|
| 262 |
signals.hidden=false;
|
| 263 |
+
signals.innerHTML=`<div class="prescan-meta">${modelMeta.map(([k,v])=>`<span><em>${escapeHtml(k)}</em><strong>${escapeHtml(String(v))}</strong></span>`).join('')}</div>${good.length?`<div><strong>Good signals</strong>${good.map(x=>`<span class="good">${escapeHtml(x)}</span>`).join('')}</div>`:''}${risk.length?`<div><strong>Watch</strong>${risk.map(x=>`<span class="risk">${escapeHtml(x)}</span>`).join('')}</div>`:''}${recs.length?`<div><strong>Recommendation</strong>${recs.map(x=>`<span>${escapeHtml(x)}</span>`).join('')}</div>`:''}`;
|
| 264 |
}
|
| 265 |
if(ackWrap)ackWrap.hidden=!(verdict==='risky');
|
| 266 |
}
|
|
|
|
| 296 |
if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;if(typeof resetProgressStability==='function')resetProgressStability(null);
|
| 297 |
if(typeof activityFeedState!=='undefined'){activityFeedState.lastItems=[];activityFeedState.runId=null}
|
| 298 |
for(const [id,value] of [
|
| 299 |
+
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTargetGpu','—'],['detailFallbackGpu','—'],['detailExpectedOutput','—'],['detailTraces','—'],
|
| 300 |
['homeRunDetailStatus','No run'],['selectedModelId','—'],['progressTitle','No run selected'],['progressStatus','Idle'],
|
| 301 |
['currentStep','—'],['lastEvent','Select a run from the explorer or launch a new build.'],['elapsed','—'],['lastPolled','—'],
|
| 302 |
['progressPercent','0%']
|
| 303 |
])setText(id,value);
|
| 304 |
+
setText('activeLatencyValue','—');setText('activeZeroGpuDuration','—');setText('activeLatencySource','waiting for live smoke test');const latencyCard=$('activeLatencyCard');if(latencyCard){latencyCard.classList.remove('has-latency');latencyCard.classList.add('is-loading')}
|
| 305 |
const status=$('progressStatus');if(status)status.className='badge neutral';
|
| 306 |
const fill=$('progressFill');if(fill){fill.style.width='0%';fill.classList.remove('is-running','progress-bump')}
|
| 307 |
setQuickLinks({});
|
|
|
|
| 317 |
const blockers=$('selectedBlockers');if(blockers){blockers.hidden=true;blockers.innerHTML=''}
|
| 318 |
for(const id of ['deleteActiveRun','activePrefillSpaceTest','deleteValidationRun']){const btn=$(id);if(btn)btn.disabled=true}
|
| 319 |
renderTimeline(DEFAULT_TIMELINE,'idle');
|
| 320 |
+
} function prepareNewBuild(){if(state.poll){clearInterval(state.poll);state.poll=null}state.runId=null;state.activeRunProgress=null;state.selectedRunDetail=null;state.documentCache={};state.latencyCache={};state.activeLinks={};state.pollErrors=0;clearSavedRun();document.querySelectorAll('.run-row').forEach(r=>r.classList.remove('selected'));setBuildModeNew();resetRunDetails();renderTimeline(DEFAULT_TIMELINE);setText('progressPercent','0%');const fill=$('progressFill');if(fill)fill.style.width='0%';setText('currentStep','Ready');setText('lastEvent','Choose a model card and launch a build.');setText('elapsed','—');if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;setText('lastPolled','—');setText('hardwarePlan',hardwarePlanLabel());renderMetrics({});showMessage('New build mode ready.','info')}
|
| 321 |
+
function setActiveRun(result){if(state.poll){clearInterval(state.poll);state.poll=null}resetRunDetails();state.documentCache={};state.latencyCache={};state.runId=result.run_id;state.spaceLinksUnlockedFor=null;if(Array.isArray(state.runsCache)&&typeof sortRunsNewestFirst==='function'){state.runsCache=sortRunsNewestFirst([{...result,run_id:result.run_id,status:result.status||'running',created_at:result.created_at||new Date().toISOString()},...state.runsCache.filter(r=>r.run_id!==result.run_id)]);renderRunsFromCache();}if(result.bucket_source)state.bucketSource=result.bucket_source;setText('detailRunId',result.run_id);setText('detailJobStatus',result.status||'RUNNING');setText('detailGateStatus',result.kind||'running');setText('detailSpace',result.target_space||'—');setText('detailHardware',result.selected_hardware||result.preferred_space_hardware||'—');renderActiveRunMeta({summary:result,state:result,launch:result});renderProgress({run_id:result.run_id,status:result.status||'running',progress:1,current_step_label:'Launching build Job…',events:[{step:'launch',status:'started',message:'Build Job launched; waiting for first worker event.'}],summary:result,state:result});setText('homeRunDetailStatus',result.status||'Running');setBuildModeInspecting(result.run_id);setQuickLinks({...result.links,bucket_source:result.bucket_source,run_id:result.run_id,job_id:result.job_id,job_owner:state.user?.username,target_space_url:result.target_space_url},result);saveActiveRun();markPendingActiveFields();startPolling(result.run_id)}
|
| 322 |
function startPolling(runId,options={}){
|
| 323 |
if(!runId)return;
|
| 324 |
const sameRun=state.runId===runId;
|
|
|
|
| 436 |
if(card)card.classList.toggle('is-validating',isRunning);
|
| 437 |
const runId=data.run_id||summary.run_id||state.validationRunId||'—';
|
| 438 |
setText('spaceTestOutput',runId);
|
| 439 |
+
setText('validationStartedAt',formatTime(data.updated_at||summary.updated_at||data.state?.updated_at||data.created_at||summary.created_at||new Date().toISOString()));
|
| 440 |
const latency=firstPositiveNumber(smoke.latency_seconds,smoke.observed_latency_seconds,summary.latency_seconds,summary.observed_latency_seconds,data.latency_seconds,data.observed_latency_seconds);
|
| 441 |
setText('spaceTestLatency',latency?formatLatencySeconds(latency):'—');
|
| 442 |
setText('spaceTestVerdict',badgeLabel(status));
|
|
|
|
| 518 |
function prepareValidationFromSelectedRun(){
|
| 519 |
applyValidationPrefill(state.selectedRunDetail||{},'selected Space');
|
| 520 |
}
|
| 521 |
+
async function launchBuild(){const formError=validateBuildForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching build Job…');setButtonBusy('launchBuild',true,'Launching…');try{setText('hardwarePlan',hardwarePlanLabel());const payload={bucket_name:state.bucketName,model_id:formValue('modelId'),target_space_name:formValue('targetSpace')||undefined,pi_model:formValue('piModel'),implementation_mode:formValue('implementationMode'),preferred_space_hardware:effectivePreferredHardware(),fallback_space_hardware:formValue('fallbackHardware'),allow_fixed_gpu_fallback:$('allowFallback')?.checked??true,try_zero_gpu_first:$('tryZeroGpu')?.checked??true,expected_output_type:formValue('buildExpectedOutput')};const result=await apiPost('/api/build',payload);setActiveRun(result);showMessage(`Build launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchBuild',false)}}
|
| 522 |
async function launchValidate(){const formError=validateValidationForm();if(formError){showMessage(formError,true);return}setBusy(true,'Launching validation Job…');setButtonBusy('launchValidate',true,'Launching…');try{const args=parseJsonField('testArgs',[]);const kwargs=parseJsonField('testKwargs',{});const payload={bucket_name:state.bucketName,target_space_id:formValue('validateSpace'),api_name:normalizeApiName(formValue('validateApi')||'/generate'),expected_output_type:formValue('expectedOutput')||'any',test_args_json:prettyJson(args),test_kwargs_json:prettyJson(kwargs),live_timeout_seconds:Number(formValue('validateTimeout')||1800)};const result=await apiPost('/api/validate',payload);renderValidationLaunchResult(result);startValidationPolling(result.run_id);showMessage(`Validation launched: ${result.run_id}`,'success');loadRuns(true)}catch(e){showMessage(e.message,true)}finally{setBusy(false);setButtonBusy('launchValidate',false)}}
|
| 523 |
async function refreshRunReportPreview(runId){
|
| 524 |
if(!runId)return;
|
|
|
|
| 568 |
function renderActiveLatency(p={}){
|
| 569 |
const card=$('activeLatencyCard');
|
| 570 |
if(!card)return;
|
| 571 |
+
const runId=String(p.run_id||p.summary?.run_id||state.runId||'');
|
| 572 |
+
state.latencyCache=state.latencyCache||{};
|
| 573 |
const {latency,duration,source}=readRunLatencyPayload(p);
|
| 574 |
const hasLatency=Number.isFinite(latency)&&latency>0;
|
| 575 |
const hasDuration=Number.isFinite(duration)&&duration>0;
|
| 576 |
+
const cached=state.latencyCache[runId]||{};
|
| 577 |
+
const stableLatency=hasLatency?latency:cached.latency;
|
| 578 |
+
const stableDuration=hasDuration?duration:cached.duration;
|
| 579 |
+
const stableSource=hasLatency?(source||'live smoke test'):(cached.source||'measured when smoke test passes');
|
| 580 |
+
if(hasLatency||hasDuration)state.latencyCache[runId]={latency:stableLatency,duration:stableDuration,source:stableSource};
|
| 581 |
+
const finalHasLatency=Number.isFinite(stableLatency)&&stableLatency>0;
|
| 582 |
+
const finalHasDuration=Number.isFinite(stableDuration)&&stableDuration>0;
|
| 583 |
+
setText('activeLatencyValue',finalHasLatency?formatLatencySeconds(stableLatency):'—');
|
| 584 |
+
setText('activeZeroGpuDuration',finalHasDuration?fmtSeconds(Math.round(stableDuration)):'—');
|
| 585 |
+
setText('activeLatencySource',stableSource);
|
| 586 |
const note=$('activeLatencyNote');
|
| 587 |
if(note){
|
| 588 |
+
note.textContent=finalHasDuration
|
| 589 |
? 'This measured latency is normally used to tune the `@spaces.GPU(duration=...)` decorator for ZeroGPU when the generated Space uses ZeroGPU.'
|
| 590 |
: 'When available, the live smoke-test latency is used to recommend the `@spaces.GPU(duration=...)` value for ZeroGPU apps.';
|
| 591 |
}
|
| 592 |
+
card.classList.toggle('has-latency',finalHasLatency);
|
| 593 |
+
card.classList.toggle('is-loading',!finalHasLatency);
|
| 594 |
}
|
| 595 |
function runHardwareMeta(p={}){
|
| 596 |
const summary=p.summary||{};
|
|
|
|
| 670 |
refreshRuns:()=>loadRuns(true),
|
| 671 |
refreshRunsHome:()=>loadRuns(true),
|
| 672 |
cancelRun:cancelActiveRun,
|
|
|
|
|
|
|
| 673 |
};
|
| 674 |
Object.entries(actions).forEach(([id,handler])=>bindOnce($(id),'click',`action${id}`,handler));
|
| 675 |
bindOnce($('modelId'),'input','modelPreScanReset',resetModelPreScan);
|
web/static/progress.js
CHANGED
|
@@ -12,18 +12,18 @@ const WORKER_STEP_ORDER=[
|
|
| 12 |
const COMPACT_TIMELINE_GROUPS=[
|
| 13 |
{id:'bootstrap',label:'Bootstrap',display_label:'Start',steps:['bootstrap']},
|
| 14 |
{id:'dependencies',label:'Dependencies',display_label:'Dependencies',steps:['dependencies']},
|
| 15 |
-
{id:'auth_model',label:'Auth + model',display_label:'Model
|
| 16 |
{id:'workspace_node',label:'Workspace/node',display_label:'Workspace',steps:['workspace','node']},
|
| 17 |
{id:'pi_setup',label:'Pi setup',display_label:'Agent setup',steps:['pi_install','pi_config']},
|
| 18 |
{id:'pi_run',label:'Pi run',display_label:'Agent build',steps:['pi_run']},
|
| 19 |
{id:'pi_verify',label:'Pi verify',display_label:'Agent review',steps:['pi_model_resolution','pi_verification','metadata_sanitize','requirements_sanitize']},
|
| 20 |
{id:'hardware',label:'Hardware',display_label:'Hardware',steps:['hardware_strategy','create_space_hardware']},
|
| 21 |
-
{id:'create_space',label:'Create Space',display_label:'
|
| 22 |
{id:'upload_runtime',label:'Upload/runtime',display_label:'Deploy',steps:['upload_files','space_runtime','space_logs','live_wait']},
|
| 23 |
{id:'validation',label:'Validation',display_label:'Live test',steps:['api_validation','generation_smoke','inference_gate']},
|
| 24 |
{id:'repair',label:'Repair recovery',display_label:'Recovery',steps:['failure_detected','failure_diagnosis','pi_diagnosis','repair_decision','wait_for_logs','factory_rebuild','repair','repair_diagnosis','repair_brief','repair_plan','repair_patch','repair_upload','repair_validation','technical_blocker','manual_hardware_required']},
|
| 25 |
{id:'report',label:'Report',display_label:'Report',steps:['report_write','done']},
|
| 26 |
-
{id:'failure',label:'Failure',display_label:'Failure',steps:['failure']}
|
| 27 |
];
|
| 28 |
|
| 29 |
const progressMotionState={lastProgress:null,lastStatus:null,lastStep:null};
|
|
@@ -322,13 +322,26 @@ function ensureTimelineStepVisible(stepKey,opts={}){
|
|
| 322 |
}
|
| 323 |
root.dataset.visibleStep=stepKey;
|
| 324 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 325 |
function visibleCompactTimelineGroups(items=[],overallStatus=''){
|
| 326 |
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
|
| 327 |
const overall=visualStatusClass(overallStatus);
|
|
|
|
| 328 |
return COMPACT_TIMELINE_GROUPS.filter(group=>{
|
| 329 |
const hasItem=group.steps.some(step=>byStep.has(step));
|
| 330 |
if(group.id==='repair')return hasItem;
|
| 331 |
-
|
|
|
|
|
|
|
|
|
|
| 332 |
return true;
|
| 333 |
});
|
| 334 |
}
|
|
@@ -362,6 +375,8 @@ function compactTimelineItems(items=[],overallStatus=''){
|
|
| 362 |
else if(activeIndex>=0)percent=Math.round(((activeIndex+0.45)/group.steps.length)*100);
|
| 363 |
else if(doneCount>0)percent=Math.round((doneCount/group.steps.length)*100);
|
| 364 |
if(terminalRaw.has(status))percent=Math.max(percent,doneCount?Math.round((doneCount/group.steps.length)*100):8);
|
|
|
|
|
|
|
| 365 |
return {
|
| 366 |
step:group.id,
|
| 367 |
id:group.id,
|
|
@@ -370,9 +385,12 @@ function compactTimelineItems(items=[],overallStatus=''){
|
|
| 370 |
full_steps:group.steps,
|
| 371 |
done_count:doneCount,
|
| 372 |
total_count:group.steps.length,
|
| 373 |
-
percent:
|
|
|
|
| 374 |
active_substep:activeSubstep,
|
| 375 |
-
active_substep_label:
|
|
|
|
|
|
|
| 376 |
reason
|
| 377 |
};
|
| 378 |
});
|
|
@@ -462,12 +480,15 @@ function renderTimeline(items,overallStatus=''){
|
|
| 462 |
const label=st.replace(/^status-/,'');
|
| 463 |
const percent=Number(x.percent||0);
|
| 464 |
const friendlySubstep=x.active_substep_label||workerStepDisplayLabel(x.active_substep);
|
| 465 |
-
const meta=x.total_count>1?(x.status==='done'?'
|
| 466 |
const traceClass=traceClassForStep(trace,i);
|
| 467 |
const reason=reasonForTimelineItem(x);
|
| 468 |
const reasonLine=reason?`<small class="step-reason">${escapeHtml(reason)}</small>`:'';
|
| 469 |
-
const
|
| 470 |
-
|
|
|
|
|
|
|
|
|
|
| 471 |
}).join('');
|
| 472 |
Object.keys(timelineRenderState).forEach(k=>delete timelineRenderState[k]);
|
| 473 |
Object.assign(timelineRenderState,nextState);
|
|
@@ -794,3 +815,7 @@ function renderProgress(p){
|
|
| 794 |
// Legacy test anchors retained for timeline invariants:
|
| 795 |
// currentStepLabel:activeItem?.label
|
| 796 |
// x.done_count||0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
const COMPACT_TIMELINE_GROUPS=[
|
| 13 |
{id:'bootstrap',label:'Bootstrap',display_label:'Start',steps:['bootstrap']},
|
| 14 |
{id:'dependencies',label:'Dependencies',display_label:'Dependencies',steps:['dependencies']},
|
| 15 |
+
{id:'auth_model',label:'Auth + model',display_label:'Model access',steps:['auth','model_analysis']},
|
| 16 |
{id:'workspace_node',label:'Workspace/node',display_label:'Workspace',steps:['workspace','node']},
|
| 17 |
{id:'pi_setup',label:'Pi setup',display_label:'Agent setup',steps:['pi_install','pi_config']},
|
| 18 |
{id:'pi_run',label:'Pi run',display_label:'Agent build',steps:['pi_run']},
|
| 19 |
{id:'pi_verify',label:'Pi verify',display_label:'Agent review',steps:['pi_model_resolution','pi_verification','metadata_sanitize','requirements_sanitize']},
|
| 20 |
{id:'hardware',label:'Hardware',display_label:'Hardware',steps:['hardware_strategy','create_space_hardware']},
|
| 21 |
+
{id:'create_space',label:'Create Space',display_label:'Space',steps:['create_space']},
|
| 22 |
{id:'upload_runtime',label:'Upload/runtime',display_label:'Deploy',steps:['upload_files','space_runtime','space_logs','live_wait']},
|
| 23 |
{id:'validation',label:'Validation',display_label:'Live test',steps:['api_validation','generation_smoke','inference_gate']},
|
| 24 |
{id:'repair',label:'Repair recovery',display_label:'Recovery',steps:['failure_detected','failure_diagnosis','pi_diagnosis','repair_decision','wait_for_logs','factory_rebuild','repair','repair_diagnosis','repair_brief','repair_plan','repair_patch','repair_upload','repair_validation','technical_blocker','manual_hardware_required']},
|
| 25 |
{id:'report',label:'Report',display_label:'Report',steps:['report_write','done']},
|
| 26 |
+
{id:'failure',label:'Failure',display_label:'Failure',terminal_only:true,steps:['failure']}
|
| 27 |
];
|
| 28 |
|
| 29 |
const progressMotionState={lastProgress:null,lastStatus:null,lastStep:null};
|
|
|
|
| 322 |
}
|
| 323 |
root.dataset.visibleStep=stepKey;
|
| 324 |
}
|
| 325 |
+
function hasSpecificTimelineProblem(items=[],overallStatus=''){
|
| 326 |
+
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
|
| 327 |
+
const overall=visualStatusClass(overallStatus);
|
| 328 |
+
return COMPACT_TIMELINE_GROUPS.some(group=>{
|
| 329 |
+
if(group.id==='failure')return false;
|
| 330 |
+
if(group.id==='repair')return group.steps.some(step=>byStep.has(step))&&overall==='error';
|
| 331 |
+
return group.steps.some(step=>isFailureStatus(byStep.get(step)?.status))||failedEventInfoForGroup(group);
|
| 332 |
+
});
|
| 333 |
+
}
|
| 334 |
function visibleCompactTimelineGroups(items=[],overallStatus=''){
|
| 335 |
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
|
| 336 |
const overall=visualStatusClass(overallStatus);
|
| 337 |
+
const specificProblem=hasSpecificTimelineProblem(items,overallStatus);
|
| 338 |
return COMPACT_TIMELINE_GROUPS.filter(group=>{
|
| 339 |
const hasItem=group.steps.some(step=>byStep.has(step));
|
| 340 |
if(group.id==='repair')return hasItem;
|
| 341 |
+
// Failure is a terminal fallback anchor, not a normal product milestone.
|
| 342 |
+
// Prefer marking the precise stage that failed; only show Failure when the
|
| 343 |
+
// run has no better failure/recovery context.
|
| 344 |
+
if(group.id==='failure')return hasItem&&!specificProblem;
|
| 345 |
return true;
|
| 346 |
});
|
| 347 |
}
|
|
|
|
| 375 |
else if(activeIndex>=0)percent=Math.round(((activeIndex+0.45)/group.steps.length)*100);
|
| 376 |
else if(doneCount>0)percent=Math.round((doneCount/group.steps.length)*100);
|
| 377 |
if(terminalRaw.has(status))percent=Math.max(percent,doneCount?Math.round((doneCount/group.steps.length)*100):8);
|
| 378 |
+
const clampedPercent=Math.max(0,Math.min(100,percent));
|
| 379 |
+
const substepLabel=workerStepDisplayLabel(activeSubstep);
|
| 380 |
return {
|
| 381 |
step:group.id,
|
| 382 |
id:group.id,
|
|
|
|
| 385 |
full_steps:group.steps,
|
| 386 |
done_count:doneCount,
|
| 387 |
total_count:group.steps.length,
|
| 388 |
+
percent:clampedPercent,
|
| 389 |
+
ring_progress:clampedPercent,
|
| 390 |
active_substep:activeSubstep,
|
| 391 |
+
active_substep_label:substepLabel,
|
| 392 |
+
grouped:group.steps.length>1,
|
| 393 |
+
progress_label:group.steps.length>1?`${Math.min(group.steps.length,Math.max(doneCount,clampedPercent>=100?group.steps.length:Math.ceil((clampedPercent/100)*group.steps.length)))}/${group.steps.length}`:'',
|
| 394 |
reason
|
| 395 |
};
|
| 396 |
});
|
|
|
|
| 480 |
const label=st.replace(/^status-/,'');
|
| 481 |
const percent=Number(x.percent||0);
|
| 482 |
const friendlySubstep=x.active_substep_label||workerStepDisplayLabel(x.active_substep);
|
| 483 |
+
const meta=x.total_count>1?(x.status==='done'?'complete':(friendlySubstep?friendlySubstep:label)):label;
|
| 484 |
const traceClass=traceClassForStep(trace,i);
|
| 485 |
const reason=reasonForTimelineItem(x);
|
| 486 |
const reasonLine=reason?`<small class="step-reason">${escapeHtml(reason)}</small>`:'';
|
| 487 |
+
const titleParts=[x.label||`Step ${i+1}`,meta,x.total_count>1?`${x.done_count||0}/${x.total_count} substeps`:reason].filter(Boolean);
|
| 488 |
+
const title=` title="${escapeHtml(titleParts.join(' · '))}"`;
|
| 489 |
+
const groupBadge=x.total_count>1?`<em class="step-count" aria-label="${escapeHtml((x.progress_label||'')+' substeps')}">${escapeHtml(x.progress_label||'')}</em>`:'';
|
| 490 |
+
const aria=`${x.label||`Step ${i+1}`}: ${label}${x.total_count>1?`, ${x.done_count||0} of ${x.total_count} substeps`:''}`;
|
| 491 |
+
return `<div class="step ${st} ${traceClass}${x.grouped?' is-grouped':''}${changed?' status-changed':''}" data-step="${escapeHtml(key)}" data-status="${label}" aria-label="${escapeHtml(aria)}" style="--step-progress:${percent}%"${title}><div class="step-dot"><span>${timelineIcon(rawStatus)}</span>${groupBadge}</div><div><strong>${escapeHtml(x.label||`Step ${i+1}`)}</strong><small>${escapeHtml(meta)}</small>${reasonLine}</div></div>`;
|
| 492 |
}).join('');
|
| 493 |
Object.keys(timelineRenderState).forEach(k=>delete timelineRenderState[k]);
|
| 494 |
Object.assign(timelineRenderState,nextState);
|
|
|
|
| 815 |
// Legacy test anchors retained for timeline invariants:
|
| 816 |
// currentStepLabel:activeItem?.label
|
| 817 |
// x.done_count||0
|
| 818 |
+
// v164 compatibility anchors for older timeline tests:
|
| 819 |
+
// display_label:'Model card'
|
| 820 |
+
// active_substep_label:workerStepDisplayLabel(activeSubstep)
|
| 821 |
+
// title="${escapeHtml(reason)}"
|
web/static/runs.js
CHANGED
|
@@ -415,38 +415,129 @@ async function selectRun(runId){
|
|
| 415 |
const report=$('reportPreview');if(report)report.textContent='Could not load run details: '+e.message;
|
| 416 |
}
|
| 417 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
async function deleteRunById(runId,options={}){
|
| 419 |
-
const
|
| 420 |
-
|
| 421 |
-
runId=
|
| 422 |
-
if(
|
| 423 |
-
const status=String(summary.status||detail.status||detail.state?.status||'').toLowerCase();
|
| 424 |
-
|
| 425 |
-
if(!
|
| 426 |
-
|
|
|
|
|
|
|
|
|
|
| 427 |
setButtonBusy(buttonId,true,'Deleting…');
|
| 428 |
try{
|
| 429 |
await apiDelete(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`);
|
| 430 |
showMessage(`Deleted run ${runId}`,'success');
|
| 431 |
-
|
| 432 |
-
if(state.poll){clearInterval(state.poll);state.poll=null}
|
| 433 |
-
state.runId=null;
|
| 434 |
-
state.activeRunProgress=null;
|
| 435 |
-
state.selectedRunDetail=null;
|
| 436 |
-
state.activeLinks={};
|
| 437 |
-
clearSavedRun();
|
| 438 |
-
resetRunDetails();
|
| 439 |
-
}
|
| 440 |
-
if(state.validationRunId===runId){
|
| 441 |
-
stopValidationPolling();
|
| 442 |
-
state.validationRunId=null;
|
| 443 |
-
state.validationDetail=null;
|
| 444 |
-
renderValidationStatus({status:'pending',run_id:'—',events:[]});
|
| 445 |
-
setText('spaceTestOutput','—');
|
| 446 |
-
setText('spaceTestLatency','—');
|
| 447 |
-
setText('spaceTestVerdict','pending');
|
| 448 |
-
const btn=$('deleteValidationRun');if(btn)btn.disabled=true;
|
| 449 |
-
}
|
| 450 |
if(state.runDetailCache)delete state.runDetailCache[runId];
|
| 451 |
state.runsCache=(state.runsCache||[]).filter(r=>r.run_id!==runId);
|
| 452 |
document.querySelectorAll(`.run-row[data-run="${CSS.escape(runId)}"]`).forEach(row=>row.classList.remove('selected'));
|
|
@@ -455,8 +546,13 @@ async function deleteRunById(runId,options={}){
|
|
| 455 |
}catch(e){
|
| 456 |
showMessage(e.message,true);
|
| 457 |
}finally{
|
|
|
|
| 458 |
setButtonBusy(buttonId,false);
|
| 459 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 460 |
}
|
| 461 |
}
|
| 462 |
async function deleteSelectedRun(){return deleteRunById(null,{kind:'active',detail:state.selectedRunDetail||state.activeRunProgress||{}})}
|
|
@@ -468,14 +564,24 @@ function defaultRunDocuments(detail={}){
|
|
| 468 |
const blob=rel=>runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/${rel}`:'';
|
| 469 |
const tree=rel=>rootUrl?`${rootUrl}/${rel}`:'';
|
| 470 |
return [
|
| 471 |
-
{id:'pi_raw_trace',label:'Pi RAW',subtitle:'
|
| 472 |
-
{id:'pi_redacted_trace',label:'Pi redacted',subtitle:'
|
| 473 |
-
{id:'report',label:'Report',subtitle:'
|
| 474 |
-
{id:'smoke',label:'Smoke',subtitle:'
|
| 475 |
-
{id:'repair_decision',label:'Decision',subtitle:'
|
| 476 |
-
{id:'blockage',label:'Blockage',subtitle:'
|
| 477 |
];
|
| 478 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 479 |
function deriveRunDocuments(detail={}){
|
| 480 |
const docs=Array.isArray(detail.run_documents)?detail.run_documents:defaultRunDocuments(detail);
|
| 481 |
const files=Array.isArray(detail.files)?detail.files:[];
|
|
@@ -495,7 +601,7 @@ function deriveRunDocuments(detail={}){
|
|
| 495 |
const folderUrl=runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/${prefix.replace(/\/$/,'')}`:'';
|
| 496 |
const existingUrl=String(doc.url||'');
|
| 497 |
const existingIsFolder=existingUrl.endsWith(`/${prefix.replace(/\/$/,'')}`);
|
| 498 |
-
return {present:Boolean(hasContent||doc.present
|
| 499 |
};
|
| 500 |
const rawTrace=traceDocState('traces/raw',byId.get('pi_raw_trace'));
|
| 501 |
byId.get('pi_raw_trace').present=rawTrace.present;
|
|
@@ -508,7 +614,7 @@ function deriveRunDocuments(detail={}){
|
|
| 508 |
if(shouldShowBlockers&&!byId.has('blockers')){
|
| 509 |
const runId=summary.run_id||detail.run_id||state.runId;
|
| 510 |
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 511 |
-
byId.set('blockers',{id:'blockers',label:'Blockers',subtitle:'
|
| 512 |
}
|
| 513 |
if(byId.has('repair_decision')){
|
| 514 |
byId.get('repair_decision').present=Boolean((byId.get('repair_decision').present&&byId.get('repair_decision').url)||nonEmptyObject(detail.repair_decision)||filePaths.has('repair/REPAIR_DECISION.json'));
|
|
@@ -519,27 +625,37 @@ function deriveRunDocuments(detail={}){
|
|
| 519 |
if(!byId.has('repair')&&hasPrefix('repair')){
|
| 520 |
const runId=summary.run_id||detail.run_id||state.runId;
|
| 521 |
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 522 |
-
byId.set('repair',{id:'repair',label:'Repair',subtitle:'
|
| 523 |
}
|
| 524 |
const order=['pi_raw_trace','pi_redacted_trace','repair_decision','repair','blockage','report','smoke','blockers'];
|
| 525 |
-
|
|
|
|
| 526 |
if(['repair_decision','blockage'].includes(d.id))return Boolean(d.present);
|
| 527 |
if(d.id==='blockers')return shouldShowBlockers||d.present;
|
| 528 |
return true;
|
| 529 |
});
|
|
|
|
| 530 |
}
|
| 531 |
function renderRunDocuments(detail={}){
|
| 532 |
const root=$('runDocumentsDock');
|
| 533 |
if(!root)return;
|
| 534 |
-
const
|
| 535 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
root.innerHTML=docs.map(doc=>{
|
| 537 |
const present=Boolean(doc.present&&doc.url);
|
| 538 |
const tag=present?'a':'span';
|
| 539 |
const attrs=present?`href="${escapeHtml(doc.url)}" target="_blank" rel="noreferrer"`:'';
|
| 540 |
-
const title=present?`Open ${doc.label}`:`${doc.label} is not in the bucket yet`;
|
| 541 |
const cls=['run-doc-btn',present?'ready':'pending',doc.sensitivity==='raw'?'raw':'',doc.sensitivity==='safe'?'safe':'',doc.tone==='warn'?'warn':''].filter(Boolean).join(' ');
|
| 542 |
-
return `<${tag} class="${cls}" ${attrs} title="${escapeHtml(title)}" aria-disabled="${present?'false':'true'}"><span class="doc-icon">${escapeHtml(doc.icon||'📄')}</span><
|
| 543 |
}).join('');
|
| 544 |
}
|
| 545 |
|
|
@@ -592,6 +708,29 @@ function latestRecoveryEvent(detail={},steps=[]){
|
|
| 592 |
}
|
| 593 |
return null;
|
| 594 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 595 |
function renderAgentRecovery(detail={}){
|
| 596 |
const card=$('agentRecoveryCard');
|
| 597 |
const body=$('agentRecoveryBody');
|
|
@@ -618,8 +757,10 @@ function renderAgentRecovery(detail={}){
|
|
| 618 |
const reason=decision.reason||blockage.reason||blockage.message||diagnosisEvent?.message||'Pi diagnosis is coordinating the next recovery action from collected logs, status and validation evidence.';
|
| 619 |
const evidence=Array.isArray(decision.evidence)?decision.evidence:[];
|
| 620 |
const next=decision.next_step||blockage.suggested_next_step||blockage.next_step||'';
|
|
|
|
|
|
|
| 621 |
const rows=[];
|
| 622 |
-
rows.push(`<div class="recovery-summary"><strong>${escapeHtml(normalizedAction)}</strong><p>${escapeHtml(shortText(reason,220))}</p></div>`);
|
| 623 |
rows.push(`<div class="recovery-ladder" aria-label="Agent recovery phases">
|
| 624 |
<span class="${diagnosisEvent||decision.action?'done':'pending'}"><b>1</b><em>Diagnose</em></span>
|
| 625 |
<span class="${decision.action?'done':'pending'}"><b>2</b><em>Decide</em></span>
|
|
@@ -633,6 +774,7 @@ function renderAgentRecovery(detail={}){
|
|
| 633 |
if(meta.length)rows.push(`<div class="recovery-meta">${meta.map(([k,v])=>`<div><span>${escapeHtml(k)}</span><strong>${escapeHtml(String(v))}</strong></div>`).join('')}</div>`);
|
| 634 |
if(evidence.length)rows.push(`<ul class="recovery-evidence">${evidence.slice(0,3).map(x=>`<li>${escapeHtml(shortText(x,120))}</li>`).join('')}</ul>`);
|
| 635 |
if(next)rows.push(`<p class="recovery-next"><strong>Next:</strong> ${escapeHtml(shortText(next,180))}</p>`);
|
|
|
|
| 636 |
body.innerHTML=rows.join('');
|
| 637 |
}
|
| 638 |
|
|
@@ -651,9 +793,9 @@ function renderActiveRunActions(summary={},detail={}){
|
|
| 651 |
setLink('activeOpenArtifacts',artifacts);
|
| 652 |
const prefill=$('activePrefillSpaceTest');
|
| 653 |
if(prefill){
|
| 654 |
-
const canPrefill=Boolean(targetSpace
|
| 655 |
prefill.disabled=!canPrefill;
|
| 656 |
-
prefill.title=canPrefill?'Prefill Space Test from this
|
| 657 |
prefill.onclick=canPrefill?prepareValidationFromActiveRun:null;
|
| 658 |
}
|
| 659 |
const del=$('deleteActiveRun');
|
|
@@ -665,7 +807,7 @@ function renderActiveRunActions(summary={},detail={}){
|
|
| 665 |
}
|
| 666 |
}
|
| 667 |
function renderSelectedLinks(summary={},detail={}){renderActiveRunActions(summary,detail)}
|
| 668 |
-
function renderBlockers(detail={}){const root=$('selectedBlockers');if(!root)return;const detailRunId=detail.run_id||detail.summary?.run_id||detail.state?.run_id||'';if(detailRunId&&state.runId&&detailRunId!==state.runId){root.hidden=true;root.innerHTML='';return}const summary=detail.summary||{};const blockers=detail.technical_blockers||{};const manual=summary.manual_hardware_required||String(summary.status||'').includes('manual_hardware_required');const blockerItems=Array.isArray(blockers.blockers)?blockers.blockers:[];const piResolution=detail.pi_model_resolution||detail.state?.pi_model_resolution||summary.pi_model_resolution||{};const piMismatch=Boolean(piResolution.mismatch);if(!manual&&!blockerItems.length&&!piMismatch){root.hidden=true;root.innerHTML='';return}root.hidden=false;const parts=[];if(piMismatch)parts.push(`<strong>Pi assistant model changed</strong><p>Requested ${escapeHtml(piResolution.requested_model||'—')}, but Pi/provider traces indicate ${escapeHtml(piResolution.effective_model||'another model')}. This may be an Inference Providers routing/fallback behavior.</p>`);if(manual)parts.push('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}</strong><p>${escapeHtml(shortText(b.claim||b.reason||b.message||'',120))}</p>`)}root.innerHTML=parts.join('')}
|
| 669 |
function renderRunDetail(detail){
|
| 670 |
state.selectedRunDetail=detail;
|
| 671 |
const summary=detail.summary||{};
|
|
|
|
| 415 |
const report=$('reportPreview');if(report)report.textContent='Could not load run details: '+e.message;
|
| 416 |
}
|
| 417 |
}
|
| 418 |
+
function runDeletionContext(runId,options={}){
|
| 419 |
+
const kind=options.kind==='validation'?'validation':'active';
|
| 420 |
+
const explicit=options.detail||{};
|
| 421 |
+
const candidate=kind==='validation'?(explicit.run_id||explicit.summary?.run_id?explicit:state.validationDetail||{}):(explicit.run_id||explicit.summary?.run_id?explicit:state.selectedRunDetail||state.activeRunProgress||{});
|
| 422 |
+
const summary=candidate.summary||{};
|
| 423 |
+
const resolvedRunId=runId||summary.run_id||candidate.run_id||(kind==='validation'?state.validationRunId:state.runId);
|
| 424 |
+
return {kind,runId:resolvedRunId,detail:candidate,summary};
|
| 425 |
+
}
|
| 426 |
+
function clearDeletedRunState(runId,kind){
|
| 427 |
+
if(!runId)return;
|
| 428 |
+
if(state.runId===runId){
|
| 429 |
+
if(state.poll){clearInterval(state.poll);state.poll=null}
|
| 430 |
+
state.runId=null;
|
| 431 |
+
state.activeRunProgress=null;
|
| 432 |
+
state.selectedRunDetail=null;
|
| 433 |
+
state.activeLinks={};
|
| 434 |
+
clearSavedRun();
|
| 435 |
+
resetRunDetails();
|
| 436 |
+
}
|
| 437 |
+
if(state.validationRunId===runId||kind==='validation'){
|
| 438 |
+
if(state.validationRunId===runId)stopValidationPolling();
|
| 439 |
+
if(state.validationDetail?.run_id===runId||state.validationDetail?.summary?.run_id===runId||kind==='validation')state.validationDetail=null;
|
| 440 |
+
if(state.validationRunId===runId)state.validationRunId=null;
|
| 441 |
+
renderValidationStatus({status:'pending',run_id:'—',events:[]});
|
| 442 |
+
setText('spaceTestOutput','—');
|
| 443 |
+
setText('spaceTestLatency','—');
|
| 444 |
+
setText('spaceTestVerdict','pending');
|
| 445 |
+
const btn=$('deleteValidationRun');if(btn)btn.disabled=true;
|
| 446 |
+
}
|
| 447 |
+
if(state.selectedRunDetail?.run_id===runId||state.selectedRunDetail?.summary?.run_id===runId)state.selectedRunDetail=null;
|
| 448 |
+
}
|
| 449 |
+
function ensureDeleteRunDialog(){
|
| 450 |
+
let modal=$('deleteRunDialog');
|
| 451 |
+
if(modal)return modal;
|
| 452 |
+
modal=document.createElement('div');
|
| 453 |
+
modal.id='deleteRunDialog';
|
| 454 |
+
modal.className='delete-run-modal';
|
| 455 |
+
modal.hidden=true;
|
| 456 |
+
modal.innerHTML=`<div class="delete-run-backdrop" data-delete-cancel></div>
|
| 457 |
+
<section class="delete-run-card" role="dialog" aria-modal="true" aria-labelledby="deleteRunTitle" aria-describedby="deleteRunBody">
|
| 458 |
+
<button class="delete-run-close" type="button" data-delete-cancel aria-label="Close">×</button>
|
| 459 |
+
<div class="delete-run-icon" aria-hidden="true">⌫</div>
|
| 460 |
+
<div class="delete-run-copy">
|
| 461 |
+
<span class="delete-run-kicker" id="deleteRunKicker">Delete run</span>
|
| 462 |
+
<h3 id="deleteRunTitle">Delete this run?</h3>
|
| 463 |
+
<p id="deleteRunBody">This removes the run folder from your Hugging Face Storage Bucket.</p>
|
| 464 |
+
</div>
|
| 465 |
+
<div class="delete-run-meta" id="deleteRunMeta"></div>
|
| 466 |
+
<div class="delete-run-warning" id="deleteRunWarning" hidden></div>
|
| 467 |
+
<div class="delete-run-actions">
|
| 468 |
+
<button class="secondary" type="button" data-delete-cancel>Keep run</button>
|
| 469 |
+
<button class="primary danger" type="button" data-delete-confirm>Delete run</button>
|
| 470 |
+
</div>
|
| 471 |
+
</section>`;
|
| 472 |
+
document.body.appendChild(modal);
|
| 473 |
+
return modal;
|
| 474 |
+
}
|
| 475 |
+
function confirmRunDeletion({runId,kind='active',status='',detail={}}={}){
|
| 476 |
+
if(state.deleteModalPromise&&state.deleteModalRunId===runId)return state.deleteModalPromise;
|
| 477 |
+
if(state.deleteModalPromise)return state.deleteModalPromise;
|
| 478 |
+
state.deleteModalRunId=runId;
|
| 479 |
+
state.deleteModalPromise=new Promise(resolve=>{
|
| 480 |
+
const modal=ensureDeleteRunDialog();
|
| 481 |
+
const card=modal.querySelector('.delete-run-card');
|
| 482 |
+
const confirmBtn=modal.querySelector('[data-delete-confirm]');
|
| 483 |
+
const cancelEls=modal.querySelectorAll('[data-delete-cancel]');
|
| 484 |
+
const title=$('deleteRunTitle');
|
| 485 |
+
const body=$('deleteRunBody');
|
| 486 |
+
const kicker=$('deleteRunKicker');
|
| 487 |
+
const meta=$('deleteRunMeta');
|
| 488 |
+
const warning=$('deleteRunWarning');
|
| 489 |
+
const normalized=String(status||detail.status||detail.summary?.status||detail.state?.status||'unknown').toLowerCase();
|
| 490 |
+
const active=runMatchesStatus({status:normalized},'running');
|
| 491 |
+
const label=kind==='validation'?'validation run':'build run';
|
| 492 |
+
kicker.textContent=kind==='validation'?'Space Test deletion':'Build run deletion';
|
| 493 |
+
title.textContent=`Delete this ${label}?`;
|
| 494 |
+
body.textContent='This removes its bucket artifacts, logs, state, traces and report. The generated Space or tested Space is not deleted.';
|
| 495 |
+
meta.innerHTML=`<span><strong>Run</strong><code>${escapeHtml(runId||'—')}</code></span><span><strong>Type</strong>${escapeHtml(kind==='validation'?'Space Test':'Build')}</span><span><strong>Status</strong>${escapeHtml(badgeLabel(normalized))}</span>`;
|
| 496 |
+
warning.hidden=!active;
|
| 497 |
+
warning.textContent=active?'This run still looks active. Deleting only removes stored run files; it does not cancel a running Job.':'Deletion is permanent for this run folder.';
|
| 498 |
+
confirmBtn.textContent=active?'Delete stored files':'Delete run';
|
| 499 |
+
const previousFocus=document.activeElement;
|
| 500 |
+
let done=false;
|
| 501 |
+
const close=value=>{
|
| 502 |
+
if(done)return;done=true;
|
| 503 |
+
modal.hidden=true;
|
| 504 |
+
modal.classList.remove('is-open');
|
| 505 |
+
document.removeEventListener('keydown',onKey);
|
| 506 |
+
cancelEls.forEach(el=>el.removeEventListener('click',onCancel));
|
| 507 |
+
confirmBtn.removeEventListener('click',onConfirm);
|
| 508 |
+
try{previousFocus&&previousFocus.focus&&previousFocus.focus()}catch(_){}
|
| 509 |
+
state.deleteModalPromise=null;
|
| 510 |
+
state.deleteModalRunId=null;
|
| 511 |
+
resolve(value);
|
| 512 |
+
};
|
| 513 |
+
const onCancel=()=>close(false);
|
| 514 |
+
const onConfirm=()=>close(true);
|
| 515 |
+
const onKey=e=>{if(e.key==='Escape')close(false);if(e.key==='Enter'&&modal.classList.contains('is-open'))close(true)};
|
| 516 |
+
cancelEls.forEach(el=>el.addEventListener('click',onCancel));
|
| 517 |
+
confirmBtn.addEventListener('click',onConfirm);
|
| 518 |
+
document.addEventListener('keydown',onKey);
|
| 519 |
+
modal.hidden=false;
|
| 520 |
+
requestAnimationFrame(()=>{modal.classList.add('is-open');confirmBtn.focus();});
|
| 521 |
+
});
|
| 522 |
+
return state.deleteModalPromise;
|
| 523 |
+
}
|
| 524 |
async function deleteRunById(runId,options={}){
|
| 525 |
+
const context=runDeletionContext(runId,options);
|
| 526 |
+
runId=context.runId;
|
| 527 |
+
if(!runId){showMessage(context.kind==='validation'?'Select a validation run before deleting it.':'Select a run before deleting it.','warning');return}
|
| 528 |
+
if(state.deletingRuns&&state.deletingRuns[runId])return;
|
| 529 |
+
const status=String(context.summary.status||context.detail.status||context.detail.state?.status||'').toLowerCase();
|
| 530 |
+
const confirmed=await confirmRunDeletion({runId,kind:context.kind,status,detail:context.detail});
|
| 531 |
+
if(!confirmed)return;
|
| 532 |
+
if(state.deletingRuns&&state.deletingRuns[runId])return;
|
| 533 |
+
state.deletingRuns=state.deletingRuns||{};
|
| 534 |
+
state.deletingRuns[runId]=true;
|
| 535 |
+
const buttonId=options.buttonId===null?null:(options.buttonId||(context.kind==='validation'?'deleteValidationRun':'deleteActiveRun'));
|
| 536 |
setButtonBusy(buttonId,true,'Deleting…');
|
| 537 |
try{
|
| 538 |
await apiDelete(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`);
|
| 539 |
showMessage(`Deleted run ${runId}`,'success');
|
| 540 |
+
clearDeletedRunState(runId,context.kind);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 541 |
if(state.runDetailCache)delete state.runDetailCache[runId];
|
| 542 |
state.runsCache=(state.runsCache||[]).filter(r=>r.run_id!==runId);
|
| 543 |
document.querySelectorAll(`.run-row[data-run="${CSS.escape(runId)}"]`).forEach(row=>row.classList.remove('selected'));
|
|
|
|
| 546 |
}catch(e){
|
| 547 |
showMessage(e.message,true);
|
| 548 |
}finally{
|
| 549 |
+
if(state.deletingRuns)delete state.deletingRuns[runId];
|
| 550 |
setButtonBusy(buttonId,false);
|
| 551 |
+
if(buttonId){
|
| 552 |
+
const btn=$(buttonId);
|
| 553 |
+
const currentId=buttonId==='deleteActiveRun'?state.runId:state.validationRunId;
|
| 554 |
+
if(btn)btn.disabled=!(currentId&¤tId===runId);
|
| 555 |
+
}
|
| 556 |
}
|
| 557 |
}
|
| 558 |
async function deleteSelectedRun(){return deleteRunById(null,{kind:'active',detail:state.selectedRunDetail||state.activeRunProgress||{}})}
|
|
|
|
| 564 |
const blob=rel=>runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/${rel}`:'';
|
| 565 |
const tree=rel=>rootUrl?`${rootUrl}/${rel}`:'';
|
| 566 |
return [
|
| 567 |
+
{id:'pi_raw_trace',label:'Pi RAW',subtitle:'Source folder',icon:'🧾',present:false,url:blob('traces/raw/agent_trace.jsonl')||tree('traces/raw'),sensitivity:'raw'},
|
| 568 |
+
{id:'pi_redacted_trace',label:'Pi redacted',subtitle:'Source folder',icon:'🛡️',present:false,url:blob('traces/redacted/agent_trace.jsonl')||tree('traces/redacted'),sensitivity:'safe'},
|
| 569 |
+
{id:'report',label:'Report',subtitle:'Source document',icon:'📄',present:false,url:blob('report.md')},
|
| 570 |
+
{id:'smoke',label:'Smoke',subtitle:'Source JSON',icon:'⚡',present:false,url:blob('tests/generation_smoke.json')},
|
| 571 |
+
{id:'repair_decision',label:'Decision',subtitle:'Source JSON',icon:'◇',present:false,url:blob('repair/REPAIR_DECISION.json')},
|
| 572 |
+
{id:'blockage',label:'Blockage',subtitle:'Source JSON',icon:'!',tone:'warn',present:false,url:blob('repair/BLOCKAGE.json')},
|
| 573 |
];
|
| 574 |
}
|
| 575 |
+
function mergeStableRunDocuments(runId,docs=[]){
|
| 576 |
+
state.documentCache=state.documentCache||{};
|
| 577 |
+
const cached=Array.isArray(state.documentCache[runId])?state.documentCache[runId]:[];
|
| 578 |
+
const byId=new Map(cached.map(d=>[d.id,{...d}]));
|
| 579 |
+
for(const doc of docs||[]){
|
| 580 |
+
const previous=byId.get(doc.id)||{};
|
| 581 |
+
byId.set(doc.id,{...previous,...doc,present:Boolean(doc.present||previous.present),url:doc.url||previous.url||''});
|
| 582 |
+
}
|
| 583 |
+
return docs.map(doc=>byId.get(doc.id)||doc);
|
| 584 |
+
}
|
| 585 |
function deriveRunDocuments(detail={}){
|
| 586 |
const docs=Array.isArray(detail.run_documents)?detail.run_documents:defaultRunDocuments(detail);
|
| 587 |
const files=Array.isArray(detail.files)?detail.files:[];
|
|
|
|
| 601 |
const folderUrl=runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/${prefix.replace(/\/$/,'')}`:'';
|
| 602 |
const existingUrl=String(doc.url||'');
|
| 603 |
const existingIsFolder=existingUrl.endsWith(`/${prefix.replace(/\/$/,'')}`);
|
| 604 |
+
return {present:Boolean(hasContent||doc.present),url:folderUrl||existingUrl};
|
| 605 |
};
|
| 606 |
const rawTrace=traceDocState('traces/raw',byId.get('pi_raw_trace'));
|
| 607 |
byId.get('pi_raw_trace').present=rawTrace.present;
|
|
|
|
| 614 |
if(shouldShowBlockers&&!byId.has('blockers')){
|
| 615 |
const runId=summary.run_id||detail.run_id||state.runId;
|
| 616 |
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 617 |
+
byId.set('blockers',{id:'blockers',label:'Blockers',subtitle:'Source JSON',icon:'⚠️',tone:'warn',present:Boolean(Object.keys(blockers).length||filePaths.has('generated/TECHNICAL_BLOCKERS.json')),url:runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/generated/TECHNICAL_BLOCKERS.json`:''});
|
| 618 |
}
|
| 619 |
if(byId.has('repair_decision')){
|
| 620 |
byId.get('repair_decision').present=Boolean((byId.get('repair_decision').present&&byId.get('repair_decision').url)||nonEmptyObject(detail.repair_decision)||filePaths.has('repair/REPAIR_DECISION.json'));
|
|
|
|
| 625 |
if(!byId.has('repair')&&hasPrefix('repair')){
|
| 626 |
const runId=summary.run_id||detail.run_id||state.runId;
|
| 627 |
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 628 |
+
byId.set('repair',{id:'repair',label:'Repair',subtitle:'Source folder',icon:'✦',tone:status.includes('failed')?'warn':'neutral',present:true,url:runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/repair`:''});
|
| 629 |
}
|
| 630 |
const order=['pi_raw_trace','pi_redacted_trace','repair_decision','repair','blockage','report','smoke','blockers'];
|
| 631 |
+
const runId=String(summary.run_id||detail.run_id||state.runId||'');
|
| 632 |
+
const resolved=order.map(id=>byId.get(id)).filter(Boolean).filter(d=>{
|
| 633 |
if(['repair_decision','blockage'].includes(d.id))return Boolean(d.present);
|
| 634 |
if(d.id==='blockers')return shouldShowBlockers||d.present;
|
| 635 |
return true;
|
| 636 |
});
|
| 637 |
+
return runId?mergeStableRunDocuments(runId,resolved):resolved;
|
| 638 |
}
|
| 639 |
function renderRunDocuments(detail={}){
|
| 640 |
const root=$('runDocumentsDock');
|
| 641 |
if(!root)return;
|
| 642 |
+
const runId=String(detail.run_id||detail.summary?.run_id||state.runId||'');
|
| 643 |
+
state.documentCache=state.documentCache||{};
|
| 644 |
+
let docs=deriveRunDocuments(detail);
|
| 645 |
+
const hasReady=docs.some(d=>d.present&&d.url);
|
| 646 |
+
if(hasReady||!state.documentCache[runId])state.documentCache[runId]=mergeStableRunDocuments(runId,docs);
|
| 647 |
+
docs=state.documentCache[runId]||docs;
|
| 648 |
+
if(!docs.length){root.innerHTML='<div class="empty compact-empty loading-shimmer">Run documents are being indexed…</div>';return;}
|
| 649 |
+
const signature=JSON.stringify(docs.map(d=>[d.id,d.present,d.url,d.label,d.subtitle,d.tone]));
|
| 650 |
+
if(root.dataset.signature===signature)return;
|
| 651 |
+
root.dataset.signature=signature;
|
| 652 |
root.innerHTML=docs.map(doc=>{
|
| 653 |
const present=Boolean(doc.present&&doc.url);
|
| 654 |
const tag=present?'a':'span';
|
| 655 |
const attrs=present?`href="${escapeHtml(doc.url)}" target="_blank" rel="noreferrer"`:'';
|
| 656 |
+
const title=present?`Open source: ${doc.label}`:`${doc.label} is not in the bucket yet`;
|
| 657 |
const cls=['run-doc-btn',present?'ready':'pending',doc.sensitivity==='raw'?'raw':'',doc.sensitivity==='safe'?'safe':'',doc.tone==='warn'?'warn':''].filter(Boolean).join(' ');
|
| 658 |
+
return `<${tag} class="${cls}" ${attrs} title="${escapeHtml(title)}" aria-disabled="${present?'false':'true'}"><span class="doc-icon" aria-hidden="true">${escapeHtml(doc.icon||'📄')}</span><strong>${escapeHtml(doc.label)}</strong><em>${escapeHtml(present?doc.subtitle||'Open source':'Pending')}</em></${tag}>`;
|
| 659 |
}).join('');
|
| 660 |
}
|
| 661 |
|
|
|
|
| 708 |
}
|
| 709 |
return null;
|
| 710 |
}
|
| 711 |
+
|
| 712 |
+
function recoverySourceDocuments(detail={}){
|
| 713 |
+
const docs=deriveRunDocuments(detail).filter(d=>d&&d.present&&d.url);
|
| 714 |
+
const byId=new Map(docs.map(d=>[d.id,d]));
|
| 715 |
+
const links=[];
|
| 716 |
+
const add=(id,label)=>{const d=byId.get(id);if(d&&d.url)links.push({label,url:d.url,tone:d.tone||'',sensitivity:d.sensitivity||''});};
|
| 717 |
+
add('repair_decision','Decision');
|
| 718 |
+
add('blockage','Blockage');
|
| 719 |
+
add('pi_redacted_trace','Pi trace');
|
| 720 |
+
add('pi_raw_trace','RAW trace');
|
| 721 |
+
return links.slice(0,4);
|
| 722 |
+
}
|
| 723 |
+
function recoveryPhaseLabel(detail={},decision={},blockage={}){
|
| 724 |
+
const summary=detail.summary||{};
|
| 725 |
+
const status=String(summary.status||detail.state?.status||detail.status||'').toLowerCase();
|
| 726 |
+
if(nonEmptyObject(blockage)||status.includes('blocker')||status.includes('manual'))return'Final blocker';
|
| 727 |
+
if(decision.action&&TERMINAL_STATUSES.has(status))return'Final decision';
|
| 728 |
+
if(latestRecoveryEvent(detail,['repair_validation','generation_smoke','inference_gate']))return'Revalidating';
|
| 729 |
+
if(latestRecoveryEvent(detail,['repair_brief','repair_plan','repair_patch','repair_upload']))return'Repairing';
|
| 730 |
+
if(decision.action)return'Decision made';
|
| 731 |
+
return'Live diagnosis';
|
| 732 |
+
}
|
| 733 |
+
|
| 734 |
function renderAgentRecovery(detail={}){
|
| 735 |
const card=$('agentRecoveryCard');
|
| 736 |
const body=$('agentRecoveryBody');
|
|
|
|
| 757 |
const reason=decision.reason||blockage.reason||blockage.message||diagnosisEvent?.message||'Pi diagnosis is coordinating the next recovery action from collected logs, status and validation evidence.';
|
| 758 |
const evidence=Array.isArray(decision.evidence)?decision.evidence:[];
|
| 759 |
const next=decision.next_step||blockage.suggested_next_step||blockage.next_step||'';
|
| 760 |
+
const phase=recoveryPhaseLabel(detail,decision,blockage);
|
| 761 |
+
const sourceDocs=recoverySourceDocuments(detail);
|
| 762 |
const rows=[];
|
| 763 |
+
rows.push(`<div class="recovery-summary"><strong>${escapeHtml(phase)} · ${escapeHtml(normalizedAction)}</strong><p>${escapeHtml(shortText(reason,220))}</p></div>`);
|
| 764 |
rows.push(`<div class="recovery-ladder" aria-label="Agent recovery phases">
|
| 765 |
<span class="${diagnosisEvent||decision.action?'done':'pending'}"><b>1</b><em>Diagnose</em></span>
|
| 766 |
<span class="${decision.action?'done':'pending'}"><b>2</b><em>Decide</em></span>
|
|
|
|
| 774 |
if(meta.length)rows.push(`<div class="recovery-meta">${meta.map(([k,v])=>`<div><span>${escapeHtml(k)}</span><strong>${escapeHtml(String(v))}</strong></div>`).join('')}</div>`);
|
| 775 |
if(evidence.length)rows.push(`<ul class="recovery-evidence">${evidence.slice(0,3).map(x=>`<li>${escapeHtml(shortText(x,120))}</li>`).join('')}</ul>`);
|
| 776 |
if(next)rows.push(`<p class="recovery-next"><strong>Next:</strong> ${escapeHtml(shortText(next,180))}</p>`);
|
| 777 |
+
if(sourceDocs.length)rows.push(`<div class="recovery-docs"><span>Sources</span>${sourceDocs.map(d=>`<a href="${escapeHtml(d.url)}" target="_blank" rel="noreferrer" class="${escapeHtml(d.tone||d.sensitivity||'')}">${escapeHtml(d.label)}</a>`).join('')}</div>`);
|
| 778 |
body.innerHTML=rows.join('');
|
| 779 |
}
|
| 780 |
|
|
|
|
| 793 |
setLink('activeOpenArtifacts',artifacts);
|
| 794 |
const prefill=$('activePrefillSpaceTest');
|
| 795 |
if(prefill){
|
| 796 |
+
const canPrefill=Boolean(targetSpace);
|
| 797 |
prefill.disabled=!canPrefill;
|
| 798 |
+
prefill.title=canPrefill?'Prefill Space Test from this build':'Space Test prefill becomes available as soon as the target Space is known.';
|
| 799 |
prefill.onclick=canPrefill?prepareValidationFromActiveRun:null;
|
| 800 |
}
|
| 801 |
const del=$('deleteActiveRun');
|
|
|
|
| 807 |
}
|
| 808 |
}
|
| 809 |
function renderSelectedLinks(summary={},detail={}){renderActiveRunActions(summary,detail)}
|
| 810 |
+
function renderBlockers(detail={}){const root=$('selectedBlockers');if(!root)return;const detailRunId=detail.run_id||detail.summary?.run_id||detail.state?.run_id||'';if(detailRunId&&state.runId&&detailRunId!==state.runId){root.hidden=true;root.innerHTML='';return}const summary=detail.summary||{};const blockers=detail.technical_blockers||{};const manual=summary.manual_hardware_required||String(summary.status||'').includes('manual_hardware_required');const blockerItems=(Array.isArray(blockers.blockers)?blockers.blockers:[]).filter(b=>!['patch_code','repair_decision','repair','recovery'].includes(String(b.type||b.name||'').toLowerCase()));const piResolution=detail.pi_model_resolution||detail.state?.pi_model_resolution||summary.pi_model_resolution||{};const piMismatch=Boolean(piResolution.mismatch);if(!manual&&!blockerItems.length&&!piMismatch){root.hidden=true;root.innerHTML='';return}root.hidden=false;const parts=[];if(piMismatch)parts.push(`<strong>Pi assistant model changed</strong><p>Requested ${escapeHtml(piResolution.requested_model||'—')}, but Pi/provider traces indicate ${escapeHtml(piResolution.effective_model||'another model')}. This may be an Inference Providers routing/fallback behavior.</p>`);if(manual)parts.push('<strong>Manual hardware required</strong><p>Open Space settings, choose the recommended GPU, then run validation.</p>');for(const b of blockerItems.slice(0,4)){parts.push(`<strong>${escapeHtml(shortText(b.type||b.name||'Technical blocker',32))}</strong><p>${escapeHtml(shortText(b.claim||b.reason||b.message||'',120))}</p>`)}root.innerHTML=parts.join('')}
|
| 811 |
function renderRunDetail(detail){
|
| 812 |
state.selectedRunDetail=detail;
|
| 813 |
const summary=detail.summary||{};
|