Files
neonail-database/resources/views/nail-polish-imports/create.blade.php
T
HousemannandClaude Sonnet 4.6 f715bc4552 Add AI photo import via Ollama Vision
Users can upload a photo of a nail polish bottle; a background queue
job sends it to Ollama (llama3.2-vision at 192.168.30.172:11434),
extracts name, number and manufacturer, then creates the nail polish
and adds it to the user's collection automatically.

- ProcessNailPolishImage job (180s timeout, 2 tries)
- NailPolishImport model + migration (status tracking)
- AiSetting model + migration (Ollama URL + model, admin-configurable)
- NailPolishImportController: upload, status page (auto-refresh every 5s)
- Admin\AiSettingController: configure Ollama connection
- Views: drag-drop upload form, live status page, import history
- Navbar: "Foto importieren" link for all users
- Admin dropdown: "KI-Einstellungen"
- neonail-worker.conf: Supervisor config for the queue worker

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

94 lines
3.8 KiB
PHP
Raw 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">
<form method="POST" action="{{ route('nail-polish-imports.store') }}" enctype="multipart/form-data" id="importForm">
@csrf
<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" required>
@error('image')
<div class="text-danger mt-2" style="font-size:.85rem;">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-primary w-100 btn-lg" id="submitBtn">
<i class="fas fa-magic me-2"></i>KI-Analyse starten
</button>
</form>
</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');
input.addEventListener('change', e => showPreview(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; showPreview(file); }
});
function showPreview(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = e => {
preview.src = e.target.result;
preview.style.display = 'block';
content.style.display = 'none';
};
reader.readAsDataURL(file);
}
document.getElementById('importForm').addEventListener('submit', () => {
const btn = document.getElementById('submitBtn');
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Wird hochgeladen…';
});
</script>
@endsection