cck-0702's picture
Clean commit without binary (image) files
c8c00f0
Raw
History Blame Contribute Delete
40.8 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ArtiAgent Web UI โ€” Synthetic Defect Generation</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
<!-- Header -->
<header class="header">
<h1>
๐Ÿ”ง ArtiAgent Web UI
<span class="badge" id="cuda-badge">Checking CUDA...</span>
</h1>
<div class="model-selector">
<button class="model-btn active" data-model="defectfill">DefectFill</button>
<button class="model-btn" data-model="defectdiffu">DefectDiffu</button>
</div>
</header>
<div class="container">
<!-- ==================== DEFECTFILL TAB ==================== -->
<div id="defectfill-tabs">
<div class="tabs">
<button class="tab-btn active" data-tab="training">
๐ŸŽ“ Few-Shot Training
</button>
<button class="tab-btn" data-tab="generation">
โœจ Generate Defects
</button>
</div>
<!-- ==================== TRAINING TAB ==================== -->
<div id="tab-training" class="tab-content active">
<div class="grid-2">
<!-- Left Column: Wizard Steps -->
<div class="flex flex-col gap-2">
<!-- Step 1: Product Info -->
<div class="card" id="step1">
<div class="card-header">
<h2><span class="step-number">1</span> Product & Defect Type</h2>
</div>
<div class="card-body">
<div class="form-group">
<label for="train-object-class">Product Name (Object Class)</label>
<input type="text" id="train-object-class" placeholder="e.g., triac, vcsel, xray_PCB" required>
<p class="hint">This becomes the checkpoint folder name. Use lowercase with underscores.</p>
</div>
<div class="form-group">
<label for="train-defect-type">Defect Type Name</label>
<input type="text" id="train-defect-type" placeholder="e.g., unsorted, scratch, bubble, xray_die" required>
<p class="hint">The specific defect category to learn (subfolder name).</p>
</div>
<button class="btn btn-primary" id="btn-step1">Continue โ†’</button>
<div id="step1-alert" class="alert hidden"></div>
</div>
</div>
<!-- Step 2: Good Images -->
<div class="card hidden" id="step2">
<div class="card-header">
<h2><span class="step-number">2</span> Upload Good (Clean) Images</h2>
</div>
<div class="card-body">
<div id="good-images-status" class="alert alert-info hidden"></div>
<div class="upload-zone" id="good-upload-zone">
<div class="icon">๐Ÿ“</div>
<p><strong>Click to upload</strong> or drag & drop clean images</p>
<p class="text-xs text-gray mt-1">PNG/JPG/BMP/TIF accepted. At least 1 clean image recommended.</p>
</div>
<input type="file" id="good-images" multiple accept="image/*" class="hidden">
<button class="btn btn-primary w-full mt-2" id="btn-upload-good">
๐Ÿ“ค Upload Good Images
</button>
<div id="good-images-gallery" class="image-gallery"></div>
<div class="flex gap-1 mt-2 justify-between">
<button class="btn btn-secondary hidden" id="btn-use-existing-good">โœ… Use Existing</button>
<button class="btn btn-primary hidden" id="btn-proceed-good">Continue โ†’</button>
</div>
<div id="step2-alert" class="alert hidden"></div>
</div>
</div>
<!-- Step 3: Defect Images -->
<div class="card hidden" id="step3">
<div class="card-header">
<h2><span class="step-number">3</span> Upload Defect Images</h2>
</div>
<div class="card-body">
<div class="upload-zone" id="defect-upload-zone">
<div class="icon">๐Ÿ“</div>
<p><strong>Click to upload</strong> or drag & drop</p>
<p class="text-xs text-gray mt-1">Minimum 1 images. PNG/JPG/BMP/TIF accepted.</p>
</div>
<input type="file" id="defect-images" multiple accept="image/*" class="hidden">
<button class="btn btn-primary w-full mt-2" id="btn-step3-upload">
๐Ÿ“ค Upload Images
</button>
<div id="defect-upload-gallery" class="image-gallery"></div>
<div id="step3-alert" class="alert hidden"></div>
</div>
</div>
<!-- Step 3.5: Image Alignment -->
<div class="card hidden" id="step3-5">
<div class="card-header">
<h2><span class="step-number">3.5</span> Image Alignment</h2>
</div>
<div class="card-body">
<p class="text-sm text-gray mb-2">
Could your uploaded images be rotated or facing different directions? <br>
If yes, click "Yes, align them" to automatically align all images to a reference orientation.
</p>
<div class="flex gap-1 mt-2 mb-3">
<button class="btn btn-primary" id="btn-align-yes">Yes, align them</button>
<button class="btn btn-secondary" id="btn-align-no">No, all same direction</button>
</div>
<div id="align-reference-section" class="hidden">
<div class="alert alert-info mb-2">
Upload a <strong>reference image</strong> showing the correct orientation.
All uploaded images will be automatically aligned to match this reference.
</div>
<div class="upload-zone" id="reference-upload-zone">
<div class="icon">๐Ÿ“</div>
<p><strong>Click to upload</strong> reference image</p>
<p class="text-xs text-gray mt-1">PNG/JPG accepted. One image only.</p>
</div>
<input type="file" id="reference-image" accept="image/*" class="hidden">
<div id="reference-gallery" class="image-gallery"></div>
<button class="btn btn-success w-full mt-2" id="btn-align-proceed">
<span class="spinner hidden" id="align-spinner"></span>
Align & Proceed
</button>
</div>
<div id="step3-5-alert" class="alert hidden"></div>
</div>
</div>
<!-- Step 4: Draw Masks -->
<div class="card hidden" id="step4">
<div class="card-header">
<h2><span class="step-number">4</span> Draw Defect Masks</h2>
<span id="mask-counter" class="text-sm text-gray">No image selected</span>
</div>
<div class="card-body">
<p class="text-sm text-gray mb-2">
Draw a <strong>rectangle</strong> around the defect area on each image.
You must save masks for <strong>at least 1 images</strong> before proceeding.
</p>
<div class="canvas-toolbar">
<button class="btn btn-sm btn-secondary" id="btn-prev-image">โ† Prev</button>
<button class="btn btn-sm btn-secondary" id="btn-next-image">Next โ†’</button>
<button class="btn btn-sm btn-danger" id="btn-clear-rect">Clear</button>
<span>Click & drag to draw rectangle</span>
</div>
<div class="canvas-container" id="step4-canvas">
<canvas id="mask-canvas"></canvas>
</div>
<div class="flex gap-1 mt-2">
<button class="btn btn-success" id="btn-save-mask">๐Ÿ’พ Save Mask</button>
<button class="btn btn-secondary" id="btn-skip-mask">Skip โ†’</button>
<button class="btn btn-primary" id="btn-done-masks" style="margin-left:auto;">โœ… Done</button>
</div>
<div id="step4-alert" class="alert hidden"></div>
</div>
</div>
<!-- Step 5: Domain Instructions -->
<div class="card hidden" id="step5">
<div class="card-header">
<h2><span class="step-number">5</span> Domain-Specific Instructions</h2>
</div>
<div class="card-body">
<p class="text-sm text-gray mb-2">
Add custom instructions to guide the VLM planner for this product type.
These are injected into <code>plan_defects_for_product()</code> prompts.
</p>
<div class="form-group">
<label>Load Example Template</label>
<select id="domain-example-select">
<option value="">-- Select Example --</option>
<option value="triac">TO-220 Triac (Pins/Leads)</option>
<option value="xray_pcb">X-ray PCB (Die/Solder)</option>
<option value="vcsel">VCSEL Laser Diode</option>
<option value="generic">Generic Electronic Component</option>
</select>
<button class="btn btn-sm btn-secondary mt-1" id="btn-load-domain-example">Load Example</button>
</div>
<div class="example-box hidden" id="domain-example-preview">
<h4>Example for Triac:</h4>
<pre>Use "metallic pins/leads" rather than "leg".
Target only the bottom 35% of the component where pins are located.
Defect coverage ratio must be 0.4-0.6 for pin defects.</pre>
</div>
<div class="form-group">
<label for="domain-instructions">Custom Domain Instructions</label>
<textarea id="domain-instructions" placeholder="Enter domain-specific instructions here..."></textarea>
<p class="hint">These instructions will be appended to the product description during generation to guide VLM planning.</p>
</div>
<button class="btn btn-primary" id="btn-save-domain">๐Ÿ’พ Save & Continue</button>
<div id="step5-alert" class="alert hidden"></div>
</div>
</div>
<!-- Step 6: Training Config -->
<div class="card hidden" id="step6">
<div class="card-header">
<h2><span class="step-number">6</span> Training Configuration</h2>
</div>
<div class="card-body">
<div class="grid-2">
<div class="form-group">
<label>LoRA Rank</label>
<input type="number" id="cfg-lora-rank" value="8" min="1" max="64">
</div>
<div class="form-group">
<label>LoRA Alpha</label>
<input type="number" id="cfg-lora-alpha" value="16" min="1" max="128">
</div>
<div class="form-group">
<label>Max Training Steps</label>
<input type="number" id="cfg-max-steps" value="1500" min="100" max="10000">
</div>
<div class="form-group">
<label>Batch Size</label>
<input type="number" id="cfg-batch-size" value="2" min="1" max="16">
</div>
<div class="form-group">
<label>Gradient Accumulation</label>
<input type="number" id="cfg-grad-accum" value="2" min="1" max="8">
</div>
<div class="form-group">
<label>ฮป Defect Loss</label>
<input type="number" id="cfg-lambda-defect" value="0.5" step="0.1" min="0" max="2">
</div>
<div class="form-group">
<label>ฮป Object Loss</label>
<input type="number" id="cfg-lambda-obj" value="0.2" step="0.1" min="0" max="2">
</div>
<div class="form-group">
<label>ฮป Attention Loss</label>
<input type="number" id="cfg-lambda-attn" value="0.05" step="0.01" min="0" max="1">
</div>
<div class="form-group">
<label>Alpha (BG Weight)</label>
<input type="number" id="cfg-alpha" value="0.3" step="0.1" min="0" max="1">
</div>
</div>
<div class="flex gap-1 mt-2">
<button class="btn btn-success btn-lg" id="btn-start-training">๐Ÿš€ Start Training</button>
<button class="btn btn-danger hidden" id="btn-stop-training">โน Stop</button>
</div>
<div id="step6-alert" class="alert hidden"></div>
</div>
</div>
</div>
<!-- Right Column: Training Monitor -->
<div>
<div class="card hidden" id="training-monitor">
<div class="card-header">
<h2>๐Ÿ“Š Training Monitor</h2>
<span class="status-badge idle" id="training-status-badge">
<span>โ—</span> Idle
</span>
</div>
<div class="card-body">
<div class="progress-bar mb-2">
<div class="fill" id="training-progress" style="width: 0%"></div>
</div>
<div class="log-viewer" id="training-log">
<div class="log-line text-gray">Training logs will appear here...</div>
</div>
<p class="text-xs text-gray mt-2">
Logs update every 2 seconds. Training runs in the background.
</p>
</div>
</div>
<div class="card mt-2">
<div class="card-header">
<h2>๐Ÿ“‹ Quick Guide</h2>
</div>
<div class="card-body">
<ol style="padding-left: 1.25rem; font-size: 0.875rem; color: var(--gray-600);">
<li class="mb-1">Enter product & defect names</li>
<li class="mb-1">Upload โ‰ฅ1 defect images</li>
<li class="mb-1">Draw rectangle masks on defects</li>
<li class="mb-1">Add domain instructions (optional)</li>
<li class="mb-1">Start training & wait</li>
<li>Switch to "Generate Defects" tab</li>
</ol>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== GENERATION TAB (DefectFill) ==================== -->
<div id="tab-generation" class="tab-content">
<div class="grid-2">
<!-- STEP 1: DETECT SIMILAR PRODUCT -->
<div class="card" id="card-detect-product">
<div class="card-header">
<h2><span class="step-number">1</span> Product Verification (Detect Similar Product)</h2>
</div>
<div class="card-body">
<p class="text-sm text-gray mb-2">
Not sure if this product model is trained? Upload 1 clean image to auto-detect and select the trained model. (Make sure the image is aligned, not random orientations)
</p>
<div class="upload-zone" id="detect-upload-zone">
<div class="icon">๐Ÿ”</div>
<p><strong>Click to upload</strong> clean product image</p>
<p class="text-xs text-gray mt-1">PNG/JPG/BMP/TIF accepted</p>
</div>
<input type="file" id="detect-product-image" accept="image/*" class="hidden">
<div id="detect-image-preview" class="mt-2 hidden" style="max-width: 120px;"></div>
<button class="btn btn-primary w-full mt-2" id="btn-detect-product">
๐Ÿ” Verify Product Model
</button>
<div id="detect-product-alert" class="alert hidden mt-2"></div>
</div>
</div>
<!-- STEP 2: GENERATION PARAMETERS -->
<div class="card">
<div class="card-header">
<h2><span class="step-number">2</span> Synthetic Defect Generation (DefectFill)</h2>
<button class="btn btn-sm btn-secondary" id="btn-refresh-checkpoints">๐Ÿ”„ Refresh</button>
</div>
<div class="card-body">
<form id="gen-form">
<div class="form-group">
<label for="gen-object-class">Trained Product (Object Class) *</label>
<select name="object_class" id="gen-object-class" required>
<option value="">-- Select Product --</option>
</select>
<p class="hint">Trained checkpoints auto-populate here. Train a product first.</p>
</div>
<div class="form-group">
<label for="gen-defect-type">Defect Type (Optional)</label>
<select name="defect_type" id="gen-defect-type">
<option value="">-- Auto (VLM selects) --</option>
</select>
<p class="hint">Leave empty to let the VLM choose the most plausible defect.</p>
</div>
<div class="form-group">
<label for="gen-detection-mode">Defect Area Detection Type *</label>
<select name="detection_mode" id="gen-detection-mode" required>
<option value="auto">โš™๏ธ Auto (VLM decides based on description)</option>
<option value="dots">๐Ÿ”ต Rounded Array (Solder Balls, BGA, Die Pads)</option>
<option value="lines">๐Ÿ“ Leg / Line / Rectangle (Pins, Leads, Traces)</option>
<option value="single_rounded">๐ŸŸ  Single Rounded Feature (Isolated Pads, Holes, or Dots)</option>
</select>
<p class="hint">Select "Auto" to let the VLM decide, or manually choose to guide the perception module.</p>
</div>
<div class="form-group">
<label for="gen-product-desc">Product Description *</label>
<textarea name="product_desc" id="gen-product-desc" rows="3" placeholder="Describe the product name and its key features (e.g., Triac component with three metallic leads)" required></textarea>
<p class="hint">Be specific. This drives VLM defect planning and placement.</p>
</div>
<div class="grid-2">
<div class="form-group">
<label for="gen-max-defects">Number of Defects per Image *</label>
<input type="number" name="num_defects" id="gen-num-defects" value="3" min="1" max="10" required>
</div>
<div class="form-group">
<label for="gen-guidance-scale">Guidance Scale *</label>
<input type="number" name="guidance_scale" id="gen-guidance-scale" value="8.0" step="0.5" min="1" max="20" required>
</div>
<div class="form-group">
<label for="gen-num-steps">Denoising Steps</label>
<input type="number" name="num_steps" id="gen-num-steps" value="50" min="10" max="100">
</div>
<div class="form-group">
<label for="gen-image-size">Image Size</label>
<input type="number" name="image_size" id="gen-image-size" value="512" min="256" max="1024" step="64">
</div>
</div>
<div class="form-group">
<label>Input Mode</label>
<div style="display: flex; gap: 1rem; margin-top: 0.25rem;">
<label style="cursor: pointer; font-weight: 400;">
<input type="radio" name="input_mode" value="single" checked onchange="toggleInputMode()"> Single Image
</label>
<label style="cursor: pointer; font-weight: 400;">
<input type="radio" name="input_mode" value="folder" onchange="toggleInputMode()"> Folder
</label>
</div>
</div>
<div class="form-group">
<div id="single-image-input">
<label for="gen-clean-image">Clean Product Image *</label>
<input type="file" name="clean_images" id="gen-clean-image" accept="image/*">
</div>
<div id="folder-input" class="hidden">
<label for="gen-clean-folder">Clean Product Images Folder *</label>
<input type="file" name="clean_images" id="gen-clean-folder" webkitdirectory directory multiple accept="image/*">
</div>
<p class="hint" id="input-hint">Upload a clean (non-defective) image of the product.</p>
</div>
<div class="form-group">
<label for="gen-device">Device</label>
<select name="device" id="gen-device">
<option value="cuda">CUDA (GPU)</option>
<option value="cpu">CPU (Slow)</option>
</select>
</div>
<button type="submit" class="btn btn-primary btn-lg w-full" id="btn-generate">
โœจ Generate Synthetic Defects
</button>
</form>
<div id="gen-alert" class="alert hidden"></div>
</div>
</div>
<!-- Results Preview -->
<div>
<div class="card">
<div class="card-header">
<h2>๐Ÿ–ผ๏ธ Generation Results</h2>
</div>
<div class="card-body">
<div id="gen-summary"></div>
<div id="gen-results">
<p class="text-gray text-sm" style="text-align: center; padding: 3rem 0;">
Generated images will appear here.<br>
Fill the form and click Generate.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- ==================== DEFECTDIFFU TAB ==================== -->
<div id="defectdiffu-tabs" class="hidden">
<div class="grid-2">
<!-- Left Column: Config + Generation Form -->
<div class="flex flex-col gap-2">
<!-- Model Configuration -->
<div class="card" id="dd-config-card">
<div class="card-header">
<h2>โš™๏ธ DefectDiffu Model Configuration</h2>
</div>
<div class="card-body">
<div class="form-group">
<label for="dd-ckpt-path">DefectDiffu Checkpoint Path *</label>
<input type="text" id="dd-ckpt-path" placeholder="e.g., /path/to/defectdiffu_ckpt.pt">
<p class="hint">Absolute path to the trained DefectDiffu checkpoint (.pt file).</p>
</div>
<div class="form-group">
<label for="dd-vae-path">VAE Path *</label>
<input type="text" id="dd-vae-path" placeholder="e.g., stabilityai/sd-vae-ft-mse or /path/to/vae">
<p class="hint">Path to Stable Diffusion VAE (HuggingFace model name or local path).</p>
</div>
<div class="form-group">
<label for="dd-vlm-model">VLM Model</label>
<input type="text" id="dd-vlm-model" value="gemma3:12b">
<p class="hint">Local VLM model for defect planning (e.g., gemma3:12b).</p>
</div>
<button class="btn btn-success" id="btn-save-dd-config">๐Ÿ’พ Save Configuration</button>
<div id="dd-config-alert" class="alert hidden"></div>
</div>
</div>
<!-- Generation Form -->
<div class="card">
<div class="card-header">
<h2>โœจ Generate Defects (DefectDiffu)</h2>
</div>
<div class="card-body">
<form id="dd-gen-form">
<div class="form-group">
<label for="dd-product-desc">Product Description *</label>
<textarea name="product_desc" id="dd-product-desc" rows="3" placeholder="Describe the product (e.g., VCSEL laser diode with glass lens cap and metallic can package)" required></textarea>
<p class="hint">Be specific. This drives VLM defect planning and DefectDiffu generation.</p>
</div>
<div class="form-group">
<label for="dd-defect-type">Target Defect Type (Optional)</label>
<input type="text" name="defect_type" id="dd-defect-type" placeholder="e.g., bubble, scratch, crack">
<p class="hint">Leave empty to let the VLM choose the most plausible defect type.</p>
</div>
<div class="grid-2">
<div class="form-group">
<label for="dd-num-defects">Max Defects per Image *</label>
<input type="number" name="num_defects" id="dd-num-defects" value="3" min="1" max="10" required>
</div>
<div class="form-group">
<label for="dd-num-steps">Denoising Steps</label>
<input type="number" name="num_steps" id="dd-num-steps" value="50" min="10" max="100">
</div>
<div class="form-group">
<label for="dd-image-size">Image Size</label>
<input type="number" name="image_size" id="dd-image-size" value="512" min="256" max="1024" step="64">
</div>
<div class="form-group">
<label for="dd-device">Device</label>
<select name="device" id="dd-device">
<option value="cuda">CUDA (GPU)</option>
<option value="cpu">CPU (Slow)</option>
</select>
</div>
</div>
<div class="form-group">
<label>Input Mode</label>
<div style="display: flex; gap: 1rem; margin-top: 0.25rem;">
<label style="cursor: pointer; font-weight: 400;">
<input type="radio" name="dd_input_mode" value="single" checked onchange="toggleDDInputMode()"> Single Image
</label>
<label style="cursor: pointer; font-weight: 400;">
<input type="radio" name="dd_input_mode" value="folder" onchange="toggleDDInputMode()"> Folder
</label>
</div>
</div>
<div class="form-group">
<div id="dd-single-image-input">
<label for="dd-clean-image">Clean Product Image *</label>
<input type="file" name="clean_images" id="dd-clean-image" accept="image/*">
</div>
<div id="dd-folder-input" class="hidden">
<label for="dd-clean-folder">Clean Product Images Folder *</label>
<input type="file" name="clean_images" id="dd-clean-folder" webkitdirectory directory multiple accept="image/*">
</div>
<p class="hint" id="dd-input-hint">Upload a clean (non-defective) image of the product.</p>
</div>
<button type="submit" class="btn btn-primary btn-lg w-full" id="btn-dd-generate">
โœจ Generate with DefectDiffu
</button>
</form>
<div id="dd-gen-alert" class="alert hidden"></div>
</div>
</div>
</div>
<!-- Right Column: Results -->
<div>
<div class="card">
<div class="card-header">
<h2>๐Ÿ–ผ๏ธ DefectDiffu Generation Results</h2>
</div>
<div class="card-body">
<div id="dd-gen-summary"></div>
<div id="dd-gen-results">
<p class="text-gray text-sm" style="text-align: center; padding: 3rem 0;">
Generated images will appear here.<br>
Configure model paths, fill the form, and click Generate.
</p>
</div>
</div>
</div>
<div class="card mt-2">
<div class="card-header">
<h2>๐Ÿ“‹ DefectDiffu Guide</h2>
</div>
<div class="card-body">
<ol style="padding-left: 1.25rem; font-size: 0.875rem; color: var(--gray-600);">
<li class="mb-1">Set DefectDiffu checkpoint & VAE paths</li>
<li class="mb-1">Enter product description</li>
<li class="mb-1">Optionally specify a target defect type</li>
<li class="mb-1">Upload clean product image(s)</li>
<li>Click Generate โ€” no training needed!</li>
</ol>
<p class="text-xs text-gray mt-2">
<strong>Note:</strong> DefectDiffu uses text-guided diffusion with three disentangled prompts (c_p, c_d, c_f) and does not require few-shot training like DefectFill.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Duplicate Product Modal -->
<div id="duplicate-product-modal" class="modal-overlay hidden">
<div class="modal-content" style="max-width: 520px; padding: 2rem;">
<h3 style="margin-bottom: 0.75rem; color: var(--warning);">โš ๏ธ Product Already Exists</h3>
<p style="margin-bottom: 1rem; color: var(--gray-600); font-size: 0.9375rem;">
The product name <strong id="dup-modal-product-name" style="color: var(--gray-800);"></strong> already exists in the training database.
</p>
<div id="dup-modal-image-container" style="text-align: center; margin-bottom: 1.25rem;">
<img id="dup-modal-sample-image" src="" alt="Sample product image"
style="max-width: 100%; max-height: 220px; border-radius: var(--radius); border: 1px solid var(--gray-200); object-fit: contain; background: var(--gray-50);">
<p style="font-size: 0.75rem; color: var(--gray-400); margin-top: 0.25rem;">Sample from existing training data</p>
</div>
<p style="margin-bottom: 0.5rem; color: var(--gray-600); font-size: 0.875rem;">
Existing defect types: <span id="dup-modal-defect-types" style="font-weight: 600; color: var(--primary);"></span>
</p>
<p style="margin-bottom: 1.5rem; color: var(--gray-600); font-size: 0.875rem;">
What would you like to do?
</p>
<div style="display: flex; gap: 0.75rem; flex-direction: column;">
<button class="btn btn-primary" id="btn-add-defect-type" style="justify-content: flex-start; text-align: left;">
โž• Add a <strong>new defect type</strong> to this existing product
</button>
<button class="btn btn-secondary" id="btn-new-product" style="justify-content: flex-start; text-align: left;">
๐Ÿ†• Create a <strong>new product</strong> (requires a different name)
</button>
</div>
<button class="modal-close" id="btn-close-dup-modal">ร—</button>
</div>
</div>
<script src="{{ url_for('static', filename='js/app.js') }}"></script>
</body>
</html>