Download multiindic.mjs from sourojitdotcom/AksharaLP-MultiIndic-8M: direct link, hf CLI and curl.
- Browser
- Download file 6.17 kB
-
https://huggingface.co/sourojitdotcom/AksharaLP-MultiIndic-8M/resolve/main/multiindic.mjs
- Command line
-
hf download hf://sourojitdotcom/AksharaLP-MultiIndic-8M/multiindic.mjs
-
curl -L -o multiindic.mjs https://huggingface.co/sourojitdotcom/AksharaLP-MultiIndic-8M/resolve/main/multiindic.mjs
6.17 kB
| /* AksharaLP MultiIndic runtime. Apache-2.0 code; selected non-Hindi weights are CC-BY-NC-4.0. See NOTICE. */ | |
| import {BhashaCore, XlitModel, parseWeights, TOKEN_RE} from './aksharalp.mjs'; | |
| const norm=s=>s.replace(/[\u200c\u200d]/g,'').normalize('NFC'); | |
| const protectedTokens=/(https?:\/\/\S+|www\.\S+|[\w.+-]+@[\w.-]+\.[A-Za-z]+|[@#][\p{L}\p{N}_]+)|[A-Za-z]+/gu; | |
| async function decompress(b) { | |
| if (typeof process !== 'undefined' && process.versions?.node) {const {gunzipSync}=await import('node:zlib');const x=gunzipSync(new Uint8Array(b));return x.buffer.slice(x.byteOffset,x.byteOffset+x.byteLength);} | |
| if (!globalThis.DecompressionStream) throw new Error('This browser needs DecompressionStream (Chrome/Edge 80+, Firefox 113+, Safari 16.4+).'); | |
| return new Response(new Blob([b]).stream().pipeThrough(new DecompressionStream('gzip'))).arrayBuffer(); | |
| } | |
| function rerank(cands,lex,p) { | |
| const rr=p.reranker, unis=cands.map(([s])=>lex.get(s)), mu=Math.max(...unis.filter(x=>x!==undefined),-Infinity), msc=Math.max(...cands.map(c=>c[1])); | |
| return cands.map(([text,sc],i)=>{const u=unis[i],inside=+(u!==undefined),f=[sc,sc/(text.length+1),inside,inside?u:0,+(i===0),i/cands.length,sc-msc,inside?u-mu:0]; | |
| const score=f.reduce((a,x,j)=>a+(x-rr.mu[j])/rr.sd[j]*rr.w[j],0);return {text,score};}).sort((a,b)=>b.score-a.score); | |
| } | |
| export class MultiIndic { | |
| static async load(base='./', {language='hi',sentence=false,fetch:fetchImpl=globalThis.fetch.bind(globalThis),onProgress}={}) { | |
| // Native worker fetch must keep its WorkerGlobalScope receiver. | |
| const e=new MultiIndic();e.base=base.replace(/\/$/,'');e.fetch=fetchImpl;e.onProgress=onProgress;e.cache=new Map();e.pending=new Map();e.sentenceMode=false;await e.setLanguage(language);if(sentence)await e.setSentence(true);return e; | |
| } | |
| async bytes(path) {const r=await this.fetch(`${this.base}/${path}`);if(!r.ok)throw new Error(`Could not load ${path}: HTTP ${r.status}`);let b=await r.arrayBuffer();this.onProgress?.(path,b.byteLength);return path.endsWith('.gz')?decompress(b):b;} | |
| async compressedFetch(url) {const path=url.slice(this.base.length+1),b=await this.bytes(path+'.gz');return new Response(b);} | |
| async setLanguage(language) { | |
| if(!['hi','bn','te','kn','ta'].includes(language))throw new Error('Unsupported language'); | |
| if(!this.cache.has(language)) { | |
| if(!this.pending.has(language))this.pending.set(language,(async()=>{ | |
| if(language==='hi'){const hi=await BhashaCore.load(`${this.base}/models/hi`,{fetch:u=>this.compressedFetch(u),cache:false,dict:true});if(!hi.hasDict)throw new Error(hi.dictError||'Hindi dictionary failed to load');return {hi};} | |
| const prefix=`models/${language}/`,[mb,pb,lb]=await Promise.all([this.bytes(prefix+'xlit.q4.gz'),this.bytes(prefix+'params.json'),this.bytes(prefix+'dict.tsv.gz')]); | |
| const p=JSON.parse(new TextDecoder().decode(pb)),lex=new Map(); | |
| for(const line of new TextDecoder().decode(lb).trim().split('\n')){const j=line.lastIndexOf('\t');lex.set(line.slice(0,j),Math.log(Number(line.slice(j+1))/p.dictionary_total));} | |
| return {model:new XlitModel(parseWeights(mb)),p,lex,memo:new Map()}; | |
| })()); | |
| try {this.cache.set(language,await this.pending.get(language));} | |
| finally {this.pending.delete(language);} // A failed download must be retryable. | |
| } | |
| this.language=language;this.active=this.cache.get(language);this.sentenceMode=false;if(this.active.hi)this.active.hi.sentenceMode=false;return this; | |
| } | |
| async setSentence(enabled=true) { | |
| if(this.language!=='hi'&&enabled)throw new Error('Sentence mode is Hindi-only'); | |
| if(enabled&&!this.active.hi.nlm) {const [{attachSentence},bytes]=await Promise.all([import('./sent.mjs'),this.bytes('models/hi/sentence/nlm.q8.gz')]);attachSentence(this.active.hi,bytes);} | |
| this.sentenceMode=enabled;if(this.active.hi)this.active.hi.sentenceMode=enabled;return this; | |
| } | |
| word(word,k=8) { | |
| if(!/^[A-Za-z]+$/.test(word))return [{text:word,score:0}]; | |
| const a=this.active;if(a.hi)return a.hi.word(word,k); | |
| word=word.toLowerCase();if(!a.memo.has(word)){ | |
| const ids=[...word.slice(0,31)].map(c=>c.charCodeAt(0)-97+4).concat([2]),enc=a.model.encode(ids),seen=new Set(),cands=[]; | |
| for(const [s,sc] of a.model.beam(enc,ids.length,{beam:8,nbest:8})){const t=norm(s);if(t&&!seen.has(t)){seen.add(t);cands.push([t,sc]);}} | |
| if(a.memo.size>1000)a.memo.clear();a.memo.set(word,rerank(cands,a.lex,a.p)); | |
| } | |
| return a.memo.get(word).slice(0,k); | |
| } | |
| transliterate(text,{k=8,overrides={}}={}) { | |
| const t0=performance.now(); | |
| if(this.active.hi){ | |
| // Plain reference conversion, without the older UI's brand/acronym/punctuation heuristics. | |
| const h=this.active.hi,spans=[...text.matchAll(TOKEN_RE)].map(m=>[m.index,m.index+m[0].length,m[0]]); | |
| const slots=spans.map(([a,b,t])=>overrides[a]!==undefined?['fix',overrides[a]]:/^[A-Za-z]+$/.test(t)?['x',t]:['fix',t]); | |
| const r=h.decodeSlots(slots,this.sentenceMode);if(r.out.at(-1)==='.')r.out[r.out.length-1]='\u0964'; | |
| let output='',pos=0;const tokens=[]; | |
| spans.forEach(([a,b,t],i)=>{output+=text.slice(pos,a)+r.out[i];pos=b;tokens.push({text:t,start:a,end:b,output:r.out[i],candidates:slots[i][0]==='x'?h.word(t,k):[]});}); | |
| return {output:output+text.slice(pos),tokens,ms:performance.now()-t0,sentence:null,guard:r.guard||{ok:true,words:0,outside:0}}; | |
| } | |
| const tokens=[];const output=text.replace(protectedTokens,(word,protectedWord,offset)=>{ | |
| if(protectedWord)return word;const candidates=this.word(word,k),out=overrides[offset]??candidates[0]?.text??word; | |
| tokens.push({text:word,start:offset,end:offset+word.length,output:out,candidates});return out;}); | |
| return {output,tokens,ms:performance.now()-t0,sentence:null}; | |
| } | |
| clearCache(){if(this.active.memo)this.active.memo.clear();if(this.active.hi)this.active.hi.cache.clear();} | |
| } | |
| export async function loadFromDir(dir,opt={}) {const fs=await import('node:fs/promises');return MultiIndic.load(dir,{...opt,fetch:async u=>{try{return new Response(await fs.readFile(u));}catch{return new Response(null,{status:404});}}});} | |
| export default MultiIndic; | |