Spaces:
Sleeping
Sleeping
| <html lang="zh-Hant"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Presence Detection Demo</title> | |
| <style> | |
| :root { | |
| --panel-bg: #dbdbdb; | |
| --box-bg: #bcd1e5; | |
| --box-bg-strong: #b6cbe0; | |
| --line: #6f6f6f; | |
| --text: #4f4f4f; | |
| } | |
| * { | |
| box-sizing: border-box; | |
| } | |
| body { | |
| margin: 0; | |
| min-height: 100vh; | |
| display: grid; | |
| place-items: center; | |
| background: #f2f2f2; | |
| color: var(--text); | |
| font-family: "Noto Sans TC", "PingFang TC", "Microsoft JhengHei", sans-serif; | |
| } | |
| .panel { | |
| width: min(92vw, 680px); | |
| min-height: 520px; | |
| background: var(--panel-bg); | |
| border: 2px solid var(--line); | |
| padding: 52px 38px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 34px; | |
| } | |
| .top-row { | |
| display: grid; | |
| grid-template-columns: minmax(220px, 1fr) 1fr; | |
| gap: 34px; | |
| align-items: center; | |
| } | |
| .upload-wrap { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 10px; | |
| } | |
| .field-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .field-label { | |
| font-size: 14px; | |
| color: #555; | |
| letter-spacing: 0.2px; | |
| } | |
| .model-select { | |
| width: 100%; | |
| padding: 11px 12px; | |
| border: 2px solid var(--line); | |
| background: #edf2f7; | |
| color: #333; | |
| font-size: 15px; | |
| } | |
| .upload-box { | |
| width: 100%; | |
| min-height: 280px; | |
| border: 2px solid var(--line); | |
| background: var(--box-bg); | |
| color: #111; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: 14px; | |
| text-align: center; | |
| cursor: pointer; | |
| position: relative; | |
| font-size: 22px; | |
| } | |
| .upload-box img { | |
| width: min(100%, 280px); | |
| height: 280px; | |
| min-width: 220px; | |
| min-height: 220px; | |
| max-width: 100%; | |
| max-height: 320px; | |
| object-fit: contain; | |
| background: #fff; | |
| } | |
| .upload-hint { | |
| font-size: 13px; | |
| color: #666; | |
| text-align: center; | |
| } | |
| .stats { | |
| font-size: clamp(20px, 2.6vw, 30px); | |
| line-height: 1.35; | |
| letter-spacing: 0.2px; | |
| } | |
| .stats .error { | |
| margin-top: 14px; | |
| color: #a22; | |
| font-size: 14px; | |
| line-height: 1.4; | |
| word-break: break-word; | |
| } | |
| .actions { | |
| margin-top: 6px; | |
| display: grid; | |
| place-items: center; | |
| gap: 14px; | |
| } | |
| .result-pill { | |
| width: min(100%, 320px); | |
| height: 88px; | |
| border: 2px solid var(--line); | |
| background: var(--box-bg-strong); | |
| display: grid; | |
| place-items: center; | |
| font-size: clamp(22px, 4.5vw, 36px); | |
| color: #4c4c4c; | |
| } | |
| .submit-btn { | |
| width: min(100%, 320px); | |
| padding: 12px 16px; | |
| border: 2px solid var(--line); | |
| background: #e9edf1; | |
| color: #333; | |
| font-size: 16px; | |
| cursor: pointer; | |
| } | |
| .submit-btn:hover { | |
| background: #f1f4f7; | |
| } | |
| .file-input { | |
| display: none; | |
| } | |
| @media (max-width: 640px) { | |
| .panel { | |
| min-height: auto; | |
| padding: 24px 18px; | |
| gap: 22px; | |
| } | |
| .top-row { | |
| grid-template-columns: 1fr; | |
| gap: 18px; | |
| } | |
| .upload-box { | |
| min-height: 220px; | |
| font-size: 18px; | |
| } | |
| .upload-box img { | |
| width: min(100%, 220px); | |
| height: 220px; | |
| min-width: 160px; | |
| min-height: 160px; | |
| } | |
| .stats { | |
| font-size: 22px; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <form class="panel" action="/demo" method="post" enctype="multipart/form-data"> | |
| <input type="hidden" name="existing_image_data_url" id="existingImageDataUrl" value="{{ image_data_url or '' }}"> | |
| <div class="top-row"> | |
| <div class="upload-wrap"> | |
| <div class="field-group"> | |
| <label class="field-label" for="modelSelect">推論模型</label> | |
| <select class="model-select" id="modelSelect" name="model_name"> | |
| {% for model in model_options %} | |
| <option value="{{ model.name }}" {% if model.name == selected_model %}selected{% endif %}> | |
| {{ model.name }} ({{ model.backend }}) | |
| </option> | |
| {% endfor %} | |
| </select> | |
| </div> | |
| <label class="upload-box" for="fileInput" id="uploadBox"> | |
| {% if image_data_url %} | |
| <img src="{{ image_data_url }}" alt="Input image preview" id="previewImage"> | |
| {% else %} | |
| <span id="placeholderText">Input Image</span> | |
| <img src="" alt="Input image preview" id="previewImage" style="display:none;"> | |
| {% endif %} | |
| </label> | |
| <input class="file-input" id="fileInput" type="file" name="file" accept="image/*"> | |
| <div class="upload-hint">點擊圖片區塊選擇檔案</div> | |
| </div> | |
| <div class="stats"> | |
| <div>Model: {{ selected_model }}</div> | |
| <div>Class: {{ class_label }}</div> | |
| <div>Confidence: {{ confidence }}</div> | |
| <div>Acc: {{ acc }}</div> | |
| {% if error %} | |
| <div class="error">{{ error }}</div> | |
| {% endif %} | |
| </div> | |
| </div> | |
| <div class="actions"> | |
| <div class="result-pill">{{ result_label_zh }}</div> | |
| <button class="submit-btn" type="submit">開始預測</button> | |
| </div> | |
| </form> | |
| <script> | |
| const form = document.querySelector(".panel"); | |
| const fileInput = document.getElementById("fileInput"); | |
| const previewImage = document.getElementById("previewImage"); | |
| const placeholderText = document.getElementById("placeholderText"); | |
| const existingImageDataUrl = document.getElementById("existingImageDataUrl"); | |
| fileInput.addEventListener("change", (event) => { | |
| const file = event.target.files?.[0]; | |
| if (!file) return; | |
| const reader = new FileReader(); | |
| reader.onload = () => { | |
| if (placeholderText) placeholderText.style.display = "none"; | |
| previewImage.src = reader.result; | |
| previewImage.style.display = "block"; | |
| existingImageDataUrl.value = reader.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| }); | |
| form.addEventListener("submit", (event) => { | |
| const hasNewFile = Boolean(fileInput.files?.length); | |
| const hasExistingImage = Boolean(existingImageDataUrl.value); | |
| if (!hasNewFile && !hasExistingImage) { | |
| event.preventDefault(); | |
| alert("請先上傳圖片。"); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |