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>
84 lines
3.9 KiB
PHP
84 lines
3.9 KiB
PHP
@extends('layouts.app')
|
||
|
||
@section('title', 'Meine Importe – NeoNail DB')
|
||
|
||
@section('content')
|
||
|
||
<div class="page-hero d-flex align-items-center justify-content-between flex-wrap gap-3 mb-4">
|
||
<div>
|
||
<h1><i class="fas fa-history me-2"></i>Meine Importe</h1>
|
||
<p>KI-Analysen deiner hochgeladenen Fotos</p>
|
||
</div>
|
||
<a href="{{ route('nail-polish-imports.create') }}" class="btn btn-glass">
|
||
<i class="fas fa-camera me-2"></i>Neues Foto
|
||
</a>
|
||
</div>
|
||
|
||
@if($imports->isEmpty())
|
||
<div class="empty-state">
|
||
<div class="empty-icon"><i class="fas fa-camera"></i></div>
|
||
<h4>Noch keine Importe</h4>
|
||
<p>Lade ein Foto hoch und lass die KI deinen Nagellack erkennen.</p>
|
||
<a href="{{ route('nail-polish-imports.create') }}" class="btn btn-primary">
|
||
<i class="fas fa-camera me-2"></i>Jetzt importieren
|
||
</a>
|
||
</div>
|
||
@else
|
||
<div class="card">
|
||
<div class="card-body p-0">
|
||
<div class="table-responsive">
|
||
<table class="table mb-0" style="font-size:.875rem;">
|
||
<thead style="background:#f9fafb; border-bottom:1px solid #f3f4f6;">
|
||
<tr>
|
||
<th style="padding:12px 16px; font-weight:600; color:#374151;">Bild</th>
|
||
<th style="padding:12px 16px; font-weight:600; color:#374151;">Ergebnis</th>
|
||
<th style="padding:12px 16px; font-weight:600; color:#374151;">Status</th>
|
||
<th style="padding:12px 16px; font-weight:600; color:#374151;">Datum</th>
|
||
<th style="padding:12px 16px;"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
@foreach($imports as $import)
|
||
<tr style="border-bottom:1px solid #f3f4f6;">
|
||
<td style="padding:12px 16px;">
|
||
<img src="{{ Storage::url($import->image_path) }}"
|
||
alt=""
|
||
style="width:52px; height:52px; object-fit:cover; border-radius:10px;">
|
||
</td>
|
||
<td style="padding:12px 16px;">
|
||
@if($import->result)
|
||
<div style="font-weight:600; color:#111827;">{{ $import->result['name'] ?? '—' }}</div>
|
||
<div style="color:#9ca3af; font-size:.78rem;">{{ $import->result['manufacturer'] ?? '' }}</div>
|
||
@elseif($import->status === 'failed')
|
||
<span style="color:#dc2626; font-size:.8rem;">{{ Str::limit($import->error_message, 60) }}</span>
|
||
@else
|
||
<span style="color:#9ca3af;">wird analysiert…</span>
|
||
@endif
|
||
</td>
|
||
<td style="padding:12px 16px;">
|
||
<span class="badge bg-{{ $import->statusColor() }}">{{ $import->statusLabel() }}</span>
|
||
</td>
|
||
<td style="padding:12px 16px; color:#9ca3af;">
|
||
{{ $import->created_at->format('d.m.Y H:i') }}
|
||
</td>
|
||
<td style="padding:12px 16px; text-align:right;">
|
||
<a href="{{ route('nail-polish-imports.show', $import) }}"
|
||
class="btn btn-outline-secondary btn-sm">
|
||
<i class="fas fa-eye"></i>
|
||
</a>
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-3">
|
||
{{ $imports->links() }}
|
||
</div>
|
||
@endif
|
||
|
||
@endsection
|