66import Base from " ../layouts/Base.astro"
77import catalogue from " ../data/maps-catalogue.json"
88
9- const systems = Object .entries (catalogue as Record <string , { data: { name: string ; language: string } }>)
10- .map (([code , m ]) => ({ code , name: m .data .name , lang: m .data .language }))
11- .sort ((a , b ) => a .name .localeCompare (b .name ))
129---
1310
1411<Base title =" Playground" >
@@ -30,6 +27,9 @@ const systems = Object.entries(catalogue as Record<string, { data: { name: strin
3027 <option value =" " >Off — deterministic maps only</option >
3128 <option value =" ara-diac-layerdrop-1.0-int4" >Arabic lite (int4, 95 MB, downloads once)</option >
3229 <option value =" ara-diac-small-2.1-int8" >Arabic 2.1 (int8, 264 MB, downloads once)</option >
30+ <option value =" spec:ara-diac-small-2.1-int8" >
31+ Arabic 2.1 + speculative (int4 drafts it, 359 MB, downloads once)
32+ </option >
3333 </select >
3434
3535 <label for =" filter" >System</label >
@@ -168,6 +168,10 @@ const systems = Object.entries(catalogue as Record<string, { data: { name: strin
168168 if (!voc) {
169169 return `import { transliterateAsync } from "interscript"\n\nconst out = await transliterateAsync(\n "${code}",\n "…",\n)`
170170 }
171+ if (voc.startsWith("spec:")) {
172+ const verifier = voc.slice(5)
173+ return `import { transliterateAsync } from "interscript"\nimport { imf } from "interscript/ml"\n\nconst drafter = await imf.IMFModel.fromZipBytes(\n (await imf.resolve("ara-diac-layerdrop-1.0-int4")).bytes,\n)\nconst verifierModel = await imf.IMFModel.fromZipBytes(\n (await imf.resolve("${verifier}")).bytes,\n)\nconst spec = new imf.SpeculativeModel(drafter, verifierModel)\nconst vocalized = await spec.translate("…")\nconst out = await transliterateAsync(\n "${code}",\n vocalized,\n)`
174+ }
171175 return `import { transliterateAsync } from "interscript"\nimport { imf } from "interscript/ml"\n\nconst resolved = await imf.resolve("${voc}")\nconst model = await imf.IMFModel.fromZipBytes(resolved.bytes)\nconst vocalized = await model.translate("…")\nconst out = await transliterateAsync(\n "${code}",\n vocalized,\n)`
172176 }
173177 $("snippet").textContent = snippetText()
@@ -185,15 +189,27 @@ const systems = Object.entries(catalogue as Record<string, { data: { name: strin
185189 async function ensureModel(id: string) {
186190 if (mlModel && mlModelId === id) return mlModel
187191 const status = $("status")
188- status.hidden = false
189- status.textContent = `Downloading ${id} (once per browser)…`
190- const resolved = await imf.resolve(id, ASSET_INDEX, {
191- onProgress: (f: number, b: number) => {
192- status.textContent = `${id}: ${(b / 1e6).toFixed(0)} MB (${Math.round(f * 100)}%)`
193- },
194- })
195- const model = await imf.IMFModel.fromZipBytes(resolved.bytes)
196- mlModel = model as unknown as { translate(t: string, m?: number): Promise<string> }
192+ const spec = id.startsWith("spec:")
193+ const verifierId = spec ? id.slice(5) : id
194+ const drafterId = spec ? "ara-diac-layerdrop-1.0-int4" : ""
195+ const wanted = spec ? [drafterId, verifierId] : [verifierId]
196+ const models: Record<string, Awaited<ReturnType<typeof imf.IMFModel.fromZipBytes>>> = {}
197+ for (const wantedId of wanted) {
198+ status.hidden = false
199+ status.textContent = `Downloading ${wantedId} (once per browser)…`
200+ const resolved = await imf.resolve(wantedId, ASSET_INDEX, {
201+ onProgress: (f: number, b: number) => {
202+ status.textContent = `${wantedId}: ${(b / 1e6).toFixed(0)} MB (${Math.round(f * 100)}%)`
203+ },
204+ })
205+ models[wantedId] = await imf.IMFModel.fromZipBytes(resolved.bytes)
206+ }
207+ mlModel = spec
208+ ? (new imf.SpeculativeModel(models[drafterId]!, models[verifierId]!) as unknown as {
209+ translate(t: string, m?: number): Promise<string>
210+ stats(): { accepted: number; drafted: number; blocks: number } | undefined
211+ })
212+ : (models[verifierId] as unknown as { translate(t: string, m?: number): Promise<string> })
197213 mlModelId = id
198214 status.hidden = true
199215 return mlModel
@@ -218,6 +234,15 @@ const systems = Object.entries(catalogue as Record<string, { data: { name: strin
218234 }
219235 const result = await worker.transliterate(systemSelect.value, text)
220236 out.textContent = result
237+ if (modelId?.startsWith("spec:") && mlModel) {
238+ const stats = (
239+ mlModel as { stats?: () => { accepted: number; drafted: number; blocks: number } }
240+ ).stats?.()
241+ if (stats) {
242+ status.hidden = false
243+ status.textContent = `Speculative: ${stats.accepted}/${stats.drafted} draft tokens accepted across ${stats.blocks} verifier passes`
244+ }
245+ }
221246 const share = new URLSearchParams({
222247 q: ($("input") as HTMLTextAreaElement).value,
223248 sys: systemSelect.value,
0 commit comments