Download mathly2/static/developer.js from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 9.17 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/static/developer.js
- Command line
-
hf download hf://spaces/wzender/mathly/mathly2/static/developer.js
-
curl -L -o developer.js https://huggingface.co/spaces/wzender/mathly/resolve/main/mathly2/static/developer.js
9.17 kB
| // The developer's home: a status card (is each part working, which version runs, when it was last updated), an activity chart | |
| // (how many people used the app on each of the last 7 or 30 days) and every permission (the other roles' chips open their screens). | |
| // The other tools (prompter, lab, AI cost) come later. | |
| import { api } from "./api.js"; | |
| import { esc } from "./math.js"; | |
| const STATES = { ok: { icon: "โ", word: "ืชืงืื" }, warn: { icon: "!", word: "ืฉืืื ืื" }, error: { icon: "โ", word: "ืชืงืื" }, off: { icon: "โ", word: "ืืืื" } }; | |
| const OVERALL = { ok: "ืืืขืจืืช ืชืงืื ื", warn: "ืืืขืจืืช ืขืืืืช, ืขื ืืขืจืืช", error: "ืืฉ ืชืงืื ืืืขืจืืช" }; | |
| const when = iso => iso ? new Intl.DateTimeFormat("he-IL", { day: "numeric", month: "short", year: "numeric", hour: "2-digit", minute: "2-digit", timeZone: "Asia/Jerusalem" }).format(new Date(iso)) : "ืื ืืืืข"; | |
| function since(seconds) { | |
| const d = Math.floor(seconds / 86400), h = Math.floor(seconds % 86400 / 3600), m = Math.floor(seconds % 3600 / 60); | |
| return d ? `ืืคื ื ${d} ืืืื ืึพ${h} ืฉืขืืช` : h ? `ืืคื ื ${h} ืฉืขืืช ืึพ${m} ืืงืืช` : m ? `ืืคื ื ${m} ืืงืืช` : "ืืคื ื ืคืืืช ืืืงื"; | |
| } | |
| function card(s) { | |
| const v = s.version; | |
| return `<div class="st-head st-${esc(s.overall)}" id="st-overall" data-state="${esc(s.overall)}"> | |
| <span class="st-dot" aria-hidden="true">${STATES[s.overall].icon}</span><b>${OVERALL[s.overall]}</b></div> | |
| <dl class="st-facts"> | |
| <div><dt>ืืจืกื</dt><dd id="st-version" dir="ltr">${v.short ? `<span title="${esc(v.commit)}">${esc(v.short)}</span>` : "ืื ืืืืขื"}</dd> | |
| ${v.title ? `<dd class="st-title" id="st-title-change" dir="auto">${esc(v.title)}</dd>` : ""}</div> | |
| <div><dt>ืขืืืื ืืืจืื</dt><dd id="st-updated">${esc(when(v.date))}</dd></div> | |
| <div><dt>ืืฉืจืช ืขืื</dt><dd id="st-started">${esc(when(s.started_at))} <span class="muted">(${since(s.uptime_seconds)})</span></dd></div> | |
| </dl> | |
| <ul class="plain st-checks" id="st-checks">${s.checks.map(c => `<li data-key="${esc(c.key)}" data-state="${esc(c.state)}"> | |
| <span><span class="st-dot st-${esc(c.state)}" aria-hidden="true">${STATES[c.state].icon}</span> <b>${esc(c.name)}</b> | |
| <span class="muted">${esc(c.detail)}</span></span><span class="st-word st-${esc(c.state)}">${STATES[c.state].word}</span></li>`).join("")}</ul> | |
| <p class="muted st-foot">ื ืืืง ืึพ${esc(when(s.now))} ยท Python ${esc(s.python)}</p>`; | |
| } | |
| let asked = 0; // only the latest check is drawn, if two overlap | |
| async function load() { | |
| const mine = ++asked; | |
| let html; | |
| try { | |
| html = card(await api("/api/dev/status")); | |
| } catch (error) { | |
| html = `<p class="error" role="alert">ืื ืืฆืืื ื ืืงืจืื ืืช ืืฆื ืืืขืจืืช: ${esc(error.message || "")}</p>`; | |
| } | |
| const box = document.getElementById("st-body"); | |
| if (box && mine === asked) box.innerHTML = html; | |
| } | |
| // ---- activity: one column per Israel day, oldest on the left; the height is how many different people were active ---- | |
| const DAY = new Intl.DateTimeFormat("he-IL", { weekday: "short", timeZone: "UTC" }); | |
| const DATE = new Intl.DateTimeFormat("he-IL", { day: "numeric", month: "numeric", timeZone: "UTC" }); | |
| const LONG = new Intl.DateTimeFormat("he-IL", { weekday: "long", day: "numeric", month: "long", timeZone: "UTC" }); | |
| const at = iso => new Date(`${iso}T00:00:00Z`); // a calendar day, drawn as that day whatever the device's zone | |
| const people = n => n === 1 ? "ืืฉืชืืฉ ืืื" : `${n.toLocaleString("he-IL")} ืืฉืชืืฉืื`; | |
| /** A clean top for the axis (1, 2 or 5 times a power of ten) and its step, about four gridlines. */ | |
| function scale(max) { | |
| if (max <= 4) return { top: Math.max(max, 1), step: 1 }; | |
| const rough = max / 4, power = 10 ** Math.floor(Math.log10(rough)); | |
| const step = [1, 2, 5, 10].map(m => m * power).find(s => s >= rough); | |
| return { top: Math.ceil(max / step) * step, step }; | |
| } | |
| function chart(a) { | |
| const days = a.days, n = days.length, { top, step } = scale(Math.max(...days.map(d => d.users))); | |
| const peak = days.reduce((best, d, i) => d.users > days[best].users ? i : best, 0); | |
| const every = n <= 7 ? 1 : 5; // which days get a label under the axis | |
| const ticks = []; for (let v = 0; v <= top; v += step) ticks.push(v); | |
| return `<div class="ac-plot" dir="ltr" style="--n:${n}"> | |
| <div class="ac-grid" aria-hidden="true">${ticks.map(v => `<div class="ac-tick" style="bottom:${v / top * 100}%"><span>${v}</span></div>`).join("")}</div> | |
| <ol class="ac-cols" aria-label="ืืฉืชืืฉืื ืคืขืืืื ืืื ืืื">${days.map((d, i) => { | |
| const label = `${LONG.format(at(d.day))}: ${people(d.users)}, ${d.answers} ืชืฉืืืืช ืืชืจืืื, ${d.attempts} ืืื ืื ืืืืื ืื`; | |
| const shown = d.users > 0 && (i === n - 1 || i === peak); // label the latest day and the busiest, not every column | |
| return `<li class="ac-col" tabindex="0" data-day="${d.day}" data-users="${d.users}" aria-label="${esc(label)}"> | |
| <span class="ac-bar" style="height:${d.users / top * 100}%">${shown ? `<b class="ac-val">${d.users}</b>` : ""}</span> | |
| <span class="ac-tip" role="tooltip"><b>${esc(LONG.format(at(d.day)))}</b><br>${people(d.users)}<br>${d.answers} ืชืฉืืืืช ืืชืจืืื<br>${d.attempts} ืืื ืื ืืืืื ืื</span> | |
| <span class="ac-x" aria-hidden="true">${i % every === (n - 1) % every ? (n <= 7 ? `${esc(DAY.format(at(d.day)))}<br>${esc(DATE.format(at(d.day)))}` : esc(DATE.format(at(d.day)))) : ""}</span> | |
| </li>`; | |
| }).join("")}</ol></div>`; | |
| } | |
| function activityCard(a) { | |
| const today = a.days[a.days.length - 1], n = a.days.length; | |
| return `<dl class="st-facts ac-tiles"> | |
| <div><dt>ืืืื</dt><dd id="ac-today">${people(today.users)}</dd></div> | |
| <div><dt>ืึพ${n} ืืืืื ืืืืจืื ืื</dt><dd id="ac-range">${people(a.users)}</dd></div> | |
| <div><dt>ืชืฉืืืืช ืืชืจืืื</dt><dd>${a.answers.toLocaleString("he-IL")}</dd></div> | |
| <div><dt>ืืื ืื ืืืืื ืื ืฉืืชืืืื</dt><dd>${a.attempts.toLocaleString("he-IL")}</dd></div> | |
| </dl>${chart(a)} | |
| <details class="ac-table"><summary>ืื ืชืื ืื ืืืืื</summary><table><thead><tr><th>ืืื</th><th>ืืฉืชืืฉืื</th><th>ืชืฉืืืืช</th><th>ืืื ืื ืืืืื ืื</th></tr></thead> | |
| <tbody>${a.days.slice().reverse().map(d => `<tr><td>${esc(LONG.format(at(d.day)))}</td><td>${d.users}</td><td>${d.answers}</td><td>${d.attempts}</td></tr>`).join("")}</tbody></table></details> | |
| <p class="muted st-foot">ืืฉืชืืฉ ืคืขืื: ืขื ื ืขื ืฉืืื ืืชืจืืื ืื ืืชืืื ืืืื ืื ืืืื ืืืืชื ืืื (ืืคื ืฉืขืื ืืฉืจืื).</p>`; | |
| } | |
| let range = 7, askedActivity = 0; | |
| async function loadActivity() { | |
| const mine = ++askedActivity; | |
| let html; | |
| try { | |
| html = activityCard(await api(`/api/dev/activity?days=${range}`)); | |
| } catch (error) { | |
| html = `<p class="error" role="alert">ืื ืืฆืืื ื ืืงืจืื ืืช ืืคืขืืืืช: ${esc(error.message || "")}</p>`; | |
| } | |
| const box = document.getElementById("ac-body"); | |
| if (box && mine === askedActivity) box.innerHTML = html; | |
| } | |
| export async function show(me, view) { | |
| view(`<section class="card" id="status" aria-labelledby="st-title"> | |
| <div class="st-top"><h2 id="st-title">ืืฆื ืืืขืจืืช</h2><button type="button" id="st-refresh">ืืืืงื ืืืืฉ</button></div> | |
| <div id="st-body" aria-live="polite"><p class="muted">ืืืืงืืโฆ</p></div></section> | |
| <section class="card" id="activity" aria-labelledby="ac-title"> | |
| <div class="st-top"><h2 id="ac-title">ืคืขืืืืช</h2> | |
| <div class="ac-range" role="group" aria-label="ืืืื">${[7, 30].map(d => | |
| `<button type="button" data-range="${d}" aria-pressed="${d === range}">${d} ืืืื</button>`).join("")}</div></div> | |
| <div id="ac-body" aria-live="polite"><p class="muted">ืืืขื ืืโฆ</p></div></section> | |
| <section class="card" id="prompter" aria-label="ืคืจืืืคืืจ"></section> | |
| <section class="card" id="developer"><h2>ืืคืชื</h2> | |
| <p>ืืฉ ืื ืืช ืื ืืืจืฉืืืช: ืืคืฉืจ ืืคืชืื ืืช ืืืกืืื ืฉื ืื ืืชืคืงืืืื ืืืืคืชืืจืื ืืืขืื.</p> | |
| <p class="muted">ืขืื ืืื ืืคืชื (ืืขืืื, ืขืืืช ืึพAI) ืืชืืืกืคื ืืฉืืืื ืืืืื.</p> | |
| <p class="muted" dir="ltr">${esc(me.email)}</p></section>`); | |
| document.getElementById("st-refresh").addEventListener("click", () => { load(); loadActivity(); }); | |
| document.querySelector("#activity .ac-range").addEventListener("click", event => { | |
| const button = event.target.closest("[data-range]"); | |
| if (!button) return; | |
| range = Number(button.dataset.range); | |
| document.querySelectorAll("#activity [data-range]").forEach(b => b.setAttribute("aria-pressed", String(b === button))); | |
| loadActivity(); | |
| }); | |
| await Promise.all([load(), loadActivity(), import("./prompter_card.js").then(m => m.mount(document.getElementById("prompter")))]); | |
| } | |