anmol-unitmole's picture
Deploy Grammar Error Correction static application
714aaf8 verified
Raw History Blame Contribute Delete
7.43 kB
import { normalizeText } from './text_preprocessing.js';
import { correctText } from './correction_inference.js';
import { tokenize, extractTrackedEdits, renderHighlighted } from './tracked_edits.js';
import { categorizeErrors } from './error_categories.js';
import { checkMeaningPreservation } from './meaning_preservation.js';
import { detectOvercorrection } from './overcorrection_warning.js';
import { correctBatch, downloadCsv } from './batch_correction.js';
import { renderEvaluationDashboard } from './evaluation_dashboard.js';
const state = { metadata: null, batchRows: [], samples: null };
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => [...document.querySelectorAll(selector)];
function confidenceProxy(meaning, tracked, mode) {
const editRatio = tracked.changedTokenCount / Math.max(1, tracked.sourceTokenCount);
const modeComponent = mode === 'live-browser-model' ? 0.72 : 0.52;
const score = Math.max(0, Math.min(1, modeComponent * 0.45 + meaning.score * 0.45 + (1 - Math.min(1, editRatio)) * 0.1));
return {
score,
label: score >= 0.8 ? 'high' : score >= 0.6 ? 'medium' : 'low',
note: 'Confidence proxy only; not a calibrated correctness probability.',
};
}
function enrich(generation) {
const tracked = extractTrackedEdits(generation.original, generation.corrected);
const categories = categorizeErrors(tracked);
const meaning = checkMeaningPreservation(generation.original, generation.corrected);
const over = detectOvercorrection(tracked, meaning, false, categories.categories);
const confidence = confidenceProxy(meaning, tracked, generation.mode);
return { ...generation, tracked, categories, meaning, over, confidence };
}
function setStatus(message, type = 'neutral') {
const element = $('#run-status');
element.className = `run-status ${type}`;
element.textContent = message;
}
function renderResult(result) {
$('#results-panel').hidden = false;
$('#original-output').textContent = result.original;
$('#corrected-output').textContent = result.corrected;
$('#original-highlight').innerHTML = renderHighlighted(tokenize(result.original), result.tracked.edits, 'source');
$('#corrected-highlight').innerHTML = renderHighlighted(tokenize(result.corrected), result.tracked.edits, 'target');
$('#mode-badge').textContent = result.mode.replaceAll('-', ' ');
$('#confidence-value').textContent = `${(result.confidence.score * 100).toFixed(0)}% (${result.confidence.label})`;
$('#meaning-value').textContent = `${(result.meaning.score * 100).toFixed(0)}%`;
$('#latency-value').textContent = `${result.latencyMs.toFixed(1)} ms`;
$('#edit-count-value').textContent = String(result.tracked.wordLevelEditCount);
$('#category-list').innerHTML = result.categories.categories.map((category) => `<span class="tag">${category}</span>`).join('');
$('#edit-list').innerHTML = result.categories.categorizedEdits.length
? result.categories.categorizedEdits.map((edit) => `<li><span class="edit-op">${edit.operation}</span><code>${edit.before || '∅'}</code><span>→</span><code>${edit.after || '∅'}</code><span class="tag subtle">${edit.category}</span></li>`).join('')
: '<li>No edits detected.</li>';
const warning = $('#overcorrection-warning');
warning.className = `warning-card ${result.over.flag ? 'active' : 'clear'}`;
warning.innerHTML = `<strong>${result.over.message}</strong>${result.over.reasons.length ? `<ul>${result.over.reasons.map((reason) => `<li>${reason}</li>`).join('')}</ul>` : ''}`;
$('#meaning-warnings').innerHTML = result.meaning.warnings.length ? result.meaning.warnings.map((warningText) => `<li>${warningText}</li>`).join('') : '<li>No heuristic meaning warning detected.</li>';
$('#fallback-disclosure').hidden = result.mode === 'live-browser-model';
}
async function runSingle() {
const text = normalizeText($('#text-input').value);
if (!text) { setStatus('Enter a sentence or paragraph first.', 'error'); return; }
$('#run-button').disabled = true;
setStatus('Running correction…', 'loading');
try {
const result = enrich(await correctText(text, state.metadata, (progress) => setStatus(progress?.status ?? 'Loading browser model…', 'loading')));
renderResult(result);
setStatus(result.mode === 'live-browser-model' ? 'Live browser model completed.' : 'Fallback demonstration completed. Review the disclosure.', result.mode === 'live-browser-model' ? 'success' : 'warning');
} catch (error) {
setStatus(error.message, 'error');
} finally {
$('#run-button').disabled = false;
}
}
function renderBatchTable(rows) {
const body = $('#batch-results-body');
body.innerHTML = rows.map((row) => `<tr>
<td>${row.rowId}</td><td>${row.original}</td><td>${row.corrected}</td><td>${row.tracked.wordLevelEditCount}</td>
<td>${row.categories.categories.join(', ')}</td><td>${(row.confidence.score*100).toFixed(0)}%</td><td>${(row.meaning.score*100).toFixed(0)}%</td><td>${row.over.flag ? 'Review' : 'No'}</td><td>${row.latencyMs.toFixed(1)}</td>
</tr>`).join('');
$('#batch-result-panel').hidden = false;
}
async function runBatch() {
$('#batch-run-button').disabled = true;
try {
state.batchRows = await correctBatch($('#batch-input').value, state.metadata, enrich);
renderBatchTable(state.batchRows);
} catch (error) {
setStatus(error.message, 'error');
} finally {
$('#batch-run-button').disabled = false;
}
}
function setupTabs() {
$$('.tab-button').forEach((button) => button.addEventListener('click', () => {
$$('.tab-button').forEach((item) => item.classList.toggle('active', item === button));
$$('.tab-panel').forEach((panel) => { panel.hidden = panel.id !== button.dataset.tab; });
}));
}
function setupSamples() {
const select = $('#sample-select');
const samples = [...state.samples.sentences, ...state.samples.paragraphs];
select.innerHTML = '<option value="">Choose a safe sample…</option>' + samples.map((sample, index) => `<option value="${index}">${sample.slice(0, 76)}${sample.length > 76 ? '…' : ''}</option>`).join('');
select.addEventListener('change', () => { if (select.value !== '') $('#text-input').value = samples[Number(select.value)]; });
}
async function initialize() {
const [metadataResponse, samplesResponse] = await Promise.all([fetch(new URL('./metadata.json', import.meta.url)), fetch(new URL('./sample_texts/samples.json', import.meta.url))]);
state.metadata = await metadataResponse.json();
state.samples = await samplesResponse.json();
$('#deployment-mode').textContent = state.metadata.enable_live_model ? 'Live browser ONNX model configured' : 'Static fallback mode active';
$('#model-name').textContent = state.metadata.selected_base_model;
$('#legacy-model-name').textContent = state.metadata.legacy_notebook_model;
setupTabs(); setupSamples(); renderEvaluationDashboard($('#evaluation-dashboard'), state.metadata);
$('#run-button').addEventListener('click', runSingle);
$('#batch-run-button').addEventListener('click', runBatch);
$('#download-button').addEventListener('click', () => downloadCsv(state.batchRows));
$('#clear-button').addEventListener('click', () => { $('#text-input').value=''; $('#results-panel').hidden=true; setStatus('Ready.', 'neutral'); });
setStatus('Ready.', 'neutral');
}
initialize().catch((error) => setStatus(`Application initialization failed: ${error.message}`, 'error'));