Inquisiter07 commited on
Commit
07da06b
·
1 Parent(s): d7e9642

Deploy Flask skin disease app with Grad-CAM

Browse files
Files changed (6) hide show
  1. .gitignore +10 -0
  2. Dockerfile +19 -0
  3. requirements.txt +8 -0
  4. templates/index.html +289 -0
  5. templates/result.html +655 -0
  6. templates/upload.html +517 -0
.gitignore ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pth
5
+ env/
6
+ .venv/
7
+ *.env
8
+ .DS_Store
9
+ .cache/
10
+
Dockerfile ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11
2
+
3
+ WORKDIR /app
4
+
5
+ # Copy all code
6
+ COPY . .
7
+
8
+ # Install dependencies
9
+ RUN pip install --upgrade pip && pip install -r requirements.txt
10
+
11
+ # Expose port for Hugging Face
12
+ EXPOSE 7860
13
+
14
+ # Run Flask
15
+ ENV FLASK_APP=app.py
16
+ ENV FLASK_RUN_HOST=0.0.0.0
17
+ ENV FLASK_RUN_PORT=7860
18
+ CMD ["flask", "run"]
19
+
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ flask
2
+ torch
3
+ transformers
4
+ torchvision
5
+ huggingface_hub
6
+ Pillow
7
+ torchcam
8
+
templates/index.html ADDED
@@ -0,0 +1,289 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>SkinAI</title>
7
+ <style>
8
+ /* Reset */
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ background-color: #ffffff;
18
+ color: #000;
19
+ }
20
+
21
+ /* Top Navigation with Enhanced Hover Effects */
22
+ header {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ padding: 15px 5%;
27
+ background-color: #fff;
28
+ border-bottom: 1px solid #ccc;
29
+ position: relative;
30
+ z-index: 10;
31
+ }
32
+
33
+ .logo {
34
+ font-size: 1.5rem;
35
+ font-weight: bold;
36
+ color: #333;
37
+ }
38
+
39
+ nav a {
40
+ text-decoration: none;
41
+ color: #333;
42
+ margin-left: 20px;
43
+ font-weight: 500;
44
+ padding: 5px 0;
45
+ position: relative;
46
+ transition: color 0.3s ease;
47
+ }
48
+
49
+ /* Underline hover effect for nav links */
50
+ nav a:after {
51
+ content: '';
52
+ position: absolute;
53
+ width: 0;
54
+ height: 2px;
55
+ bottom: 0;
56
+ left: 0;
57
+ background-color: #007BFF;
58
+ transition: width 0.3s ease;
59
+ }
60
+
61
+ nav a:hover {
62
+ color: #007BFF;
63
+ text-decoration: none;
64
+ }
65
+
66
+ nav a:hover:after {
67
+ width: 100%;
68
+ }
69
+
70
+ .help-btn {
71
+ background-color: #007BFF;
72
+ color: #fff;
73
+ border: none;
74
+ padding: 8px 16px;
75
+ border-radius: 4px;
76
+ cursor: pointer;
77
+ margin-left: 20px;
78
+ transition: all 0.3s ease;
79
+ }
80
+
81
+ .help-btn:hover {
82
+ background-color: #0056b3;
83
+ transform: translateY(-2px);
84
+ box-shadow: 0 4px 8px rgba(0,123,255,0.3);
85
+ }
86
+
87
+ /* Hero Section with Video Background */
88
+ .hero {
89
+ position: relative;
90
+ height: 60vh;
91
+ display: flex;
92
+ align-items: center;
93
+ justify-content: center;
94
+ text-align: center;
95
+ padding: 0 5%;
96
+ overflow: hidden; /* Ensure video doesn't spill out */
97
+ }
98
+
99
+ /* Video Background Styling */
100
+ .video-bg {
101
+ position: absolute;
102
+ top: 0;
103
+ left: 0;
104
+ width: 100%;
105
+ height: 100%;
106
+ object-fit: cover; /* Cover the entire container */
107
+ z-index: 1;
108
+ }
109
+
110
+ .hero-overlay {
111
+ position: absolute;
112
+ top: 0; left: 0;
113
+ width: 100%; height: 100%;
114
+ background-color: rgba(0,0,0,0.4); /* Dark overlay for readability */
115
+ z-index: 2;
116
+ }
117
+
118
+ .hero-content {
119
+ position: relative; /* So it sits on top of the overlay */
120
+ color: #fff;
121
+ max-width: 600px;
122
+ z-index: 3; /* Ensure content is above video and overlay */
123
+ }
124
+
125
+ .hero-content h1 {
126
+ font-size: 2.5rem;
127
+ margin-bottom: 15px;
128
+ font-weight: 700;
129
+ }
130
+
131
+ .hero-content p {
132
+ font-size: 1.1rem;
133
+ margin-bottom: 20px;
134
+ }
135
+
136
+ .upload-btn {
137
+ background-color: #007BFF;
138
+ color: #fff;
139
+ border: none;
140
+ padding: 12px 24px;
141
+ font-size: 1rem;
142
+ border-radius: 4px;
143
+ cursor: pointer;
144
+ transition: all 0.3s ease;
145
+ }
146
+
147
+ .upload-btn:hover {
148
+ background-color: #0056b3;
149
+ transform: translateY(-2px);
150
+ box-shadow: 0 4px 12px rgba(0,123,255,0.4);
151
+ }
152
+
153
+ /* Benefits Section with Enhanced Card Hover Effects */
154
+ .benefits {
155
+ padding: 60px 5%;
156
+ text-align: center;
157
+ }
158
+
159
+ .benefits h2 {
160
+ font-size: 2rem;
161
+ margin-bottom: 40px;
162
+ font-weight: 700;
163
+ }
164
+
165
+ .benefit-cards {
166
+ display: flex;
167
+ flex-wrap: wrap;
168
+ justify-content: center;
169
+ gap: 30px;
170
+ }
171
+
172
+ .card {
173
+ flex: 1 1 250px;
174
+ max-width: 300px;
175
+ background-color: #f9f9f9;
176
+ border-radius: 8px;
177
+ padding: 25px;
178
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
179
+ transition: all 0.3s ease;
180
+ border-bottom: 3px solid transparent;
181
+ }
182
+
183
+ /* Card hover effects */
184
+ .card:hover {
185
+ transform: translateY(-10px);
186
+ box-shadow: 0 10px 20px rgba(0,0,0,0.15);
187
+ border-bottom: 3px solid #007BFF;
188
+ background-color: #fff;
189
+ }
190
+
191
+ .card h3 {
192
+ margin-bottom: 15px;
193
+ font-size: 1.2rem;
194
+ font-weight: 600;
195
+ color: #333;
196
+ transition: color 0.3s ease;
197
+ }
198
+
199
+ .card:hover h3 {
200
+ color: #007BFF;
201
+ }
202
+
203
+ .card p {
204
+ font-size: 0.95rem;
205
+ line-height: 1.4;
206
+ color: #555;
207
+ }
208
+
209
+ /* Responsive */
210
+ @media (max-width: 768px) {
211
+ .hero {
212
+ height: 50vh;
213
+ }
214
+ .hero-content h1 {
215
+ font-size: 2rem;
216
+ }
217
+ .benefits h2 {
218
+ font-size: 1.7rem;
219
+ }
220
+ .card {
221
+ margin: 0 auto;
222
+ }
223
+ }
224
+ </style>
225
+ </head>
226
+ <body>
227
+
228
+ <!-- Top Navigation -->
229
+ <header>
230
+ <div class="logo">SkinAI</div>
231
+ <nav>
232
+ <a href="/" style="color: #007BFF;">Home</a>
233
+ <a href="/upload">Upload</a>
234
+ <a href="/result">Results</a>
235
+ <a href="#">Contact</a>
236
+ <button class="help-btn">Help</button>
237
+ </nav>
238
+ </header>
239
+
240
+ <!-- Hero Section with Video Background -->
241
+ <section class="hero">
242
+ <!-- Video Background -->
243
+ <video class="video-bg" autoplay muted loop>
244
+ <source src="{{ url_for('static', filename='videos/background-video.mp4') }}" type="video/mp4">
245
+ <!-- Add fallback sources if needed -->
246
+ <source src="{{ url_for('static', filename='videos/background-video.webm') }}" type="video/webm">
247
+ <!-- Fallback for browsers that don't support video -->
248
+ Your browser does not support the video tag.
249
+ </video>
250
+
251
+ <div class="hero-overlay"></div>
252
+ <div class="hero-content">
253
+ <h1>Identify Skin Issues Instantly</h1>
254
+ <p>Upload an image to get AI-driven skin health insights.</p>
255
+ <a href="/upload"><button class="upload-btn">Upload Image</button></a>
256
+ </div>
257
+ </section>
258
+
259
+ <!-- Benefits Section -->
260
+ <section class="benefits">
261
+ <h2>Benefits of the Service</h2>
262
+ <div class="benefit-cards">
263
+ <!-- Card 1 -->
264
+ <div class="card">
265
+ <h3>Accurate Identification</h3>
266
+ <p>Our tool analyzes skin images to provide precise detections, minimizing human error.</p>
267
+ </div>
268
+ <!-- Card 2 -->
269
+ <div class="card">
270
+ <h3>Diverse Skin Conditions</h3>
271
+ <p>Identify various issues like psoriasis, eczema, and more with our comprehensive database.</p>
272
+ </div>
273
+ <!-- Card 3 -->
274
+ <div class="card">
275
+ <h3>Speed and Efficiency</h3>
276
+ <p>Our service provides rapid results, enabling timely medical interventions.</p>
277
+ </div>
278
+ </div>
279
+ </section>
280
+
281
+ <script>
282
+ // JavaScript can be added here for additional functionality
283
+ document.querySelector('.upload-btn').addEventListener('click', function() {
284
+ window.location.href = '/upload';
285
+ });
286
+ </script>
287
+
288
+ </body>
289
+ </html>
templates/result.html ADDED
@@ -0,0 +1,655 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>SkinAI - Results</title>
7
+ <style>
8
+ /* Reset */
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ background-color: #f5f8fa;
18
+ color: #000;
19
+ }
20
+
21
+ /* Top Navigation with Enhanced Hover Effects */
22
+ header {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ padding: 15px 5%;
27
+ background-color: #fff;
28
+ border-bottom: 1px solid #ccc;
29
+ position: relative;
30
+ z-index: 10;
31
+ }
32
+
33
+ .logo {
34
+ font-size: 1.5rem;
35
+ font-weight: bold;
36
+ color: #333;
37
+ }
38
+
39
+ nav a {
40
+ text-decoration: none;
41
+ color: #333;
42
+ margin-left: 20px;
43
+ font-weight: 500;
44
+ padding: 5px 0;
45
+ position: relative;
46
+ transition: color 0.3s ease;
47
+ }
48
+
49
+ /* Underline hover effect for nav links */
50
+ nav a:after {
51
+ content: '';
52
+ position: absolute;
53
+ width: 0;
54
+ height: 2px;
55
+ bottom: 0;
56
+ left: 0;
57
+ background-color: #007BFF;
58
+ transition: width 0.3s ease;
59
+ }
60
+
61
+ nav a:hover {
62
+ color: #007BFF;
63
+ text-decoration: none;
64
+ }
65
+
66
+ nav a:hover:after {
67
+ width: 100%;
68
+ }
69
+
70
+ .help-btn {
71
+ background-color: #007BFF;
72
+ color: #fff;
73
+ border: none;
74
+ padding: 8px 16px;
75
+ border-radius: 4px;
76
+ cursor: pointer;
77
+ margin-left: 20px;
78
+ transition: all 0.3s ease;
79
+ }
80
+
81
+ .help-btn:hover {
82
+ background-color: #0056b3;
83
+ transform: translateY(-2px);
84
+ box-shadow: 0 4px 8px rgba(0,123,255,0.3);
85
+ }
86
+
87
+ /* Main Content Area */
88
+ .container {
89
+ max-width: 1200px;
90
+ margin: 40px auto;
91
+ padding: 0 20px;
92
+ }
93
+
94
+ /* Results Section */
95
+ .results-section {
96
+ background-color: #fff;
97
+ border-radius: 10px;
98
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
99
+ padding: 40px;
100
+ margin-bottom: 40px;
101
+ }
102
+
103
+ .results-header {
104
+ display: flex;
105
+ align-items: center;
106
+ justify-content: space-between;
107
+ margin-bottom: 30px;
108
+ }
109
+
110
+ .results-header h1 {
111
+ font-size: 2rem;
112
+ color: #333;
113
+ }
114
+
115
+ .results-header .new-analysis {
116
+ background-color: #f8f9fa;
117
+ color: #333;
118
+ border: 1px solid #ddd;
119
+ padding: 8px 16px;
120
+ border-radius: 4px;
121
+ text-decoration: none;
122
+ transition: all 0.3s ease;
123
+ }
124
+
125
+ .results-header .new-analysis:hover {
126
+ background-color: #e9ecef;
127
+ transform: translateY(-2px);
128
+ }
129
+
130
+ /* Results Grid */
131
+ .results-grid {
132
+ display: grid;
133
+ grid-template-columns: 1fr 2fr;
134
+ gap: 30px;
135
+ }
136
+
137
+ /* Image Section */
138
+ .image-section {
139
+ background-color: #f8f9fa;
140
+ border-radius: 8px;
141
+ padding: 20px;
142
+ text-align: center;
143
+ }
144
+
145
+ .image-section img {
146
+ max-width: 100%;
147
+ max-height: 300px;
148
+ border-radius: 8px;
149
+ margin-bottom: 15px;
150
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
151
+ }
152
+
153
+ .image-caption {
154
+ font-size: 0.9rem;
155
+ color: #666;
156
+ }
157
+
158
+ /* Analysis Section */
159
+ .analysis-section {
160
+ display: flex;
161
+ flex-direction: column;
162
+ gap: 20px;
163
+ }
164
+
165
+ .analysis-card {
166
+ background-color: #f8f9fa;
167
+ border-radius: 8px;
168
+ padding: 20px;
169
+ border-left: 4px solid #007BFF;
170
+ }
171
+
172
+ .analysis-card h2 {
173
+ font-size: 1.4rem;
174
+ color: #333;
175
+ margin-bottom: 10px;
176
+ display: flex;
177
+ align-items: center;
178
+ justify-content: space-between;
179
+ }
180
+
181
+ .confidence-badge {
182
+ background-color: #e6f4ff;
183
+ color: #0066cc;
184
+ font-size: 0.9rem;
185
+ padding: 4px 12px;
186
+ border-radius: 20px;
187
+ font-weight: normal;
188
+ }
189
+
190
+ .analysis-card p {
191
+ font-size: 1rem;
192
+ color: #555;
193
+ line-height: 1.6;
194
+ margin-bottom: 15px;
195
+ }
196
+
197
+ /* Recommendations List */
198
+ .recommendations-list {
199
+ margin-top: 10px;
200
+ }
201
+
202
+ .recommendations-list h3 {
203
+ font-size: 1.1rem;
204
+ color: #333;
205
+ margin-bottom: 15px;
206
+ }
207
+
208
+ .recommendations-list ul {
209
+ padding-left: 20px;
210
+ }
211
+
212
+ .recommendations-list li {
213
+ margin-bottom: 10px;
214
+ line-height: 1.5;
215
+ font-size: 0.95rem;
216
+ color: #444;
217
+ }
218
+
219
+ /* Other Possible Conditions */
220
+ .other-conditions {
221
+ margin-top: 20px;
222
+ }
223
+
224
+ .other-conditions h3 {
225
+ font-size: 1.1rem;
226
+ color: #333;
227
+ margin-bottom: 15px;
228
+ }
229
+
230
+ .condition-bars {
231
+ display: flex;
232
+ flex-direction: column;
233
+ gap: 10px;
234
+ }
235
+
236
+ .condition-bar {
237
+ display: flex;
238
+ align-items: center;
239
+ gap: 10px;
240
+ }
241
+
242
+ .condition-name {
243
+ min-width: 100px;
244
+ font-size: 0.9rem;
245
+ }
246
+
247
+ .progress-bar {
248
+ flex-grow: 1;
249
+ height: 10px;
250
+ background-color: #e9ecef;
251
+ border-radius: 5px;
252
+ overflow: hidden;
253
+ }
254
+
255
+ .progress-fill {
256
+ height: 100%;
257
+ background-color: #007BFF;
258
+ }
259
+
260
+ .condition-percentage {
261
+ font-size: 0.9rem;
262
+ color: #666;
263
+ min-width: 50px;
264
+ text-align: right;
265
+ }
266
+
267
+ /* Q&A Section */
268
+ .qa-section {
269
+ background-color: #fff;
270
+ border-radius: 10px;
271
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
272
+ padding: 40px;
273
+ }
274
+
275
+ .qa-section h2 {
276
+ font-size: 1.6rem;
277
+ color: #333;
278
+ margin-bottom: 25px;
279
+ }
280
+
281
+ .qa-form {
282
+ display: flex;
283
+ gap: 10px;
284
+ margin-bottom: 30px;
285
+ }
286
+
287
+ .qa-input {
288
+ flex-grow: 1;
289
+ padding: 12px 15px;
290
+ border: 1px solid #ddd;
291
+ border-radius: 4px;
292
+ font-size: 1rem;
293
+ }
294
+
295
+ .qa-btn {
296
+ background-color: #007BFF;
297
+ color: #fff;
298
+ border: none;
299
+ padding: 0 20px;
300
+ border-radius: 4px;
301
+ cursor: pointer;
302
+ transition: all 0.3s ease;
303
+ }
304
+
305
+ .qa-btn:hover {
306
+ background-color: #0056b3;
307
+ }
308
+
309
+ .qa-response {
310
+ background-color: #f8f9fa;
311
+ border-radius: 8px;
312
+ padding: 20px;
313
+ border-left: 4px solid #28a745;
314
+ margin-bottom: 20px;
315
+ display: none; /* Initially hidden */
316
+ }
317
+
318
+ .qa-response h3 {
319
+ font-size: 1.1rem;
320
+ color: #333;
321
+ margin-bottom: 10px;
322
+ }
323
+
324
+ .qa-response p {
325
+ font-size: 0.95rem;
326
+ color: #555;
327
+ line-height: 1.6;
328
+ }
329
+
330
+ .disclaimer {
331
+ margin-top: 20px;
332
+ padding: 15px;
333
+ background-color: #fff8e1;
334
+ border-left: 4px solid #ffc107;
335
+ font-size: 0.9rem;
336
+ color: #6c4a00;
337
+ line-height: 1.5;
338
+ }
339
+
340
+ /* Footer */
341
+ footer {
342
+ text-align: center;
343
+ padding: 20px;
344
+ margin-top: 40px;
345
+ color: #777;
346
+ font-size: 0.9rem;
347
+ }
348
+
349
+ /* Loader */
350
+ .loader-container {
351
+ display: flex;
352
+ justify-content: center;
353
+ align-items: center;
354
+ margin: 20px 0;
355
+ }
356
+
357
+ .loader {
358
+ border: 4px solid #f3f3f3;
359
+ border-top: 4px solid #007BFF;
360
+ border-radius: 50%;
361
+ width: 30px;
362
+ height: 30px;
363
+ animation: spin 1s linear infinite;
364
+ margin-right: 10px;
365
+ }
366
+
367
+ @keyframes spin {
368
+ 0% { transform: rotate(0deg); }
369
+ 100% { transform: rotate(360deg); }
370
+ }
371
+
372
+ /* Responsive */
373
+ @media (max-width: 768px) {
374
+ .container {
375
+ padding: 0 15px;
376
+ margin: 20px auto;
377
+ }
378
+
379
+ .results-section, .qa-section {
380
+ padding: 30px 15px;
381
+ }
382
+
383
+ .results-grid {
384
+ grid-template-columns: 1fr;
385
+ }
386
+
387
+ .results-header {
388
+ flex-direction: column;
389
+ align-items: flex-start;
390
+ gap: 15px;
391
+ }
392
+ }
393
+ </style>
394
+ </head>
395
+ <body>
396
+
397
+ <!-- Top Navigation -->
398
+ <header>
399
+ <div class="logo">SkinAI</div>
400
+ <nav>
401
+ <a href="/">Home</a>
402
+ <a href="/upload">Upload</a>
403
+ <a href="/result" style="color: #007BFF;">Results</a>
404
+ <a href="#">Contact</a>
405
+ <button class="help-btn">Help</button>
406
+ </nav>
407
+ </header>
408
+
409
+ <div class="container">
410
+ <!-- Results Section -->
411
+ <section class="results-section">
412
+ <div class="results-header">
413
+ <h1>Analysis Results</h1>
414
+ <a href="upload" class="new-analysis">Upload New Image</a>
415
+ </div>
416
+
417
+ <div class="results-grid">
418
+ <!-- Image Section -->
419
+ <div class="image-section">
420
+ <img id="analyzed-image" src="#" alt="Analyzed Skin Image">
421
+ <div class="image-caption">Uploaded on <span id="upload-date">April 8, 2025</span></div>
422
+ </div>
423
+
424
+ <!-- Analysis Section -->
425
+ <div class="analysis-section">
426
+ <div class="analysis-card">
427
+ <h2>
428
+ <span id="condition-name">Loading result...</span>
429
+ <span class="confidence-badge" id="confidence-level">--</span>
430
+ </h2>
431
+ <p id="condition-description">Analyzing your image...</p>
432
+
433
+ <!-- Loader (initially shown) -->
434
+ <div class="loader-container" id="analysis-loader">
435
+ <div class="loader"></div>
436
+ <span>Processing image with AI...</span>
437
+ </div>
438
+
439
+ <!-- Recommendations (initially hidden) -->
440
+ <div class="recommendations-list" id="recommendations-section" style="display: none;">
441
+ <h3>Recommendations</h3>
442
+ <ul id="recommendations-list">
443
+ <!-- Recommendations will be inserted here -->
444
+ </ul>
445
+ </div>
446
+
447
+ <!-- Other Possible Conditions -->
448
+ <div class="other-conditions" id="other-conditions-section" style="display: none;">
449
+ <h3>Other Possible Conditions</h3>
450
+ <div class="condition-bars" id="condition-bars">
451
+ <!-- Condition bars will be inserted here -->
452
+ </div>
453
+ </div>
454
+ </div>
455
+
456
+ <div class="disclaimer">
457
+ <strong>Disclaimer:</strong> This analysis is for informational purposes only and should not replace professional medical advice. Please consult a healthcare provider for proper diagnosis and treatment.
458
+ </div>
459
+ </div>
460
+ </div>
461
+ </section>
462
+
463
+ <!-- Q&A Section -->
464
+ <section class="qa-section">
465
+ <h2>Ask About Your Condition</h2>
466
+ <div class="qa-form">
467
+ <input type="text" class="qa-input" id="question-input" placeholder="Ask a question about your skin condition...">
468
+ <button class="qa-btn" id="ask-btn">Ask</button>
469
+ </div>
470
+
471
+ <!-- QA Response (initially hidden) -->
472
+ <div class="qa-response" id="qa-response">
473
+ <h3>Response</h3>
474
+ <p id="qa-answer">The answer will appear here.</p>
475
+ </div>
476
+
477
+ <div class="disclaimer">
478
+ <strong>Important:</strong> The AI assistant provides general information about skin conditions. Your specific case may vary, and medical professionals should be consulted for personalized advice and treatment.
479
+ </div>
480
+ </section>
481
+ </div>
482
+
483
+ <footer>
484
+ <p>© 2025 SkinAI. All rights reserved. For educational purposes only. Not a substitute for professional medical advice.</p>
485
+ </footer>
486
+
487
+ <script>
488
+ document.addEventListener('DOMContentLoaded', function() {
489
+ // Retrieve data from sessionStorage (set during upload)
490
+ const imageData = sessionStorage.getItem('analyzedImage');
491
+ const analysisResult = JSON.parse(sessionStorage.getItem('analysisResult') || '{}');
492
+ const condition = sessionStorage.getItem('detectedCondition') || '';
493
+
494
+ // Set the image
495
+ if (imageData) {
496
+ document.getElementById('analyzed-image').src = imageData;
497
+ } else {
498
+ document.getElementById('analyzed-image').src = "/api/placeholder/400/320";
499
+ document.getElementById('analyzed-image').alt = "No image available";
500
+ }
501
+
502
+ // Set the current date
503
+ const now = new Date();
504
+ document.getElementById('upload-date').textContent = now.toLocaleDateString('en-US', {
505
+ year: 'numeric',
506
+ month: 'long',
507
+ day: 'numeric'
508
+ });
509
+
510
+ // Process and display results if available
511
+ if (Object.keys(analysisResult).length > 0) {
512
+ displayResults(analysisResult);
513
+ } else {
514
+ // If no results in session storage, check URL params
515
+ // This allows direct navigation to this page with the results as parameters
516
+ const urlParams = new URLSearchParams(window.location.search);
517
+ const resultParam = urlParams.get('result');
518
+
519
+ if (resultParam) {
520
+ try {
521
+ const decodedResult = JSON.parse(decodeURIComponent(resultParam));
522
+ displayResults(decodedResult);
523
+ } catch (e) {
524
+ console.error("Failed to parse result from URL parameter", e);
525
+ document.getElementById('condition-name').textContent = "No results available";
526
+ document.getElementById('condition-description').textContent = "Please upload an image for analysis.";
527
+ document.getElementById('analysis-loader').style.display = "none";
528
+ }
529
+ } else {
530
+ // No results available - show message
531
+ document.getElementById('condition-name').textContent = "No results available";
532
+ document.getElementById('condition-description').textContent = "Please upload an image for analysis.";
533
+ document.getElementById('analysis-loader').style.display = "none";
534
+ }
535
+ }
536
+
537
+ // Handle Q&A form submission
538
+ document.getElementById('ask-btn').addEventListener('click', function() {
539
+ askQuestion();
540
+ });
541
+
542
+ document.getElementById('question-input').addEventListener('keypress', function(e) {
543
+ if (e.key === 'Enter') {
544
+ askQuestion();
545
+ }
546
+ });
547
+
548
+ // Function to display analysis results
549
+ function displayResults(result) {
550
+ // Hide loader
551
+ document.getElementById('analysis-loader').style.display = "none";
552
+
553
+ // Display primary condition and confidence
554
+ document.getElementById('condition-name').textContent = result.prediction || "Unknown Condition";
555
+
556
+ const confidencePercent = result.confidence ? Math.round(result.confidence * 100) : 0;
557
+ document.getElementById('confidence-level').textContent = `${confidencePercent}% Confidence`;
558
+
559
+ // Display description
560
+ document.getElementById('condition-description').textContent = result.description ||
561
+ "No detailed information available for this condition.";
562
+
563
+ // Display recommendations
564
+ if (result.recommendations && result.recommendations.length > 0) {
565
+ const recommendationsList = document.getElementById('recommendations-list');
566
+ recommendationsList.innerHTML = '';
567
+
568
+ result.recommendations.forEach(rec => {
569
+ const li = document.createElement('li');
570
+ li.textContent = rec;
571
+ recommendationsList.appendChild(li);
572
+ });
573
+
574
+ document.getElementById('recommendations-section').style.display = "block";
575
+ }
576
+
577
+ // Display other possible conditions
578
+ if (result.topConditions && result.topConditions.length > 0) {
579
+ const conditionBars = document.getElementById('condition-bars');
580
+ conditionBars.innerHTML = '';
581
+
582
+ // Skip the first condition (already shown as primary) if it's the same as the top result
583
+ const startIndex = (result.topConditions[0][0] === result.prediction) ? 1 : 0;
584
+
585
+ // Display up to 4 additional conditions
586
+ for (let i = startIndex; i < Math.min(startIndex + 4, result.topConditions.length); i++) {
587
+ const condition = result.topConditions[i];
588
+ const conditionName = condition[0];
589
+ const probability = condition[1];
590
+ const percentage = Math.round(probability * 100);
591
+
592
+ const conditionBar = document.createElement('div');
593
+ conditionBar.className = 'condition-bar';
594
+ conditionBar.innerHTML = `
595
+ <div class="condition-name">${conditionName}</div>
596
+ <div class="progress-bar">
597
+ <div class="progress-fill" style="width: ${percentage}%"></div>
598
+ </div>
599
+ <div class="condition-percentage">${percentage}%</div>
600
+ `;
601
+
602
+ conditionBars.appendChild(conditionBar);
603
+ }
604
+
605
+ document.getElementById('other-conditions-section').style.display = "block";
606
+ }
607
+
608
+ // Store detected condition for Q&A
609
+ sessionStorage.setItem('detectedCondition', result.prediction || '');
610
+ }
611
+
612
+ // Function to handle Q&A
613
+ function askQuestion() {
614
+ const questionInput = document.getElementById('question-input');
615
+ const question = questionInput.value.trim();
616
+
617
+ if (!question) return;
618
+
619
+ // Get the detected condition
620
+ const detectedCondition = sessionStorage.getItem('detectedCondition') || '';
621
+
622
+ // Show loading indication
623
+ document.getElementById('qa-answer').textContent = "Getting answer...";
624
+ document.getElementById('qa-response').style.display = "block";
625
+
626
+ // Call the backend API
627
+ fetch('/ask', {
628
+ method: 'POST',
629
+ headers: {
630
+ 'Content-Type': 'application/json',
631
+ },
632
+ body: JSON.stringify({
633
+ question: question,
634
+ condition: detectedCondition
635
+ })
636
+ })
637
+ .then(response => response.json())
638
+ .then(data => {
639
+ document.getElementById('qa-answer').textContent = data.answer ||
640
+ "I couldn't generate an answer for that question.";
641
+ })
642
+ .catch(error => {
643
+ console.error('Error:', error);
644
+ document.getElementById('qa-answer').textContent =
645
+ "Sorry, there was an error processing your question. Please try again later.";
646
+ });
647
+
648
+ // Clear the input
649
+ questionInput.value = '';
650
+ }
651
+ });
652
+ </script>
653
+
654
+ </body>
655
+ </html>
templates/upload.html ADDED
@@ -0,0 +1,517 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>SkinAI - Upload</title>
7
+ <style>
8
+ /* Reset */
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: Arial, sans-serif;
17
+ background-color: #f5f8fa;
18
+ color: #000;
19
+ }
20
+
21
+ /* Top Navigation with Enhanced Hover Effects */
22
+ header {
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ padding: 15px 5%;
27
+ background-color: #fff;
28
+ border-bottom: 1px solid #ccc;
29
+ position: relative;
30
+ z-index: 10;
31
+ }
32
+
33
+ .logo {
34
+ font-size: 1.5rem;
35
+ font-weight: bold;
36
+ color: #333;
37
+ }
38
+
39
+ nav a {
40
+ text-decoration: none;
41
+ color: #333;
42
+ margin-left: 20px;
43
+ font-weight: 500;
44
+ padding: 5px 0;
45
+ position: relative;
46
+ transition: color 0.3s ease;
47
+ }
48
+
49
+ /* Underline hover effect for nav links */
50
+ nav a:after {
51
+ content: '';
52
+ position: absolute;
53
+ width: 0;
54
+ height: 2px;
55
+ bottom: 0;
56
+ left: 0;
57
+ background-color: #007BFF;
58
+ transition: width 0.3s ease;
59
+ }
60
+
61
+ nav a:hover {
62
+ color: #007BFF;
63
+ text-decoration: none;
64
+ }
65
+
66
+ nav a:hover:after {
67
+ width: 100%;
68
+ }
69
+
70
+ .help-btn {
71
+ background-color: #007BFF;
72
+ color: #fff;
73
+ border: none;
74
+ padding: 8px 16px;
75
+ border-radius: 4px;
76
+ cursor: pointer;
77
+ margin-left: 20px;
78
+ transition: all 0.3s ease;
79
+ }
80
+
81
+ .help-btn:hover {
82
+ background-color: #0056b3;
83
+ transform: translateY(-2px);
84
+ box-shadow: 0 4px 8px rgba(0,123,255,0.3);
85
+ }
86
+
87
+ /* Main Content Area */
88
+ .container {
89
+ max-width: 1200px;
90
+ margin: 40px auto;
91
+ padding: 0 20px;
92
+ }
93
+
94
+ /* Upload Section */
95
+ .upload-section {
96
+ background-color: #fff;
97
+ border-radius: 10px;
98
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
99
+ padding: 40px;
100
+ margin-bottom: 40px;
101
+ text-align: center;
102
+ }
103
+
104
+ .upload-section h1 {
105
+ font-size: 2rem;
106
+ color: #333;
107
+ margin-bottom: 20px;
108
+ }
109
+
110
+ .upload-section p {
111
+ font-size: 1.1rem;
112
+ color: #666;
113
+ margin-bottom: 30px;
114
+ max-width: 700px;
115
+ margin-left: auto;
116
+ margin-right: auto;
117
+ }
118
+
119
+ /* Upload Area */
120
+ .upload-area {
121
+ border: 2px dashed #aac;
122
+ border-radius: 10px;
123
+ padding: 50px 20px;
124
+ background-color: #f8faff;
125
+ cursor: pointer;
126
+ transition: all 0.3s ease;
127
+ margin-bottom: 30px;
128
+ }
129
+
130
+ .upload-area:hover {
131
+ border-color: #007BFF;
132
+ background-color: #f0f8ff;
133
+ }
134
+
135
+ .upload-area.active {
136
+ border-color: #007BFF;
137
+ background-color: #e6f4ff;
138
+ }
139
+
140
+ .upload-icon {
141
+ font-size: 3rem;
142
+ color: #007BFF;
143
+ margin-bottom: 15px;
144
+ }
145
+
146
+ .upload-text {
147
+ font-size: 1.2rem;
148
+ color: #666;
149
+ margin-bottom: 10px;
150
+ }
151
+
152
+ .upload-subtext {
153
+ font-size: 0.9rem;
154
+ color: #888;
155
+ }
156
+
157
+ /* File Input */
158
+ #file-input {
159
+ display: none;
160
+ }
161
+
162
+ /* Preview Area */
163
+ .preview-area {
164
+ display: none;
165
+ margin: 30px auto;
166
+ max-width: 500px;
167
+ }
168
+
169
+ .preview-area img {
170
+ max-width: 100%;
171
+ max-height: 400px;
172
+ border-radius: 8px;
173
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
174
+ }
175
+
176
+ .preview-info {
177
+ margin-top: 15px;
178
+ font-size: 0.9rem;
179
+ color: #666;
180
+ }
181
+
182
+ /* Button Styles */
183
+ .btn {
184
+ background-color: #007BFF;
185
+ color: #fff;
186
+ border: none;
187
+ padding: 12px 24px;
188
+ font-size: 1rem;
189
+ border-radius: 4px;
190
+ cursor: pointer;
191
+ transition: all 0.3s ease;
192
+ display: inline-block;
193
+ }
194
+
195
+ .btn:hover {
196
+ background-color: #0056b3;
197
+ transform: translateY(-2px);
198
+ box-shadow: 0 4px 12px rgba(0,123,255,0.4);
199
+ }
200
+
201
+ .btn-secondary {
202
+ background-color: #6c757d;
203
+ margin-right: 10px;
204
+ }
205
+
206
+ .btn-secondary:hover {
207
+ background-color: #5a6268;
208
+ }
209
+
210
+ /* Instructions Section */
211
+ .instructions {
212
+ background-color: #fff;
213
+ border-radius: 10px;
214
+ box-shadow: 0 5px 15px rgba(0,0,0,0.08);
215
+ padding: 30px;
216
+ }
217
+
218
+ .instructions h2 {
219
+ font-size: 1.5rem;
220
+ color: #333;
221
+ margin-bottom: 20px;
222
+ }
223
+
224
+ .instruction-cards {
225
+ display: flex;
226
+ flex-wrap: wrap;
227
+ gap: 20px;
228
+ }
229
+
230
+ .instruction-card {
231
+ flex: 1 1 300px;
232
+ background-color: #f9f9f9;
233
+ border-radius: 8px;
234
+ padding: 20px;
235
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
236
+ transition: all 0.3s ease;
237
+ border-left: 3px solid transparent;
238
+ }
239
+
240
+ .instruction-card:hover {
241
+ transform: translateY(-5px);
242
+ box-shadow: 0 8px 15px rgba(0,0,0,0.1);
243
+ border-left: 3px solid #007BFF;
244
+ }
245
+
246
+ .instruction-card h3 {
247
+ font-size: 1.1rem;
248
+ color: #333;
249
+ margin-bottom: 10px;
250
+ }
251
+
252
+ .instruction-card p {
253
+ font-size: 0.95rem;
254
+ color: #666;
255
+ line-height: 1.5;
256
+ }
257
+
258
+ /* Footer */
259
+ footer {
260
+ text-align: center;
261
+ padding: 20px;
262
+ margin-top: 40px;
263
+ color: #777;
264
+ font-size: 0.9rem;
265
+ }
266
+
267
+ /* Responsive */
268
+ @media (max-width: 768px) {
269
+ .container {
270
+ padding: 0 15px;
271
+ margin: 20px auto;
272
+ }
273
+
274
+ .upload-section {
275
+ padding: 30px 15px;
276
+ }
277
+
278
+ .upload-section h1 {
279
+ font-size: 1.7rem;
280
+ }
281
+
282
+ .upload-area {
283
+ padding: 30px 15px;
284
+ }
285
+
286
+ .instructions {
287
+ padding: 20px 15px;
288
+ }
289
+ }
290
+ </style>
291
+ </head>
292
+ <body>
293
+
294
+ <!-- Top Navigation -->
295
+ <header>
296
+ <div class="logo">SkinAI</div>
297
+ <nav>
298
+ <a href="/">Home</a>
299
+ <a href="/upload" style="color: #007BFF;">Upload</a>
300
+ <a href="/result">Results</a>
301
+ <a href="#">Contact</a>
302
+ <button class="help-btn">Help</button>
303
+ </nav>
304
+ </header>
305
+
306
+ <div class="container">
307
+ <!-- Upload Section -->
308
+ <section class="upload-section">
309
+ <h1>Upload Your Skin Image</h1>
310
+ <p>Our AI will analyze your image and provide insights about potential skin conditions. Please upload a clear, well-lit photo of the affected area.</p>
311
+
312
+ <!-- Upload Area -->
313
+ <div class="upload-area" id="drop-area">
314
+ <div class="upload-icon">📤</div>
315
+ <div class="upload-text">Drag & Drop your image here</div>
316
+ <div class="upload-subtext">or click to browse files</div>
317
+ <input type="file" id="file-input" accept="image/*">
318
+ </div>
319
+
320
+ <!-- Preview Area (initially hidden) -->
321
+ <div class="preview-area" id="preview-area">
322
+ <img id="preview-image" src="#" alt="Preview">
323
+ <div class="preview-info" id="file-info">File information will appear here</div>
324
+ <div style="margin-top: 20px;">
325
+ <button class="btn btn-secondary" id="cancel-btn">Cancel</button>
326
+ <button class="btn" id="analyze-btn">Analyze Image</button>
327
+ </div>
328
+ </div>
329
+ </section>
330
+
331
+ <!-- Instructions Section -->
332
+ <section class="instructions">
333
+ <h2>Best Practices for Accurate Results</h2>
334
+ <div class="instruction-cards">
335
+ <!-- Instruction 1 -->
336
+ <div class="instruction-card">
337
+ <h3>Good Lighting</h3>
338
+ <p>Ensure your photo is taken in bright, natural light. Avoid shadows or harsh lighting that might distort colors or details.</p>
339
+ </div>
340
+ <!-- Instruction 2 -->
341
+ <div class="instruction-card">
342
+ <h3>Clear Focus</h3>
343
+ <p>Take a clear, in-focus image of the affected area. Blurry images may lead to inaccurate results.</p>
344
+ </div>
345
+ <!-- Instruction 3 -->
346
+ <div class="instruction-card">
347
+ <h3>Proper Distance</h3>
348
+ <p>Capture the image from about 6-12 inches away to show sufficient detail while maintaining context of the affected area.</p>
349
+ </div>
350
+ </div>
351
+ </section>
352
+ </div>
353
+
354
+ <footer>
355
+ <p>© 2025 SkinAI. All rights reserved. For educational purposes only. Not a substitute for professional medical advice.</p>
356
+ </footer>
357
+
358
+ <script>
359
+ // JavaScript for handling file uploads and preview
360
+ // JavaScript for handling file uploads, preview, and analysis
361
+ document.addEventListener('DOMContentLoaded', function() {
362
+ const dropArea = document.getElementById('drop-area');
363
+ const fileInput = document.getElementById('file-input');
364
+ const previewArea = document.getElementById('preview-area');
365
+ const previewImage = document.getElementById('preview-image');
366
+ const fileInfo = document.getElementById('file-info');
367
+ const cancelBtn = document.getElementById('cancel-btn');
368
+ const analyzeBtn = document.getElementById('analyze-btn');
369
+
370
+ // Open file browser when clicking the upload area
371
+ dropArea.addEventListener('click', () => {
372
+ fileInput.click();
373
+ });
374
+
375
+ // Handle file selection
376
+ fileInput.addEventListener('change', handleFiles);
377
+
378
+ // Prevent default drag behaviors
379
+ ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
380
+ dropArea.addEventListener(eventName, preventDefaults, false);
381
+ document.body.addEventListener(eventName, preventDefaults, false);
382
+ });
383
+
384
+ // Highlight drop area when dragging over it
385
+ ['dragenter', 'dragover'].forEach(eventName => {
386
+ dropArea.addEventListener(eventName, highlight, false);
387
+ });
388
+
389
+ ['dragleave', 'drop'].forEach(eventName => {
390
+ dropArea.addEventListener(eventName, unhighlight, false);
391
+ });
392
+
393
+ // Handle dropped files
394
+ dropArea.addEventListener('drop', handleDrop, false);
395
+
396
+ // Cancel button functionality
397
+ cancelBtn.addEventListener('click', () => {
398
+ resetUpload();
399
+ });
400
+
401
+ // Analyze button functionality - send to backend
402
+ analyzeBtn.addEventListener('click', () => {
403
+ sendImageForAnalysis();
404
+ });
405
+
406
+ // Functions
407
+ function preventDefaults(e) {
408
+ e.preventDefault();
409
+ e.stopPropagation();
410
+ }
411
+
412
+ function highlight() {
413
+ dropArea.classList.add('active');
414
+ }
415
+
416
+ function unhighlight() {
417
+ dropArea.classList.remove('active');
418
+ }
419
+
420
+ function handleDrop(e) {
421
+ const dt = e.dataTransfer;
422
+ const files = dt.files;
423
+ handleFiles(files);
424
+ }
425
+
426
+ function handleFiles(e) {
427
+ const files = this.files || e;
428
+ if (files && files[0]) {
429
+ const file = files[0];
430
+
431
+ // Check if it's an image
432
+ if (!file.type.match('image.*')) {
433
+ alert('Please upload an image file');
434
+ return;
435
+ }
436
+
437
+ // Display file info
438
+ const size = (file.size / 1024).toFixed(2);
439
+ fileInfo.textContent = `${file.name} (${size} KB)`;
440
+
441
+ // Show preview
442
+ const reader = new FileReader();
443
+ reader.onload = function(e) {
444
+ previewImage.src = e.target.result;
445
+ dropArea.style.display = 'none';
446
+ previewArea.style.display = 'block';
447
+
448
+ // Store image data for results page
449
+ sessionStorage.setItem('analyzedImage', e.target.result);
450
+ }
451
+ reader.readAsDataURL(file);
452
+ }
453
+ }
454
+
455
+ function resetUpload() {
456
+ fileInput.value = '';
457
+ previewImage.src = '#';
458
+ previewArea.style.display = 'none';
459
+ dropArea.style.display = 'block';
460
+
461
+ // Clear session storage data
462
+ sessionStorage.removeItem('analyzedImage');
463
+ sessionStorage.removeItem('analysisResult');
464
+ sessionStorage.removeItem('detectedCondition');
465
+ }
466
+
467
+ function sendImageForAnalysis() {
468
+ // Show loading state
469
+ analyzeBtn.disabled = true;
470
+ analyzeBtn.textContent = 'Analyzing...';
471
+
472
+ // Get the file
473
+ const file = fileInput.files[0];
474
+ if (!file) {
475
+ alert('Please select an image first');
476
+ analyzeBtn.disabled = false;
477
+ analyzeBtn.textContent = 'Analyze Image';
478
+ return;
479
+ }
480
+
481
+ // Create FormData object
482
+ const formData = new FormData();
483
+ formData.append('image', file);
484
+
485
+ // Send to backend
486
+ fetch('/analyze', {
487
+ method: 'POST',
488
+ body: formData
489
+ })
490
+ .then(response => {
491
+ if (!response.ok) {
492
+ throw new Error('Network response was not ok');
493
+ }
494
+ return response.json();
495
+ })
496
+ .then(result => {
497
+ // Store result in session storage
498
+ sessionStorage.setItem('analysisResult', JSON.stringify(result));
499
+ sessionStorage.setItem('detectedCondition', result.prediction || '');
500
+
501
+ // Redirect to results page
502
+ window.location.href = 'result';
503
+ })
504
+ .catch(error => {
505
+ console.error('Error:', error);
506
+ alert('There was an error analyzing your image. Please try again.');
507
+
508
+ // Reset button state
509
+ analyzeBtn.disabled = false;
510
+ analyzeBtn.textContent = 'Analyze Image';
511
+ });
512
+ }
513
+ });
514
+ </script>
515
+
516
+ </body>
517
+ </html>