Files
ai-detection/app/static/index.html
2026-05-29 00:47:49 +02:00

383 lines
25 KiB
HTML

<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FruitVision AI - POS Objekterkennung</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;500;600;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="app-container">
<!-- Header -->
<header class="app-header">
<div class="header-logo">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2C6.48 2 2 6.48 2 12C2 17.52 6.48 22 12 22C17.52 22 22 17.52 22 12C22 6.48 17.52 2 12 2ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20Z" fill="var(--color-primary)"/>
<path d="M11 6H13V12H11V6ZM11 14H13V16H11V14Z" fill="var(--color-primary)"/>
<circle cx="12" cy="12" r="3" fill="var(--color-accent)"/>
</svg>
<h1>FruitVision <span class="badge">POS-AI</span></h1>
</div>
<nav class="header-nav">
<button class="nav-btn active" id="tab-btn-checkout" onclick="switchTab('checkout')">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><line x1="12" y1="4" x2="12" y2="20"/><line x1="2" y1="12" x2="22" y2="12"/></svg>
Kassensystem
</button>
<button class="nav-btn" id="tab-btn-articles" onclick="switchTab('articles')">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><circle cx="10" cy="9" r="1"/></svg>
Artikel-Verwaltung
</button>
<button class="nav-btn" id="tab-btn-dashboard" onclick="switchTab('dashboard')">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
Developer-Dashboard
</button>
</nav>
</header>
<!-- Main Content Grid -->
<main class="app-main">
<!-- CHECKOUT TAB -->
<section id="tab-checkout" class="tab-content active">
<div class="checkout-grid">
<!-- Left: Camera Frame & Scale -->
<div class="card camera-card">
<div class="card-header">
<div>
<h2 class="card-title">Kamera-Vorschau</h2>
<p class="card-subtitle">Positioniere das lose Obst & Gemüse auf der Waage</p>
</div>
<span class="status-indicator live" id="stream-status">Simuliert</span>
</div>
<div class="camera-viewport-container">
<video id="webcam" autoplay playsinline muted style="display: none;"></video>
<canvas id="camera-canvas" width="640" height="480"></canvas>
<!-- Calibration Overlay / Grid -->
<div class="scale-target-overlay">
<div class="scale-target-box"></div>
</div>
<div class="stream-badge" id="camera-type-badge">SIMULATION-MODUS</div>
</div>
<div class="camera-controls">
<button id="toggle-camera-btn" class="btn btn-secondary">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
Webcam aktivieren
</button>
<button id="trigger-scale-btn" class="btn btn-primary btn-glow">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="2" width="12" height="20" rx="2"/><path d="M12 18h.01"/></svg>
Waage auslösen (Trigger)
</button>
<label class="toggle-control">
<input type="checkbox" id="auto-detect-toggle" checked>
<span class="toggle-slider"></span>
<span class="toggle-label">Auto-Detect</span>
</label>
</div>
<!-- Real Data Capture Panel -->
<div class="capture-training-panel">
<h3>Reales Produktbild erfassen (Datensammler)</h3>
<div class="capture-controls-row">
<select id="capture-class-select">
<!-- Options populated by JS -->
</select>
<button id="capture-image-btn" class="btn btn-accent" onclick="captureForTraining()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>
Bild aufnehmen
</button>
<span class="capture-count-badge" id="capture-count-display">Eigene Bilder: 0</span>
</div>
<p class="panel-info-text">
Legen Sie Ihre echte Kartoffel mittig in das Zielkreuz und nehmen Sie 10-15 Bilder aus verschiedenen Winkeln/Entfernungen auf.
</p>
</div>
</div>
<!-- Right: Quick Selection & Shopping Cart -->
<div class="checkout-sidebar">
<!-- AI Recommendations / Schnellwahl -->
<div class="card quick-select-card">
<div class="card-header">
<h2 class="card-title">KI Schnellwahltasten</h2>
<p class="card-subtitle">Vorschläge über Confidence-Schwellenwert &gt; 10%</p>
</div>
<div class="quick-buttons-container" id="quick-selection-buttons">
<div class="empty-state">
<p>Warte auf Objekterkennung...</p>
<span>Legen Sie Obst auf die Waage</span>
</div>
</div>
</div>
<!-- Virtual Barcode-Scanner (Tastatur-Emulator) -->
<div class="card scanner-card">
<div class="card-header">
<h2 class="card-title">Virtueller Barcode-Scanner</h2>
<label class="toggle-control">
<input type="checkbox" id="auto-scan-toggle" checked>
<span class="toggle-slider"></span>
<span class="toggle-label">Auto-Scan</span>
</label>
</div>
<div class="card-body" style="padding: 16px 20px;">
<div class="scanner-status-box" style="display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 10px;">
<span class="scanner-status-label" style="color: var(--text-secondary);">Scanner-Status:</span>
<span class="scanner-indicator" id="scanner-indicator" style="font-weight: 600; color: var(--color-success);">Bereit</span>
</div>
<div class="progress-bar-container" id="stability-progress-wrapper" style="height: 6px; margin-bottom: 12px; display: none;">
<div class="progress-bar-fill" id="stability-progress-fill" style="width: 0%; background: var(--color-accent); box-shadow: 0 0 10px var(--color-accent-glow);"></div>
</div>
<div class="terminal-container" style="margin-top: 0; margin-bottom: 10px;">
<div class="terminal-header">Scanner-Verlauf (Keystroke Logs)</div>
<div class="scanner-log-output" id="scanner-log" style="font-family: monospace; font-size: 11px; color: var(--color-success); height: 80px; overflow-y: auto; line-height: 1.4;">Warte auf Erkennung...</div>
</div>
<button id="manual-scan-btn" class="btn btn-accent btn-glow" style="width: 100%;" onclick="triggerManualScan()">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7V4h3M4 17v3h3m10-16h3v3m-3 13v3h3M8 12h8m-4-4v8"/></svg>
EAN an aktive Kasse senden
</button>
</div>
</div>
<!-- Shopping Cart / Bon -->
<div class="card cart-card">
<div class="card-header">
<h2 class="card-title">Kassenzettel</h2>
<p class="card-subtitle">Aktuelle Transaktion</p>
</div>
<div class="cart-items" id="cart-list">
<div class="empty-cart-state" id="empty-cart-msg">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="9" cy="21" r="1"/><circle cx="20" cy="21" r="1"/><path d="M1 1h4l2.68 13.39a2 2 0 0 0 2 1.61h9.72a2 2 0 0 0 2-1.61L23 6H6"/></svg>
<p>Einkaufswagen ist leer</p>
</div>
</div>
<div class="cart-summary">
<div class="summary-row">
<span>Zwischensumme:</span>
<span id="cart-subtotal">0,00 €</span>
</div>
<div class="summary-row total">
<span>Gesamtsumme:</span>
<span id="cart-total">0,00 €</span>
</div>
</div>
<div class="cart-actions">
<button id="cancel-cart-btn" class="btn btn-danger" onclick="clearCart()">Storno</button>
<button id="pay-btn" class="btn btn-success btn-glow" onclick="checkoutCart()">Bezahlen</button>
</div>
</div>
</div>
</div>
</section>
<!-- DEVELOPER DASHBOARD TAB -->
<section id="tab-dashboard" class="tab-content">
<div class="dashboard-grid">
<!-- Left: Pipeline Steps -->
<div class="dashboard-column">
<!-- Step 1: Synthetic Dataset & Augmentation -->
<div class="card">
<div class="card-header">
<span class="step-num">Schritt 1</span>
<h2 class="card-title">Synthetische Datenpipeline</h2>
<p class="card-subtitle">Generiere Trainingsdaten & Augmentierungen (Albumentations)</p>
</div>
<div class="card-body">
<div class="action-row">
<button class="btn btn-secondary" onclick="generateDataset()">
Datensatz generieren
</button>
<span class="action-desc">Erstellt 200 Trainings- & 50 Validierungsbilder (YOLO-Format)</span>
</div>
<div class="augmentation-section">
<div class="section-title-row">
<h3>Augmentierungs-Vorschau (Live-Pipeline)</h3>
<button class="btn btn-text" onclick="refreshAugmentedPreview()">Aktualisieren</button>
</div>
<p class="section-desc">Echtzeit-Störung (Shift, Rotate, Scale, Brightness, Noise) zur Vermeidung von Overfitting an Lichtbedingungen.</p>
<div class="preview-container" id="aug-preview-box">
<div class="preview-placeholder">
Klicke auf "Aktualisieren" um ein Bild und seine Albumentations-Störung anzuzeigen.
</div>
</div>
</div>
</div>
</div>
<!-- Step 3: Export & Hardware-Performance -->
<div class="card">
<div class="card-header">
<span class="step-num">Schritt 3</span>
<h2 class="card-title">ONNX-Export & Hardware-Inferenz</h2>
<p class="card-subtitle">Modell-Optimierung für flüssigen Kassenbetrieb unter 50 ms</p>
</div>
<div class="card-body">
<div class="action-row">
<button class="btn btn-accent" id="export-onnx-btn" onclick="exportModel()">
Modell in ONNX exportieren
</button>
<span class="action-desc">Konvertiert PyTorch (.pt) in ONNX (.onnx) für die Inferenz-Engine.</span>
</div>
<div class="terminal-container">
<div class="terminal-header">ONNX Compiler Logs</div>
<pre class="terminal-output" id="onnx-log">Warte auf Export-Trigger...</pre>
</div>
<div class="metric-grid">
<div class="metric-card">
<span class="metric-label">Inferenz-Zeit (CPU/NPU)</span>
<span class="metric-value text-accent" id="onnx-latency">- ms</span>
<span class="metric-desc" id="onnx-latency-status">Warte auf Export</span>
</div>
<div class="metric-card">
<span class="metric-label">Lizenzen & Schutzrecht</span>
<span class="metric-value text-success">100% OK</span>
<span class="metric-desc">Royalty-Free (BSD / MIT), kein GPL-Copyleft</span>
</div>
</div>
</div>
</div>
</div>
<!-- Right: Model Training Dashboard -->
<div class="dashboard-column">
<!-- Step 2: Model Training -->
<div class="card training-card">
<div class="card-header">
<span class="step-num">Schritt 2</span>
<h2 class="card-title">Transfer Learning (PyTorch)</h2>
<p class="card-subtitle">Modelltraining auf SSDLite MobileNetV3-Large Basis</p>
</div>
<div class="card-body">
<div class="training-controls-row">
<div class="control-group">
<label for="param-epochs">Epochen</label>
<input type="number" id="param-epochs" value="15" min="5" max="100">
</div>
<div class="control-group">
<label for="param-batch">Batch Size</label>
<select id="param-batch">
<option value="4">4</option>
<option value="8" selected>8</option>
<option value="16">16</option>
</select>
</div>
<button class="btn btn-primary" id="start-training-btn" onclick="startTraining()">
Training starten
</button>
</div>
<!-- Training Status -->
<div class="training-status-box" id="training-status-box">
<div class="status-header-row">
<span class="status-title">Status: <strong id="training-state">Bereit</strong></span>
<span class="status-epoch" id="training-epoch">Epoche: -/-</span>
</div>
<div class="progress-bar-container">
<div class="progress-bar-fill" id="training-progress-fill" style="width: 0%"></div>
</div>
<p class="status-log-message" id="training-log-msg">Klicke "Training starten" um den Feintuning-Prozess anzustoßen.</p>
</div>
<!-- Chart Area -->
<div class="chart-container">
<h3>Lernkurve (Loss History)</h3>
<div class="chart-wrapper">
<svg viewBox="0 0 500 200" class="training-chart" id="training-loss-chart">
<!-- Grid Lines -->
<line x1="40" y1="20" x2="480" y2="20" stroke="#333842" stroke-dasharray="4"/>
<line x1="40" y1="70" x2="480" y2="70" stroke="#333842" stroke-dasharray="4"/>
<line x1="40" y1="120" x2="480" y2="120" stroke="#333842" stroke-dasharray="4"/>
<line x1="40" y1="170" x2="480" y2="170" stroke="#4a505e"/>
<!-- Y-Axis Ticks -->
<text x="30" y="25" fill="#8a92a3" font-size="10" text-anchor="end">2.0</text>
<text x="30" y="75" fill="#8a92a3" font-size="10" text-anchor="end">1.0</text>
<text x="30" y="125" fill="#8a92a3" font-size="10" text-anchor="end">0.5</text>
<text x="30" y="175" fill="#8a92a3" font-size="10" text-anchor="end">0.0</text>
<!-- Chart Curves -->
<path id="train-loss-path" d="" fill="none" stroke="var(--color-primary)" stroke-width="3"/>
<path id="val-loss-path" d="" fill="none" stroke="var(--color-accent)" stroke-width="2" stroke-dasharray="2"/>
</svg>
</div>
<div class="chart-legend">
<div class="legend-item"><span class="legend-color train"></span>Train Loss</div>
<div class="legend-item"><span class="legend-color val"></span>Validation Loss</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- ARTICLE MANAGEMENT TAB -->
<section id="tab-articles" class="tab-content">
<div class="card">
<div class="card-header">
<div>
<h2 class="card-title">Artikel- & EAN-Verwaltung</h2>
<p class="card-subtitle">Hinterlegen Sie die Barcodes (EAN) Ihrer Produkte für den virtuellen Scanner</p>
</div>
<div style="display: flex; gap: 12px; align-items: center;">
<button class="btn btn-secondary" onclick="addNewArticlePrompt()">
+ Artikel hinzufügen
</button>
<button class="btn btn-primary btn-glow" onclick="saveArticlesDatabase()">
Änderungen speichern
</button>
</div>
</div>
<div class="card-body">
<p style="font-size: 13px; color: var(--text-secondary); margin-bottom: 20px; line-height: 1.5;">
Geben Sie für jede Produktklasse den gewünschten Barcode (EAN) ein. Wenn das System dieses Produkt stabil erkennt, simuliert der <strong>virtuelle Tastatur-Scanner</strong> diesen Barcode direkt in Ihr Kassenfenster (gefolgt von Enter).
</p>
<div class="articles-table-wrapper" style="overflow-x: auto;">
<table class="articles-table" style="width: 100%; border-collapse: collapse; text-align: left;">
<thead>
<tr style="border-bottom: 1px solid var(--border-glass); color: var(--text-secondary); font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px;">
<th style="padding: 12px 16px;">Klasse (ML-Key)</th>
<th style="padding: 12px 16px;">Name (Anzeige)</th>
<th style="padding: 12px 16px;">EAN-Barcode (Tastaturemulation)</th>
</tr>
</thead>
<tbody id="articles-table-body" style="font-size: 14px;">
<!-- Dynamic rows loaded by JS -->
</tbody>
</table>
</div>
</div>
</div>
</section>
</main>
<!-- Footer -->
<footer class="app-footer">
<p>Lizenz-Fokus: 100% kommerziell nutzbar, BSD & MIT Open-Source Lizenzen. ONNX Runtime Inference.</p>
</footer>
</div>
<script src="app.js"></script>
</body>
</html>