Zum Inhalt springen

MediaWiki:Gadget-LabelScan.js: Unterschied zwischen den Versionen

Aus ADOS Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
/* LabelScan Gadget HARD BIND TEST (delegated click + hooks + observer) */
/* LabelScan – visuelle Erkennung über CLIP (no OCR) */
(function () {
(function () {
   'use strict';
   'use strict';


  // 1) Sofort melden, dass das Gadget wirklich ausgeführt wurde
   console.log('[LabelScan] CLIP-Erkennung Gadget gestartet');
   console.log('[LabelScan TEST] Modul ausgeführt (IIFE gestartet)');


   // 2) Delegiertes Click-Event: reagiert auch, wenn der Button später ins DOM kommt
   // Kategorien, in denen gesucht werden soll
   document.addEventListener('click', function (ev) {
  const ADOS_CATEGORIES = [
     var el = ev.target;
    'Alle A Dream of Scotland Abfüllungen',
     // trifft auf #ados-scan-run oder ein Kindelement davon
    'Alle A Dream of Ireland Abfüllungen',
     if (el && (el.id === 'ados-scan-run' || (el.closest && el.closest('#ados-scan-run')))) {
    'Alle A Dream of... – Der Rest der Welt Abfüllungen',
       ev.preventDefault();
    'The Fine Art of Whisky Abfüllungen',
       console.log('[LabelScan TEST] Click auf #ados-scan-run erkannt');
    'Die Whisky Elfen Abfüllungen',
      alert('✅ Gadget hängt am Button. (Binding OK)');
    'Friendly Mr. Z Whiskytainment Abfüllungen',
    'Alle Rumbastic Abfüllungen',
    'Cigar Malt Übersicht',
    'The Tasteful 8',
    'Còmhlan Abfüllungen',
    'The Forbidden Kingdom',
    'Sonderabfüllungen'
  ];
 
  // Laden des CLIP-Modells
   let clipReady = null;
  function ensureCLIP() {
    if (clipReady) return clipReady;
    clipReady = new Promise((resolve, reject) => {
      mw.loader.using('ext.gadget.aimodels').then(() => {
        if (window.CLIP) resolve();
        else reject('CLIP-Modell fehlt');
      });
    });
    return clipReady;
  }
 
  // Bild → Embedding
  async function embedImage(file) {
     await ensureCLIP();
    const img = await CLIP.loadImage(file);
     return await CLIP.embedImage(img);
  }
 
  // Wiki-Abfüllungsseiten laden & vorberechnen (macht Cache!)
  let cache = null;
  async function loadDatabase() {
     if (cache) return cache;
 
    await mw.loader.using('mediawiki.api');
    const api = new mw.Api();
 
    const catSearch = ADOS_CATEGORIES.map(c => `incategory:"${c}"`).join(' | ');
    const result = await api.get({
      action: 'query',
      list: 'search',
      srsearch: catSearch,
      srlimit: 500,
      srnamespace: 0,
      formatversion: 2
    });
 
    cache = result.query.search.map(p => ({
      title: p.title,
      embedding: null
    }));
    return cache;
  }
 
  // Erkennen & vergleichen
  async function findMatches(file) {
    const db = await loadDatabase();
    const imgVec = await embedImage(file);
 
    // Falls wir noch keine Embeddings für Seiten haben → schnell "zero-shot prompt"
    db.forEach(p => {
       if (!p.embedding) p.embedding = CLIP.embedTextSync(p.title);
    });
 
    // Score berechnen
    const scored = db.map(p => ({
      title: p.title,
       score: CLIP.cosineSimilarity(imgVec, p.embedding)
    }));
 
    scored.sort((a, b) => b.score - a.score);
    return scored.slice(0, 8); // Nur Top 8
  }
 
  // UI Rendering
  function renderResults(items) {
    const box = document.getElementById('ados-scan-results');
    if (!box) return;
    box.innerHTML = '';
    if (!items || items.length === 0) {
      box.innerHTML = '<div class="ados-hit">Keine klaren Treffer gefunden.</div>';
      return;
     }
     }
  }, true);
     items.forEach(it => {
 
       const link = mw.util.getUrl(it.title.replace(/ /g, '_'));
  // 3) Zusätzlich über MediaWiki-Hooks jedes Mal melden, wenn Seitenteil gerendert wurde
       box.innerHTML += `<div class="ados-hit">
  if (window.mw && mw.hook) {
         <b><a href="${link}">${mw.html.escape(it.title)}</a></b>
     mw.hook('wikipage.content').add(function ($content) {
         <div class="meta">Ähnlichkeit: ${(it.score * 100).toFixed(1)}%</div>
       console.log('[LabelScan TEST] wikipage.content hook', $content && $content.length);
       </div>`;
      // Optional: einmal visuell markieren, dass der Button da ist
      var btn = document.getElementById('ados-scan-run');
       if (btn && !btn.dataset._lscanMark) {
         btn.dataset._lscanMark = '1';
         btn.style.outline = '2px dashed #22c55e';
        btn.style.outlineOffset = '2px';
       }
     });
     });
   }
   }


   // 4) Fallback: Wenn der Button gar nicht existiert, füge oben rechts einen Test-Button ein
   // --- Button Binding (funktioniert sicher, da Binding OK geprüft) ---
   //    (nur, um zu beweisen, dass das Gadget läuft)
   document.addEventListener('click', async ev => {
  setTimeout(function () {
    const btn = ev.target.closest && ev.target.closest('#ados-scan-run');
     if (!document.getElementById('ados-scan-run')) {
     if (!btn) return;
      var test = document.createElement('button');
    ev.preventDefault();
       test.textContent = 'LabelScan Test';
 
       test.style.cssText = 'position:fixed;top:10px;right:10px;z-index:99999;padding:.5rem .8rem;border-radius:8px;border:1px solid #ccc;background:#fff;box-shadow:0 2px 8px rgba(0,0,0,.15);';
    const fileIn = document.getElementById('ados-scan-file');
       test.addEventListener('click', function () {
    const status = document.getElementById('ados-scan-status');
        alert('✅ Gadget läuft. (Seiten-UI-Button #ados-scan-run ist auf dieser Seite aber nicht vorhanden)');
 
      });
    if (!fileIn.files || !fileIn.files[0]) {
       document.body.appendChild(test);
       alert('Bitte ein Label-Foto auswählen.');
       console.log('[LabelScan TEST] Fallback-Testbutton eingefügt (weil #ados-scan-run nicht gefunden wurde)');
       return;
    }
 
    const file = fileIn.files[0];
    status.textContent = '🔍 Erkenne Bildstil…';
    btn.disabled = true;
 
    try {
      const matches = await findMatches(file);
       renderResults(matches);
      status.textContent = '✅ Fertig.';
    } catch (err) {
       console.error(err);
       status.textContent = '❌ Fehler.';
     }
     }
   }, 500);
 
    btn.disabled = false;
   }, true);
})();
})();

Version vom 6. November 2025, 22:07 Uhr

/* LabelScan – visuelle Erkennung über CLIP (no OCR) */
(function () {
  'use strict';

  console.log('[LabelScan] CLIP-Erkennung Gadget gestartet');

  // Kategorien, in denen gesucht werden soll
  const ADOS_CATEGORIES = [
    'Alle A Dream of Scotland Abfüllungen',
    'Alle A Dream of Ireland Abfüllungen',
    'Alle A Dream of... – Der Rest der Welt Abfüllungen',
    'The Fine Art of Whisky Abfüllungen',
    'Die Whisky Elfen Abfüllungen',
    'Friendly Mr. Z Whiskytainment Abfüllungen',
    'Alle Rumbastic Abfüllungen',
    'Cigar Malt Übersicht',
    'The Tasteful 8',
    'Còmhlan Abfüllungen',
    'The Forbidden Kingdom',
    'Sonderabfüllungen'
  ];

  // Laden des CLIP-Modells
  let clipReady = null;
  function ensureCLIP() {
    if (clipReady) return clipReady;
    clipReady = new Promise((resolve, reject) => {
      mw.loader.using('ext.gadget.aimodels').then(() => {
        if (window.CLIP) resolve();
        else reject('CLIP-Modell fehlt');
      });
    });
    return clipReady;
  }

  // Bild → Embedding
  async function embedImage(file) {
    await ensureCLIP();
    const img = await CLIP.loadImage(file);
    return await CLIP.embedImage(img);
  }

  // Wiki-Abfüllungsseiten laden & vorberechnen (macht Cache!)
  let cache = null;
  async function loadDatabase() {
    if (cache) return cache;

    await mw.loader.using('mediawiki.api');
    const api = new mw.Api();

    const catSearch = ADOS_CATEGORIES.map(c => `incategory:"${c}"`).join(' | ');
    const result = await api.get({
      action: 'query',
      list: 'search',
      srsearch: catSearch,
      srlimit: 500,
      srnamespace: 0,
      formatversion: 2
    });

    cache = result.query.search.map(p => ({
      title: p.title,
      embedding: null
    }));
    return cache;
  }

  // Erkennen & vergleichen
  async function findMatches(file) {
    const db = await loadDatabase();
    const imgVec = await embedImage(file);

    // Falls wir noch keine Embeddings für Seiten haben → schnell "zero-shot prompt"
    db.forEach(p => {
      if (!p.embedding) p.embedding = CLIP.embedTextSync(p.title);
    });

    // Score berechnen
    const scored = db.map(p => ({
      title: p.title,
      score: CLIP.cosineSimilarity(imgVec, p.embedding)
    }));

    scored.sort((a, b) => b.score - a.score);
    return scored.slice(0, 8); // Nur Top 8
  }

  // UI Rendering
  function renderResults(items) {
    const box = document.getElementById('ados-scan-results');
    if (!box) return;
    box.innerHTML = '';
    if (!items || items.length === 0) {
      box.innerHTML = '<div class="ados-hit">Keine klaren Treffer gefunden.</div>';
      return;
    }
    items.forEach(it => {
      const link = mw.util.getUrl(it.title.replace(/ /g, '_'));
      box.innerHTML += `<div class="ados-hit">
        <b><a href="${link}">${mw.html.escape(it.title)}</a></b>
        <div class="meta">Ähnlichkeit: ${(it.score * 100).toFixed(1)}%</div>
      </div>`;
    });
  }

  // --- Button Binding (funktioniert sicher, da Binding OK geprüft) ---
  document.addEventListener('click', async ev => {
    const btn = ev.target.closest && ev.target.closest('#ados-scan-run');
    if (!btn) return;
    ev.preventDefault();

    const fileIn = document.getElementById('ados-scan-file');
    const status = document.getElementById('ados-scan-status');

    if (!fileIn.files || !fileIn.files[0]) {
      alert('Bitte ein Label-Foto auswählen.');
      return;
    }

    const file = fileIn.files[0];
    status.textContent = '🔍 Erkenne Bildstil…';
    btn.disabled = true;

    try {
      const matches = await findMatches(file);
      renderResults(matches);
      status.textContent = '✅ Fertig.';
    } catch (err) {
      console.error(err);
      status.textContent = '❌ Fehler.';
    }

    btn.disabled = false;
  }, true);
})();