Zum Inhalt springen

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

Aus ADOS Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 115: Zeile 115:
     document.addEventListener('DOMContentLoaded', bindUI);
     document.addEventListener('DOMContentLoaded', bindUI);
   } else bindUI();
   } else bindUI();
})();
// --- kleine UI-Helfer für LabelScan-Seite ---
(function(){
  const wrap = document.getElementById('ados-labelscan');
  if (!wrap) return;
  const fileIn = document.getElementById('ados-scan-file');
  const bigBtn = document.getElementById('ados-scan-bigbtn');
  const drop = document.getElementById('ados-scan-drop');
  const preview = document.getElementById('ados-scan-preview');
  // großer Button triggert File-Input
  if (bigBtn && fileIn) {
    bigBtn.addEventListener('click', () => fileIn.click());
  }
  // Bildvorschau
  if (fileIn && preview) {
    fileIn.addEventListener('change', () => {
      if (fileIn.files && fileIn.files[0]) {
        const url = URL.createObjectURL(fileIn.files[0]);
        preview.innerHTML = `<img src="${url}" alt="Vorschau">`;
      }
    });
  }
  // Drag&Drop
  if (drop && fileIn) {
    ['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => {
      e.preventDefault(); e.stopPropagation(); drop.classList.add('is-dragover');
    }));
    ['dragleave','drop'].forEach(ev => drop.addEventListener(ev, e => {
      e.preventDefault(); e.stopPropagation(); drop.classList.remove('is-dragover');
    }));
    drop.addEventListener('drop', e => {
      const dt = e.dataTransfer;
      if (dt && dt.files && dt.files[0]) {
        fileIn.files = dt.files;
        const url = URL.createObjectURL(dt.files[0]);
        preview.innerHTML = `<img src="${url}" alt="Vorschau">`;
      }
    });
  }
})();
})();

Version vom 7. November 2025, 21:28 Uhr

/* global mw */

(function () {
  'use strict';

  // -------------------------------------------------------
  // 1) INDEX LADEN
  // -------------------------------------------------------
  const INDEX_URL = '/wiki?title=MediaWiki:Gadget-LabelScan-index.json&action=raw&ctype=application/json';
  let LABEL_INDEX = null;

  async function loadIndex() {
    if (LABEL_INDEX) return LABEL_INDEX;
    const res = await fetch(INDEX_URL);
    LABEL_INDEX = await res.json();
    console.log('[LabelScan] Index geladen:', LABEL_INDEX.length, 'Einträge');
    return LABEL_INDEX;
  }

  // -------------------------------------------------------
  // 2) KOSINUS-SIMILARITÄT (Bild→Vektor→Vergleich)
  //    -> ultra leichtgewichtige Bildähnlichkeit
  // -------------------------------------------------------
  async function imgToVec(fileOrUrl) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = () => {
        const c = document.createElement('canvas');
        c.width = 16; c.height = 16;
        const ctx = c.getContext('2d');
        ctx.drawImage(img, 0, 0, 16, 16);
        const d = ctx.getImageData(0, 0, 16, 16).data;
        const vec = [];
        for (let i = 0; i < d.length; i += 4) vec.push((d[i] + d[i+1] + d[i+2]) / 3);
        resolve(vec);
      };
      img.onerror = reject;
      img.src = typeof fileOrUrl === 'string' ? fileOrUrl : URL.createObjectURL(fileOrUrl);
    });
  }

  function cosine(a, b) {
    let dot = 0, na = 0, nb = 0;
    for (let i = 0; i < a.length; i++) {
      dot += a[i] * b[i];
      na += a[i] * a[i];
      nb += b[i] * b[i];
    }
    return dot / (Math.sqrt(na) * Math.sqrt(nb));
  }

  // -------------------------------------------------------
  // 3) SUCHE BESTES MATCH
  // -------------------------------------------------------
  async function findMatch(file) {
    await loadIndex();
    const vec = await imgToVec(file);

    let best = null;
    for (const entry of LABEL_INDEX) {
      if (!entry.img) continue;
      const v = await imgToVec(entry.img);
      const score = cosine(vec, v);
      if (!best || score > best.score) best = { score, entry };
    }

    if (!best || best.score < 0.82) return null; // Schwelle fein justierbar
    return best.entry;
  }

  // -------------------------------------------------------
  // 4) UI
  // -------------------------------------------------------
  function hasUI() {
    return document.getElementById('ados-scan-run');
  }

  function bindUI() {
    if (!hasUI()) return;
    const fileIn = document.getElementById('ados-scan-file');
    const runBtn = document.getElementById('ados-scan-run');
    const results = document.getElementById('ados-scan-results');

    runBtn.addEventListener('click', async function (ev) {
      ev.preventDefault();
      if (!(fileIn.files && fileIn.files[0])) {
        alert('Bitte ein Bild auswählen.');
        return;
      }

      runBtn.disabled = true;
      runBtn.textContent = 'Erkenne…';

      const match = await findMatch(fileIn.files[0]);

      runBtn.disabled = false;
      runBtn.textContent = 'Erkennen & suchen';

      if (!match) {
        results.innerHTML = '<div style="padding:6px;">Keine Treffer. Bitte anderes Foto versuchen.</div>';
        return;
      }

      const link = mw.util.getUrl(match.title.replace(/ /g, '_'));
      results.innerHTML = `
        <div class="ados-hit">
          <b><a href="${link}">${match.title}</a></b><br/>
          <img src="${match.img}" style="max-width:150px; margin-top:6px;">
        </div>`;
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', bindUI);
  } else bindUI();
})();








// --- kleine UI-Helfer für LabelScan-Seite ---
(function(){
  const wrap = document.getElementById('ados-labelscan');
  if (!wrap) return;

  const fileIn = document.getElementById('ados-scan-file');
  const bigBtn = document.getElementById('ados-scan-bigbtn');
  const drop = document.getElementById('ados-scan-drop');
  const preview = document.getElementById('ados-scan-preview');

  // großer Button triggert File-Input
  if (bigBtn && fileIn) {
    bigBtn.addEventListener('click', () => fileIn.click());
  }

  // Bildvorschau
  if (fileIn && preview) {
    fileIn.addEventListener('change', () => {
      if (fileIn.files && fileIn.files[0]) {
        const url = URL.createObjectURL(fileIn.files[0]);
        preview.innerHTML = `<img src="${url}" alt="Vorschau">`;
      }
    });
  }

  // Drag&Drop
  if (drop && fileIn) {
    ['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => {
      e.preventDefault(); e.stopPropagation(); drop.classList.add('is-dragover');
    }));
    ['dragleave','drop'].forEach(ev => drop.addEventListener(ev, e => {
      e.preventDefault(); e.stopPropagation(); drop.classList.remove('is-dragover');
    }));
    drop.addEventListener('drop', e => {
      const dt = e.dataTransfer;
      if (dt && dt.files && dt.files[0]) {
        fileIn.files = dt.files;
        const url = URL.createObjectURL(dt.files[0]);
        preview.innerHTML = `<img src="${url}" alt="Vorschau">`;
      }
    });
  }
})();