anmol-unitmole's picture
Deploy Grammar Error Correction static application
714aaf8 verified
Raw History Blame Contribute Delete
4 kB
import { normalizeText } from './text_preprocessing.js';
let generator = null;
let loadingPromise = null;
let precomputedMap = null;
const RULES = [
[/\b[Ss]he go\b/g, (match) => `${match[0]}es`],
[/\b[Hh]e go\b/g, (match) => `${match[0]}es`],
[/\b[Ee]very (\w+) have\b/g, 'Every $1 has'],
[/\b([Tt]he quality results) is\b/g, '$1 are'],
[/\bdiscussed about\b/gi, 'discussed'],
[/\bequipments\b/gi, 'equipment'],
[/\bpeople is\b/gi, 'people are'],
[/\bthere is many\b/gi, 'there are many'],
[/\bwe was\b/gi, 'we were'],
[/\bcan sings\b/gi, 'can sing'],
[/\bdidn['’]?t went\b/gi, "didn't go"],
[/\bdidn['’]?t knew\b/gi, "didn't know"],
[/\bmore better\b/gi, 'better'],
[/\bmarried with\b/gi, 'married to'],
[/\blook forward to meet\b/gi, 'look forward to meeting'],
[/\bI has\b/g, 'I have'],
[/\bi has\b/g, 'i have'],
[/\bworks to do\b/gi, 'work to do'],
];
async function loadPrecomputed() {
if (precomputedMap) return precomputedMap;
const response = await fetch(new URL('./sample_outputs/precomputed.json', import.meta.url));
const samples = await response.json();
precomputedMap = new Map(samples.map((item) => [normalizeText(item.original).toLowerCase(), item]));
return precomputedMap;
}
function buildPrompt(text) {
return `Task:\nCorrect the grammar, spelling, punctuation, and wording of the text while preserving the original meaning.\n\nRules:\n- Preserve the original meaning.\n- Do not add unsupported information.\n- Do not remove important details.\n- Keep named entities, numbers, technical terms, and product names unchanged unless clearly incorrect.\n- Return only the corrected text.\n\nInput:\n${text}\n\nCorrected Text:`;
}
export async function loadLiveModel(metadata, onProgress = () => {}) {
if (!metadata.enable_live_model || metadata.browser_model_id.startsWith('YOUR_')) {
throw new Error('Live browser model is not configured. The app will use the disclosed fallback mode.');
}
if (generator) return generator;
if (!loadingPromise) {
loadingPromise = (async () => {
const { pipeline } = await import('https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.1/+esm');
const options = {
dtype: metadata.browser_dtype || 'q8',
device: metadata.preferred_device || 'wasm',
progress_callback: onProgress,
};
generator = await pipeline(metadata.browser_task || 'text2text-generation', metadata.browser_model_id, options);
return generator;
})();
}
return loadingPromise;
}
function deterministicFallback(text) {
let corrected = text;
RULES.forEach(([pattern, replacement]) => { corrected = corrected.replace(pattern, replacement); });
corrected = corrected.replace(/\bi\b/g, 'I').replace(/\s+([.,!?;:])/g, '$1');
if (corrected && !/[.!?]$/.test(corrected)) corrected += '.';
return corrected;
}
export async function correctText(rawText, metadata, onProgress = () => {}) {
const original = normalizeText(rawText);
if (!original) throw new Error('Enter text before running correction.');
const start = performance.now();
if (metadata.enable_live_model && !metadata.browser_model_id.startsWith('YOUR_')) {
const pipe = await loadLiveModel(metadata, onProgress);
const output = await pipe(buildPrompt(original), { max_new_tokens: 256, num_beams: 4, do_sample: false });
const corrected = output?.[0]?.generated_text?.trim() ?? '';
return { original, corrected, latencyMs: performance.now() - start, mode: 'live-browser-model', confidenceSource: 'generation output proxy' };
}
const examples = await loadPrecomputed();
const sample = examples.get(original.toLowerCase());
const corrected = sample?.corrected ?? deterministicFallback(original);
return {
original,
corrected,
latencyMs: performance.now() - start,
mode: sample ? 'precomputed-fallback-sample' : 'rule-based-fallback-demo',
confidenceSource: 'heuristic only',
disclosure: metadata.fallback_disclosure,
};
}