MediaWiki:Gadget-LabelScan.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 10: | Zeile 10: | ||
modelId: 'Xenova/clip-vit-base-patch32', | modelId: 'Xenova/clip-vit-base-patch32', | ||
maxSide: 1024, | maxSide: 1024, | ||
debug: true | debug: true, | ||
// ⇩ Für Debug: Modell auch dann laden, wenn Index keine Embeddings hat | |||
forceModelWarmup: true | |||
}; | }; | ||
| Zeile 55: | Zeile 58: | ||
}); | }); | ||
log('Index geladen:',INDEX.length,'Einträge'); | const withEmb = INDEX_EMB.filter(v=>v && v.length).length; | ||
log('Index geladen:', INDEX.length, 'Einträge'); | |||
log('Embeddings vorhanden:', withEmb, '/', INDEX.length); | |||
// Exponiere Debug-Infos ins Fenster | |||
window._LabelScan = window._LabelScan || {}; | |||
window._LabelScan.indexInfo = { total: INDEX.length, withEmbeddings: withEmb }; | |||
if(ui) setProgress(0.06); | if(ui) setProgress(0.06); | ||
return INDEX; | return INDEX; | ||
| Zeile 68: | Zeile 78: | ||
} | } | ||
// ------------------------- CLIP / Transformers ------------------------- | |||
let _clipModulePromise=null; | let _clipModulePromise=null; | ||
async function ensureClipExtractor(){ | async function ensureClipExtractor(){ | ||
if(_clipModulePromise) return _clipModulePromise; | if(_clipModulePromise) return _clipModulePromise; | ||
| Zeile 76: | Zeile 88: | ||
_clipModulePromise = (async()=>{ | _clipModulePromise = (async()=>{ | ||
const mod = await import(/* webpackIgnore: true */ CFG.transformersURL); | try{ | ||
const mod = await import(/* webpackIgnore: true */ CFG.transformersURL); | |||
mod.env.localModelPath=null; | |||
mod.env.remoteModels=true; | |||
mod.env.allowRemoteModels=true; | |||
mod.env.useBrowserCache=true; | |||
const pipe = await mod.pipeline( | |||
'feature-extraction', | |||
CFG.modelId, | |||
{ quantized:true } | |||
); | |||
log('CLIP ready:', pipe.model?.constructor?.name || 'unknown'); | |||
return { mod, pipe }; | |||
} catch(e){ | |||
err('CLIP load failed:', e); | |||
throw e; | |||
} | |||
})(); | })(); | ||
| Zeile 114: | Zeile 130: | ||
w=Math.round(w*s); h=Math.round(h*s); | w=Math.round(w*s); h=Math.round(h*s); | ||
c.width=w; c.height=h; | c.width=w; c.height=h; | ||
c.getContext('2d').drawImage(img,0,0,w,h); | const g=c.getContext('2d'); | ||
g.imageSmoothingEnabled=true; | |||
g.drawImage(img,0,0,w,h); | |||
return c; | return c; | ||
} | } | ||
| Zeile 128: | Zeile 146: | ||
const out = await pipe(canvas); | const out = await pipe(canvas); | ||
const raw = out && out.data; | const raw = out && out.data; | ||
let vec; | |||
if (raw instanceof Float32Array) { | |||
vec = raw; | |||
} else if (Array.isArray(raw)) { | |||
// 1D/2D -> Float32 | |||
vec = Array.isArray(raw[0]) ? meanPool2D(raw) : new Float32Array(raw); | |||
} else { | |||
throw new Error('Embedding-Format unerwartet'); | |||
} | |||
return normalize(vec); | return normalize(vec); | ||
} | |||
function meanPool2D(arr2d){ | |||
const rows=arr2d.length; | |||
const dim=rows?arr2d[0].length:0; | |||
const sum=new Float32Array(dim); | |||
for(let r=0;r<rows;r++){ | |||
const row=arr2d[r]; | |||
for(let i=0;i<dim;i++) sum[i]+=row[i]||0; | |||
} | |||
for(let i=0;i<dim;i++) sum[i]/=(rows||1); | |||
return sum; | |||
} | } | ||
function normalize(v){ | function normalize(v){ | ||
let n=0; for(let i=0;i<v.length;i++) n+=v[i]*v[i]; | let n=0; for(let i=0;i<v.length;i++) n+=v[i]*i? v[i]:v[i]*v[i]; // avoid JIT weirdness | ||
n=0; for(let i=0;i<v.length;i++) n+=v[i]*v[i]; | |||
n=Math.sqrt(n)||1; | n=Math.sqrt(n)||1; | ||
const o=new Float32Array(v.length); | const o=new Float32Array(v.length); | ||
| Zeile 139: | Zeile 179: | ||
return o; | return o; | ||
} | } | ||
function cosine(a,b){ let s=0, L=Math.min(a.length,b.length); for(let i=0;i<L;i++) s+=a[i]*b[i]; return s; } | function cosine(a,b){ let s=0,L=Math.min(a.length,b.length); for(let i=0;i<L;i++) s+=a[i]*b[i]; return s; } | ||
// ------------------------- Ranking / Render ------------------------- | |||
function rankByCosine(q){ | function rankByCosine(q){ | ||
const s=[]; | const s=[]; | ||
| Zeile 165: | Zeile 206: | ||
${thumb?`<img src="${thumb}" style="width:60px;border-radius:6px;">`:`<div></div>`} | ${thumb?`<img src="${thumb}" style="width:60px;border-radius:6px;">`:`<div></div>`} | ||
<div><b><a href="${link}">${mw.html.escape(it.title||'')}</a></b></div> | <div><b><a href="${link}">${mw.html.escape(it.title||'')}</a></b></div> | ||
<div style="color:#666">${score.toFixed(3)}</div> | <div style="color:#666;font-variant-numeric:tabular-nums">${score.toFixed(3)}</div> | ||
</div>`; | </div>`; | ||
}); | }); | ||
} | } | ||
// ------------------------- UI Bindings ------------------------- | |||
let BOUND=false; | let BOUND=false; | ||
function bindUI(){ | function bindUI(){ | ||
if(BOUND) return; | if(BOUND) return; | ||
const btnRun=qs('ados-scan-run'); | const btnRun=qs('ados-scan-run'); | ||
const inCam=qs('ados-scan-file- | const inCam=qs('ados-scan-file-c | ||