Files
HousemannandClaude Sonnet 4.6 f94f53abbd Improve AI import UX and recognition quality
- AJAX upload with progress bar — browser no longer blocks during upload;
  redirects to status page immediately after file transfer completes
- Improved Ollama prompt: clearly separates manufacturer (brand logo),
  product name (descriptive text) and number (article code) to fix
  misidentification like treating the product name as manufacturer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-13 17:52:19 +02:00

151 lines
5.8 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@extends('layouts.app')
@section('title', 'Foto importieren NeoNail DB')
@section('content')
<div class="page-hero mb-4">
<h1><i class="fas fa-camera me-2"></i>Foto importieren</h1>
<p>Lade ein Foto deines Nagellacks hoch die KI erkennt Name, Nummer und Hersteller automatisch.</p>
</div>
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="card">
<div class="card-body p-4">
<div id="errorBox" class="alert alert-danger mb-3 d-none"></div>
<div class="mb-4">
<label class="form-label">Foto des Nagellacks</label>
<div id="dropzone"
style="border: 2px dashed #d1d5db; border-radius: 16px; padding: 48px 20px;
text-align: center; cursor: pointer; transition: all .2s;"
onclick="document.getElementById('image').click()">
<div id="dropzoneContent">
<i class="fas fa-cloud-upload-alt" style="font-size: 2.5rem; color: #9ca3af; margin-bottom: 12px; display: block;"></i>
<p style="color: #6b7280; margin: 0; font-weight: 500;">Foto hierher ziehen oder klicken</p>
<p style="color: #9ca3af; font-size: .8rem; margin: 4px 0 0;">JPG, PNG, WebP max. 10 MB</p>
</div>
<img id="preview" src="" alt="" style="display:none; max-height:280px; border-radius:10px; max-width:100%;">
</div>
<input type="file" id="image" name="image" accept="image/*" class="d-none">
</div>
{{-- Upload-Progress --}}
<div id="progressWrap" class="d-none mb-3">
<div class="d-flex justify-content-between mb-1" style="font-size:.82rem; color:#6b7280;">
<span>Wird hochgeladen…</span>
<span id="progressPct">0%</span>
</div>
<div class="progress" style="height:6px; border-radius:3px;">
<div id="progressBar" class="progress-bar"
style="width:0%; background:linear-gradient(135deg,#7c3aed,#ec4899); transition:width .2s;"></div>
</div>
</div>
<button id="submitBtn" class="btn btn-primary w-100 btn-lg" onclick="startUpload()" disabled>
<i class="fas fa-magic me-2"></i>KI-Analyse starten
</button>
</div>
</div>
<div class="text-center mt-3">
<a href="{{ route('nail-polish-imports.index') }}" style="color: rgba(255,255,255,.65); font-size:.85rem;">
<i class="fas fa-history me-1"></i>Meine Importe ansehen
</a>
</div>
</div>
</div>
@endsection
@section('scripts')
<script>
const dropzone = document.getElementById('dropzone');
const input = document.getElementById('image');
const preview = document.getElementById('preview');
const content = document.getElementById('dropzoneContent');
const submitBtn = document.getElementById('submitBtn');
input.addEventListener('change', e => { if (e.target.files[0]) selectFile(e.target.files[0]); });
dropzone.addEventListener('dragover', e => { e.preventDefault(); dropzone.style.borderColor = '#7c3aed'; });
dropzone.addEventListener('dragleave', () => { dropzone.style.borderColor = '#d1d5db'; });
dropzone.addEventListener('drop', e => {
e.preventDefault();
dropzone.style.borderColor = '#d1d5db';
const file = e.dataTransfer.files[0];
if (file) { input.files = e.dataTransfer.files; selectFile(file); }
});
function selectFile(file) {
const reader = new FileReader();
reader.onload = e => {
preview.src = e.target.result;
preview.style.display = 'block';
content.style.display = 'none';
};
reader.readAsDataURL(file);
submitBtn.disabled = false;
}
function startUpload() {
const file = input.files[0];
if (!file) return;
submitBtn.disabled = true;
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Wird hochgeladen…';
document.getElementById('progressWrap').classList.remove('d-none');
document.getElementById('errorBox').classList.add('d-none');
const formData = new FormData();
formData.append('image', file);
formData.append('_token', '{{ csrf_token() }}');
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', e => {
if (e.lengthComputable) {
const pct = Math.round(e.loaded / e.total * 100);
document.getElementById('progressBar').style.width = pct + '%';
document.getElementById('progressPct').textContent = pct + '%';
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
try {
const data = JSON.parse(xhr.responseText);
if (data.redirect) { window.location.href = data.redirect; return; }
} catch(e) {}
}
showError('Upload fehlgeschlagen (Status ' + xhr.status + '). Bitte erneut versuchen.');
resetBtn();
});
xhr.addEventListener('error', () => {
showError('Netzwerkfehler beim Upload. Bitte erneut versuchen.');
resetBtn();
});
xhr.open('POST', '{{ route('nail-polish-imports.store') }}');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.send(formData);
}
function showError(msg) {
const box = document.getElementById('errorBox');
box.textContent = msg;
box.classList.remove('d-none');
document.getElementById('progressWrap').classList.add('d-none');
}
function resetBtn() {
submitBtn.disabled = false;
submitBtn.innerHTML = '<i class="fas fa-magic me-2"></i>KI-Analyse starten';
}
</script>
@endsection