AssanaliAidarkhan commited on
Commit
8b3cfea
·
verified ·
1 Parent(s): 850fda8

Upload 3 files

Browse files
Files changed (3) hide show
  1. README_FOR_SPACE.md +29 -0
  2. SIMPLE_DEPLOY.md +68 -0
  3. app.html +572 -0
README_FOR_SPACE.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Neurobox AI Quality Control
3
+ emoji: 🤖
4
+ colorFrom: blue
5
+ colorTo: indigo
6
+ sdk: static
7
+ pinned: false
8
+ ---
9
+
10
+ # Neurobox AI - Автоматический Контроль Качества
11
+
12
+ ## 🎯 Кейс 1: Контроль качества на конвейере
13
+
14
+ Интерактивный дашборд для демонстрации системы автоматического контроля качества с использованием искусственного интеллекта.
15
+
16
+ ### Описание проблемы
17
+ Ручной контроль качества пропускает до 5-7% брака из-за усталости и невнимательности оператора.
18
+
19
+ ### Решение с ИИ
20
+ Neurobox анализирует видеопоток и автоматически выявляет дефекты: царапины, сколы, непрокрас.
21
+
22
+ ### Результаты
23
+ - ✅ Снижение брака на 90%
24
+ - ✅ Увеличение скорости линии на 15%
25
+ - ✅ Работа 24/7 без потери точности
26
+
27
+ ---
28
+
29
+ **Разработано для демонстрации возможностей Neurobox AI**
SIMPLE_DEPLOY.md ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🚀 ПРОСТАЯ ИНСТРУКЦИЯ ДЛЯ ДЕПЛОЯ
2
+
3
+ ## ✅ ЧТО ЗАГРУЖАТЬ НА HUGGING FACE:
4
+
5
+ Загрузите только **2 файла**:
6
+
7
+ 1. **`README_FOR_SPACE.md`** → переименуйте в **`README.md`**
8
+ 2. **`app.html`** → переименуйте в **`index.html`**
9
+
10
+ ---
11
+
12
+ ## 📝 Пошаговая инструкция:
13
+
14
+ ### Шаг 1: Создайте Space
15
+ 1. Откройте https://huggingface.co/spaces
16
+ 2. Нажмите **"Create new Space"**
17
+ 3. Заполните:
18
+ - **Name**: `neurobox-quality-control`
19
+ - **SDK**: выберите **Static**
20
+ - **Visibility**: Public
21
+ 4. Нажмите **"Create Space"**
22
+
23
+ ### Шаг 2: Удалите стандартные файлы
24
+ 1. В созданном Space удалите все файлы которые там есть
25
+
26
+ ### Шаг 3: Загрузите файлы
27
+ 1. Нажмите **"Files"** → **"Add file"** → **"Upload files"**
28
+ 2. Загрузите файл **`README_FOR_SPACE.md`**
29
+ 3. При загрузке переименуйте его в **`README.md`**
30
+ 4. Загрузите файл **`app.html`**
31
+ 5. При загрузке переименуйте его в **`index.html`**
32
+ 6. Нажмите **"Commit changes to main"**
33
+
34
+ ### Шаг 4: Подождите
35
+ Через 30-60 секунд приложение запустится автоматически!
36
+
37
+ ---
38
+
39
+ ## 🎉 Готово!
40
+
41
+ Ваш дашборд будет доступен по адресу:
42
+ ```
43
+ https://huggingface.co/spaces/ВАШ_USERNAME/neurobox-quality-control
44
+ ```
45
+
46
+ ---
47
+
48
+ ## 🔧 Если что-то не работает:
49
+
50
+ 1. **Проверьте что файлы называются:**
51
+ - `README.md` (не README_FOR_SPACE.md)
52
+ - `index.html` (не app.html)
53
+
54
+ 2. **Проверьте что SDK = Static**
55
+
56
+ 3. **Подождите 1-2 минуты** - иногда Space нужно время на запуск
57
+
58
+ ---
59
+
60
+ ## 📁 Структура файлов в Space должна быть:
61
+
62
+ ```
63
+ your-space/
64
+ ├── README.md ← метаданные Space
65
+ └── index.html ← ваше приложение
66
+ ```
67
+
68
+ Всё! Больше ничего не нужно! 🚀
app.html ADDED
@@ -0,0 +1,572 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Neurobox AI - Контроль Качества</title>
7
+ <script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
8
+ <script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
9
+ <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
10
+ <script src="https://cdn.tailwindcss.com"></script>
11
+ <style>
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ }
17
+ body {
18
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
19
+ }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <div id="root"></div>
24
+
25
+ <script type="text/babel">
26
+ const { useState, useEffect, useRef } = React;
27
+
28
+ // Lucide Icons as inline SVG components
29
+ const Camera = ({ size = 24, className = "" }) => (
30
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
31
+ <path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/>
32
+ <circle cx="12" cy="13" r="4"/>
33
+ </svg>
34
+ );
35
+
36
+ const AlertTriangle = ({ size = 24, className = "" }) => (
37
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
38
+ <path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
39
+ <line x1="12" y1="9" x2="12" y2="13"/>
40
+ <line x1="12" y1="17" x2="12.01" y2="17"/>
41
+ </svg>
42
+ );
43
+
44
+ const CheckCircle = ({ size = 24, className = "" }) => (
45
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
46
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
47
+ <polyline points="22 4 12 14.01 9 11.01"/>
48
+ </svg>
49
+ );
50
+
51
+ const Activity = ({ size = 24, className = "" }) => (
52
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
53
+ <polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
54
+ </svg>
55
+ );
56
+
57
+ const Clock = ({ size = 24, className = "" }) => (
58
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
59
+ <circle cx="12" cy="12" r="10"/>
60
+ <polyline points="12 6 12 12 16 14"/>
61
+ </svg>
62
+ );
63
+
64
+ const TrendingUp = ({ size = 24, className = "" }) => (
65
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
66
+ <polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/>
67
+ <polyline points="17 6 23 6 23 12"/>
68
+ </svg>
69
+ );
70
+
71
+ const Play = ({ size = 24, className = "" }) => (
72
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
73
+ <polygon points="5 3 19 12 5 21 5 3"/>
74
+ </svg>
75
+ );
76
+
77
+ const Pause = ({ size = 24, className = "" }) => (
78
+ <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={className}>
79
+ <rect x="6" y="4" width="4" height="16"/>
80
+ <rect x="14" y="4" width="4" height="16"/>
81
+ </svg>
82
+ );
83
+
84
+ const QualityControlDashboard = () => {
85
+ const [currentTime, setCurrentTime] = useState(new Date());
86
+ const [inspectedCount, setInspectedCount] = useState(4512);
87
+ const [defectsFound, setDefectsFound] = useState(28);
88
+ const [isRunning, setIsRunning] = useState(true);
89
+ const [events, setEvents] = useState([
90
+ { id: 77812, time: '10:32:04', type: 'defect', defectType: 'Царапина', severity: 'high' },
91
+ { id: 77811, time: '10:31:55', type: 'ok', defectType: null, severity: null },
92
+ { id: 77810, time: '10:31:48', type: 'defect', defectType: 'Непрокрас', severity: 'medium' },
93
+ { id: 77809, time: '10:31:41', type: 'ok', defectType: null, severity: null },
94
+ { id: 77808, time: '10:31:34', type: 'defect', defectType: 'Скол', severity: 'high' },
95
+ ]);
96
+
97
+ const [currentItem, setCurrentItem] = useState({ id: 77813, status: 'checking' });
98
+ const [defectStats, setDefectStats] = useState({
99
+ 'Царапина': 15,
100
+ 'Скол': 9,
101
+ 'Непрокрас': 4
102
+ });
103
+
104
+ const canvasRef = useRef(null);
105
+
106
+ useEffect(() => {
107
+ const timer = setInterval(() => {
108
+ setCurrentTime(new Date());
109
+ }, 1000);
110
+ return () => clearInterval(timer);
111
+ }, []);
112
+
113
+ useEffect(() => {
114
+ if (!isRunning) return;
115
+
116
+ const interval = setInterval(() => {
117
+ const newId = currentItem.id + 1;
118
+ const isDefective = Math.random() < 0.0062;
119
+
120
+ setInspectedCount(prev => prev + 1);
121
+
122
+ if (isDefective) {
123
+ setDefectsFound(prev => prev + 1);
124
+ const defectTypes = ['Царапина', 'Скол', 'Непрокрас'];
125
+ const severities = ['high', 'medium', 'low'];
126
+ const defectType = defectTypes[Math.floor(Math.random() * defectTypes.length)];
127
+ const severity = severities[Math.floor(Math.random() * severities.length)];
128
+
129
+ setDefectStats(prev => ({
130
+ ...prev,
131
+ [defectType]: (prev[defectType] || 0) + 1
132
+ }));
133
+
134
+ const newEvent = {
135
+ id: newId,
136
+ time: new Date().toLocaleTimeString('ru-RU'),
137
+ type: 'defect',
138
+ defectType,
139
+ severity
140
+ };
141
+
142
+ setEvents(prev => [newEvent, ...prev.slice(0, 9)]);
143
+ setCurrentItem({ id: newId, status: 'defect', defectType });
144
+ } else {
145
+ const newEvent = {
146
+ id: newId,
147
+ time: new Date().toLocaleTimeString('ru-RU'),
148
+ type: 'ok',
149
+ defectType: null,
150
+ severity: null
151
+ };
152
+
153
+ setEvents(prev => [newEvent, ...prev.slice(0, 9)]);
154
+ setCurrentItem({ id: newId, status: 'ok' });
155
+ }
156
+ }, 3000);
157
+
158
+ return () => clearInterval(interval);
159
+ }, [isRunning, currentItem.id]);
160
+
161
+ useEffect(() => {
162
+ const canvas = canvasRef.current;
163
+ if (!canvas) return;
164
+
165
+ const ctx = canvas.getContext('2d');
166
+ let animationFrame;
167
+ let offset = 0;
168
+
169
+ const animate = () => {
170
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
171
+ gradient.addColorStop(0, '#ffffff');
172
+ gradient.addColorStop(1, '#f0f9ff');
173
+ ctx.fillStyle = gradient;
174
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
175
+
176
+ ctx.strokeStyle = 'rgba(59, 130, 246, 0.1)';
177
+ ctx.lineWidth = 1;
178
+ for (let i = 0; i < 5; i++) {
179
+ ctx.beginPath();
180
+ ctx.moveTo(0, 50 + i * 60);
181
+ ctx.lineTo(canvas.width, 50 + i * 60);
182
+ ctx.stroke();
183
+ }
184
+
185
+ const lightGradient = ctx.createRadialGradient(canvas.width / 2, 100, 50, canvas.width / 2, 100, 300);
186
+ lightGradient.addColorStop(0, 'rgba(59, 130, 246, 0.15)');
187
+ lightGradient.addColorStop(1, 'rgba(59, 130, 246, 0)');
188
+ ctx.fillStyle = lightGradient;
189
+ ctx.fillRect(0, 0, canvas.width, 300);
190
+
191
+ const beltGradient = ctx.createLinearGradient(0, canvas.height - 80, 0, canvas.height);
192
+ beltGradient.addColorStop(0, '#64748b');
193
+ beltGradient.addColorStop(0.5, '#475569');
194
+ beltGradient.addColorStop(1, '#64748b');
195
+ ctx.fillStyle = beltGradient;
196
+ ctx.fillRect(0, canvas.height - 80, canvas.width, 80);
197
+
198
+ ctx.fillStyle = '#94a3b8';
199
+ ctx.fillRect(0, canvas.height - 82, canvas.width, 2);
200
+ ctx.fillRect(0, canvas.height, canvas.width, 2);
201
+
202
+ ctx.strokeStyle = '#cbd5e1';
203
+ ctx.lineWidth = 2;
204
+ for (let i = -20; i < canvas.width; i += 40) {
205
+ ctx.beginPath();
206
+ ctx.moveTo(i + offset, canvas.height - 80);
207
+ ctx.lineTo(i + offset, canvas.height);
208
+ ctx.stroke();
209
+ }
210
+
211
+ for (let x of [50, canvas.width - 50]) {
212
+ ctx.fillStyle = '#94a3b8';
213
+ ctx.beginPath();
214
+ ctx.arc(x, canvas.height - 40, 15, 0, Math.PI * 2);
215
+ ctx.fill();
216
+ ctx.strokeStyle = '#475569';
217
+ ctx.lineWidth = 2;
218
+ ctx.stroke();
219
+ }
220
+
221
+ const itemX = canvas.width / 2 - 50;
222
+ const itemY = canvas.height - 180;
223
+
224
+ ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';
225
+ ctx.beginPath();
226
+ ctx.ellipse(itemX + 50, itemY + 110, 55, 15, 0, 0, Math.PI * 2);
227
+ ctx.fill();
228
+
229
+ const productGradient = ctx.createLinearGradient(itemX, itemY, itemX, itemY + 100);
230
+
231
+ if (currentItem.status === 'defect') {
232
+ productGradient.addColorStop(0, '#fca5a5');
233
+ productGradient.addColorStop(0.5, '#f87171');
234
+ productGradient.addColorStop(1, '#ef4444');
235
+ } else if (currentItem.status === 'ok') {
236
+ productGradient.addColorStop(0, '#6ee7b7');
237
+ productGradient.addColorStop(0.5, '#34d399');
238
+ productGradient.addColorStop(1, '#10b981');
239
+ } else {
240
+ productGradient.addColorStop(0, '#93c5fd');
241
+ productGradient.addColorStop(0.5, '#60a5fa');
242
+ productGradient.addColorStop(1, '#3b82f6');
243
+ }
244
+
245
+ ctx.fillStyle = productGradient;
246
+
247
+ ctx.fillRect(itemX + 35, itemY, 30, 15);
248
+ ctx.fillRect(itemX + 20, itemY + 15, 60, 85);
249
+
250
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
251
+ ctx.fillRect(itemX + 25, itemY + 20, 15, 70);
252
+
253
+ ctx.fillStyle = currentItem.status === 'defect' ? '#dc2626' : '#2563eb';
254
+ ctx.fillRect(itemX + 25, itemY + 40, 50, 30);
255
+ ctx.fillStyle = '#ffffff';
256
+ ctx.font = 'bold 10px Arial';
257
+ ctx.fillText('NEURO', itemX + 32, itemY + 52);
258
+ ctx.fillText('PRODUCT', itemX + 28, itemY + 64);
259
+
260
+ ctx.fillStyle = '#334155';
261
+ ctx.fillRect(itemX + 32, itemY - 8, 36, 8);
262
+ ctx.fillStyle = '#475569';
263
+ ctx.fillRect(itemX + 35, itemY - 5, 30, 5);
264
+
265
+ ctx.strokeStyle = '#334155';
266
+ ctx.lineWidth = 2;
267
+ ctx.strokeRect(itemX + 20, itemY + 15, 60, 85);
268
+ ctx.strokeRect(itemX + 35, itemY, 30, 15);
269
+
270
+ if (currentItem.status === 'defect') {
271
+ ctx.strokeStyle = '#ef4444';
272
+ ctx.lineWidth = 5;
273
+ ctx.setLineDash([10, 5]);
274
+ ctx.strokeRect(itemX + 10, itemY - 20, 80, 130);
275
+ ctx.setLineDash([]);
276
+
277
+ if (currentItem.defectType === 'Царапина') {
278
+ ctx.strokeStyle = '#dc2626';
279
+ ctx.lineWidth = 4;
280
+ ctx.beginPath();
281
+ ctx.moveTo(itemX + 30, itemY + 35);
282
+ ctx.lineTo(itemX + 45, itemY + 50);
283
+ ctx.stroke();
284
+ } else if (currentItem.defectType === 'Скол') {
285
+ ctx.fillStyle = '#dc2626';
286
+ ctx.beginPath();
287
+ ctx.moveTo(itemX + 75, itemY + 60);
288
+ ctx.lineTo(itemX + 80, itemY + 55);
289
+ ctx.lineTo(itemX + 80, itemY + 65);
290
+ ctx.fill();
291
+ } else if (currentItem.defectType === 'Непрокрас') {
292
+ ctx.fillStyle = 'rgba(220, 38, 38, 0.7)';
293
+ ctx.beginPath();
294
+ ctx.arc(itemX + 40, itemY + 70, 12, 0, Math.PI * 2);
295
+ ctx.fill();
296
+ }
297
+
298
+ ctx.fillStyle = '#ef4444';
299
+ ctx.beginPath();
300
+ ctx.arc(itemX + 85, itemY - 10, 20, 0, Math.PI * 2);
301
+ ctx.fill();
302
+
303
+ ctx.fillStyle = '#ffffff';
304
+ ctx.font = 'bold 24px Arial';
305
+ ctx.fillText('!', itemX + 80, itemY - 1);
306
+
307
+ ctx.fillStyle = '#dc2626';
308
+ ctx.font = 'bold 18px Arial';
309
+ ctx.fillText('БРАК ОБНАРУЖЕН', itemX - 30, itemY - 35);
310
+ } else if (currentItem.status === 'ok') {
311
+ ctx.strokeStyle = '#10b981';
312
+ ctx.lineWidth = 5;
313
+ ctx.beginPath();
314
+ ctx.arc(itemX + 50, itemY - 30, 20, 0, Math.PI * 2);
315
+ ctx.stroke();
316
+
317
+ ctx.strokeStyle = '#10b981';
318
+ ctx.lineWidth = 5;
319
+ ctx.beginPath();
320
+ ctx.moveTo(itemX + 40, itemY - 30);
321
+ ctx.lineTo(itemX + 47, itemY - 23);
322
+ ctx.lineTo(itemX + 62, itemY - 38);
323
+ ctx.stroke();
324
+
325
+ ctx.fillStyle = '#059669';
326
+ ctx.font = 'bold 16px Arial';
327
+ ctx.fillText('✓ ГОДЕН', itemX + 20, itemY - 35);
328
+ }
329
+
330
+ ctx.fillStyle = 'rgba(51, 65, 85, 0.9)';
331
+ ctx.fillRect(itemX + 15, itemY + 105, 70, 20);
332
+ ctx.fillStyle = '#FFFFFF';
333
+ ctx.font = 'bold 13px monospace';
334
+ ctx.fillText(`ID: ${currentItem.id}`, itemX + 20, itemY + 118);
335
+
336
+ const scanY = (Date.now() / 10) % 200 + itemY - 30;
337
+ ctx.strokeStyle = 'rgba(59, 130, 246, 0.8)';
338
+ ctx.lineWidth = 3;
339
+ ctx.beginPath();
340
+ ctx.moveTo(itemX - 20, scanY);
341
+ ctx.lineTo(itemX + 120, scanY);
342
+ ctx.stroke();
343
+
344
+ ctx.fillStyle = 'rgba(59, 130, 246, 0.3)';
345
+ ctx.fillRect(itemX - 20, scanY - 2, 140, 5);
346
+
347
+ const drawBackgroundProduct = (x, y, scale, opacity) => {
348
+ ctx.globalAlpha = opacity;
349
+ ctx.fillStyle = '#94a3b8';
350
+ ctx.fillRect(x + 35 * scale, y, 30 * scale, 15 * scale);
351
+ ctx.fillRect(x + 20 * scale, y + 15 * scale, 60 * scale, 85 * scale);
352
+ ctx.strokeStyle = '#64748b';
353
+ ctx.lineWidth = 1;
354
+ ctx.strokeRect(x + 20 * scale, y + 15 * scale, 60 * scale, 85 * scale);
355
+ ctx.globalAlpha = 1;
356
+ };
357
+
358
+ drawBackgroundProduct(150, itemY + 80, 0.6, 0.6);
359
+ drawBackgroundProduct(550, itemY + 90, 0.5, 0.5);
360
+
361
+ if (isRunning) {
362
+ offset = (offset + 2) % 40;
363
+ }
364
+
365
+ animationFrame = requestAnimationFrame(animate);
366
+ };
367
+
368
+ animate();
369
+ return () => cancelAnimationFrame(animationFrame);
370
+ }, [currentItem, isRunning]);
371
+
372
+ const defectRate = ((defectsFound / inspectedCount) * 100).toFixed(2);
373
+
374
+ const getSeverityColor = (severity) => {
375
+ switch(severity) {
376
+ case 'high': return 'bg-red-100 text-red-700 border-red-400';
377
+ case 'medium': return 'bg-yellow-100 text-yellow-700 border-yellow-400';
378
+ case 'low': return 'bg-blue-100 text-blue-700 border-blue-400';
379
+ default: return 'bg-gray-100 text-gray-700 border-gray-400';
380
+ }
381
+ };
382
+
383
+ const getSeverityText = (severity) => {
384
+ switch(severity) {
385
+ case 'high': return 'Высокая';
386
+ case 'medium': return 'Средняя';
387
+ case 'low': return 'Низкая';
388
+ default: return '';
389
+ }
390
+ };
391
+
392
+ return (
393
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50 p-6">
394
+ <div className="max-w-7xl mx-auto">
395
+ <div className="bg-gradient-to-r from-blue-500 to-indigo-600 rounded-xl shadow-xl p-6 mb-6 border-2 border-blue-300">
396
+ <div className="flex justify-between items-center">
397
+ <div className="flex items-center gap-4">
398
+ <div className="bg-white p-2 rounded-lg">
399
+ <Camera size={32} className="text-blue-600" />
400
+ </div>
401
+ <div>
402
+ <h1 className="text-3xl font-bold text-white">Дашборд контроля качества</h1>
403
+ <p className="text-blue-50 text-sm font-medium">Линия 3 • Neurobox AI System</p>
404
+ </div>
405
+ </div>
406
+ <div className="flex items-center gap-6">
407
+ <button
408
+ onClick={() => setIsRunning(!isRunning)}
409
+ className="flex items-center gap-2 bg-white text-blue-600 px-5 py-2.5 rounded-lg hover:bg-blue-50 transition-all font-bold shadow-lg hover:shadow-xl"
410
+ >
411
+ {isRunning ? <Pause size={20} /> : <Play size={20} />}
412
+ {isRunning ? 'Пауза' : 'Запуск'}
413
+ </button>
414
+ <div className="flex items-center gap-2 bg-white px-4 py-2 rounded-lg shadow-md">
415
+ <Activity size={20} className={isRunning ? 'text-green-500 animate-pulse' : 'text-gray-400'} />
416
+ <span className={`font-bold ${isRunning ? 'text-green-600' : 'text-gray-600'}`}>
417
+ {isRunning ? 'Работа' : 'Остановлено'}
418
+ </span>
419
+ </div>
420
+ <div className="flex items-center gap-2 bg-white px-4 py-2 rounded-lg shadow-md">
421
+ <Clock size={20} className="text-indigo-600" />
422
+ <span className="font-mono text-lg text-gray-800 font-semibold">
423
+ {currentTime.toLocaleTimeString('ru-RU')} {currentTime.toLocaleDateString('ru-RU')}
424
+ </span>
425
+ </div>
426
+ </div>
427
+ </div>
428
+ </div>
429
+
430
+ <div className="grid grid-cols-3 gap-6">
431
+ <div className="col-span-2 space-y-6">
432
+ <div className="bg-white rounded-xl shadow-xl p-6 border-2 border-blue-200">
433
+ <div className="flex items-center justify-between mb-4">
434
+ <h2 className="text-xl font-bold text-gray-800 flex items-center gap-2">
435
+ <Camera className="text-blue-500" />
436
+ Прямая трансляция с камеры
437
+ </h2>
438
+ <span className="text-green-600 flex items-center gap-2 text-sm font-semibold bg-green-50 px-3 py-1 rounded-full border border-green-200">
439
+ <span className="w-2 h-2 bg-green-500 rounded-full animate-pulse"></span>
440
+ В эфире
441
+ </span>
442
+ </div>
443
+ <div className="bg-gradient-to-br from-gray-50 to-blue-50 rounded-lg overflow-hidden border-2 border-gray-300 shadow-inner">
444
+ <canvas
445
+ ref={canvasRef}
446
+ width={800}
447
+ height={400}
448
+ className="w-full"
449
+ />
450
+ </div>
451
+ </div>
452
+
453
+ <div className="bg-white rounded-xl shadow-xl p-6 border-2 border-blue-200">
454
+ <h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2">
455
+ <TrendingUp className="text-green-500" />
456
+ Статистика за смену
457
+ </h2>
458
+ <div className="grid grid-cols-3 gap-4">
459
+ <div className="bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl p-5 shadow-lg border-2 border-blue-300">
460
+ <p className="text-blue-50 text-sm mb-1 font-semibold">Проверено изделий</p>
461
+ <p className="text-4xl font-bold text-white">{inspectedCount.toLocaleString('ru-RU')}</p>
462
+ </div>
463
+ <div className="bg-gradient-to-br from-red-500 to-red-600 rounded-xl p-5 shadow-lg border-2 border-red-300">
464
+ <p className="text-red-50 text-sm mb-1 font-semibold">Выявлено брака</p>
465
+ <p className="text-4xl font-bold text-white">{defectsFound} <span className="text-xl">({defectRate}%)</span></p>
466
+ </div>
467
+ <div className="bg-gradient-to-br from-green-500 to-green-600 rounded-xl p-5 shadow-lg border-2 border-green-300">
468
+ <p className="text-green-50 text-sm mb-1 font-semibold">Годных изделий</p>
469
+ <p className="text-4xl font-bold text-white">{(inspectedCount - defectsFound).toLocaleString('ru-RU')}</p>
470
+ </div>
471
+ </div>
472
+
473
+ <div className="mt-6 bg-gradient-to-br from-gray-50 to-blue-50 rounded-xl p-5 border-2 border-gray-200 shadow-inner">
474
+ <h3 className="text-gray-800 font-bold mb-3 text-lg">Основные дефекты:</h3>
475
+ <div className="space-y-3">
476
+ {Object.entries(defectStats).map(([defect, count]) => (
477
+ <div key={defect} className="flex items-center justify-between bg-white p-3 rounded-lg shadow-sm border border-gray-200">
478
+ <span className="text-gray-700 font-semibold">{defect}</span>
479
+ <div className="flex items-center gap-3">
480
+ <div className="w-48 bg-gray-200 rounded-full h-3 overflow-hidden border border-gray-300">
481
+ <div
482
+ className="bg-gradient-to-r from-red-500 to-red-600 h-full rounded-full transition-all duration-500 shadow-sm"
483
+ style={{ width: `${(count / defectsFound) * 100}%` }}
484
+ ></div>
485
+ </div>
486
+ <span className="text-gray-800 font-bold w-12 text-right text-lg">{count}</span>
487
+ </div>
488
+ </div>
489
+ ))}
490
+ </div>
491
+ </div>
492
+ </div>
493
+ </div>
494
+
495
+ <div className="col-span-1">
496
+ <div className="bg-white rounded-xl shadow-xl p-6 h-full border-2 border-blue-200">
497
+ <h2 className="text-xl font-bold text-gray-800 mb-4 flex items-center gap-2">
498
+ <Activity className="text-yellow-500" />
499
+ Журнал событий
500
+ <span className="text-sm text-gray-500 font-normal ml-auto">Последние 10 мин</span>
501
+ </h2>
502
+ <div className="space-y-3 overflow-y-auto max-h-[calc(100vh-200px)] pr-2">
503
+ {events.map((event, index) => (
504
+ <div
505
+ key={`${event.id}-${index}`}
506
+ className={`rounded-lg p-4 border-2 transition-all duration-300 shadow-md ${
507
+ event.type === 'defect'
508
+ ? 'bg-red-50 border-red-400 hover:shadow-lg'
509
+ : 'bg-green-50 border-green-400 hover:shadow-lg'
510
+ } ${index === 0 ? 'ring-2 ring-blue-400 shadow-xl' : ''}`}
511
+ >
512
+ <div className="flex items-start justify-between mb-2">
513
+ <span className="text-gray-700 font-mono text-sm font-semibold bg-white px-2 py-1 rounded border border-gray-300">{event.time}</span>
514
+ {event.type === 'defect' ? (
515
+ <AlertTriangle size={22} className="text-red-600" />
516
+ ) : (
517
+ <CheckCircle size={22} className="text-green-600" />
518
+ )}
519
+ </div>
520
+ <div className="flex items-center justify-between mb-2">
521
+ <span className={`font-bold text-lg ${event.type === 'defect' ? 'text-red-700' : 'text-green-700'}`}>
522
+ {event.type === 'defect' ? `Дефект: ${event.defectType}` : 'OK'}
523
+ </span>
524
+ <span className="text-gray-600 text-sm font-semibold bg-white px-2 py-1 rounded border border-gray-300">ID: {event.id}</span>
525
+ </div>
526
+ {event.type === 'defect' && (
527
+ <div className="mt-2">
528
+ <span className={`inline-block px-3 py-1 rounded-full text-xs font-bold border-2 ${getSeverityColor(event.severity)}`}>
529
+ Срочность: {getSeverityText(event.severity)}
530
+ </span>
531
+ </div>
532
+ )}
533
+ {event.type === 'defect' && (
534
+ <div className="mt-3 bg-white rounded-lg p-3 h-24 flex items-center justify-center border-2 border-gray-300 shadow-inner">
535
+ <div className="w-20 h-20 bg-gradient-to-br from-red-400 to-red-600 rounded-lg border-2 border-red-500 flex items-center justify-center shadow-md">
536
+ <span className="text-white text-xs font-bold">ФОТО</span>
537
+ </div>
538
+ </div>
539
+ )}
540
+ </div>
541
+ ))}
542
+ </div>
543
+ </div>
544
+ </div>
545
+ </div>
546
+
547
+ <div className="mt-6 bg-white rounded-xl shadow-xl p-6 border-2 border-blue-200">
548
+ <div className="grid grid-cols-3 gap-6 text-center">
549
+ <div className="bg-gradient-to-br from-green-50 to-emerald-50 p-5 rounded-xl border-2 border-green-300 shadow-md">
550
+ <p className="text-gray-700 text-sm font-semibold mb-1">Снижение брака</p>
551
+ <p className="text-4xl font-bold text-green-600">↓ 90%</p>
552
+ </div>
553
+ <div className="bg-gradient-to-br from-blue-50 to-indigo-50 p-5 rounded-xl border-2 border-blue-300 shadow-md">
554
+ <p className="text-gray-700 text-sm font-semibold mb-1">Увеличение скорости линии</p>
555
+ <p className="text-4xl font-bold text-blue-600">↑ 15%</p>
556
+ </div>
557
+ <div className="bg-gradient-to-br from-purple-50 to-pink-50 p-5 rounded-xl border-2 border-purple-300 shadow-md">
558
+ <p className="text-gray-700 text-sm font-semibold mb-1">Режим работы</p>
559
+ <p className="text-4xl font-bold text-purple-600">24/7</p>
560
+ </div>
561
+ </div>
562
+ </div>
563
+ </div>
564
+ </div>
565
+ );
566
+ };
567
+
568
+ const root = ReactDOM.createRoot(document.getElementById('root'));
569
+ root.render(<QualityControlDashboard />);
570
+ </script>
571
+ </body>
572
+ </html>