Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>YOLO Webcam + Chat</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
| </head> | |
| <body class="bg-dark text-white"> | |
| <div class="container py-4"> | |
| <div class="d-flex justify-content-between align-items-center mb-4"> | |
| <h4>Welcome, {{ username }}</h4> | |
| <a href="/logout" class="btn btn-outline-light btn-sm">Logout</a> | |
| </div> | |
| <div class="row"> | |
| <!-- Left Column - Webcam --> | |
| <div class="col-md-8 mb-4"> | |
| <div class="text-center"> | |
| <h5>Live Webcam</h5> | |
| <img src="{{ url_for('video_feed') }}" class="img-fluid rounded" style="max-height: 600px; width: 100%;"> | |
| </div> | |
| </div> | |
| <!-- Right Column - Highlights and Chat --> | |
| <div class="col-md-4 mb-4"> | |
| <div class="sticky-top" style="top: 20px;"> | |
| <!-- Biome Display --> | |
| <div class="mb-3 p-2 bg-info bg-opacity-25 rounded text-center"> | |
| <strong>Current Biome:</strong> <span id="current-biome">Loading...</span> | |
| </div> | |
| <!-- Detection Timeline --> | |
| <div class="mb-4"> | |
| <h5>Detection Highlights</h5> | |
| <div id="categorized-timeline" class="bg-secondary rounded p-3" style="max-height: 300px; overflow-y: auto;"> | |
| <p class="text-muted">Loading detection events...</p> | |
| </div> | |
| </div> | |
| <!-- Group Chat - Moved here --> | |
| <div class="mb-4"> | |
| <h5>Group Chat</h5> | |
| <div id="chatbox" class="bg-secondary rounded p-3 mb-3" style="height: 200px; overflow-y: scroll;"></div> | |
| <div class="input-group"> | |
| <input id="message" class="form-control" placeholder="Type a message..."> | |
| <button class="btn btn-primary" onclick="sendMessage()">Send</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // === Chat Functions === | |
| function loadChat() { | |
| $.get("/chat", function(data) { | |
| let html = ""; | |
| data.forEach(msg => { | |
| html += `<p><strong>${msg.user}</strong>: ${msg.message}<br><small class="text-muted">${msg.timestamp}</small></p>`; | |
| }); | |
| $("#chatbox").html(html).scrollTop(9999); | |
| }); | |
| } | |
| function sendMessage() { | |
| $.post("/chat", {message: $("#message").val()}, function() { | |
| $("#message").val(""); | |
| loadChat(); | |
| }); | |
| } | |
| // === Detection Timeline (Categorized) === | |
| function loadTimeline() { | |
| $.get("/detections", function(response) { | |
| // Update current biome | |
| const biome = response.current_biome || "Unknown"; | |
| // Format biome name: "biome-meadows" β "Meadows" | |
| const formattedBiome = biome | |
| .replace(/^biome-/, '') | |
| .replace(/-/g, ' ') | |
| .replace(/\b\w/g, l => l.toUpperCase()); | |
| $("#current-biome").text(formattedBiome); | |
| // Render categorized events | |
| const grouped = response.events || {}; | |
| const categoryTitles = { | |
| "status": "Active Status Effects", | |
| "recent_buff": "Recent Consumables", | |
| "boss_ability": "Boss Powers", | |
| "boss_health": "Boss Health", | |
| "creature": "Creatures Nearby", | |
| "crafting": "Crafting Stations", | |
| "plant": "Plants & Crops", | |
| "storage": "Storage", | |
| "vehicle": "Vehicles", | |
| "world": "World Events", | |
| "other": "Other" | |
| }; | |
| const renderOrder = [ | |
| "status", "recent_buff", "boss_ability", "boss_health", | |
| "creature", "crafting", "plant", "storage", "vehicle", "world", "other" | |
| ]; | |
| let html = ""; | |
| let hasContent = false; | |
| renderOrder.forEach(cat => { | |
| const events = grouped[cat]; | |
| if (events && events.length > 0) { | |
| hasContent = true; | |
| const title = categoryTitles[cat] || cat.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()); | |
| html += `<h6 class="mt-3">${title}</h6><div class="ps-2">`; | |
| events.forEach(e => { | |
| const conf = (e.confidence * 100).toFixed(0); | |
| let badgeColor = "secondary"; | |
| if (cat === "status") badgeColor = "warning"; | |
| else if (cat === "boss_health") badgeColor = "danger"; | |
| else if (cat === "creature") badgeColor = "success"; | |
| else if (cat === "recent_buff") badgeColor = "primary"; | |
| else if (cat === "boss_ability") badgeColor = "dark"; | |
| else if (cat === "world") badgeColor = "info"; | |
| html += `<p class="mb-1"><span class="badge bg-${badgeColor}">${e.class}</span> ${conf}%</p>`; | |
| }); | |
| html += `</div>`; | |
| } | |
| }); | |
| $("#categorized-timeline").html( | |
| hasContent ? html : "<p class='text-muted'>No active detections.</p>" | |
| ); | |
| }).fail(() => { | |
| $("#current-biome").text("Offline"); | |
| $("#categorized-timeline").html("<p class='text-muted'>Failed to load detections.</p>"); | |
| }); | |
| } | |
| // === Polling === | |
| setInterval(loadChat, 3000); | |
| setInterval(loadTimeline, 2000); | |
| // Initial loads | |
| loadChat(); | |
| loadTimeline(); | |
| </script> | |
| <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script> | |
| </body> | |
| </html> |