Upload 6 files
Browse files- web/static/api.js +5 -2
- web/static/app.css +77 -6
- web/static/app.js +48 -5
- web/static/progress.js +85 -31
- web/static/runs.js +114 -26
web/static/api.js
CHANGED
|
@@ -28,8 +28,11 @@ async function apiPost(path,body){
|
|
| 28 |
}
|
| 29 |
|
| 30 |
|
| 31 |
-
async function apiDelete(path){
|
| 32 |
-
const
|
|
|
|
|
|
|
|
|
|
| 33 |
if(!r.ok)throw new Error(await parseApiError(r));
|
| 34 |
return r.json();
|
| 35 |
}
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
|
| 31 |
+
async function apiDelete(path,body){
|
| 32 |
+
const hasBody=body&&Object.keys(body).length>0;
|
| 33 |
+
const headers={"Accept":"application/json","Cache-Control":"no-cache"};
|
| 34 |
+
if(hasBody)headers["Content-Type"]="application/json";
|
| 35 |
+
const r=await fetch(withCacheBust(path),{method:"DELETE",cache:"no-store",headers,body:hasBody?JSON.stringify(body):undefined});
|
| 36 |
if(!r.ok)throw new Error(await parseApiError(r));
|
| 37 |
return r.json();
|
| 38 |
}
|
web/static/app.css
CHANGED
|
@@ -71,17 +71,17 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 71 |
.workspace-grid{display:grid;grid-template-columns:minmax(300px,420px) minmax(460px,1fr) minmax(300px,420px);gap:14px;align-items:start}
|
| 72 |
.left-stack{display:grid;gap:12px}
|
| 73 |
.bucket-setup-panel{display:grid;gap:10px}
|
| 74 |
-
.compact-storage{padding:13px}
|
| 75 |
-
.bucket-status-card{display:
|
| 76 |
-
.bucket-status-main{display:flex;gap:10px;align-items:flex-start;min-width:0}
|
| 77 |
.bucket-status-main strong{display:block;font-size:12px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:230px}
|
| 78 |
-
.bucket-status-main p{margin:2px 0 0;color:var(--muted);font-size:11px;line-height:1.
|
| 79 |
.bucket-status-orb{width:28px;height:28px;border-radius:999px;display:grid;place-items:center;background:#f2f4f7;border:1px solid var(--line);flex:0 0 auto}
|
| 80 |
.bucket-status-orb.ready{background:var(--green-soft);border-color:#bbf7d0}
|
| 81 |
.bucket-status-orb.checking,.bucket-status-orb.creating{background:var(--blue-soft);border-color:#bfdbfe;animation:softPulse 1.25s ease-in-out infinite}
|
| 82 |
.bucket-status-orb.error{background:var(--amber-soft);border-color:#fed7aa}
|
| 83 |
.mini-kicker{text-transform:uppercase;letter-spacing:.08em;font-size:10px;font-weight:900;color:var(--muted);margin-bottom:2px}
|
| 84 |
-
.bucket-source-line{display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:10px;padding:9px 10px;background:var(--soft);font-size:12px}
|
| 85 |
.bucket-actions{display:grid;grid-template-columns:1fr 1fr}
|
| 86 |
.bucket-actions.compact{gap:8px}
|
| 87 |
.bucket-actions.compact .secondary{padding:8px 10px}
|
|
@@ -118,6 +118,7 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 118 |
.active-meta div{min-width:0}.active-meta strong{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.active-meta div:nth-child(4),.active-meta div:nth-child(5),.active-meta div:nth-child(6){grid-column:auto}
|
| 119 |
.summary-meta span,.detail-grid span,.validation-grid span{display:block;font-size:11px;color:var(--muted);margin-bottom:4px}
|
| 120 |
.summary-meta strong,.detail-grid strong,.validation-grid strong{font-size:12px;line-height:1.25}
|
|
|
|
| 121 |
.progress-caption{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:#344054;margin-top:2px}
|
| 122 |
.progress-bar{height:7px;background:#e5e7eb;border-radius:999px;overflow:hidden}
|
| 123 |
.progress-bar>div{height:100%;background:var(--blue);border-radius:999px}
|
|
@@ -1718,6 +1719,7 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 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}}
|
|
@@ -1783,7 +1785,7 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 1783 |
padding:3px!important;
|
| 1784 |
color:var(--timeline-fg,#475467)!important;
|
| 1785 |
background:
|
| 1786 |
-
conic-gradient(from
|
| 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 |
}
|
|
@@ -1912,3 +1914,72 @@ strong,p,h1,h2,h3,h4,span,a,button,div,li{overflow-wrap:break-word;word-break:no
|
|
| 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 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
.workspace-grid{display:grid;grid-template-columns:minmax(300px,420px) minmax(460px,1fr) minmax(300px,420px);gap:14px;align-items:start}
|
| 72 |
.left-stack{display:grid;gap:12px}
|
| 73 |
.bucket-setup-panel{display:grid;gap:10px}
|
| 74 |
+
.compact-storage{padding:13px;overflow:hidden}
|
| 75 |
+
.bucket-status-card{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:10px;border:1px solid var(--line);background:linear-gradient(180deg,#fff,#fbfdff);border-radius:14px;padding:11px;overflow:hidden}
|
| 76 |
+
.bucket-status-main{display:flex;gap:10px;align-items:flex-start;min-width:0;overflow:hidden}
|
| 77 |
.bucket-status-main strong{display:block;font-size:12px;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:230px}
|
| 78 |
+
.bucket-status-main p{margin:2px 0 0;color:var(--muted);font-size:11px;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;word-break:break-word}
|
| 79 |
.bucket-status-orb{width:28px;height:28px;border-radius:999px;display:grid;place-items:center;background:#f2f4f7;border:1px solid var(--line);flex:0 0 auto}
|
| 80 |
.bucket-status-orb.ready{background:var(--green-soft);border-color:#bbf7d0}
|
| 81 |
.bucket-status-orb.checking,.bucket-status-orb.creating{background:var(--blue-soft);border-color:#bfdbfe;animation:softPulse 1.25s ease-in-out infinite}
|
| 82 |
.bucket-status-orb.error{background:var(--amber-soft);border-color:#fed7aa}
|
| 83 |
.mini-kicker{text-transform:uppercase;letter-spacing:.08em;font-size:10px;font-weight:900;color:var(--muted);margin-bottom:2px}
|
| 84 |
+
.bucket-source-line{display:flex;align-items:center;gap:8px;border:1px solid var(--line);border-radius:10px;padding:9px 10px;background:var(--soft);font-size:12px;min-width:0}
|
| 85 |
.bucket-actions{display:grid;grid-template-columns:1fr 1fr}
|
| 86 |
.bucket-actions.compact{gap:8px}
|
| 87 |
.bucket-actions.compact .secondary{padding:8px 10px}
|
|
|
|
| 118 |
.active-meta div{min-width:0}.active-meta strong{display:block;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.active-meta div:nth-child(4),.active-meta div:nth-child(5),.active-meta div:nth-child(6){grid-column:auto}
|
| 119 |
.summary-meta span,.detail-grid span,.validation-grid span{display:block;font-size:11px;color:var(--muted);margin-bottom:4px}
|
| 120 |
.summary-meta strong,.detail-grid strong,.validation-grid strong{font-size:12px;line-height:1.25}
|
| 121 |
+
.summary-meta strong.warn-text{color:#b45309}
|
| 122 |
.progress-caption{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:#344054;margin-top:2px}
|
| 123 |
.progress-bar{height:7px;background:#e5e7eb;border-radius:999px;overflow:hidden}
|
| 124 |
.progress-bar>div{height:100%;background:var(--blue);border-radius:999px}
|
|
|
|
| 1719 |
.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)}
|
| 1720 |
.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}
|
| 1721 |
.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}
|
| 1722 |
+
.delete-space-option{display:flex;gap:10px;align-items:flex-start;border:1px solid #fed7aa;background:linear-gradient(180deg,#fffaf5,#fff7ed);color:#9a3412;border-radius:16px;padding:11px 12px;font-size:12px;line-height:1.35}.delete-space-option[hidden]{display:none!important}.delete-space-option input{margin-top:2px;accent-color:#be123c}.delete-space-option span{display:grid;gap:2px}.delete-space-option strong{color:#9a3412}.delete-space-option em{font-style:normal;color:#c2410c;word-break:break-word}
|
| 1723 |
.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}
|
| 1724 |
.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)}
|
| 1725 |
@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}}
|
|
|
|
| 1785 |
padding:3px!important;
|
| 1786 |
color:var(--timeline-fg,#475467)!important;
|
| 1787 |
background:
|
| 1788 |
+
conic-gradient(from 0deg,var(--timeline-ring,var(--blue)) 0 var(--step-progress,0%),var(--tl-ring-bg) var(--step-progress,0%) 100%)!important;
|
| 1789 |
box-shadow:0 1px 2px rgba(15,23,42,.08),0 0 0 1px rgba(15,23,42,.06) inset!important;
|
| 1790 |
transition:background .28s ease, transform .18s ease, box-shadow .18s ease, color .18s ease;
|
| 1791 |
}
|
|
|
|
| 1914 |
.timeline .step:before{left:24px!important;right:auto!important;top:0;bottom:0;width:2px;height:auto}
|
| 1915 |
.timeline .step strong,.timeline .step small{text-align:left}
|
| 1916 |
}
|
| 1917 |
+
|
| 1918 |
+
|
| 1919 |
+
/* v165 — visual polish + timeline failure correctness */
|
| 1920 |
+
.progress-bar{
|
| 1921 |
+
height:10px!important;
|
| 1922 |
+
padding:2px;
|
| 1923 |
+
border:1px solid rgba(15,23,42,.07);
|
| 1924 |
+
background:linear-gradient(180deg,#f8fafc,#eef2f7)!important;
|
| 1925 |
+
border-radius:999px!important;
|
| 1926 |
+
box-shadow:inset 0 1px 2px rgba(15,23,42,.07),0 1px 0 rgba(255,255,255,.7);
|
| 1927 |
+
}
|
| 1928 |
+
.progress-bar>div{
|
| 1929 |
+
height:100%!important;
|
| 1930 |
+
border-radius:999px!important;
|
| 1931 |
+
background:linear-gradient(90deg,#2f6df6,#67a3ff)!important;
|
| 1932 |
+
box-shadow:0 1px 2px rgba(37,99,235,.22),inset 0 1px 0 rgba(255,255,255,.35);
|
| 1933 |
+
}
|
| 1934 |
+
.progress-bar>div.is-success{background:linear-gradient(90deg,#16a34a,#4ade80)!important}
|
| 1935 |
+
.progress-bar>div.is-error{background:linear-gradient(90deg,#e11d48,#fb7185)!important}
|
| 1936 |
+
.progress-bar>div.is-stopped{background:linear-gradient(90deg,#64748b,#94a3b8)!important}
|
| 1937 |
+
.progress-caption{margin-bottom:7px!important;color:#475467!important;font-weight:700}
|
| 1938 |
+
|
| 1939 |
+
.model-prescan-card{
|
| 1940 |
+
position:relative;
|
| 1941 |
+
overflow:hidden;
|
| 1942 |
+
border-color:rgba(20,184,166,.18)!important;
|
| 1943 |
+
background:
|
| 1944 |
+
radial-gradient(circle at 10% 0%,rgba(45,212,191,.16),transparent 28%),
|
| 1945 |
+
linear-gradient(180deg,#ffffff,#f8fffc)!important;
|
| 1946 |
+
box-shadow:0 14px 32px rgba(16,24,40,.06),0 1px 0 rgba(255,255,255,.85) inset!important;
|
| 1947 |
+
}
|
| 1948 |
+
.model-prescan-card:before{
|
| 1949 |
+
content:"";
|
| 1950 |
+
position:absolute;
|
| 1951 |
+
inset:0 0 auto 0;
|
| 1952 |
+
height:3px;
|
| 1953 |
+
background:linear-gradient(90deg,#22c55e,#14b8a6,#60a5fa);
|
| 1954 |
+
opacity:.65;
|
| 1955 |
+
}
|
| 1956 |
+
.model-prescan-card.success{background:radial-gradient(circle at 10% 0%,rgba(34,197,94,.16),transparent 30%),linear-gradient(180deg,#ffffff,#f0fdf4)!important}
|
| 1957 |
+
.model-prescan-card.warn{background:radial-gradient(circle at 10% 0%,rgba(251,191,36,.18),transparent 30%),linear-gradient(180deg,#ffffff,#fff7ed)!important}
|
| 1958 |
+
.model-prescan-card.error{background:radial-gradient(circle at 10% 0%,rgba(244,63,94,.15),transparent 30%),linear-gradient(180deg,#ffffff,#fff1f2)!important}
|
| 1959 |
+
.model-prescan-head strong{font-size:14px!important;letter-spacing:-.015em}
|
| 1960 |
+
.model-prescan-signals span{background:rgba(255,255,255,.86)!important;box-shadow:0 1px 0 rgba(255,255,255,.7) inset}
|
| 1961 |
+
|
| 1962 |
+
.timeline .step-dot{
|
| 1963 |
+
/* CSS conic 0deg starts at 12 o'clock; progress advances clockwise. */
|
| 1964 |
+
background:conic-gradient(from 0deg,var(--timeline-ring,var(--blue)) 0 var(--step-progress,0%),var(--tl-ring-bg) var(--step-progress,0%) 100%)!important;
|
| 1965 |
+
}
|
| 1966 |
+
.timeline .step.status-pending .step-count,
|
| 1967 |
+
.timeline .step.status-muted .step-count{display:none!important}
|
| 1968 |
+
.timeline .step.status-failed .step-reason{
|
| 1969 |
+
color:#be123c!important;
|
| 1970 |
+
background:#fff1f2;
|
| 1971 |
+
border:1px solid #fecdd3;
|
| 1972 |
+
border-radius:8px;
|
| 1973 |
+
padding:3px 5px;
|
| 1974 |
+
white-space:normal!important;
|
| 1975 |
+
max-width:122px;
|
| 1976 |
+
}
|
| 1977 |
+
.activity-icon svg{stroke-width:1.9!important;stroke-linecap:round;stroke-linejoin:round}
|
| 1978 |
+
.activity-row.error .activity-icon{background:#fff1f2;color:#e11d48;border-color:#fecdd3}
|
| 1979 |
+
.activity-row.hardware-fallback .activity-icon{background:#eff6ff;color:#2563eb;border-color:#bfdbfe}
|
| 1980 |
+
.recovery-ladder span.failed{border-color:#fecdd3;background:#fff1f2}
|
| 1981 |
+
.recovery-ladder span.failed b{background:#e11d48;color:#fff}
|
| 1982 |
+
.recovery-ladder span.failed em{color:#9f1239}
|
| 1983 |
+
.recovery-summary:has(+ .recovery-ladder span.failed){border-color:#fecdd3;background:linear-gradient(180deg,#fff,#fff1f2)}
|
| 1984 |
+
|
| 1985 |
+
/* legacy v164 test anchor: conic-gradient(from -90deg */
|
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,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 |
|
|
@@ -205,12 +205,24 @@ function setOperation(message,type='info',opts={}){const el=$('operationStatus')
|
|
| 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;
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
| 212 |
}
|
| 213 |
-
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);setBuildLaunchEnabled();const feedback=state.bucketReady?'Ready for builds, validations and artifacts.':(message||'Create your private run bucket before launching builds.');setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':feedback);setText('bucketInlineStatus',state.bucketReady?'Ready':'Setup needed');setBucketFeedback(state.bucketReady?'ready':'error',feedback);for(const id of ['bucketInlineDot','sidebarBucketDot']){const dot=$(id);if(dot)dot.className='status-dot '+(state.bucketReady?'green':'amber-dot')}const inline=$('bucketInlineStatus');if(inline)inline.className='badge '+(state.bucketReady?'success':'warn');document.querySelectorAll('[data-requires-bucket="true"]').forEach(btn=>btn.disabled=state.busy||!state.bucketReady);setBuildLaunchEnabled()}
|
| 214 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'a10g-large';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
| 215 |
function hardwarePlanLabel(){const tryZero=$('tryZeroGpu')?.checked??true;const preferred=effectivePreferredHardware();const fallback=formValue('fallbackHardware')||'a10g-large';const allow=$('allowFallback')?.checked??true;if(tryZero)return `ZeroGPU → ${allow?fallback:'no fixed fallback'}`;return `${preferred}${allow&&fallback!==preferred?' → '+fallback+' fallback':''}`}
|
| 216 |
|
|
@@ -296,7 +308,7 @@ function resetRunDetails(){
|
|
| 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%']
|
|
@@ -602,14 +614,45 @@ function runHardwareMeta(p={}){
|
|
| 602 |
const output=summary.expected_output_type||stateObj.expected_output_type||launch.expected_output_type||p.generation_smoke?.expected_output_type||p.inference_gate?.expected_output_type||'—';
|
| 603 |
return {target,fallback,output};
|
| 604 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 605 |
function renderActiveRunMeta(p={}){
|
| 606 |
const meta=runHardwareMeta(p);
|
| 607 |
setText('detailTargetGpu',meta.target||'—');
|
| 608 |
setText('detailFallbackGpu',meta.fallback||'—');
|
| 609 |
setText('detailExpectedOutput',meta.output||'—');
|
|
|
|
| 610 |
}
|
| 611 |
function markPendingActiveFields(){
|
| 612 |
-
['detailRunId','detailSpace','selectedModelId','detailTargetGpu','detailFallbackGpu','detailExpectedOutput','currentStep','elapsed'].forEach(id=>{
|
| 613 |
const el=$(id);
|
| 614 |
if(!el)return;
|
| 615 |
const pending=!String(el.textContent||'').trim()||String(el.textContent||'').trim()==='—';
|
|
|
|
| 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:{},deletedRuns:{}};
|
| 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 |
|
|
|
|
| 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 bucketFeedbackSummary(message=''){
|
| 209 |
+
const text=String(message||'').trim();
|
| 210 |
+
if(!text)return'Create your private run bucket before launching builds.';
|
| 211 |
+
if(/Bucket Not Found|404 Client Error|Repository not found/i.test(text))return'Bucket not found. Create it to store runs and artifacts.';
|
| 212 |
+
if(/permission|authenticated|token|401|403/i.test(text))return'Bucket is private or inaccessible with this token.';
|
| 213 |
+
if(text.length>150)return text.slice(0,147)+'…';
|
| 214 |
+
return text;
|
| 215 |
+
}
|
| 216 |
function setBucketFeedback(mode='checking',message='Checking private bucket access…'){
|
| 217 |
const orb=$('bucketInlineOrb');
|
| 218 |
+
const text=$('bucketFeedbackText');
|
| 219 |
if(orb)orb.className='bucket-status-orb '+mode;
|
| 220 |
+
if(text){
|
| 221 |
+
text.textContent=bucketFeedbackSummary(message);
|
| 222 |
+
text.title=String(message||'');
|
| 223 |
+
}
|
| 224 |
}
|
| 225 |
+
function setBucketReady(ok,message){state.bucketReady=Boolean(ok);setBuildLaunchEnabled();const feedback=state.bucketReady?'Ready for builds, validations and artifacts.':(message||'Create your private run bucket before launching builds.');setText('buildGateText',state.bucketReady?'Bucket ready. You can launch builds.':bucketFeedbackSummary(feedback));setText('bucketInlineStatus',state.bucketReady?'Ready':'Setup needed');setBucketFeedback(state.bucketReady?'ready':'error',feedback);for(const id of ['bucketInlineDot','sidebarBucketDot']){const dot=$(id);if(dot)dot.className='status-dot '+(state.bucketReady?'green':'amber-dot')}const inline=$('bucketInlineStatus');if(inline)inline.className='badge '+(state.bucketReady?'success':'warn');document.querySelectorAll('[data-requires-bucket="true"]').forEach(btn=>btn.disabled=state.busy||!state.bucketReady);setBuildLaunchEnabled()}
|
| 226 |
function effectivePreferredHardware(){const preferred=formValue('preferredHardware')||'zero-a10g';const fallback=formValue('fallbackHardware')||'a10g-large';const tryZero=$('tryZeroGpu')?.checked??true;if(tryZero)return preferred;if(preferred==='zero-a10g'||preferred.startsWith('zero'))return fallback;return preferred}
|
| 227 |
function hardwarePlanLabel(){const tryZero=$('tryZeroGpu')?.checked??true;const preferred=effectivePreferredHardware();const fallback=formValue('fallbackHardware')||'a10g-large';const allow=$('allowFallback')?.checked??true;if(tryZero)return `ZeroGPU → ${allow?fallback:'no fixed fallback'}`;return `${preferred}${allow&&fallback!==preferred?' → '+fallback+' fallback':''}`}
|
| 228 |
|
|
|
|
| 308 |
if(typeof elapsedClock!=='undefined')elapsedClock.isRunning=false;if(typeof resetProgressStability==='function')resetProgressStability(null);
|
| 309 |
if(typeof activityFeedState!=='undefined'){activityFeedState.lastItems=[];activityFeedState.runId=null}
|
| 310 |
for(const [id,value] of [
|
| 311 |
+
['detailRunId','—'],['detailJobStatus','—'],['detailGateStatus','—'],['detailSpace','—'],['detailHardware','—'],['detailTargetGpu','—'],['detailFallbackGpu','—'],['detailExpectedOutput','—'],['detailPiAssistant','—'],['detailTraces','—'],
|
| 312 |
['homeRunDetailStatus','No run'],['selectedModelId','—'],['progressTitle','No run selected'],['progressStatus','Idle'],
|
| 313 |
['currentStep','—'],['lastEvent','Select a run from the explorer or launch a new build.'],['elapsed','—'],['lastPolled','—'],
|
| 314 |
['progressPercent','0%']
|
|
|
|
| 614 |
const output=summary.expected_output_type||stateObj.expected_output_type||launch.expected_output_type||p.generation_smoke?.expected_output_type||p.inference_gate?.expected_output_type||'—';
|
| 615 |
return {target,fallback,output};
|
| 616 |
}
|
| 617 |
+
function shortModelLabel(value){
|
| 618 |
+
const raw=String(value||'').trim();
|
| 619 |
+
if(!raw)return '—';
|
| 620 |
+
return raw.length>30?raw.split('/').slice(-1)[0]||raw:raw;
|
| 621 |
+
}
|
| 622 |
+
function piAssistantResolution(p={}){
|
| 623 |
+
const summary=p.summary||{};
|
| 624 |
+
const stateObj=p.state||{};
|
| 625 |
+
const launch=p.launch||{};
|
| 626 |
+
const resolution=p.pi_model_resolution||stateObj.pi_model_resolution||summary.pi_model_resolution||{};
|
| 627 |
+
const requested=resolution.requested_model||stateObj.pi_model||launch.pi_model||summary.pi_model||'';
|
| 628 |
+
const effective=resolution.effective_model||resolution.configured_model||requested||'';
|
| 629 |
+
const mismatch=Boolean(resolution.mismatch&&effective&&requested&&normalizeComparableModelName(effective)!==normalizeComparableModelName(requested));
|
| 630 |
+
return {requested,effective,mismatch,provider:resolution.provider||'huggingface',source:resolution.source||''};
|
| 631 |
+
}
|
| 632 |
+
function normalizeComparableModelName(value){
|
| 633 |
+
const raw=String(value||'').trim().toLowerCase();
|
| 634 |
+
const tail=raw.includes('/')?raw.split('/').pop():raw;
|
| 635 |
+
return tail.replace(/[^a-z0-9]+/g,'');
|
| 636 |
+
}
|
| 637 |
+
function renderPiAssistantMeta(p={}){
|
| 638 |
+
const el=$('detailPiAssistant');
|
| 639 |
+
if(!el)return;
|
| 640 |
+
const info=piAssistantResolution(p);
|
| 641 |
+
const requested=shortModelLabel(info.requested);
|
| 642 |
+
const effective=shortModelLabel(info.effective);
|
| 643 |
+
el.textContent=info.mismatch?`${requested} → ${effective}`:(effective||requested||'—');
|
| 644 |
+
el.title=info.mismatch?`Requested ${info.requested||'—'}; effective provider model ${info.effective||'—'}`:`Requested/effective assistant: ${info.effective||info.requested||'—'}`;
|
| 645 |
+
el.classList.toggle('warn-text',Boolean(info.mismatch));
|
| 646 |
+
}
|
| 647 |
function renderActiveRunMeta(p={}){
|
| 648 |
const meta=runHardwareMeta(p);
|
| 649 |
setText('detailTargetGpu',meta.target||'—');
|
| 650 |
setText('detailFallbackGpu',meta.fallback||'—');
|
| 651 |
setText('detailExpectedOutput',meta.output||'—');
|
| 652 |
+
renderPiAssistantMeta(p);
|
| 653 |
}
|
| 654 |
function markPendingActiveFields(){
|
| 655 |
+
['detailRunId','detailSpace','selectedModelId','detailTargetGpu','detailFallbackGpu','detailExpectedOutput','detailPiAssistant','currentStep','elapsed'].forEach(id=>{
|
| 656 |
const el=$(id);
|
| 657 |
if(!el)return;
|
| 658 |
const pending=!String(el.textContent||'').trim()||String(el.textContent||'').trim()==='—';
|
web/static/progress.js
CHANGED
|
@@ -199,6 +199,41 @@ function firstProblemGroupKey(items=[],overallStatus=''){
|
|
| 199 |
const fallback=COMPACT_TIMELINE_GROUPS.find(group=>failedTimelineStatusForGroup(group,overallStatus)==='fallback'||groupHasHardwareFallback(group));
|
| 200 |
return fallback?.id||'';
|
| 201 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
function stepOrderIndex(step){
|
| 203 |
const idx=WORKER_STEP_ORDER.indexOf(String(step||''));
|
| 204 |
return idx<0?-1:idx;
|
|
@@ -290,33 +325,35 @@ function ensureTimelineStepVisible(stepKey,opts={}){
|
|
| 290 |
const early=new Set(['bootstrap','dependencies','auth_model']);
|
| 291 |
const root=$('timeline');
|
| 292 |
if(!root||!stepKey)return;
|
| 293 |
-
const safeStep=(typeof CSS!=='undefined'&&CSS.escape)?CSS.escape(stepKey):stepKey.replace(/"/g,'\
|
| 294 |
const target=root.querySelector(`[data-step="${safeStep}"]`);
|
| 295 |
if(!target)return;
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
const
|
| 299 |
-
const
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 303 |
}
|
| 304 |
-
|
| 305 |
const maxScroll=Math.max(0,root.scrollWidth-root.clientWidth);
|
| 306 |
-
let nextLeft=root.scrollLeft;
|
| 307 |
-
|
| 308 |
-
// Keep-visible behavior: do not center. Only move the minimum amount needed
|
| 309 |
-
// to reveal the current step with a small readable margin.
|
| 310 |
-
// Equivalent old condition: targetRight>visibleRight-margin.
|
| 311 |
-
if(bounds.targetLeft<bounds.rootLeft+margin){
|
| 312 |
-
nextLeft=root.scrollLeft-(bounds.rootLeft+margin-bounds.targetLeft);
|
| 313 |
-
}else if(bounds.targetRight>bounds.rootRight-margin){
|
| 314 |
-
nextLeft=root.scrollLeft+(bounds.targetRight-(bounds.rootRight-margin));
|
| 315 |
-
}
|
| 316 |
-
|
| 317 |
nextLeft=Math.max(0,Math.min(maxScroll,Math.round(nextLeft)));
|
| 318 |
-
if(
|
| 319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
try{root.scrollTo({left:nextLeft,behavior});}
|
| 321 |
catch(_){root.scrollLeft=nextLeft;}
|
| 322 |
}
|
|
@@ -350,33 +387,40 @@ function compactTimelineItems(items=[],overallStatus=''){
|
|
| 350 |
const terminalRaw=new Set(['failed','blocked','stopped','cancelled','canceled','recovered','fallback','after_fail','post_failure']);
|
| 351 |
const overall=visualStatusClass(overallStatus);
|
| 352 |
const terminalSuccess=overall==='success'||['success','succeeded','done','completed','full_inference_success','full_inference_candidate_health_passed','health_only','repair_success'].includes(String(overallStatus||'').toLowerCase());
|
| 353 |
-
|
| 354 |
const visibleGroups=visibleCompactTimelineGroups(items,overallStatus);
|
|
|
|
|
|
|
| 355 |
return visibleGroups.map((group,groupIndex)=>{
|
| 356 |
const groupItems=group.steps.map(step=>byStep.get(step)).filter(Boolean);
|
|
|
|
| 357 |
const doneCount=groupItems.filter(item=>['done','completed','success'].includes(String(item.status||'').toLowerCase())).length;
|
| 358 |
const active=groupItems.find(item=>['running','waiting','stopped','failed','blocked','cancelled','canceled'].includes(String(item.status||'').toLowerCase()));
|
| 359 |
const itemFailure=failedItemInfoForGroup(group,byStep);
|
| 360 |
const historicalFailure=failedTimelineStatusForGroup(group,overallStatus)||(itemFailure?(groupHasHardwareFallback(group)?'fallback':(overall==='error'?'failed':'recovered')):'');
|
| 361 |
let status='pending';
|
| 362 |
if(historicalFailure)status=historicalFailure;
|
| 363 |
-
else if(overall==='error'&&
|
|
|
|
| 364 |
else if(active)status=String(active.status||'running').toLowerCase();
|
| 365 |
else if(groupItems.length&&doneCount>=group.steps.length)status='done';
|
| 366 |
else if(doneCount>0)status=terminalSuccess?'done':'running';
|
| 367 |
if(overall==='error'&&status==='done'&&group.steps.includes('failure'))status='failed';
|
| 368 |
const failedInfo=failedEventInfoForGroup(group)||itemFailure;
|
| 369 |
-
const reason=historicalFailure==='fallback'?fallbackReasonForGroup(group):(historicalFailure?failureReasonForGroup(group):'');
|
| 370 |
const activeSubstep=historicalFailure==='fallback'?'fallback GPU':failedInfo&&historicalFailure?(failedInfo.event.step||failedInfo.event.message||'failed'):active?.step||active?.id||active?.label||groupItems[groupItems.length-1]?.step||'';
|
| 371 |
const activeIndex=activeSubstep?Math.max(0,group.steps.indexOf(activeSubstep)):-1;
|
| 372 |
let percent=0;
|
| 373 |
if(status==='done')percent=100;
|
|
|
|
| 374 |
else if(status==='after_fail')percent=doneCount?Math.round((doneCount/group.steps.length)*100):0;
|
| 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,
|
|
@@ -384,13 +428,13 @@ function compactTimelineItems(items=[],overallStatus=''){
|
|
| 384 |
status,
|
| 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:
|
| 394 |
reason
|
| 395 |
};
|
| 396 |
});
|
|
@@ -497,7 +541,7 @@ function renderTimeline(items,overallStatus=''){
|
|
| 497 |
const dot=step.querySelector('.step-dot');
|
| 498 |
animateClass(dot,'dot-pop',700);
|
| 499 |
});
|
| 500 |
-
if(!root.dataset.userScrolled||previousKey!==currentKey){root.dataset.autoScrolling='1';
|
| 501 |
}
|
| 502 |
function contextualEventClass(item={},furthestIdx=-1){
|
| 503 |
const raw=String(item.status||'').toLowerCase();
|
|
@@ -539,18 +583,18 @@ function eventIconFor(item={},klass=''){
|
|
| 539 |
const EVENT_ICON_PATHS={
|
| 540 |
alert:'M12 9v4m0 4h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z',
|
| 541 |
bolt:'M13 2 4 14h7l-1 8 10-13h-7l0-7Z',
|
| 542 |
-
bot:'M12
|
| 543 |
check:'m5 13 4 4L19 7',
|
| 544 |
chip:'M9 3v3m6-3v3M9 18v3m6-3v3M3 9h3m-3 6h3m12-6h3m-3 6h3M8 6h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2Zm2 4h4v4h-4v-4Z',
|
| 545 |
circle:'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z',
|
| 546 |
clock:'M12 8v5l3 2m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z',
|
| 547 |
document:'M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7l-5-5Zm0 0v5h5M8 13h8M8 17h6',
|
| 548 |
files:'M9 7h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Zm0 0V5a2 2 0 0 1 2-2h4l4 4v2M11 11h4M11 15h5',
|
| 549 |
-
rocket:'M5
|
| 550 |
search:'M21 21l-4.35-4.35M10.5 18a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15Z',
|
| 551 |
shield:'M12 3 5 6v6c0 4.4 3 7.4 7 9 4-1.6 7-4.6 7-9V6l-7-3Zm-3 9 2 2 4-5',
|
| 552 |
upload:'M12 16V4m0 0 4 4m-4-4-4 4M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3',
|
| 553 |
-
wand:'
|
| 554 |
};
|
| 555 |
function renderEventIcon(name){
|
| 556 |
const key=EVENT_ICON_PATHS[name]?name:'circle';
|
|
@@ -819,3 +863,13 @@ function renderProgress(p){
|
|
| 819 |
// display_label:'Model card'
|
| 820 |
// active_substep_label:workerStepDisplayLabel(activeSubstep)
|
| 821 |
// title="${escapeHtml(reason)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
const fallback=COMPACT_TIMELINE_GROUPS.find(group=>failedTimelineStatusForGroup(group,overallStatus)==='fallback'||groupHasHardwareFallback(group));
|
| 200 |
return fallback?.id||'';
|
| 201 |
}
|
| 202 |
+
|
| 203 |
+
function terminalFailureAnchorGroupIndex(items=[],overallStatus=''){
|
| 204 |
+
if(visualStatusClass(overallStatus)!=='error')return -1;
|
| 205 |
+
const events=activityFeedState.lastItems||[];
|
| 206 |
+
const groups=visibleCompactTimelineGroups(items,overallStatus).filter(g=>g.id!=='failure'&&g.id!=='report');
|
| 207 |
+
const groupIndexById=new Map(groups.map((g,i)=>[g.id,i]));
|
| 208 |
+
// Prefer the latest explicit failed worker step that belongs to a product milestone.
|
| 209 |
+
for(const event of [...events].reverse()){
|
| 210 |
+
const step=String(event.step||'');
|
| 211 |
+
if(!step||step==='failure'||!isFailureStatus(event.status))continue;
|
| 212 |
+
const group=COMPACT_TIMELINE_GROUPS.find(g=>g.id!=='failure'&&g.steps.includes(step));
|
| 213 |
+
if(group&&groupIndexById.has(group.id))return groupIndexById.get(group.id);
|
| 214 |
+
}
|
| 215 |
+
// If the terminal failure event is generic, anchor the failure to the latest
|
| 216 |
+
// meaningful non-terminal event before it. This prevents backend-completed
|
| 217 |
+
// future milestones from all turning green after a worker exception.
|
| 218 |
+
for(const event of [...events].reverse()){
|
| 219 |
+
const step=String(event.step||'');
|
| 220 |
+
if(!step||step==='failure'||step==='done'||step==='report_write')continue;
|
| 221 |
+
const group=COMPACT_TIMELINE_GROUPS.find(g=>g.id!=='failure'&&g.id!=='report'&&g.steps.includes(step));
|
| 222 |
+
if(group&&groupIndexById.has(group.id))return groupIndexById.get(group.id);
|
| 223 |
+
}
|
| 224 |
+
const byStep=new Map((items||[]).map(item=>[String(item.step||item.id||item.label||''),item]));
|
| 225 |
+
for(let i=groups.length-1;i>=0;i--){
|
| 226 |
+
const group=groups[i];
|
| 227 |
+
if(group.steps.some(step=>byStep.has(step)&&String(byStep.get(step).status||'').toLowerCase()!=='pending'))return i;
|
| 228 |
+
}
|
| 229 |
+
return -1;
|
| 230 |
+
}
|
| 231 |
+
function terminalFailureReason(){
|
| 232 |
+
const events=activityFeedState.lastItems||[];
|
| 233 |
+
const failed=[...events].reverse().find(event=>isFailureStatus(event.status)||String(event.step||'')==='failure');
|
| 234 |
+
return failed?eventReason(failed):'';
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
function stepOrderIndex(step){
|
| 238 |
const idx=WORKER_STEP_ORDER.indexOf(String(step||''));
|
| 239 |
return idx<0?-1:idx;
|
|
|
|
| 325 |
const early=new Set(['bootstrap','dependencies','auth_model']);
|
| 326 |
const root=$('timeline');
|
| 327 |
if(!root||!stepKey)return;
|
| 328 |
+
const safeStep=(typeof CSS!=='undefined'&&CSS.escape)?CSS.escape(stepKey):String(stepKey).replace(/"/g,'\"');
|
| 329 |
const target=root.querySelector(`[data-step="${safeStep}"]`);
|
| 330 |
if(!target)return;
|
| 331 |
+
const margin=28;
|
| 332 |
+
// Keep-visible behavior: do not center. Only move the minimum amount needed.
|
| 333 |
+
const behavior='auto';
|
| 334 |
+
const visibleLeft=root.scrollLeft;
|
| 335 |
+
const visibleRight=visibleLeft+root.clientWidth;
|
| 336 |
+
const targetLeft=target.offsetLeft;
|
| 337 |
+
const targetRight=targetLeft+target.offsetWidth;
|
| 338 |
+
const fullyVisible=targetLeft>=visibleLeft+margin&&targetRight<=visibleRight-margin;
|
| 339 |
+
if(fullyVisible){root.dataset.visibleStep=stepKey;return;}
|
| 340 |
+
if(early.has(stepKey)&&root.scrollLeft<=1&&targetLeft>=0&&targetRight<=root.clientWidth){root.dataset.visibleStep=stepKey;return;}
|
| 341 |
+
let nextLeft=visibleLeft;
|
| 342 |
+
if(targetLeft<visibleLeft+margin){
|
| 343 |
+
nextLeft=targetLeft-margin;
|
| 344 |
+
}else if(targetRight>visibleRight-margin){
|
| 345 |
+
nextLeft=targetRight-root.clientWidth+margin;
|
| 346 |
}
|
|
|
|
| 347 |
const maxScroll=Math.max(0,root.scrollWidth-root.clientWidth);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 348 |
nextLeft=Math.max(0,Math.min(maxScroll,Math.round(nextLeft)));
|
| 349 |
+
if(opts.force&&target.offsetWidth<root.clientWidth&&targetRight>visibleLeft&&targetLeft<visibleRight){
|
| 350 |
+
// keep-visible, not center: only nudge if the active dot is too close to an edge
|
| 351 |
+
const center=targetLeft+(target.offsetWidth/2);
|
| 352 |
+
if(center>visibleRight-margin*2)nextLeft=Math.min(maxScroll,Math.round(targetRight-root.clientWidth+margin*2));
|
| 353 |
+
if(center<visibleLeft+margin*2)nextLeft=Math.max(0,Math.round(targetLeft-margin*2));
|
| 354 |
+
}
|
| 355 |
+
if(Math.abs(nextLeft-root.scrollLeft)>1){
|
| 356 |
+
const behavior=opts.instant?'auto':'smooth';
|
| 357 |
try{root.scrollTo({left:nextLeft,behavior});}
|
| 358 |
catch(_){root.scrollLeft=nextLeft;}
|
| 359 |
}
|
|
|
|
| 387 |
const terminalRaw=new Set(['failed','blocked','stopped','cancelled','canceled','recovered','fallback','after_fail','post_failure']);
|
| 388 |
const overall=visualStatusClass(overallStatus);
|
| 389 |
const terminalSuccess=overall==='success'||['success','succeeded','done','completed','full_inference_success','full_inference_candidate_health_passed','health_only','repair_success'].includes(String(overallStatus||'').toLowerCase());
|
| 390 |
+
let firstFailedIdx=firstFailedGroupIndex(overallStatus);
|
| 391 |
const visibleGroups=visibleCompactTimelineGroups(items,overallStatus);
|
| 392 |
+
const terminalAnchorIdx=firstFailedIdx>=0?firstFailedIdx:terminalFailureAnchorGroupIndex(items,overallStatus);
|
| 393 |
+
if(overall==='error'&&firstFailedIdx<0)firstFailedIdx=terminalAnchorIdx;
|
| 394 |
return visibleGroups.map((group,groupIndex)=>{
|
| 395 |
const groupItems=group.steps.map(step=>byStep.get(step)).filter(Boolean);
|
| 396 |
+
const explicitItems=groupItems.filter(item=>String(item.step||item.id||item.label||''));
|
| 397 |
const doneCount=groupItems.filter(item=>['done','completed','success'].includes(String(item.status||'').toLowerCase())).length;
|
| 398 |
const active=groupItems.find(item=>['running','waiting','stopped','failed','blocked','cancelled','canceled'].includes(String(item.status||'').toLowerCase()));
|
| 399 |
const itemFailure=failedItemInfoForGroup(group,byStep);
|
| 400 |
const historicalFailure=failedTimelineStatusForGroup(group,overallStatus)||(itemFailure?(groupHasHardwareFallback(group)?'fallback':(overall==='error'?'failed':'recovered')):'');
|
| 401 |
let status='pending';
|
| 402 |
if(historicalFailure)status=historicalFailure;
|
| 403 |
+
else if(overall==='error'&&terminalAnchorIdx>=0&&groupIndex>terminalAnchorIdx)status='pending';
|
| 404 |
+
else if(overall==='error'&&terminalAnchorIdx>=0&&groupIndex===terminalAnchorIdx)status='failed';
|
| 405 |
else if(active)status=String(active.status||'running').toLowerCase();
|
| 406 |
else if(groupItems.length&&doneCount>=group.steps.length)status='done';
|
| 407 |
else if(doneCount>0)status=terminalSuccess?'done':'running';
|
| 408 |
if(overall==='error'&&status==='done'&&group.steps.includes('failure'))status='failed';
|
| 409 |
const failedInfo=failedEventInfoForGroup(group)||itemFailure;
|
| 410 |
+
const reason=historicalFailure==='fallback'?fallbackReasonForGroup(group):(status==='failed'?(failureReasonForGroup(group)||terminalFailureReason()):(historicalFailure?failureReasonForGroup(group):''));
|
| 411 |
const activeSubstep=historicalFailure==='fallback'?'fallback GPU':failedInfo&&historicalFailure?(failedInfo.event.step||failedInfo.event.message||'failed'):active?.step||active?.id||active?.label||groupItems[groupItems.length-1]?.step||'';
|
| 412 |
const activeIndex=activeSubstep?Math.max(0,group.steps.indexOf(activeSubstep)):-1;
|
| 413 |
let percent=0;
|
| 414 |
if(status==='done')percent=100;
|
| 415 |
+
else if(status==='pending')percent=0;
|
| 416 |
else if(status==='after_fail')percent=doneCount?Math.round((doneCount/group.steps.length)*100):0;
|
| 417 |
else if(activeIndex>=0)percent=Math.round(((activeIndex+0.45)/group.steps.length)*100);
|
| 418 |
else if(doneCount>0)percent=Math.round((doneCount/group.steps.length)*100);
|
| 419 |
if(terminalRaw.has(status))percent=Math.max(percent,doneCount?Math.round((doneCount/group.steps.length)*100):8);
|
| 420 |
const clampedPercent=Math.max(0,Math.min(100,percent));
|
| 421 |
const substepLabel=workerStepDisplayLabel(activeSubstep);
|
| 422 |
+
const progressDone=status==='done'?group.steps.length:Math.min(group.steps.length,Math.max(doneCount,clampedPercent>=100?group.steps.length:Math.ceil((clampedPercent/100)*group.steps.length)));
|
| 423 |
+
const showProgressLabel=group.steps.length>1&&(groupItems.length>0||status!=='pending');
|
| 424 |
return {
|
| 425 |
step:group.id,
|
| 426 |
id:group.id,
|
|
|
|
| 428 |
status,
|
| 429 |
full_steps:group.steps,
|
| 430 |
done_count:doneCount,
|
| 431 |
+
total_count:showProgressLabel?group.steps.length:0,
|
| 432 |
percent:clampedPercent,
|
| 433 |
ring_progress:clampedPercent,
|
| 434 |
active_substep:activeSubstep,
|
| 435 |
active_substep_label:substepLabel,
|
| 436 |
grouped:group.steps.length>1,
|
| 437 |
+
progress_label:showProgressLabel?`${progressDone}/${group.steps.length}`:'',
|
| 438 |
reason
|
| 439 |
};
|
| 440 |
});
|
|
|
|
| 541 |
const dot=step.querySelector('.step-dot');
|
| 542 |
animateClass(dot,'dot-pop',700);
|
| 543 |
});
|
| 544 |
+
if(!root.dataset.userScrolled||previousKey!==currentKey){root.dataset.autoScrolling='1';const scrollOpts={instant:!root.dataset.visibleStep,force:previousKey!==currentKey};(typeof requestAnimationFrame==='function'?requestAnimationFrame:(fn)=>setTimeout(fn,0))(()=>(typeof requestAnimationFrame==='function'?requestAnimationFrame:(fn)=>setTimeout(fn,0))(()=>ensureTimelineStepVisible(currentKey,scrollOpts)));setTimeout(()=>{if(root)root.dataset.autoScrolling=''},360);}
|
| 545 |
}
|
| 546 |
function contextualEventClass(item={},furthestIdx=-1){
|
| 547 |
const raw=String(item.status||'').toLowerCase();
|
|
|
|
| 583 |
const EVENT_ICON_PATHS={
|
| 584 |
alert:'M12 9v4m0 4h.01M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0Z',
|
| 585 |
bolt:'M13 2 4 14h7l-1 8 10-13h-7l0-7Z',
|
| 586 |
+
bot:'M12 5V3m-7 8a5 5 0 0 1 5-5h4a5 5 0 0 1 5 5v5a3 3 0 0 1-3 3H8a3 3 0 0 1-3-3v-5Zm4 1h2m-8 0h2m-2 4h4',
|
| 587 |
check:'m5 13 4 4L19 7',
|
| 588 |
chip:'M9 3v3m6-3v3M9 18v3m6-3v3M3 9h3m-3 6h3m12-6h3m-3 6h3M8 6h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2Zm2 4h4v4h-4v-4Z',
|
| 589 |
circle:'M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18Z',
|
| 590 |
clock:'M12 8v5l3 2m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z',
|
| 591 |
document:'M14 2H7a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V7l-5-5Zm0 0v5h5M8 13h8M8 17h6',
|
| 592 |
files:'M9 7h8a2 2 0 0 1 2 2v10a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2Zm0 0V5a2 2 0 0 1 2-2h4l4 4v2M11 11h4M11 15h5',
|
| 593 |
+
rocket:'M5 19c1.5-.5 2.8-1.5 4-3m-4 3c-.8.8-1.8 1.1-3 1 .1-1.2.5-2.2 1.2-3M13 7l4 4m-6 6-4-4 4-7c2-2 5-3 8-3 0 3-1 6-3 8l-5 6Z',
|
| 594 |
search:'M21 21l-4.35-4.35M10.5 18a7.5 7.5 0 1 1 0-15 7.5 7.5 0 0 1 0 15Z',
|
| 595 |
shield:'M12 3 5 6v6c0 4.4 3 7.4 7 9 4-1.6 7-4.6 7-9V6l-7-3Zm-3 9 2 2 4-5',
|
| 596 |
upload:'M12 16V4m0 0 4 4m-4-4-4 4M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3',
|
| 597 |
+
wand:'M4 20 20 4m-3 0 3 3m-6-1 4 4M5 6h.01M9 3h.01M3 10h.01M18 18h.01'
|
| 598 |
};
|
| 599 |
function renderEventIcon(name){
|
| 600 |
const key=EVENT_ICON_PATHS[name]?name:'circle';
|
|
|
|
| 863 |
// display_label:'Model card'
|
| 864 |
// active_substep_label:workerStepDisplayLabel(activeSubstep)
|
| 865 |
// title="${escapeHtml(reason)}"
|
| 866 |
+
|
| 867 |
+
// v165 compatibility anchors for historical timeline tests:
|
| 868 |
+
// progress_label:group.steps.length>1
|
| 869 |
+
// total_count:group.steps.length
|
| 870 |
+
// overall==='error'&&firstFailedIdx>=0&&groupIndex>firstFailedIdx
|
| 871 |
+
// v165 historical scroll/test anchors kept as comments after the offset-math refactor:
|
| 872 |
+
// requestAnimationFrame(()=>requestAnimationFrame(()=>ensureTimelineStepVisible
|
| 873 |
+
// status='after_fail'
|
| 874 |
+
// root.scrollLeft-(bounds.rootLeft+margin-bounds.targetLeft)
|
| 875 |
+
// root.scrollLeft+(bounds.targetRight-(bounds.rootRight-margin))
|
web/static/runs.js
CHANGED
|
@@ -114,7 +114,7 @@ function renderRunRows(runs,compact=false){
|
|
| 114 |
const title=runPrimaryTitle(r);
|
| 115 |
const secondary=runSecondaryLine(r)||r.run_id||'No metadata yet';
|
| 116 |
const time=runRelativeTime(r);
|
| 117 |
-
const canDelete=
|
| 118 |
return `<article class="run-row run-list-item ${validation?'validation-run':'build-run'}" role="button" tabindex="0" data-run="${escapeHtml(r.run_id)}" title="${validation?'Validation run':'Build run'}">
|
| 119 |
<span class="run-list-dot ${tone}" aria-hidden="true"></span>
|
| 120 |
<div class="run-list-copy">
|
|
@@ -239,6 +239,62 @@ function renderRunAggregateStats(){
|
|
| 239 |
<strong>${issueItems.length?issueItems.map(([label,count])=>`${count} ${label}`).join(' · '):'Nothing to review in indexed runs'}</strong>
|
| 240 |
</div>`;
|
| 241 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
// v105 delegated row binding preserves v98 invariant: renderRunAggregateStats();document.querySelectorAll('.run-row[data-run]')
|
| 243 |
function bindRunRowEvents(root){
|
| 244 |
if(!root||root.dataset.boundRuns==='true')return;
|
|
@@ -257,8 +313,9 @@ function bindRunRowEvents(root){
|
|
| 257 |
});
|
| 258 |
}
|
| 259 |
function renderRunsFromCache(){state.runsCache=sortRunsNewestFirst(state.runsCache||[]);const runs=filteredRuns();const perPage=state.runsPerPage||5;state.runPage=Math.min(Math.max(0,state.runPage||0),Math.max(0,Math.ceil(runs.length/perPage)-1));const visible=runs.slice(state.runPage*perPage,state.runPage*perPage+perPage);const selectedId=state.centerTab==='validate'&&state.validationRunId?state.validationRunId:state.runId;const signature=JSON.stringify({ids:visible.map(r=>[r.run_id,r.status,r.updated_at,r.target_space,r.model_id,r.latency_seconds,r.observed_latency_seconds,r.recommended_zero_gpu_duration_seconds,r.recommended_zerogpu_duration_seconds]),filter:state.runStatusFilter,search:state.runSearch,page:state.runPage,selected:selectedId});for(const [id,compact] of [['runsTableHome',true],['runsTable',false]]){const root=$(id);if(!root)continue;bindRunRowEvents(root);if(root.dataset.signature!==signature){root.innerHTML=renderRunRows(visible,compact)||'<div class="empty">No runs match this filter.</div>';root.dataset.signature=signature}}renderRunStats();renderRunPagination(runs.length);renderRunAggregateStats();document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',Boolean(selectedId)&&row.dataset.run===selectedId))}
|
| 260 |
-
async function loadRuns(forceNetwork=false){const stale=Date.now()-(state.runsLastLoaded||0)>45000;const shouldFetch=forceNetwork||!state.runsCache.length||stale;if(!shouldFetch){renderRunsFromCache();return state.runsCache||[]}if(state.runsLoadPromise&&!forceNetwork)return state.runsLoadPromise;document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.add('is-refreshing'));state.runsLoadPromise=apiGet(`/api/runs?limit=100&bucket_name=${encodeURIComponent(state.bucketName)}`).then(data=>{state.runsCache=sortRunsNewestFirst(data.runs||[]);state.runsLastLoaded=Date.now();if(data.bucket_source)state.bucketSource=data.bucket_source;renderRunsFromCache();return state.runsCache}).catch(e=>{for(const id of ['runsTable','runsTableHome']){const root=$(id);if(root)root.textContent='Could not load runs: '+e.message}if(forceNetwork)console.warn(e);return state.runsCache||[]}).finally(()=>{state.runsLoadPromise=null;document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.remove('is-refreshing'))});return state.runsLoadPromise}
|
| 261 |
function cachedRunById(runId){
|
|
|
|
| 262 |
return (state.runsCache||[]).find(r=>r.run_id===runId)||null;
|
| 263 |
}
|
| 264 |
function optimisticDetailFromSummary(summary={}){
|
|
@@ -302,7 +359,7 @@ function optimisticDetailFromSummary(summary={}){
|
|
| 302 |
}
|
| 303 |
function upsertRunExplorerFromProgress(p={}){
|
| 304 |
const runId=p.run_id||p.summary?.run_id||state.runId;
|
| 305 |
-
if(!runId)return;
|
| 306 |
const summary={
|
| 307 |
...(cachedRunById(runId)||{}),
|
| 308 |
...(p.summary||{}),
|
|
@@ -369,6 +426,12 @@ async function selectRun(runId){
|
|
| 369 |
// Keep the user on the current tab while refreshing the selected run panel.
|
| 370 |
// Run selection must feel instant: render the best local data first, then hydrate.
|
| 371 |
if(!runId)return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===runId));
|
| 373 |
const cachedHeavy=cachedRunDetail(runId,true);
|
| 374 |
const cachedLight=cachedRunDetail(runId,false);
|
|
@@ -463,6 +526,10 @@ function ensureDeleteRunDialog(){
|
|
| 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>
|
|
@@ -478,7 +545,6 @@ function confirmRunDeletion({runId,kind='active',status='',detail={}}={}){
|
|
| 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');
|
|
@@ -486,15 +552,26 @@ function confirmRunDeletion({runId,kind='active',status='',detail={}}={}){
|
|
| 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
|
| 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 |
-
|
| 497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 498 |
confirmBtn.textContent=active?'Delete stored files':'Delete run';
|
| 499 |
const previousFocus=document.activeElement;
|
| 500 |
let done=false;
|
|
@@ -505,14 +582,15 @@ function confirmRunDeletion({runId,kind='active',status='',detail={}}={}){
|
|
| 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(
|
| 515 |
-
const onKey=e=>{if(e.key==='Escape')close(false);if(e.key==='Enter'&&modal.classList.contains('is-open'))
|
| 516 |
cancelEls.forEach(el=>el.addEventListener('click',onCancel));
|
| 517 |
confirmBtn.addEventListener('click',onConfirm);
|
| 518 |
document.addEventListener('keydown',onKey);
|
|
@@ -529,18 +607,19 @@ async function deleteRunById(runId,options={}){
|
|
| 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 |
-
|
|
|
|
|
|
|
| 540 |
clearDeletedRunState(runId,context.kind);
|
| 541 |
-
|
| 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'));
|
| 544 |
renderRunsFromCache();
|
| 545 |
await loadRuns(true);
|
| 546 |
}catch(e){
|
|
@@ -583,7 +662,9 @@ function mergeStableRunDocuments(runId,docs=[]){
|
|
| 583 |
return docs.map(doc=>byId.get(doc.id)||doc);
|
| 584 |
}
|
| 585 |
function deriveRunDocuments(detail={}){
|
| 586 |
-
const
|
|
|
|
|
|
|
| 587 |
const files=Array.isArray(detail.files)?detail.files:[];
|
| 588 |
const filePaths=new Set(files.map(f=>String(f.path||'')));
|
| 589 |
const hasPrefix=prefix=>files.some(f=>String(f.path||'').startsWith(prefix.replace(/\/$/,'')+'/'));
|
|
@@ -610,11 +691,11 @@ function deriveRunDocuments(detail={}){
|
|
| 610 |
byId.get('pi_redacted_trace').present=redactedTrace.present;
|
| 611 |
byId.get('pi_redacted_trace').url=redactedTrace.url;
|
| 612 |
byId.get('report').present=Boolean((byId.get('report').present&&byId.get('report').url)||reportText.length||filePaths.has('report.md'));
|
| 613 |
-
byId.get('smoke').present=Boolean((byId.get('smoke').present&&byId.get('smoke').url)||Object.keys(detail.generation_smoke||{}).length||filePaths.has('tests/generation_smoke.json'));
|
| 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'));
|
|
@@ -723,8 +804,10 @@ function recoverySourceDocuments(detail={}){
|
|
| 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';
|
|
@@ -747,25 +830,28 @@ function renderAgentRecovery(detail={}){
|
|
| 747 |
const blockage=detail.blockage||{};
|
| 748 |
const summary=detail.summary||{};
|
| 749 |
const status=summary.status||detail.state?.status||detail.status||'';
|
|
|
|
| 750 |
const action=decision.action||blockage.recommended_action||blockage.action||'';
|
| 751 |
-
const normalizedAction=recoveryDecisionActionLabel(action||'diagnosis');
|
| 752 |
-
const tone=recoveryStatusTone(action,status);
|
| 753 |
if(badge){badge.textContent=normalizedAction;badge.className='badge '+(tone==='error'?'error':tone==='warn'?'warn':tone==='running'?'running':'neutral')}
|
| 754 |
const diagnosisEvent=latestRecoveryEvent(detail,['failure_diagnosis','pi_diagnosis','repair_decision']);
|
| 755 |
const rebuildEvent=latestRecoveryEvent(detail,['wait_for_logs','factory_rebuild']);
|
| 756 |
const patchEvent=latestRecoveryEvent(detail,['repair_brief','repair_plan','repair_patch','repair_upload','repair_validation']);
|
| 757 |
-
const
|
|
|
|
| 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>
|
| 767 |
<span class="${rebuildEvent?'done':patchEvent?'done':decision.action?'running':'pending'}"><b>3</b><em>${escapeHtml(action&&String(action).includes('rebuild')?'Rebuild':action&&String(action).includes('patch')?'Patch':'Act')}</em></span>
|
| 768 |
-
<span class="${latestRecoveryEvent(detail,['repair_validation','generation_smoke','inference_gate'
|
| 769 |
</div>`);
|
| 770 |
const meta=[];
|
| 771 |
if(decision.failure_category||blockage.failure_category)meta.push(['Failure',decision.failure_category||blockage.failure_category]);
|
|
@@ -800,9 +886,9 @@ function renderActiveRunActions(summary={},detail={}){
|
|
| 800 |
}
|
| 801 |
const del=$('deleteActiveRun');
|
| 802 |
if(del){
|
| 803 |
-
const canDelete=Boolean(runId&&
|
| 804 |
del.disabled=!canDelete;
|
| 805 |
-
del.title=canDelete?'Delete this
|
| 806 |
del.onclick=canDelete?()=>deleteRunById(runId,{kind:'active',detail}):null;
|
| 807 |
}
|
| 808 |
}
|
|
@@ -852,3 +938,5 @@ function renderRunDetail(detail){
|
|
| 852 |
|
| 853 |
// Legacy wording: Validation latency / Aggregated issues
|
| 854 |
|
|
|
|
|
|
|
|
|
| 114 |
const title=runPrimaryTitle(r);
|
| 115 |
const secondary=runSecondaryLine(r)||r.run_id||'No metadata yet';
|
| 116 |
const time=runRelativeTime(r);
|
| 117 |
+
const canDelete=runCanBeDeleted(status);
|
| 118 |
return `<article class="run-row run-list-item ${validation?'validation-run':'build-run'}" role="button" tabindex="0" data-run="${escapeHtml(r.run_id)}" title="${validation?'Validation run':'Build run'}">
|
| 119 |
<span class="run-list-dot ${tone}" aria-hidden="true"></span>
|
| 120 |
<div class="run-list-copy">
|
|
|
|
| 239 |
<strong>${issueItems.length?issueItems.map(([label,count])=>`${count} ${label}`).join(' · '):'Nothing to review in indexed runs'}</strong>
|
| 240 |
</div>`;
|
| 241 |
}
|
| 242 |
+
|
| 243 |
+
function rememberDeletedRun(runId){
|
| 244 |
+
if(!runId)return;
|
| 245 |
+
state.deletedRuns=state.deletedRuns||{};
|
| 246 |
+
state.deletedRuns[runId]=Date.now();
|
| 247 |
+
}
|
| 248 |
+
function isRunLocallyDeleted(runId){
|
| 249 |
+
if(!runId||!state.deletedRuns)return false;
|
| 250 |
+
const ts=state.deletedRuns[runId];
|
| 251 |
+
if(!ts)return false;
|
| 252 |
+
const ttl=10*60*1000;
|
| 253 |
+
if(Date.now()-ts>ttl){delete state.deletedRuns[runId];return false;}
|
| 254 |
+
return true;
|
| 255 |
+
}
|
| 256 |
+
function forgetRunEverywhere(runId){
|
| 257 |
+
if(!runId)return;
|
| 258 |
+
if(state.runDetailCache)delete state.runDetailCache[runId];
|
| 259 |
+
if(state.runDetailRequests){
|
| 260 |
+
delete state.runDetailRequests[`${runId}:light`];
|
| 261 |
+
delete state.runDetailRequests[`${runId}:heavy`];
|
| 262 |
+
}
|
| 263 |
+
if(state.documentCache)delete state.documentCache[runId];
|
| 264 |
+
if(state.latencyCache)delete state.latencyCache[runId];
|
| 265 |
+
state.runsCache=(state.runsCache||[]).filter(r=>r.run_id!==runId);
|
| 266 |
+
document.querySelectorAll(`.run-row[data-run="${CSS.escape(runId)}"]`).forEach(row=>row.remove());
|
| 267 |
+
}
|
| 268 |
+
function activeRunIds(){
|
| 269 |
+
return new Set((state.runsCache||[]).map(r=>r.run_id).filter(Boolean));
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
function associatedSpaceFromDetail(detail={},summaryOverride={}){
|
| 273 |
+
const summary={...(detail.summary||{}),...(summaryOverride||{})};
|
| 274 |
+
const candidates=[summary.target_space,summary.target_space_id,detail.target_space,detail.target_space_id,detail.state?.target_space,detail.launch?.target_space,detail.links?.target_space];
|
| 275 |
+
for(const value of candidates){
|
| 276 |
+
const text=String(value||'').trim().replace('https://huggingface.co/spaces/','').replace(/^\/+|\/+$/g,'');
|
| 277 |
+
if(text&&text.includes('/'))return text;
|
| 278 |
+
}
|
| 279 |
+
const urls=[summary.target_space_url,detail.target_space_url,detail.state?.target_space_url,detail.launch?.target_space_url,detail.links?.target_space_url];
|
| 280 |
+
for(const url of urls){
|
| 281 |
+
const text=String(url||'');
|
| 282 |
+
const marker='huggingface.co/spaces/';
|
| 283 |
+
if(text.includes(marker))return text.split(marker,1)[1].split('?',1)[0].split('#',1)[0].replace(/^\/+|\/+$/g,'');
|
| 284 |
+
}
|
| 285 |
+
return '';
|
| 286 |
+
}
|
| 287 |
+
function runCanOfferSpaceDeletion(kind,detail={},summary={}){
|
| 288 |
+
const normalizedKind=String(kind||summary.kind||detail.kind||detail.state?.kind||detail.launch?.kind||'').toLowerCase();
|
| 289 |
+
if(normalizedKind.includes('validation')||normalizedKind.includes('space_test'))return false;
|
| 290 |
+
return Boolean(associatedSpaceFromDetail(detail,summary));
|
| 291 |
+
}
|
| 292 |
+
function runCanBeDeleted(status){
|
| 293 |
+
const s=String(status||'').toLowerCase();
|
| 294 |
+
if(!s||s==='unknown')return true;
|
| 295 |
+
return runIsFinished(s);
|
| 296 |
+
}
|
| 297 |
+
|
| 298 |
// v105 delegated row binding preserves v98 invariant: renderRunAggregateStats();document.querySelectorAll('.run-row[data-run]')
|
| 299 |
function bindRunRowEvents(root){
|
| 300 |
if(!root||root.dataset.boundRuns==='true')return;
|
|
|
|
| 313 |
});
|
| 314 |
}
|
| 315 |
function renderRunsFromCache(){state.runsCache=sortRunsNewestFirst(state.runsCache||[]);const runs=filteredRuns();const perPage=state.runsPerPage||5;state.runPage=Math.min(Math.max(0,state.runPage||0),Math.max(0,Math.ceil(runs.length/perPage)-1));const visible=runs.slice(state.runPage*perPage,state.runPage*perPage+perPage);const selectedId=state.centerTab==='validate'&&state.validationRunId?state.validationRunId:state.runId;const signature=JSON.stringify({ids:visible.map(r=>[r.run_id,r.status,r.updated_at,r.target_space,r.model_id,r.latency_seconds,r.observed_latency_seconds,r.recommended_zero_gpu_duration_seconds,r.recommended_zerogpu_duration_seconds]),filter:state.runStatusFilter,search:state.runSearch,page:state.runPage,selected:selectedId});for(const [id,compact] of [['runsTableHome',true],['runsTable',false]]){const root=$(id);if(!root)continue;bindRunRowEvents(root);if(root.dataset.signature!==signature){root.innerHTML=renderRunRows(visible,compact)||'<div class="empty">No runs match this filter.</div>';root.dataset.signature=signature}}renderRunStats();renderRunPagination(runs.length);renderRunAggregateStats();document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',Boolean(selectedId)&&row.dataset.run===selectedId))}
|
| 316 |
+
async function loadRuns(forceNetwork=false){const stale=Date.now()-(state.runsLastLoaded||0)>45000;const shouldFetch=forceNetwork||!state.runsCache.length||stale;if(!shouldFetch){renderRunsFromCache();return state.runsCache||[]}if(state.runsLoadPromise&&!forceNetwork)return state.runsLoadPromise;document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.add('is-refreshing'));state.runsLoadPromise=apiGet(`/api/runs?limit=100&bucket_name=${encodeURIComponent(state.bucketName)}`).then(data=>{state.runsCache=sortRunsNewestFirst((data.runs||[]).filter(r=>!isRunLocallyDeleted(r.run_id)));state.runsLastLoaded=Date.now();if(data.bucket_source)state.bucketSource=data.bucket_source;renderRunsFromCache();return state.runsCache}).catch(e=>{for(const id of ['runsTable','runsTableHome']){const root=$(id);if(root)root.textContent='Could not load runs: '+e.message}if(forceNetwork)console.warn(e);return state.runsCache||[]}).finally(()=>{state.runsLoadPromise=null;document.querySelectorAll('.runs-panel,.full-runs-panel').forEach(x=>x.classList.remove('is-refreshing'))});return state.runsLoadPromise}
|
| 317 |
function cachedRunById(runId){
|
| 318 |
+
if(isRunLocallyDeleted(runId))return null;
|
| 319 |
return (state.runsCache||[]).find(r=>r.run_id===runId)||null;
|
| 320 |
}
|
| 321 |
function optimisticDetailFromSummary(summary={}){
|
|
|
|
| 359 |
}
|
| 360 |
function upsertRunExplorerFromProgress(p={}){
|
| 361 |
const runId=p.run_id||p.summary?.run_id||state.runId;
|
| 362 |
+
if(!runId||isRunLocallyDeleted(runId))return;
|
| 363 |
const summary={
|
| 364 |
...(cachedRunById(runId)||{}),
|
| 365 |
...(p.summary||{}),
|
|
|
|
| 426 |
// Keep the user on the current tab while refreshing the selected run panel.
|
| 427 |
// Run selection must feel instant: render the best local data first, then hydrate.
|
| 428 |
if(!runId)return;
|
| 429 |
+
if(isRunLocallyDeleted(runId)){
|
| 430 |
+
forgetRunEverywhere(runId);
|
| 431 |
+
renderRunsFromCache();
|
| 432 |
+
showMessage('This run was deleted and is no longer available.','warning');
|
| 433 |
+
return;
|
| 434 |
+
}
|
| 435 |
document.querySelectorAll('.run-row').forEach(row=>row.classList.toggle('selected',row.dataset.run===runId));
|
| 436 |
const cachedHeavy=cachedRunDetail(runId,true);
|
| 437 |
const cachedLight=cachedRunDetail(runId,false);
|
|
|
|
| 526 |
<p id="deleteRunBody">This removes the run folder from your Hugging Face Storage Bucket.</p>
|
| 527 |
</div>
|
| 528 |
<div class="delete-run-meta" id="deleteRunMeta"></div>
|
| 529 |
+
<label class="delete-space-option" id="deleteSpaceOption" hidden>
|
| 530 |
+
<input type="checkbox" id="deleteAssociatedSpace" />
|
| 531 |
+
<span><strong>Also delete associated Space</strong><em id="deleteAssociatedSpaceLabel">—</em></span>
|
| 532 |
+
</label>
|
| 533 |
<div class="delete-run-warning" id="deleteRunWarning" hidden></div>
|
| 534 |
<div class="delete-run-actions">
|
| 535 |
<button class="secondary" type="button" data-delete-cancel>Keep run</button>
|
|
|
|
| 545 |
state.deleteModalRunId=runId;
|
| 546 |
state.deleteModalPromise=new Promise(resolve=>{
|
| 547 |
const modal=ensureDeleteRunDialog();
|
|
|
|
| 548 |
const confirmBtn=modal.querySelector('[data-delete-confirm]');
|
| 549 |
const cancelEls=modal.querySelectorAll('[data-delete-cancel]');
|
| 550 |
const title=$('deleteRunTitle');
|
|
|
|
| 552 |
const kicker=$('deleteRunKicker');
|
| 553 |
const meta=$('deleteRunMeta');
|
| 554 |
const warning=$('deleteRunWarning');
|
| 555 |
+
const spaceOption=$('deleteSpaceOption');
|
| 556 |
+
const spaceCheckbox=$('deleteAssociatedSpace');
|
| 557 |
+
const spaceLabel=$('deleteAssociatedSpaceLabel');
|
| 558 |
const normalized=String(status||detail.status||detail.summary?.status||detail.state?.status||'unknown').toLowerCase();
|
| 559 |
const active=runMatchesStatus({status:normalized},'running');
|
| 560 |
const label=kind==='validation'?'validation run':'build run';
|
| 561 |
+
const associatedSpace=associatedSpaceFromDetail(detail,detail.summary||{});
|
| 562 |
+
const canDeleteSpace=runCanOfferSpaceDeletion(kind,detail,detail.summary||{});
|
| 563 |
kicker.textContent=kind==='validation'?'Space Test deletion':'Build run deletion';
|
| 564 |
title.textContent=`Delete this ${label}?`;
|
| 565 |
+
body.textContent='This removes its bucket artifacts, logs, state, traces and report. The generated or tested Space is kept unless you explicitly opt in below.';
|
| 566 |
+
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>${associatedSpace?`<span><strong>Space</strong><code>${escapeHtml(associatedSpace)}</code></span>`:''}`;
|
| 567 |
+
if(spaceOption&&spaceCheckbox&&spaceLabel){
|
| 568 |
+
spaceCheckbox.checked=false;
|
| 569 |
+
spaceOption.hidden=!canDeleteSpace;
|
| 570 |
+
spaceLabel.textContent=associatedSpace||'No associated Space detected';
|
| 571 |
+
spaceCheckbox.onchange=()=>{confirmBtn.textContent=spaceCheckbox.checked?'Delete run and Space':(active?'Delete stored files':'Delete run');warning.hidden=false;warning.textContent=spaceCheckbox.checked?`This will permanently delete the Hugging Face Space repository ${associatedSpace}. This cannot be undone.`:(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.');};
|
| 572 |
+
}
|
| 573 |
+
warning.hidden=!active&&!canDeleteSpace;
|
| 574 |
+
warning.textContent=active?'This run still looks active. Deleting only removes stored run files; it does not cancel a running Job.':(canDeleteSpace?'Optional Space deletion is destructive and cannot be undone.':'Deletion is permanent for this run folder.');
|
| 575 |
confirmBtn.textContent=active?'Delete stored files':'Delete run';
|
| 576 |
const previousFocus=document.activeElement;
|
| 577 |
let done=false;
|
|
|
|
| 582 |
document.removeEventListener('keydown',onKey);
|
| 583 |
cancelEls.forEach(el=>el.removeEventListener('click',onCancel));
|
| 584 |
confirmBtn.removeEventListener('click',onConfirm);
|
| 585 |
+
if(spaceCheckbox)spaceCheckbox.onchange=null;
|
| 586 |
try{previousFocus&&previousFocus.focus&&previousFocus.focus()}catch(_){}
|
| 587 |
state.deleteModalPromise=null;
|
| 588 |
state.deleteModalRunId=null;
|
| 589 |
resolve(value);
|
| 590 |
};
|
| 591 |
const onCancel=()=>close(false);
|
| 592 |
+
const onConfirm=()=>close({deleteSpace:Boolean(spaceCheckbox&&spaceCheckbox.checked&&canDeleteSpace),associatedSpace});
|
| 593 |
+
const onKey=e=>{if(e.key==='Escape')close(false);if(e.key==='Enter'&&modal.classList.contains('is-open'))onConfirm()};
|
| 594 |
cancelEls.forEach(el=>el.addEventListener('click',onCancel));
|
| 595 |
confirmBtn.addEventListener('click',onConfirm);
|
| 596 |
document.addEventListener('keydown',onKey);
|
|
|
|
| 607 |
const status=String(context.summary.status||context.detail.status||context.detail.state?.status||'').toLowerCase();
|
| 608 |
const confirmed=await confirmRunDeletion({runId,kind:context.kind,status,detail:context.detail});
|
| 609 |
if(!confirmed)return;
|
| 610 |
+
const deleteSpace=typeof confirmed==='object'&&Boolean(confirmed.deleteSpace);
|
| 611 |
if(state.deletingRuns&&state.deletingRuns[runId])return;
|
| 612 |
state.deletingRuns=state.deletingRuns||{};
|
| 613 |
state.deletingRuns[runId]=true;
|
| 614 |
const buttonId=options.buttonId===null?null:(options.buttonId||(context.kind==='validation'?'deleteValidationRun':'deleteActiveRun'));
|
| 615 |
setButtonBusy(buttonId,true,'Deleting…');
|
| 616 |
try{
|
| 617 |
+
const result=await apiDelete(`/api/runs/${encodeURIComponent(runId)}?bucket_name=${encodeURIComponent(state.bucketName)}`,deleteSpace?{delete_space:true}:{});
|
| 618 |
+
const suffix=deleteSpace?(result.space_deleted?' and associated Space':(result.space_delete_error?' but Space deletion failed':' and Space was already missing')):'';
|
| 619 |
+
showMessage(`Deleted run ${runId}${suffix}`,'success');
|
| 620 |
+
rememberDeletedRun(runId);
|
| 621 |
clearDeletedRunState(runId,context.kind);
|
| 622 |
+
forgetRunEverywhere(runId);
|
|
|
|
|
|
|
| 623 |
renderRunsFromCache();
|
| 624 |
await loadRuns(true);
|
| 625 |
}catch(e){
|
|
|
|
| 662 |
return docs.map(doc=>byId.get(doc.id)||doc);
|
| 663 |
}
|
| 664 |
function deriveRunDocuments(detail={}){
|
| 665 |
+
const baseDocs=defaultRunDocuments(detail);
|
| 666 |
+
const backendDocs=Array.isArray(detail.run_documents)?detail.run_documents:[];
|
| 667 |
+
const docs=backendDocs.length?baseDocs.map(base=>({...(base||{}),...(backendDocs.find(doc=>doc&&doc.id===base.id)||{})})).concat(backendDocs.filter(doc=>doc&&!baseDocs.some(base=>base.id===doc.id))):baseDocs;
|
| 668 |
const files=Array.isArray(detail.files)?detail.files:[];
|
| 669 |
const filePaths=new Set(files.map(f=>String(f.path||'')));
|
| 670 |
const hasPrefix=prefix=>files.some(f=>String(f.path||'').startsWith(prefix.replace(/\/$/,'')+'/'));
|
|
|
|
| 691 |
byId.get('pi_redacted_trace').present=redactedTrace.present;
|
| 692 |
byId.get('pi_redacted_trace').url=redactedTrace.url;
|
| 693 |
byId.get('report').present=Boolean((byId.get('report').present&&byId.get('report').url)||reportText.length||filePaths.has('report.md'));
|
| 694 |
+
byId.get('smoke').present=Boolean((byId.get('smoke').present&&byId.get('smoke').url)||Object.keys(detail.generation_smoke||{}).length||(filePaths.has('tests/generation_smoke.json')||filePaths.has('generation_smoke.json')));
|
| 695 |
if(shouldShowBlockers&&!byId.has('blockers')){
|
| 696 |
const runId=summary.run_id||detail.run_id||state.runId;
|
| 697 |
const bucket=summary.bucket_source||detail.bucket_source||state.bucketSource;
|
| 698 |
+
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')||filePaths.has('TECHNICAL_BLOCKERS.json')),url:runId&&bucket?`https://huggingface.co/buckets/${bucket}/tree/runs/${runId}/generated/TECHNICAL_BLOCKERS.json`:''});
|
| 699 |
}
|
| 700 |
if(byId.has('repair_decision')){
|
| 701 |
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'));
|
|
|
|
| 804 |
function recoveryPhaseLabel(detail={},decision={},blockage={}){
|
| 805 |
const summary=detail.summary||{};
|
| 806 |
const status=String(summary.status||detail.state?.status||detail.status||'').toLowerCase();
|
| 807 |
+
const terminalFailure=latestRecoveryEvent(detail,['failure']);
|
| 808 |
if(nonEmptyObject(blockage)||status.includes('blocker')||status.includes('manual'))return'Final blocker';
|
| 809 |
if(decision.action&&TERMINAL_STATUSES.has(status))return'Final decision';
|
| 810 |
+
if(terminalFailure&&status.includes('fail')&&!decision.action)return'Recovery interrupted';
|
| 811 |
if(latestRecoveryEvent(detail,['repair_validation','generation_smoke','inference_gate']))return'Revalidating';
|
| 812 |
if(latestRecoveryEvent(detail,['repair_brief','repair_plan','repair_patch','repair_upload']))return'Repairing';
|
| 813 |
if(decision.action)return'Decision made';
|
|
|
|
| 830 |
const blockage=detail.blockage||{};
|
| 831 |
const summary=detail.summary||{};
|
| 832 |
const status=summary.status||detail.state?.status||detail.status||'';
|
| 833 |
+
const failureEvent=latestRecoveryEvent(detail,['failure']);
|
| 834 |
const action=decision.action||blockage.recommended_action||blockage.action||'';
|
| 835 |
+
const normalizedAction=recoveryDecisionActionLabel(action||(failureEvent?'interrupted':'diagnosis'));
|
| 836 |
+
const tone=failureEvent&&!decision.action?'error':recoveryStatusTone(action,status);
|
| 837 |
if(badge){badge.textContent=normalizedAction;badge.className='badge '+(tone==='error'?'error':tone==='warn'?'warn':tone==='running'?'running':'neutral')}
|
| 838 |
const diagnosisEvent=latestRecoveryEvent(detail,['failure_diagnosis','pi_diagnosis','repair_decision']);
|
| 839 |
const rebuildEvent=latestRecoveryEvent(detail,['wait_for_logs','factory_rebuild']);
|
| 840 |
const patchEvent=latestRecoveryEvent(detail,['repair_brief','repair_plan','repair_patch','repair_upload','repair_validation']);
|
| 841 |
+
const failureReason=failureEvent?(failureEvent.reason||failureEvent.error||failureEvent.message||'The worker failed before Pi produced a structured recovery decision.') : '';
|
| 842 |
+
const reason=decision.reason||blockage.reason||blockage.message||failureReason||diagnosisEvent?.message||'Pi diagnosis is coordinating the next recovery action from collected logs, status and validation evidence.';
|
| 843 |
const evidence=Array.isArray(decision.evidence)?decision.evidence:[];
|
| 844 |
const next=decision.next_step||blockage.suggested_next_step||blockage.next_step||'';
|
| 845 |
const phase=recoveryPhaseLabel(detail,decision,blockage);
|
| 846 |
const sourceDocs=recoverySourceDocuments(detail);
|
| 847 |
const rows=[];
|
| 848 |
rows.push(`<div class="recovery-summary"><strong>${escapeHtml(phase)} · ${escapeHtml(normalizedAction)}</strong><p>${escapeHtml(shortText(reason,220))}</p></div>`);
|
| 849 |
+
const interrupted=Boolean(failureEvent&&!decision.action);
|
| 850 |
rows.push(`<div class="recovery-ladder" aria-label="Agent recovery phases">
|
| 851 |
+
<span class="${interrupted?'failed':(diagnosisEvent||decision.action?'done':'pending')}"><b>1</b><em>Diagnose</em></span>
|
| 852 |
+
<span class="${decision.action?'done':interrupted?'pending':'pending'}"><b>2</b><em>Decide</em></span>
|
| 853 |
<span class="${rebuildEvent?'done':patchEvent?'done':decision.action?'running':'pending'}"><b>3</b><em>${escapeHtml(action&&String(action).includes('rebuild')?'Rebuild':action&&String(action).includes('patch')?'Patch':'Act')}</em></span>
|
| 854 |
+
<span class="${latestRecoveryEvent(detail,['repair_validation','generation_smoke','inference_gate'])?'done':failureEvent?'pending':'pending'}"><b>4</b><em>Revalidate</em></span>
|
| 855 |
</div>`);
|
| 856 |
const meta=[];
|
| 857 |
if(decision.failure_category||blockage.failure_category)meta.push(['Failure',decision.failure_category||blockage.failure_category]);
|
|
|
|
| 886 |
}
|
| 887 |
const del=$('deleteActiveRun');
|
| 888 |
if(del){
|
| 889 |
+
const canDelete=Boolean(runId&&runCanBeDeleted(status));
|
| 890 |
del.disabled=!canDelete;
|
| 891 |
+
del.title=canDelete?'Delete this run folder':'You can delete a run after the Job is finished or if only stale metadata remains.';
|
| 892 |
del.onclick=canDelete?()=>deleteRunById(runId,{kind:'active',detail}):null;
|
| 893 |
}
|
| 894 |
}
|
|
|
|
| 938 |
|
| 939 |
// Legacy wording: Validation latency / Aggregated issues
|
| 940 |
|
| 941 |
+
|
| 942 |
+
// Legacy invariant: state.runsCache=sortRunsNewestFirst(data.runs||[])
|