Download src/app.js from hsienberg/browser-symptom-pattern-explorer-template: direct link, hf CLI and curl.
- Browser
- Download file 5.39 kB
-
https://huggingface.co/spaces/hsienberg/browser-symptom-pattern-explorer-template/resolve/main/src/app.js
- Command line
-
hf download hf://spaces/hsienberg/browser-symptom-pattern-explorer-template/src/app.js
-
curl -L -o app.js https://huggingface.co/spaces/hsienberg/browser-symptom-pattern-explorer-template/resolve/main/src/app.js
5.39 kB
| import { | |
| MODEL_ID, | |
| compareRankings, | |
| findRedFlags, | |
| normalizeInput, | |
| rankRows, | |
| reorderSymptoms, | |
| runsToCsv, | |
| } from "./core.js"; | |
| const worker = new Worker("./src/worker.js", { type: "module" }); | |
| const runs = []; | |
| const pending = new Map(); | |
| let requestCounter = 0; | |
| let loadInfo = null; | |
| const form = document.querySelector("#analysis-form"); | |
| const input = document.querySelector("#symptoms"); | |
| const status = document.querySelector("#status"); | |
| const results = document.querySelector("#results"); | |
| const compareButton = document.querySelector("#compare"); | |
| const downloadButton = document.querySelector("#download"); | |
| const redFlags = document.querySelector("#red-flags"); | |
| const runtime = document.querySelector("#runtime"); | |
| function setStatus(message, kind = "") { | |
| status.textContent = message; | |
| status.dataset.kind = kind; | |
| } | |
| function formatBytes(bytes) { | |
| if (!bytes) return "size unavailable"; | |
| return `${(bytes / 1024 / 1024).toFixed(1)} MB`; | |
| } | |
| function renderRows(rows) { | |
| results.innerHTML = rows | |
| .map( | |
| (row) => ` | |
| <li> | |
| <span class="rank">${row.rank}</span> | |
| <span>${row.label}</span> | |
| <span class="score">${(row.score * 100).toFixed(1)}</span> | |
| </li>`, | |
| ) | |
| .join(""); | |
| } | |
| function showRedFlags(text) { | |
| const flags = findRedFlags(text); | |
| redFlags.hidden = flags.length === 0; | |
| redFlags.textContent = flags.length | |
| ? `Safety phrase detected (${flags.join(", ")}). This experiment cannot triage emergencies. Seek qualified medical help.` | |
| : ""; | |
| } | |
| function analyze(text, variant) { | |
| return new Promise((resolve, reject) => { | |
| const requestId = ++requestCounter; | |
| pending.set(requestId, { resolve, reject, text, variant }); | |
| worker.postMessage({ type: "analyze", requestId, text }); | |
| }); | |
| } | |
| worker.addEventListener("message", (event) => { | |
| const message = event.data; | |
| if (message.type === "loading") { | |
| runtime.textContent = `${MODEL_ID} · ${message.runtime.device} · ${message.runtime.dtype} · ${formatBytes(message.totalBytes)}`; | |
| setStatus("Downloading and loading the model. The first run may take a minute."); | |
| return; | |
| } | |
| if (message.type === "progress") { | |
| setStatus(`Loading model: ${Math.round(message.progress)}%`); | |
| return; | |
| } | |
| if (message.type === "ready") { | |
| loadInfo = message; | |
| runtime.textContent = `${MODEL_ID} · ${message.runtime.device} · ${message.runtime.dtype} · loaded in ${(message.loadMs / 1000).toFixed(1)} s`; | |
| return; | |
| } | |
| const request = pending.get(message.requestId); | |
| if (!request) return; | |
| pending.delete(message.requestId); | |
| if (message.type === "error") { | |
| request.reject(new Error(message.message)); | |
| return; | |
| } | |
| const rows = rankRows(message.result); | |
| const run = { | |
| timestamp: new Date().toISOString(), | |
| input: request.text, | |
| variant: request.variant, | |
| modelId: MODEL_ID, | |
| device: message.runtime.device, | |
| dtype: message.runtime.dtype, | |
| inferenceMs: message.inferenceMs, | |
| loadMs: loadInfo?.loadMs ?? null, | |
| results: rows, | |
| }; | |
| runs.push(run); | |
| downloadButton.disabled = false; | |
| request.resolve(run); | |
| }); | |
| form.addEventListener("submit", async (event) => { | |
| event.preventDefault(); | |
| const text = normalizeInput(input.value); | |
| if (!text) return; | |
| showRedFlags(text); | |
| form.querySelector("button").disabled = true; | |
| compareButton.disabled = true; | |
| setStatus("Analyzing locally…"); | |
| try { | |
| const run = await analyze(text, "original"); | |
| renderRows(run.results); | |
| setStatus( | |
| `Complete in ${(run.inferenceMs / 1000).toFixed(1)} s. Scores are model signals, not medical probabilities.`, | |
| "success", | |
| ); | |
| compareButton.disabled = false; | |
| } catch (error) { | |
| setStatus(`Could not run the model: ${error.message}`, "error"); | |
| } finally { | |
| form.querySelector("button").disabled = false; | |
| } | |
| }); | |
| compareButton.addEventListener("click", async () => { | |
| const originalText = normalizeInput(input.value); | |
| const reorderedText = reorderSymptoms(originalText); | |
| if (!reorderedText || reorderedText === originalText) { | |
| setStatus("Enter at least two comma-, period-, semicolon-, or line-separated symptoms."); | |
| return; | |
| } | |
| compareButton.disabled = true; | |
| setStatus(`Testing reordered input: ${reorderedText}`); | |
| try { | |
| const original = | |
| [...runs].reverse().find( | |
| (run) => run.variant === "original" && run.input === originalText, | |
| ) ?? (await analyze(originalText, "original")); | |
| const reordered = await analyze(reorderedText, "reordered"); | |
| const comparison = compareRankings(original.results, reordered.results); | |
| const changed = comparison.filter((row) => row.shift !== 0).length; | |
| renderRows(reordered.results); | |
| setStatus( | |
| `Reordered test complete: ${changed} of the original top-five labels changed rank. Download the run log to inspect both versions.`, | |
| "success", | |
| ); | |
| } catch (error) { | |
| setStatus(`Could not run the comparison: ${error.message}`, "error"); | |
| } finally { | |
| compareButton.disabled = false; | |
| } | |
| }); | |
| downloadButton.addEventListener("click", () => { | |
| const blob = new Blob([runsToCsv(runs)], { type: "text/csv" }); | |
| const url = URL.createObjectURL(blob); | |
| const link = document.createElement("a"); | |
| link.href = url; | |
| link.download = `symptom-pattern-runs-${new Date().toISOString().slice(0, 10)}.csv`; | |
| link.click(); | |
| URL.revokeObjectURL(url); | |
| }); | |