HoagMin commited on
Commit
0f8f8b7
·
1 Parent(s): 336f4d8

Update UI and add icons properly via Git LFS

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
static/css/style.css CHANGED
@@ -3,12 +3,12 @@
3
  }
4
 
5
  body {
6
- background-color: #0d1612;
7
  color: #ffffff;
8
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
  margin: 0;
10
  padding: 0;
11
-
12
  }
13
 
14
  /* Class tiện ích dùng chung */
@@ -38,7 +38,7 @@ body {
38
  }
39
 
40
  .hero-section .highlight {
41
- color: #00e676;
42
  }
43
 
44
  .hero-section p {
@@ -56,11 +56,11 @@ body {
56
  padding: 20px;
57
  max-width: 1000px;
58
  margin: 0 auto;
59
- flex-wrap: wrap;
60
  }
61
 
62
  .action-card {
63
- background-color: #17241e;
64
  border-radius: 16px;
65
  padding: 40px 30px;
66
  text-align: center;
@@ -69,7 +69,7 @@ body {
69
 
70
  .action-card:hover {
71
  transform: translateY(-5px);
72
- box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
73
  }
74
 
75
 
@@ -191,13 +191,8 @@ body {
191
  }
192
 
193
  @keyframes spin {
194
- 0% {
195
- transform: rotate(0deg);
196
- }
197
-
198
- 100% {
199
- transform: rotate(360deg);
200
- }
201
  }
202
 
203
  .editor-card {
@@ -205,8 +200,8 @@ body {
205
  border-radius: 16px;
206
  padding: 30px;
207
  max-width: 900px;
208
- margin: 30px auto 60px auto;
209
- box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
210
  }
211
 
212
  .editor-card img {
@@ -252,12 +247,12 @@ body {
252
 
253
  .big-text {
254
  font-weight: bold;
255
- color: #ffb74d;
256
  }
257
 
258
  .price-text {
259
  font-weight: bold;
260
- color: #00e676;
261
  }
262
 
263
 
@@ -269,7 +264,7 @@ body {
269
  background-color: #17241e;
270
  padding: 15px 30px;
271
  border-bottom: 2px solid #2b4535;
272
- position: sticky;
273
  top: 0;
274
  z-index: 1000;
275
  }
@@ -343,7 +338,7 @@ body {
343
  }
344
 
345
  .export-btn {
346
- background-color: #ffb74d;
347
  color: #000;
348
  }
349
 
@@ -357,7 +352,7 @@ body {
357
  padding: 40px;
358
  border-radius: 8px;
359
  min-height: 500px;
360
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
361
  }
362
 
363
  .restaurant-name {
@@ -447,10 +442,9 @@ body {
447
  }
448
 
449
  @media print {
450
-
451
- .navbar,
452
- .hero-section,
453
- .menu-controls,
454
  .menu-header-actions,
455
  .remove-btn {
456
  display: none !important;
@@ -476,7 +470,7 @@ body {
476
  break-inside: avoid !important;
477
  margin-bottom: 30px !important;
478
  }
479
-
480
  .tab-content {
481
  display: block !important;
482
  }
@@ -510,11 +504,11 @@ body {
510
 
511
  .smart-tag {
512
  display: inline-block;
513
- background-color: rgba(0, 230, 118, 0.15);
514
- color: #00e676;
515
  border: 1px solid #00e676;
516
  padding: 4px 12px;
517
- border-radius: 20px;
518
  font-size: 0.8rem;
519
  font-weight: bold;
520
  text-transform: uppercase;
@@ -527,4 +521,35 @@ body {
527
  color: #27ae60 !important;
528
  background-color: transparent !important;
529
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  }
 
3
  }
4
 
5
  body {
6
+ background-color: #0d1612;
7
  color: #ffffff;
8
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
9
  margin: 0;
10
  padding: 0;
11
+
12
  }
13
 
14
  /* Class tiện ích dùng chung */
 
38
  }
39
 
40
  .hero-section .highlight {
41
+ color: #00e676;
42
  }
43
 
44
  .hero-section p {
 
56
  padding: 20px;
57
  max-width: 1000px;
58
  margin: 0 auto;
59
+ flex-wrap: wrap;
60
  }
61
 
62
  .action-card {
63
+ background-color: #17241e;
64
  border-radius: 16px;
65
  padding: 40px 30px;
66
  text-align: center;
 
69
 
70
  .action-card:hover {
71
  transform: translateY(-5px);
72
+ box-shadow: 0 10px 20px rgba(0,0,0,0.4);
73
  }
74
 
75
 
 
191
  }
192
 
193
  @keyframes spin {
194
+ 0% { transform: rotate(0deg); }
195
+ 100% { transform: rotate(360deg); }
 
 
 
 
 
196
  }
197
 
198
  .editor-card {
 
200
  border-radius: 16px;
201
  padding: 30px;
202
  max-width: 900px;
203
+ margin: 30px auto 60px auto;
204
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
205
  }
206
 
207
  .editor-card img {
 
247
 
248
  .big-text {
249
  font-weight: bold;
250
+ color: #ffb74d;
251
  }
252
 
253
  .price-text {
254
  font-weight: bold;
255
+ color: #00e676;
256
  }
257
 
258
 
 
264
  background-color: #17241e;
265
  padding: 15px 30px;
266
  border-bottom: 2px solid #2b4535;
267
+ position: sticky;
268
  top: 0;
269
  z-index: 1000;
270
  }
 
338
  }
339
 
340
  .export-btn {
341
+ background-color: #ffb74d;
342
  color: #000;
343
  }
344
 
 
352
  padding: 40px;
353
  border-radius: 8px;
354
  min-height: 500px;
355
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
356
  }
357
 
358
  .restaurant-name {
 
442
  }
443
 
444
  @media print {
445
+ .navbar,
446
+ .hero-section,
447
+ .menu-controls,
 
448
  .menu-header-actions,
449
  .remove-btn {
450
  display: none !important;
 
470
  break-inside: avoid !important;
471
  margin-bottom: 30px !important;
472
  }
473
+
474
  .tab-content {
475
  display: block !important;
476
  }
 
504
 
505
  .smart-tag {
506
  display: inline-block;
507
+ background-color: rgba(0, 230, 118, 0.15);
508
+ color: #00e676;
509
  border: 1px solid #00e676;
510
  padding: 4px 12px;
511
+ border-radius: 20px;
512
  font-size: 0.8rem;
513
  font-weight: bold;
514
  text-transform: uppercase;
 
521
  color: #27ae60 !important;
522
  background-color: transparent !important;
523
  }
524
+ }
525
+
526
+ @media screen and (max-width: 768px) {
527
+ body, html {
528
+ overflow-x: hidden;
529
+ }
530
+ .navbar {
531
+ flex-direction: column;
532
+ padding: 15px 10px;
533
+ gap: 15px;
534
+ }
535
+ .nav-links {
536
+ flex-wrap: wrap;
537
+ justify-content: center;
538
+ width: 100%;
539
+ gap: 10px;
540
+ }
541
+ .lang-select {
542
+ margin-right: 0;
543
+ width: 100%;
544
+ text-align: center;
545
+ }
546
+ .nav-btn {
547
+ flex: 1 1 45%;
548
+ font-size: 0.85rem;
549
+ padding: 10px 5px;
550
+ text-align: center;
551
+ }
552
+ h1 {
553
+ font-size: 2rem !important;
554
+ }
555
  }
static/icons/icon.png ADDED

Git LFS Details

  • SHA256: 30c754b8c2e88bce2215c063027d774c4646a0c6ad837c56e2ab88516c76e4a7
  • Pointer size: 131 Bytes
  • Size of remote file: 880 kB
static/js/script.js CHANGED
@@ -1,25 +1,25 @@
1
  let currentImageUrl = "";
2
 
3
  let stream = null;
4
- let capturedFile = null;
5
 
6
- document.getElementById('imageInput').addEventListener('change', function () {
7
- capturedFile = null;
8
  if (this.files.length > 0) {
9
- analyzeFood();
10
  }
11
  });
12
 
13
  async function startCamera() {
14
  const cameraContainer = document.getElementById('cameraContainer');
15
  const video = document.getElementById('videoElement');
16
-
17
  try {
18
- stream = await navigator.mediaDevices.getUserMedia({
19
- video: { facingMode: 'environment' }
20
  });
21
  video.srcObject = stream;
22
- cameraContainer.classList.remove('hidden');
23
  } catch (err) {
24
  console.error("Lỗi xin quyền camera:", err);
25
  alert("Không thể mở Camera! Hãy đảm bảo bạn đã cấp quyền trong trình duyệt.");
@@ -31,7 +31,7 @@ function stopCamera() {
31
  if (stream) {
32
  stream.getTracks().forEach(track => track.stop());
33
  }
34
- cameraContainer.classList.add('hidden');
35
  }
36
 
37
  function capturePhoto() {
@@ -40,18 +40,18 @@ function capturePhoto() {
40
  canvas.width = video.videoWidth;
41
  canvas.height = video.videoHeight;
42
  const ctx = canvas.getContext('2d');
43
- ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
44
 
45
- canvas.toBlob(function (blob) {
46
  capturedFile = new File([blob], "camera_capture.jpg", { type: "image/jpeg" });
47
-
48
  // Thông báo và dọn dẹp
49
  alert("📸 Đã chụp ảnh thành công! Hãy bấm 'Quét ảnh' để AI phân tích.");
50
- document.getElementById('imageInput').value = "";
51
- stopCamera();
52
  analyzeFood();
53
-
54
- }, 'image/jpeg', 0.9);
55
  }
56
 
57
  async function analyzeFood() {
@@ -95,17 +95,17 @@ async function analyzeFood() {
95
  let rawPrice = String(data.data.price);
96
  let numericPrice = parseInt(rawPrice.replace(/\D/g, ''), 10);
97
  if (!isNaN(numericPrice)) {
98
- document.getElementById('editPrice').value = numericPrice.toLocaleString('en-US') + " VND";
99
- }
100
  else {
101
- document.getElementById('editPrice').value = rawPrice;
102
  }
103
  document.getElementById('editDesc').value = data.data.description;
104
- document.getElementById('editNutri').value = data.data.nutrition_summary;
105
-
106
  // Hiển thị tags WIP
107
  const tagsDiv = document.getElementById('editTags');
108
- tagsDiv.innerHTML = '';
109
  if (data.data.tags && data.data.tags.length > 0) {
110
  data.data.tags.forEach(tag => {
111
  const span = document.createElement('span');
@@ -120,9 +120,9 @@ async function analyzeFood() {
120
 
121
  editorArea.classList.remove('hidden');
122
  editorArea.scrollIntoView({ behavior: 'smooth', block: 'start' });
123
-
124
- capturedFile = null;
125
- fileInput.value = "";
126
  } else {
127
  alert("Lỗi từ Server: " + data.message);
128
  }
@@ -136,36 +136,36 @@ async function analyzeFood() {
136
 
137
 
138
  function switchTab(tabId) {
139
- document.querySelectorAll('.tab-content').forEach(tab => {
140
  tab.classList.add('hidden');
141
  });
142
-
143
-
144
  document.querySelectorAll('.nav-btn').forEach(btn => {
145
  btn.classList.remove('active');
146
  });
147
-
148
- document.getElementById(tabId).classList.remove('hidden');
149
-
150
- document.getElementById('btn-' + tabId).classList.add('active');
151
-
152
- window.scrollTo({ top: 0, behavior: 'smooth' });
153
  }
 
 
154
 
155
- let menuItems = JSON.parse(localStorage.getItem('myRestaurantMenu')) || [];
156
-
157
- document.addEventListener('DOMContentLoaded', () => {
158
  renderMenu();
159
  });
160
 
161
- async function analyzeForMenu(inputElement) {
162
  if (!inputElement.files || inputElement.files.length === 0) return;
163
-
164
  const file = inputElement.files[0];
165
  const loadingDiv = document.getElementById('menuLoading');
166
  const selectedLang = document.getElementById('outputLanguage').value;
167
  loadingDiv.classList.remove('hidden');
168
-
169
  const formData = new FormData();
170
  formData.append('file', file);
171
  formData.append('language', selectedLang);
@@ -179,12 +179,12 @@ async function analyzeForMenu(inputElement) {
179
  loadingDiv.classList.add('hidden');
180
 
181
  if (data.success) {
182
- let rawPrice = String(data.data.price);
183
  let numericPrice = parseInt(rawPrice.replace(/\D/g, ''), 10);
184
  let formattedPrice = !isNaN(numericPrice) ? numericPrice.toLocaleString('en-US') + " VND" : rawPrice;
185
 
186
- const newItem = {
187
- id: Date.now(),
188
  imageUrl: data.image_url,
189
  name: data.data.dish_name,
190
  price: formattedPrice,
@@ -194,12 +194,12 @@ async function analyzeForMenu(inputElement) {
194
  tags: data.data.tags || []
195
  };
196
 
197
- menuItems.push(newItem);
198
  localStorage.setItem('myRestaurantMenu', JSON.stringify(menuItems));
199
-
200
- renderMenu();
201
-
202
- inputElement.value = "";
203
  } else {
204
  alert("Server Error: " + data.message);
205
  }
@@ -211,15 +211,15 @@ async function analyzeForMenu(inputElement) {
211
  }
212
  }
213
 
214
- function removeFromMenu(id) {
215
  menuItems = menuItems.filter(item => item.id !== id);
216
  localStorage.setItem('myRestaurantMenu', JSON.stringify(menuItems));
217
  renderMenu();
218
  }
219
 
220
- function renderMenu() {
221
  const listContainer = document.getElementById('menuList');
222
- listContainer.innerHTML = '';
223
 
224
  if (menuItems.length === 0) {
225
  listContainer.innerHTML = '<p class="empty-menu-msg">Your menu is empty. Add some dishes!</p>';
@@ -233,9 +233,9 @@ function renderMenu() {
233
  // Delete if needed
234
  let tagsHtml = '';
235
  if (item.tags && item.tags.length > 0) {
236
- tagsHtml = '<div class="tags-container" style="margin-bottom: 10px;">' +
237
- item.tags.map(t => `<span class="smart-tag">${t}</span>`).join('') +
238
- '</div>';
239
  }
240
 
241
 
@@ -257,12 +257,12 @@ function renderMenu() {
257
  });
258
  }
259
 
260
- function exportToPDF() {
261
  if (menuItems.length === 0) {
262
  alert("Cannot export an empty menu!");
263
  return;
264
  }
265
-
266
- window.print();
267
  }
268
 
 
1
  let currentImageUrl = "";
2
 
3
  let stream = null;
4
+ let capturedFile = null;
5
 
6
+ document.getElementById('imageInput').addEventListener('change', function() {
7
+ capturedFile = null;
8
  if (this.files.length > 0) {
9
+ analyzeFood();
10
  }
11
  });
12
 
13
  async function startCamera() {
14
  const cameraContainer = document.getElementById('cameraContainer');
15
  const video = document.getElementById('videoElement');
16
+
17
  try {
18
+ stream = await navigator.mediaDevices.getUserMedia({
19
+ video: { facingMode: 'environment' }
20
  });
21
  video.srcObject = stream;
22
+ cameraContainer.classList.remove('hidden');
23
  } catch (err) {
24
  console.error("Lỗi xin quyền camera:", err);
25
  alert("Không thể mở Camera! Hãy đảm bảo bạn đã cấp quyền trong trình duyệt.");
 
31
  if (stream) {
32
  stream.getTracks().forEach(track => track.stop());
33
  }
34
+ cameraContainer.classList.add('hidden');
35
  }
36
 
37
  function capturePhoto() {
 
40
  canvas.width = video.videoWidth;
41
  canvas.height = video.videoHeight;
42
  const ctx = canvas.getContext('2d');
43
+ ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
44
 
45
+ canvas.toBlob(function(blob) {
46
  capturedFile = new File([blob], "camera_capture.jpg", { type: "image/jpeg" });
47
+
48
  // Thông báo và dọn dẹp
49
  alert("📸 Đã chụp ảnh thành công! Hãy bấm 'Quét ảnh' để AI phân tích.");
50
+ document.getElementById('imageInput').value = "";
51
+ stopCamera();
52
  analyzeFood();
53
+
54
+ }, 'image/jpeg', 0.9);
55
  }
56
 
57
  async function analyzeFood() {
 
95
  let rawPrice = String(data.data.price);
96
  let numericPrice = parseInt(rawPrice.replace(/\D/g, ''), 10);
97
  if (!isNaN(numericPrice)) {
98
+ document.getElementById('editPrice').value = numericPrice.toLocaleString('en-US') + " VND";
99
+ }
100
  else {
101
+ document.getElementById('editPrice').value = rawPrice;
102
  }
103
  document.getElementById('editDesc').value = data.data.description;
104
+ document.getElementById('editNutri').value = data.data.nutrition_summary;
105
+
106
  // Hiển thị tags WIP
107
  const tagsDiv = document.getElementById('editTags');
108
+ tagsDiv.innerHTML = '';
109
  if (data.data.tags && data.data.tags.length > 0) {
110
  data.data.tags.forEach(tag => {
111
  const span = document.createElement('span');
 
120
 
121
  editorArea.classList.remove('hidden');
122
  editorArea.scrollIntoView({ behavior: 'smooth', block: 'start' });
123
+
124
+ capturedFile = null;
125
+ fileInput.value = "";
126
  } else {
127
  alert("Lỗi từ Server: " + data.message);
128
  }
 
136
 
137
 
138
  function switchTab(tabId) {
139
+ document.querySelectorAll('.tab-content').forEach(tab => {
140
  tab.classList.add('hidden');
141
  });
142
+
143
+
144
  document.querySelectorAll('.nav-btn').forEach(btn => {
145
  btn.classList.remove('active');
146
  });
147
+
148
+ document.getElementById(tabId).classList.remove('hidden');
149
+
150
+ document.getElementById('btn-' + tabId).classList.add('active');
151
+
152
+ window.scrollTo({ top: 0, behavior: 'smooth' });
153
  }
154
+
155
+ let menuItems = JSON.parse(localStorage.getItem('myRestaurantMenu')) || [];
156
 
157
+ document.addEventListener('DOMContentLoaded', () => {
 
 
158
  renderMenu();
159
  });
160
 
161
+ async function analyzeForMenu(inputElement) {
162
  if (!inputElement.files || inputElement.files.length === 0) return;
163
+
164
  const file = inputElement.files[0];
165
  const loadingDiv = document.getElementById('menuLoading');
166
  const selectedLang = document.getElementById('outputLanguage').value;
167
  loadingDiv.classList.remove('hidden');
168
+
169
  const formData = new FormData();
170
  formData.append('file', file);
171
  formData.append('language', selectedLang);
 
179
  loadingDiv.classList.add('hidden');
180
 
181
  if (data.success) {
182
+ let rawPrice = String(data.data.price);
183
  let numericPrice = parseInt(rawPrice.replace(/\D/g, ''), 10);
184
  let formattedPrice = !isNaN(numericPrice) ? numericPrice.toLocaleString('en-US') + " VND" : rawPrice;
185
 
186
+ const newItem = {
187
+ id: Date.now(),
188
  imageUrl: data.image_url,
189
  name: data.data.dish_name,
190
  price: formattedPrice,
 
194
  tags: data.data.tags || []
195
  };
196
 
197
+ menuItems.push(newItem);
198
  localStorage.setItem('myRestaurantMenu', JSON.stringify(menuItems));
199
+
200
+ renderMenu();
201
+
202
+ inputElement.value = "";
203
  } else {
204
  alert("Server Error: " + data.message);
205
  }
 
211
  }
212
  }
213
 
214
+ function removeFromMenu(id) {
215
  menuItems = menuItems.filter(item => item.id !== id);
216
  localStorage.setItem('myRestaurantMenu', JSON.stringify(menuItems));
217
  renderMenu();
218
  }
219
 
220
+ function renderMenu() {
221
  const listContainer = document.getElementById('menuList');
222
+ listContainer.innerHTML = '';
223
 
224
  if (menuItems.length === 0) {
225
  listContainer.innerHTML = '<p class="empty-menu-msg">Your menu is empty. Add some dishes!</p>';
 
233
  // Delete if needed
234
  let tagsHtml = '';
235
  if (item.tags && item.tags.length > 0) {
236
+ tagsHtml = '<div class="tags-container" style="margin-bottom: 10px;">' +
237
+ item.tags.map(t => `<span class="smart-tag">${t}</span>`).join('') +
238
+ '</div>';
239
  }
240
 
241
 
 
257
  });
258
  }
259
 
260
+ function exportToPDF() {
261
  if (menuItems.length === 0) {
262
  alert("Cannot export an empty menu!");
263
  return;
264
  }
265
+
266
+ window.print();
267
  }
268
 
static/manifest.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Food Detection and Menu Generation ",
3
+ "short_name": "Food Detection",
4
+ "start_url": "/",
5
+ "display": "standalone",
6
+ "background_color": "#17241e",
7
+ "theme_color": "#17241e",
8
+ "orientation": "portrait",
9
+ "icons": [
10
+ {
11
+ "src": "/static/icons/icon.png",
12
+ "sizes": "192x192",
13
+ "type": "image/png"
14
+ },
15
+ {
16
+ "src": "/static/icons/icon.png",
17
+ "sizes": "512x512",
18
+ "type": "image/png"
19
+ }
20
+ ]
21
+ }
templates/index.html CHANGED
@@ -5,8 +5,12 @@
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Now i know what i eat</title>
 
 
 
 
8
  <link rel="stylesheet" href="/static/css/style.css">
9
- <script src="https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js"></script>
10
  </head>
11
 
12
  <body>
 
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>Now i know what i eat</title>
8
+
9
+ <link rel="manifest" href="/static/manifest.json">
10
+ <meta name="theme-color" content="#17241e">
11
+
12
  <link rel="stylesheet" href="/static/css/style.css">
13
+
14
  </head>
15
 
16
  <body>