import type { ProfileResult, Marker } from "./schemas"; import { store } from "./store"; const FEATURE_LABELS: Record = { avg_word_length: "Avg word length", vocabulary_richness: "Vocabulary richness", hapax_ratio: "Hapax ratio", avg_sentence_length: "Avg sentence length", sentence_length_variance: "Sentence length variance", passive_voice_ratio: "Passive voice ratio", contraction_rate: "Contraction rate", semicolon_rate: "Semicolon rate", comma_rate: "Comma rate", dash_rate: "Dash rate", exclamation_rate: "Exclamation rate", hyphenation_rate: "Hyphenation rate", }; const MARKER_TO_FEATURE: Record = { semicolon_high: "semicolon_rate", contraction_high: "contraction_rate", comma_rate: "comma_rate", colon_high: "colon_rate", dash_preference: "dash_rate", exclamation_high: "exclamation_rate", ellipsis_high: "ellipsis_rate", parenthetical_high: "parenthetical_rate", sentence_length_high: "avg_sentence_length", sentence_length_variance_high: "sentence_length_variance", passive_voice_high: "passive_voice_ratio", vocabulary_richness_high: "vocabulary_richness", hapax_ratio_high: "hapax_ratio", hyphenation_high: "hyphenation_rate", avg_word_length: "avg_word_length", avg_syllables: "avg_syllables_per_word", }; function esc(str: string): string { const div = document.createElement("div"); div.textContent = str; return div.innerHTML; } function formatMarkerName(name: string): string { return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } function $(sel: string): HTMLElement { return document.querySelector(sel)!; } export function updateStatus(): void { const { status, statusMessage } = store.getState(); const el = $("#status-indicator"); el.textContent = statusMessage; el.className = `status status-${status === "model-ready" ? "ready" : status}`; } export function renderProfile(result: ProfileResult): void { const state = store.getState(); // Summary $("#marker-count").textContent = String(result.markers.length); const summary = $("#results-summary"); summary.dataset.variant = result.markers.length === 0 ? "success" : "warning"; // Markers list const container = $("#markers-container"); container.innerHTML = ""; for (const m of result.markers) { const item = document.createElement("div"); item.className = "change-item"; const dir = m.z_score > 0 ? "HIGH" : "LOW"; item.innerHTML = `
${esc(formatMarkerName(m.name))} ${dir}
Value: ${m.value.toFixed(4)} · Baseline: ${m.baseline.toFixed(4)} · z = ${m.z_score.toFixed(2)}
`; container.appendChild(item); } // Marker lookup by feature key const flagged = new Map(); for (const m of result.markers) { const featureKey = MARKER_TO_FEATURE[m.name]; if (featureKey) flagged.set(featureKey, m); } // Forensic table const forensicBody = $("#forensic-table-body"); forensicBody.innerHTML = ""; const raw = result as Record; for (const [key, label] of Object.entries(FEATURE_LABELS)) { if (raw[key] === undefined) continue; const tr = document.createElement("tr"); const m = flagged.get(key); let statusCls: string, statusTxt: string; if (m) { statusCls = "forensic-warn"; statusTxt = m.z_score > 0 ? "\u26a0 High" : "\u26a0 Low"; } else { statusCls = "forensic-fixed"; statusTxt = "\u2713 Normal"; } const baseline = m ? m.baseline.toFixed(4) : "\u2014"; const val = raw[key]; tr.innerHTML = ` ${esc(label)} ${typeof val === "boolean" ? (val ? "Yes" : "No") : (val as number).toFixed(4)} ${baseline} ${statusTxt} `; forensicBody.appendChild(tr); } // Rewriter section const rewriterSection = $("#rewriter-section") as HTMLElement; rewriterSection.hidden = false; const btnDownload = $("#btn-download-model") as HTMLElement; const btnNeutralize = $("#btn-neutralize") as HTMLElement; if (state.modelLoaded) { btnDownload.hidden = true; btnNeutralize.hidden = false; } else { btnDownload.hidden = false; btnNeutralize.hidden = true; } const resultsSection = $("#results") as HTMLElement; resultsSection.hidden = false; resultsSection.scrollIntoView({ behavior: "smooth", block: "start" }); }