LabelScan: Unterschied zwischen den Versionen

Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
 
(37 dazwischenliegende Versionen desselben Benutzers werden nicht angezeigt)
Zeile 1: Zeile 1:
<html>
<html>
<div id="ados-labelscan" class="ados-scan">
<div id="ados-labelscan">
  <h2>📸 Abfüllung scannen</h2>
  <p>Foto der Front-Labelseite aufnehmen oder wählen. Die Erkennung läuft vollständig lokal im Browser.</p>


   <div class="ados-scan__uploader" id="ados-scan-drop">
   <!-- Kopf -->
    <button id="ados-scan-bigbtn" type="button" class="ados-scan__btn">
  <div class="card" style="margin-bottom:10px;">
      <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9 2l1.5 2H14l1.5-2H19a2 2 0 012 2v14a2 2 0 01-2 2H5a2 2 0 01-2-2V4a2 2 0 012-2h4zM12 7a5 5 0 100 10 5 5 0 000-10zm0 2.2A2.8 2.8 0 1112 15a2.8 2.8 0 010-5.6z"/></svg>
    <h2>📸 Abfüllung scannen</h2>
      <span>Foto aufnehmen / wählen</span>
     <p class="sub">Foto des Frontlabels aufnehmen oder auswählen. Der Abgleich erfolgt lokal im Browser – keine Uploads.</p>
      <small>Oder Bild hierher ziehen</small>
     </button>
    <!-- verstecktes echtes File-Input -->
    <input id="ados-scan-file" type="file" accept="image/*" capture="environment" style="display:none">
   </div>
   </div>


   <div class="ados-scan__progress" aria-live="polite">
   <div class="scan-grid">
    <div class="ados-scan__status" id="ados-scan-status">Bereit.</div>
    <progress id="ados-scan-progress" max="1" value="0" style="display:none"></progress>
  </div>
 
  <div id="ados-scan-preview" class="ados-scan__preview" aria-hidden="true"></div>


  <div class="ados-scan__actions">
    <!-- Linke Spalte -->
     <button id="ados-scan-run" type="button" class="ados-scan__run">Erkennen &amp; suchen</button>
     <div class="col">
  </div>


  <h3>Vorschläge</h3>
      <div class="card">
  <div id="ados-scan-results" class="ados-scan__results"></div>
</div>
</html>


{{#tag:html|
        <!-- Dropzone (nur für Drag & Drop) -->
<script>
        <label id="ados-scan-drop" class="ados-drop" aria-label="Bild hier ablegen">
(function(){
          <div class="icon">🖼️</div>
  document.addEventListener('DOMContentLoaded',function(){
          <div><b>Bild hierher ziehen</b> oder unten auswählen</div>
          <div class="help">Tipp: frontal, gute Beleuchtung, kein Blitz-Reflex.</div>
        </label>


    var run  = document.getElementById('ados-scan-run');
        <!-- getrennte Buttons + Inputs -->
    var file = document.getElementById('ados-scan-file');
        <div class="ados-scan__pick" style="margin-top:10px; display:flex; gap:6px; justify-content:center;">
    var big  = document.getElementById('ados-scan-bigbtn');
          <button id="ados-scan-btn-camera"  type="button" class="btn">📷 Foto aufnehmen</button>
    var prev = document.getElementById('ados-scan-preview');
          <button id="ados-scan-btn-gallery" type="button" class="btn">🖼️ Bild wählen</button>
    var stat = document.getElementById('ados-scan-status');
        </div>
    var prog = document.getElementById('ados-scan-progress');


    if(!run || !file){ console.warn('[LabelScan] UI-Elemente fehlen'); return; }
        <input id="ados-scan-file-camera"  type="file" accept="image/*" capture="environment" hidden>
        <input id="ados-scan-file-gallery" type="file" accept="image/*" hidden>


    function setStatus(t){ stat.textContent = t || ''; }
        <!-- Status + Progress -->
    function setProgress(p){
        <div class="statusbar" style="margin-top:8px;">
      if(p==null){ prog.style.display='none'; prog.value=0; return; }
          <div id="ados-scan-status">Bereit.</div>
      prog.style.display=''; prog.value=Math.max(0,Math.min(1,p));
          <progress id="ados-scan-progress" max="1" value="0" hidden></progress>
    }
        </div>
    function showPreview(f){
      var url = URL.createObjectURL(f);
      prev.innerHTML = '<img alt="Vorschau" style="max-width:100%;border-radius:10px;box-shadow:0 2px 10px rgba(0,0,0,.06)" src="'+url+'">';
      prev.setAttribute('aria-hidden','false');
    }


    // großer Button öffnet echte Dateiauswahl
        <!-- Aktionen -->
    if (big) big.addEventListener('click', function(){ file.click(); });
        <div class="action-row" style="margin-top:8px; display:flex; gap:8px; justify-content:center;">
    // Vorschau bei Auswahl
          <button id="ados-scan-run" type="button" class="btn btn-primary">🔍 Erkennen &amp; suchen</button>
    file.addEventListener('change', function(){ if (this.files && this.files[0]) showPreview(this.files[0]); });
          <button id="ados-scan-reset" type="button" class="btn btn-ghost">Zurücksetzen</button>
        </div>


    // Drag&Drop (optional, falls du #ados-scan-drop nutzt)
      </div>
    var drop = document.getElementById('ados-scan-drop');
    if (drop){
      drop.addEventListener('dragover', function(e){ e.preventDefault(); drop.classList.add('dragover'); });
      drop.addEventListener('dragleave', function(){ drop.classList.remove('dragover'); });
      drop.addEventListener('drop', function(e){
        e.preventDefault(); drop.classList.remove('dragover');
        if (e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files.length){
          file.files = e.dataTransfer.files;
          showPreview(file.files[0]);
        }
      });
    }


    // Tesseract lazy-load mit Fallback-CDN
      <!-- Vorschau -->
    function loadTesseract(){
       <div id="ados-scan-preview" class="card preview" aria-live="polite" aria-atomic="true">
       return new Promise(function(resolve,reject){
         <div class="note">Noch keine Vorschau. Wähle ein Foto.</div>
        if (window.Tesseract) return resolve();
       </div>
        var s=document.createElement('script');
        s.src='https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';
        s.async=true;
        s.onload=resolve;
         s.onerror=function(){
          var s2=document.createElement('script');
          s2.src='https://unpkg.com/tesseract.js@5/dist/tesseract.min.js';
          s2.async=true;
          s2.onload=resolve;
          s2.onerror=function(){ reject(new Error('Tesseract konnte nicht geladen werden')); };
          document.head.appendChild(s2);
        };
        document.head.appendChild(s);
       });
    }


    // Heuristik -> Query
       <div class="note" style="margin-top:6px;">
    function extractHints(text){
        🔒 Deine Fotos bleiben auf deinem Gerät. Es wird nichts auf den Server hochgeladen.
       var raw  = (text||'').replace(/\s+/g,' ').trim();
       </div>
      var words = (raw.match(/\b[A-ZÄÖÜ][A-Za-zÄÖÜäöüß\-]{3,}\b/g) || []).slice(0,5);
      var ages  = (raw.match(/\b([1-9]\d?)\s?(?:years?|yo|jahr|jahre)\b/gi) || []).map(function(s){ var m=s.match(/[1-9]\d?/); return m?m[0]:null; }).filter(Boolean);
      var years = (raw.match(/\b(19|20)\d{2}\b/g) || []);
      return {words:words, ages:ages, years:years};
    }
    function buildSearchQuery(h){
      var parts=[];
      h.words.forEach(function(w){ parts.push('"' + w + '"'); });
      h.ages.forEach(function(a){ parts.push('"' + a + '"'); });
       h.years.forEach(function(y){ parts.push('"' + y + '"'); });
      if (!parts.length) parts.push('Whisky');
      return parts.join(' ');
    }


     function renderResults(items){
     </div>
      var wrap = document.getElementById('ados-scan-results');
      wrap.innerHTML = '';
      if (!items.length){
        wrap.innerHTML = '<div class="ados-hit">Keine klaren Treffer. Bitte anderes Foto oder manuell suchen.</div>';
        return;
      }
      items.slice(0,8).forEach(function(h){
        var title = h.title || h.Seitentitel || '';
        var link  = mw.util.getUrl( title.replace(/ /g,'_') );
        var snip  = (h.snippet||'').replace(/<\/?span[^>]*>/g,'').replace(/&quot;/g,'"');
        var div  = document.createElement('div');
        div.className = 'ados-hit';
        div.style.border='1px solid #eee'; div.style.borderRadius='10px'; div.style.padding='10px'; div.style.background='#fafafa';
        div.innerHTML = '<b><a href="'+link+'">'+mw.html.escape(title)+'</a></b>' + (snip ? '<div class="meta" style="color:#666">'+snip+'</div>' : '');
        wrap.appendChild(div);
      });
    }


     // Klick-Handler: Erkennen & suchen
     <!-- Rechte Spalte -->
     run.addEventListener('click', function(ev){
     <div class="col">
       ev.preventDefault();
       <div class="card">
      if (!(file.files && file.files[0])){ alert('Bitte ein Foto auswählen oder aufnehmen.'); return; }
        <h3 style="margin:0 0 .4rem;">🔎 Ergebnisse</h3>
      var f = file.files[0];
         <div id="ados-scan-results" class="results">
 
           <div class="empty">Hier erscheinen passende Abfüllungen mit Link ins Wiki.</div>
      (async function(){
        </div>
         try{
      </div>
          run.disabled=true; run.textContent='Erkenne …';
      <div class="card">
           setStatus('Erkenne Label …');
        <h3 style="margin:0 0 .4rem;">Tipps</h3>
          await loadTesseract();
        <ul style="margin:.25rem 0 .2rem 1.1rem;">
          setProgress(0);
          <li>Frontlabel direkt fotografieren</li>
 
          <li>Gute Beleuchtung</li>
          var result = await Tesseract.recognize(f, 'deu+eng', {
           <li>Label möglichst das ganze Bild füllen</li>
            logger: function(m){
           <li>Bei Spiegelungen: leicht schräg, aber nah</li>
              if (m && m.status === 'recognizing text' && typeof m.progress === 'number') setProgress(m.progress);
           <li>keine ähnlichen Flaschen im Hintergrund die im Fokus sind</li>
            }
         </ul>
          });
       </div>
 
     </div>
          setProgress(null);
<button id="ados-install" class="mw-ui-button" style="display:none">📲 LabelScan installieren</button>
          setStatus('Suche im Wiki …');
   </div>
 
</div>
          var text  = (result && result.data && result.data.text) || '';
</html>
          var hints = extractHints(text);
          var query = buildSearchQuery(hints);
 
          mw.loader.using('mediawiki.api').then(async function(){
            var api = new mw.Api();
            var r  = await api.get({ action:'query', list:'search', srsearch:query, srlimit:10, formatversion:2 });
            var hits = (r.query && r.query.search) || [];
            renderResults(hits);
            setStatus('Fertig.');
            run.disabled=false; run.textContent='Erkennen & suchen';
           });
 
        } catch(e){
           console.error('[LabelScan] Fehler:', e);
           setProgress(null);
          setStatus('Fehler bei Erkennung/Suche. Bitte erneut versuchen.');
          run.disabled=false; run.textContent='Erkennen & suchen';
         }
       })();
     });
 
    console.log('LabelScan inline bound');
   });
})();
</script>
}}