Redesign for a clean, professional look
Browse files- app/core/config.py +2 -2
- app/main.py +2 -2
- static/css/dashboard-simple.css +6 -2
- static/js/dashboard.js +1 -1
- static/js/main.js +1 -1
- templates/base.html +3 -4
- templates/dashboard.html +11 -11
- test_dashboard.py +145 -0
app/core/config.py
CHANGED
|
@@ -12,9 +12,9 @@ class Settings(BaseSettings):
|
|
| 12 |
|
| 13 |
# API Configuration
|
| 14 |
API_V1_STR: str = "/api/v1"
|
| 15 |
-
PROJECT_NAME: str = "
|
| 16 |
VERSION: str = "1.0.0"
|
| 17 |
-
DESCRIPTION: str = "FastAPI-based
|
| 18 |
|
| 19 |
# Model Configuration
|
| 20 |
MODEL_NAME: str = "marina-benthic-33k"
|
|
|
|
| 12 |
|
| 13 |
# API Configuration
|
| 14 |
API_V1_STR: str = "/api/v1"
|
| 15 |
+
PROJECT_NAME: str = "Seamo Species API"
|
| 16 |
VERSION: str = "1.0.0"
|
| 17 |
+
DESCRIPTION: str = "FastAPI-based species identification using YOLOv5"
|
| 18 |
|
| 19 |
# Model Configuration
|
| 20 |
MODEL_NAME: str = "marina-benthic-33k"
|
app/main.py
CHANGED
|
@@ -96,7 +96,7 @@ app.include_router(api_router, prefix=settings.API_V1_STR)
|
|
| 96 |
@app.get("/", response_class=HTMLResponse, tags=["Dashboard"])
|
| 97 |
async def dashboard(request: Request):
|
| 98 |
"""
|
| 99 |
-
Main dashboard for
|
| 100 |
Provides a web interface for testing the API.
|
| 101 |
"""
|
| 102 |
return templates.TemplateResponse("dashboard.html", {
|
|
@@ -111,7 +111,7 @@ async def api_root():
|
|
| 111 |
API root endpoint providing basic API information.
|
| 112 |
"""
|
| 113 |
return {
|
| 114 |
-
"message": "
|
| 115 |
"version": settings.VERSION,
|
| 116 |
"docs": "/docs",
|
| 117 |
"health": f"{settings.API_V1_STR}/health",
|
|
|
|
| 96 |
@app.get("/", response_class=HTMLResponse, tags=["Dashboard"])
|
| 97 |
async def dashboard(request: Request):
|
| 98 |
"""
|
| 99 |
+
Main dashboard for species identification.
|
| 100 |
Provides a web interface for testing the API.
|
| 101 |
"""
|
| 102 |
return templates.TemplateResponse("dashboard.html", {
|
|
|
|
| 111 |
API root endpoint providing basic API information.
|
| 112 |
"""
|
| 113 |
return {
|
| 114 |
+
"message": "Seamo Species API",
|
| 115 |
"version": settings.VERSION,
|
| 116 |
"docs": "/docs",
|
| 117 |
"health": f"{settings.API_V1_STR}/health",
|
static/css/dashboard-simple.css
CHANGED
|
@@ -139,8 +139,10 @@
|
|
| 139 |
}
|
| 140 |
|
| 141 |
.upload-icon {
|
| 142 |
-
font-size:
|
| 143 |
color: #9ca3af;
|
|
|
|
|
|
|
| 144 |
}
|
| 145 |
|
| 146 |
.upload-primary {
|
|
@@ -264,8 +266,10 @@
|
|
| 264 |
}
|
| 265 |
|
| 266 |
.placeholder-icon {
|
| 267 |
-
font-size:
|
| 268 |
margin-bottom: 10px;
|
|
|
|
|
|
|
| 269 |
}
|
| 270 |
|
| 271 |
/* Results */
|
|
|
|
| 139 |
}
|
| 140 |
|
| 141 |
.upload-icon {
|
| 142 |
+
font-size: 4rem;
|
| 143 |
color: #9ca3af;
|
| 144 |
+
font-weight: 300;
|
| 145 |
+
line-height: 1;
|
| 146 |
}
|
| 147 |
|
| 148 |
.upload-primary {
|
|
|
|
| 266 |
}
|
| 267 |
|
| 268 |
.placeholder-icon {
|
| 269 |
+
font-size: 3rem;
|
| 270 |
margin-bottom: 10px;
|
| 271 |
+
color: #d1d5db;
|
| 272 |
+
font-weight: 300;
|
| 273 |
}
|
| 274 |
|
| 275 |
/* Results */
|
static/js/dashboard.js
CHANGED
|
@@ -136,7 +136,7 @@ class MarineDashboard {
|
|
| 136 |
this.uploadArea.classList.add('has-image');
|
| 137 |
this.uploadArea.innerHTML = `
|
| 138 |
<div class="upload-content">
|
| 139 |
-
<div class="upload-icon">
|
| 140 |
<div class="upload-text">
|
| 141 |
<p class="upload-primary">${file.name}</p>
|
| 142 |
<p class="upload-secondary">${window.MarineAPI.utils.formatFileSize(file.size)}</p>
|
|
|
|
| 136 |
this.uploadArea.classList.add('has-image');
|
| 137 |
this.uploadArea.innerHTML = `
|
| 138 |
<div class="upload-content">
|
| 139 |
+
<div class="upload-icon">β</div>
|
| 140 |
<div class="upload-text">
|
| 141 |
<p class="upload-primary">${file.name}</p>
|
| 142 |
<p class="upload-secondary">${window.MarineAPI.utils.formatFileSize(file.size)}</p>
|
static/js/main.js
CHANGED
|
@@ -78,7 +78,7 @@ const utils = {
|
|
| 78 |
|
| 79 |
// Initialize when DOM is loaded
|
| 80 |
document.addEventListener('DOMContentLoaded', () => {
|
| 81 |
-
console.log('
|
| 82 |
|
| 83 |
// Store original button text for loading states
|
| 84 |
document.querySelectorAll('.btn').forEach(btn => {
|
|
|
|
| 78 |
|
| 79 |
// Initialize when DOM is loaded
|
| 80 |
document.addEventListener('DOMContentLoaded', () => {
|
| 81 |
+
console.log('Seamo Species API Dashboard Loaded');
|
| 82 |
|
| 83 |
// Store original button text for loading states
|
| 84 |
document.querySelectorAll('.btn').forEach(btn => {
|
templates/base.html
CHANGED
|
@@ -3,7 +3,7 @@
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>{% block title %}
|
| 7 |
|
| 8 |
<!-- Inter Font -->
|
| 9 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
@@ -41,8 +41,7 @@
|
|
| 41 |
<header class="header">
|
| 42 |
<div class="container">
|
| 43 |
<div class="nav-brand">
|
| 44 |
-
<span class="nav-
|
| 45 |
-
<span class="nav-title">Marine Species API</span>
|
| 46 |
</div>
|
| 47 |
|
| 48 |
<nav class="nav-tabs">
|
|
@@ -69,7 +68,7 @@
|
|
| 69 |
<!-- Footer -->
|
| 70 |
<footer class="footer">
|
| 71 |
<div class="container">
|
| 72 |
-
<p>© 2025
|
| 73 |
</div>
|
| 74 |
</footer>
|
| 75 |
|
|
|
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{% block title %}Seamo Species API{% endblock %}</title>
|
| 7 |
|
| 8 |
<!-- Inter Font -->
|
| 9 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
|
|
| 41 |
<header class="header">
|
| 42 |
<div class="container">
|
| 43 |
<div class="nav-brand">
|
| 44 |
+
<span class="nav-title">Seamo Species API</span>
|
|
|
|
| 45 |
</div>
|
| 46 |
|
| 47 |
<nav class="nav-tabs">
|
|
|
|
| 68 |
<!-- Footer -->
|
| 69 |
<footer class="footer">
|
| 70 |
<div class="container">
|
| 71 |
+
<p>© 2025 Seamo Species API. Powered by YOLOv5.</p>
|
| 72 |
</div>
|
| 73 |
</footer>
|
| 74 |
|
templates/dashboard.html
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{% extends "base.html" %}
|
| 2 |
|
| 3 |
-
{% block title %}Dashboard -
|
| 4 |
|
| 5 |
{% block extra_css %}
|
| 6 |
<link rel="stylesheet" href="/static/css/dashboard.css">
|
|
@@ -11,9 +11,9 @@
|
|
| 11 |
<div class="dashboard">
|
| 12 |
<!-- Page Header -->
|
| 13 |
<div class="page-header">
|
| 14 |
-
<h1>
|
| 15 |
<p class="page-description">
|
| 16 |
-
Upload an image to identify marine species using our advanced YOLOv5 model.
|
| 17 |
Supports 691+ species including fish, corals, sea stars, and more.
|
| 18 |
</p>
|
| 19 |
</div>
|
|
@@ -33,13 +33,13 @@
|
|
| 33 |
<div class="panel input-panel">
|
| 34 |
<div class="card">
|
| 35 |
<div class="card-header">
|
| 36 |
-
<h3 class="card-title">
|
| 37 |
</div>
|
| 38 |
<div class="card-body">
|
| 39 |
<!-- Upload Area -->
|
| 40 |
<div class="upload-area" id="uploadArea">
|
| 41 |
<div class="upload-content">
|
| 42 |
-
<div class="upload-icon">
|
| 43 |
<div class="upload-text">
|
| 44 |
<p class="upload-primary">Drag image here or click to browse</p>
|
| 45 |
<p class="upload-secondary">PNG, JPG, JPEG up to 10MB</p>
|
|
@@ -50,7 +50,7 @@
|
|
| 50 |
|
| 51 |
<!-- Settings -->
|
| 52 |
<div class="settings-section">
|
| 53 |
-
<h4>
|
| 54 |
<div class="settings-grid">
|
| 55 |
<div class="setting-item">
|
| 56 |
<label for="confidenceSlider">Confidence Threshold</label>
|
|
@@ -71,7 +71,7 @@
|
|
| 71 |
|
| 72 |
<!-- Action Button -->
|
| 73 |
<button class="btn btn-primary btn-full" id="identifyBtn" disabled>
|
| 74 |
-
|
| 75 |
</button>
|
| 76 |
</div>
|
| 77 |
</div>
|
|
@@ -81,12 +81,12 @@
|
|
| 81 |
<div class="panel image-panel">
|
| 82 |
<div class="card">
|
| 83 |
<div class="card-header">
|
| 84 |
-
<h3 class="card-title">
|
| 85 |
</div>
|
| 86 |
<div class="card-body">
|
| 87 |
<div class="image-container" id="originalImageContainer">
|
| 88 |
<div class="image-placeholder">
|
| 89 |
-
<div class="placeholder-icon">
|
| 90 |
<p>Upload an image to see it here</p>
|
| 91 |
</div>
|
| 92 |
</div>
|
|
@@ -98,7 +98,7 @@
|
|
| 98 |
<div class="panel results-panel">
|
| 99 |
<div class="card">
|
| 100 |
<div class="card-header">
|
| 101 |
-
<h3 class="card-title">
|
| 102 |
</div>
|
| 103 |
<div class="card-body">
|
| 104 |
<!-- Annotated Image -->
|
|
@@ -106,7 +106,7 @@
|
|
| 106 |
<h4>Annotated Image</h4>
|
| 107 |
<div class="image-container" id="annotatedImageContainer">
|
| 108 |
<div class="image-placeholder">
|
| 109 |
-
<div class="placeholder-icon">
|
| 110 |
<p>Annotated results will appear here</p>
|
| 111 |
</div>
|
| 112 |
</div>
|
|
|
|
| 1 |
{% extends "base.html" %}
|
| 2 |
|
| 3 |
+
{% block title %}Dashboard - Seamo Species API{% endblock %}
|
| 4 |
|
| 5 |
{% block extra_css %}
|
| 6 |
<link rel="stylesheet" href="/static/css/dashboard.css">
|
|
|
|
| 11 |
<div class="dashboard">
|
| 12 |
<!-- Page Header -->
|
| 13 |
<div class="page-header">
|
| 14 |
+
<h1>Species Identification</h1>
|
| 15 |
<p class="page-description">
|
| 16 |
+
Upload an image to identify marine species using our advanced YOLOv5 model.
|
| 17 |
Supports 691+ species including fish, corals, sea stars, and more.
|
| 18 |
</p>
|
| 19 |
</div>
|
|
|
|
| 33 |
<div class="panel input-panel">
|
| 34 |
<div class="card">
|
| 35 |
<div class="card-header">
|
| 36 |
+
<h3 class="card-title">Upload Image</h3>
|
| 37 |
</div>
|
| 38 |
<div class="card-body">
|
| 39 |
<!-- Upload Area -->
|
| 40 |
<div class="upload-area" id="uploadArea">
|
| 41 |
<div class="upload-content">
|
| 42 |
+
<div class="upload-icon">+</div>
|
| 43 |
<div class="upload-text">
|
| 44 |
<p class="upload-primary">Drag image here or click to browse</p>
|
| 45 |
<p class="upload-secondary">PNG, JPG, JPEG up to 10MB</p>
|
|
|
|
| 50 |
|
| 51 |
<!-- Settings -->
|
| 52 |
<div class="settings-section">
|
| 53 |
+
<h4>Detection Settings</h4>
|
| 54 |
<div class="settings-grid">
|
| 55 |
<div class="setting-item">
|
| 56 |
<label for="confidenceSlider">Confidence Threshold</label>
|
|
|
|
| 71 |
|
| 72 |
<!-- Action Button -->
|
| 73 |
<button class="btn btn-primary btn-full" id="identifyBtn" disabled>
|
| 74 |
+
Identify Species
|
| 75 |
</button>
|
| 76 |
</div>
|
| 77 |
</div>
|
|
|
|
| 81 |
<div class="panel image-panel">
|
| 82 |
<div class="card">
|
| 83 |
<div class="card-header">
|
| 84 |
+
<h3 class="card-title">Original Image</h3>
|
| 85 |
</div>
|
| 86 |
<div class="card-body">
|
| 87 |
<div class="image-container" id="originalImageContainer">
|
| 88 |
<div class="image-placeholder">
|
| 89 |
+
<div class="placeholder-icon">β‘</div>
|
| 90 |
<p>Upload an image to see it here</p>
|
| 91 |
</div>
|
| 92 |
</div>
|
|
|
|
| 98 |
<div class="panel results-panel">
|
| 99 |
<div class="card">
|
| 100 |
<div class="card-header">
|
| 101 |
+
<h3 class="card-title">Detection Results</h3>
|
| 102 |
</div>
|
| 103 |
<div class="card-body">
|
| 104 |
<!-- Annotated Image -->
|
|
|
|
| 106 |
<h4>Annotated Image</h4>
|
| 107 |
<div class="image-container" id="annotatedImageContainer">
|
| 108 |
<div class="image-placeholder">
|
| 109 |
+
<div class="placeholder-icon">β‘</div>
|
| 110 |
<p>Annotated results will appear here</p>
|
| 111 |
</div>
|
| 112 |
</div>
|
test_dashboard.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to verify the dashboard implementation works correctly.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import requests
|
| 7 |
+
import time
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def test_dashboard_endpoints(base_url="http://localhost:7860"):
|
| 12 |
+
"""Test all dashboard-related endpoints."""
|
| 13 |
+
|
| 14 |
+
print("Testing Seamo Species Dashboard")
|
| 15 |
+
print("=" * 50)
|
| 16 |
+
print(f"Base URL: {base_url}")
|
| 17 |
+
|
| 18 |
+
tests = [
|
| 19 |
+
{
|
| 20 |
+
"name": "Dashboard HTML",
|
| 21 |
+
"url": f"{base_url}/",
|
| 22 |
+
"expected_content": ["Seamo Species", "Upload Image", "dashboard.js"]
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"name": "Static CSS",
|
| 26 |
+
"url": f"{base_url}/static/css/main.css",
|
| 27 |
+
"expected_content": [":root", "--primary-blue", ".btn"]
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"name": "Static JS",
|
| 31 |
+
"url": f"{base_url}/static/js/main.js",
|
| 32 |
+
"expected_content": ["MarineAPI", "utils", "showNotification"]
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"name": "API Health (existing)",
|
| 36 |
+
"url": f"{base_url}/api/v1/health",
|
| 37 |
+
"expected_json": ["status", "model_loaded"]
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"name": "API Info (existing)",
|
| 41 |
+
"url": f"{base_url}/api/v1/info",
|
| 42 |
+
"expected_json": ["name", "version", "model_info"]
|
| 43 |
+
}
|
| 44 |
+
]
|
| 45 |
+
|
| 46 |
+
results = []
|
| 47 |
+
|
| 48 |
+
for test in tests:
|
| 49 |
+
print(f"\nπ§ͺ Testing: {test['name']}")
|
| 50 |
+
print(f" URL: {test['url']}")
|
| 51 |
+
|
| 52 |
+
try:
|
| 53 |
+
response = requests.get(test['url'], timeout=10)
|
| 54 |
+
|
| 55 |
+
if response.status_code == 200:
|
| 56 |
+
print(f" β
Status: {response.status_code}")
|
| 57 |
+
|
| 58 |
+
# Check content
|
| 59 |
+
if 'expected_content' in test:
|
| 60 |
+
content = response.text
|
| 61 |
+
missing = []
|
| 62 |
+
for expected in test['expected_content']:
|
| 63 |
+
if expected not in content:
|
| 64 |
+
missing.append(expected)
|
| 65 |
+
|
| 66 |
+
if missing:
|
| 67 |
+
print(f" β Missing content: {missing}")
|
| 68 |
+
results.append(False)
|
| 69 |
+
else:
|
| 70 |
+
print(f" β
Content check passed")
|
| 71 |
+
results.append(True)
|
| 72 |
+
|
| 73 |
+
elif 'expected_json' in test:
|
| 74 |
+
try:
|
| 75 |
+
json_data = response.json()
|
| 76 |
+
missing = []
|
| 77 |
+
for expected in test['expected_json']:
|
| 78 |
+
if expected not in json_data:
|
| 79 |
+
missing.append(expected)
|
| 80 |
+
|
| 81 |
+
if missing:
|
| 82 |
+
print(f" β Missing JSON fields: {missing}")
|
| 83 |
+
results.append(False)
|
| 84 |
+
else:
|
| 85 |
+
print(f" β
JSON structure check passed")
|
| 86 |
+
results.append(True)
|
| 87 |
+
except Exception as e:
|
| 88 |
+
print(f" β JSON parsing failed: {e}")
|
| 89 |
+
results.append(False)
|
| 90 |
+
else:
|
| 91 |
+
results.append(True)
|
| 92 |
+
else:
|
| 93 |
+
print(f" β Status: {response.status_code}")
|
| 94 |
+
results.append(False)
|
| 95 |
+
|
| 96 |
+
except requests.exceptions.ConnectionError:
|
| 97 |
+
print(f" β Connection failed - is the server running?")
|
| 98 |
+
results.append(False)
|
| 99 |
+
except Exception as e:
|
| 100 |
+
print(f" β Error: {e}")
|
| 101 |
+
results.append(False)
|
| 102 |
+
|
| 103 |
+
# Summary
|
| 104 |
+
print("\n" + "=" * 50)
|
| 105 |
+
print("π― Test Summary:")
|
| 106 |
+
passed = sum(results)
|
| 107 |
+
total = len(results)
|
| 108 |
+
print(f" Passed: {passed}/{total}")
|
| 109 |
+
print(f" Success Rate: {(passed/total)*100:.1f}%")
|
| 110 |
+
|
| 111 |
+
if passed == total:
|
| 112 |
+
print("All tests passed! Dashboard is ready.")
|
| 113 |
+
return True
|
| 114 |
+
else:
|
| 115 |
+
print("Some tests failed. Check the implementation.")
|
| 116 |
+
return False
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def main():
|
| 120 |
+
"""Main test function."""
|
| 121 |
+
|
| 122 |
+
# Test with local server first
|
| 123 |
+
print("Testing local development server...")
|
| 124 |
+
local_success = test_dashboard_endpoints("http://localhost:7860")
|
| 125 |
+
|
| 126 |
+
if len(sys.argv) > 1:
|
| 127 |
+
# Test with provided URL
|
| 128 |
+
test_url = sys.argv[1]
|
| 129 |
+
print(f"\nTesting provided URL: {test_url}")
|
| 130 |
+
remote_success = test_dashboard_endpoints(test_url)
|
| 131 |
+
|
| 132 |
+
if remote_success:
|
| 133 |
+
print(f"\nDashboard is live at: {test_url}")
|
| 134 |
+
else:
|
| 135 |
+
print(f"\nDashboard has issues at: {test_url}")
|
| 136 |
+
|
| 137 |
+
print("\nNext steps:")
|
| 138 |
+
print(" 1. Start the API: python start_api.py")
|
| 139 |
+
print(" 2. Open browser: http://localhost:7860")
|
| 140 |
+
print(" 3. Test with marine species images")
|
| 141 |
+
print(" 4. Deploy to production")
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
if __name__ == "__main__":
|
| 145 |
+
main()
|