KennyOry commited on
Commit
47e40d1
·
verified ·
1 Parent(s): 68d3265

Update templates/index.html

Browse files
Files changed (1) hide show
  1. templates/index.html +205 -164
templates/index.html CHANGED
@@ -20,18 +20,21 @@
20
  --danger: #e74c3c;
21
  --transition: all 0.3s ease;
22
  }
 
23
  * {
24
  margin: 0;
25
  padding: 0;
26
  box-sizing: border-box;
27
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
28
  }
 
29
  body {
30
  background: linear-gradient(135deg, #f5f7fa 0%, #e4e7f1 100%);
31
  color: var(--dark);
32
  min-height: 100vh;
33
  padding: 20px;
34
  }
 
35
  .app-container {
36
  max-width: 1200px;
37
  margin: 0 auto;
@@ -39,6 +42,7 @@
39
  grid-template-columns: 1fr 300px;
40
  gap: 20px;
41
  }
 
42
  .card {
43
  background: rgba(255, 255, 255, 0.92);
44
  border-radius: 16px;
@@ -48,9 +52,11 @@
48
  overflow: hidden;
49
  transition: var(--transition);
50
  }
 
51
  .card:hover {
52
  box-shadow: 0 12px 40px rgba(31, 38, 135, 0.15);
53
  }
 
54
  .card-header {
55
  background: white;
56
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
@@ -60,9 +66,11 @@
60
  align-items: center;
61
  gap: 12px;
62
  }
 
63
  .card-body {
64
  padding: 0;
65
  }
 
66
  .chat-container {
67
  height: calc(100vh - 200px);
68
  display: flex;
@@ -70,6 +78,7 @@
70
  padding: 20px;
71
  overflow-y: auto;
72
  }
 
73
  .message {
74
  max-width: 80%;
75
  padding: 16px 20px;
@@ -81,28 +90,33 @@
81
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
82
  will-change: transform, opacity;
83
  }
 
84
  @keyframes fadeIn {
85
  from {
86
  opacity: 0;
87
  transform: translateY(10px);
88
  }
 
89
  to {
90
  opacity: 1;
91
  transform: translateY(0);
92
  }
93
  }
 
94
  .user-message {
95
  background: var(--primary);
96
  color: white;
97
  align-self: flex-end;
98
  border-bottom-right-radius: 4px;
99
  }
 
100
  .bot-message {
101
  background: white;
102
  border: 1px solid var(--gray);
103
  align-self: flex-start;
104
  border-bottom-left-radius: 4px;
105
  }
 
106
  .message-header {
107
  font-size: 12px;
108
  opacity: 0.8;
@@ -111,16 +125,19 @@
111
  align-items: center;
112
  gap: 6px;
113
  }
 
114
  .input-area {
115
  padding: 20px;
116
  border-top: 1px solid rgba(0, 0, 0, 0.05);
117
  background: white;
118
  transition: var(--transition);
119
  }
 
120
  .input-group {
121
  display: flex;
122
  gap: 10px;
123
  }
 
124
  #user-input {
125
  flex: 1;
126
  border: 1px solid var(--gray);
@@ -130,11 +147,13 @@
130
  transition: var(--transition);
131
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
132
  }
 
133
  #user-input:focus {
134
  border-color: var(--primary-light);
135
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.15);
136
  outline: none;
137
  }
 
138
  #send-btn {
139
  background: var(--primary);
140
  color: white;
@@ -148,24 +167,29 @@
148
  justify-content: center;
149
  box-shadow: 0 4px 6px rgba(67, 97, 238, 0.2);
150
  }
 
151
  #send-btn:hover {
152
  background: var(--secondary);
153
  transform: translateY(-1px);
154
  }
 
155
  #send-btn:active {
156
  transform: translateY(1px);
157
  }
 
158
  .typing-indicator {
159
  display: flex;
160
  gap: 6px;
161
  padding: 20px;
162
  align-items: center;
163
  }
 
164
  .typing-text {
165
  margin-left: 10px;
166
  font-style: italic;
167
  color: #6c757d;
168
  }
 
169
  .typing-dot {
170
  width: 10px;
171
  height: 10px;
@@ -173,22 +197,28 @@
173
  border-radius: 50%;
174
  animation: bounce 1.4s infinite ease-in-out both;
175
  }
 
176
  .typing-dot:nth-child(1) {
177
  animation-delay: -0.32s;
178
  }
 
179
  .typing-dot:nth-child(2) {
180
  animation-delay: -0.16s;
181
  }
 
182
  @keyframes bounce {
 
183
  0%,
184
  80%,
185
  100% {
186
  transform: scale(0);
187
  }
 
188
  40% {
189
  transform: scale(1);
190
  }
191
  }
 
192
  .log-entry {
193
  padding: 14px 20px;
194
  border-bottom: 1px solid rgba(0, 0, 0, 0.03);
@@ -196,41 +226,51 @@
196
  line-height: 1.5;
197
  animation: fadeIn 0.3s ease;
198
  }
 
199
  .log-entry:last-child {
200
  border-bottom: none;
201
  }
 
202
  .log-timestamp {
203
  font-size: 11px;
204
  opacity: 0.6;
205
  margin-right: 8px;
206
  }
 
207
  .log-info {
208
  color: var(--primary);
209
  }
 
210
  .log-success {
211
  color: var(--success);
212
  }
 
213
  .log-warning {
214
  color: var(--warning);
215
  }
 
216
  .log-error {
217
  color: var(--danger);
218
  }
 
219
  .problem-text {
220
  font-weight: 600;
221
  color: var(--primary);
222
  margin-bottom: 8px;
223
  }
 
224
  .solution-text {
225
  font-weight: 600;
226
  color: var(--secondary);
227
  margin-bottom: 8px;
228
  }
 
229
  .solution-step {
230
  padding-left: 20px;
231
  position: relative;
232
  margin-bottom: 6px;
233
  }
 
234
  .solution-step:before {
235
  content: "•";
236
  position: absolute;
@@ -238,6 +278,7 @@
238
  color: var(--primary);
239
  font-weight: bold;
240
  }
 
241
  .sources-badge {
242
  display: inline-flex;
243
  align-items: center;
@@ -251,9 +292,11 @@
251
  cursor: pointer;
252
  transition: var(--transition);
253
  }
 
254
  .sources-badge:hover {
255
  background: rgba(67, 97, 238, 0.15);
256
  }
 
257
  .status-indicator {
258
  display: inline-flex;
259
  align-items: center;
@@ -264,19 +307,23 @@
264
  background: rgba(46, 204, 113, 0.1);
265
  color: var(--success);
266
  }
 
267
  .status-indicator.offline {
268
  background: rgba(231, 76, 60, 0.1);
269
  color: var(--danger);
270
  }
 
271
  .status-dot {
272
  width: 8px;
273
  height: 8px;
274
  border-radius: 50%;
275
  background: var(--success);
276
  }
 
277
  .status-indicator.offline .status-dot {
278
  background: var(--danger);
279
  }
 
280
  .logo {
281
  display: flex;
282
  align-items: center;
@@ -285,6 +332,7 @@
285
  font-size: 20px;
286
  color: var(--dark);
287
  }
 
288
  .logo-icon {
289
  width: 36px;
290
  height: 36px;
@@ -295,19 +343,23 @@
295
  justify-content: center;
296
  color: white;
297
  }
 
298
  .source-item {
299
  padding: 12px 16px;
300
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
301
  transition: var(--transition);
302
  }
 
303
  .source-item:hover {
304
  background: rgba(67, 97, 238, 0.03);
305
  }
 
306
  .source-title {
307
  font-weight: 500;
308
  margin-bottom: 4px;
309
  display: block;
310
  }
 
311
  .source-url {
312
  font-size: 13px;
313
  color: #6c757d;
@@ -316,6 +368,7 @@
316
  overflow: hidden;
317
  text-overflow: ellipsis;
318
  }
 
319
  .empty-state {
320
  display: flex;
321
  flex-direction: column;
@@ -325,16 +378,19 @@
325
  padding: 40px 20px;
326
  color: #6c757d;
327
  }
 
328
  .empty-state i {
329
  font-size: 48px;
330
  margin-bottom: 20px;
331
  color: #dee2e6;
332
  }
 
333
  .empty-state h4 {
334
  font-weight: 500;
335
  margin-bottom: 10px;
336
  color: #495057;
337
  }
 
338
  .example-badge {
339
  display: inline-block;
340
  background: white;
@@ -345,21 +401,25 @@
345
  font-size: 14px;
346
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
347
  }
 
348
  .log-container {
349
  height: calc(100vh - 200px);
350
  overflow-y: auto;
351
  }
 
352
  .highlight {
353
  background-color: #fff9c4;
354
  padding: 2px 4px;
355
  border-radius: 4px;
356
  font-weight: 600;
357
  }
 
358
  .message-content {
359
  white-space: pre-wrap;
360
  word-wrap: break-word;
361
  line-height: 1.6;
362
  }
 
363
  .source-link {
364
  color: #4361ee;
365
  text-decoration: underline;
@@ -367,6 +427,7 @@
367
  margin-left: 5px;
368
  font-size: 0.9em;
369
  }
 
370
  .source-modal {
371
  position: fixed;
372
  top: 0;
@@ -379,6 +440,7 @@
379
  justify-content: center;
380
  z-index: 1000;
381
  }
 
382
  .source-modal-content {
383
  background: white;
384
  border-radius: 16px;
@@ -389,6 +451,7 @@
389
  display: flex;
390
  flex-direction: column;
391
  }
 
392
  .source-modal-header {
393
  padding: 20px;
394
  background: var(--primary);
@@ -397,11 +460,13 @@
397
  justify-content: space-between;
398
  align-items: center;
399
  }
 
400
  .source-modal-body {
401
  padding: 20px;
402
  overflow-y: auto;
403
  flex-grow: 1;
404
  }
 
405
  .source-modal-footer {
406
  padding: 15px 20px;
407
  background: var(--gray);
@@ -409,6 +474,7 @@
409
  justify-content: flex-end;
410
  gap: 10px;
411
  }
 
412
  .source-close {
413
  background: none;
414
  border: none;
@@ -416,6 +482,7 @@
416
  font-size: 24px;
417
  cursor: pointer;
418
  }
 
419
  .source-content {
420
  line-height: 1.6;
421
  max-height: 50vh;
@@ -425,11 +492,13 @@
425
  border-radius: 8px;
426
  background: #fafafa;
427
  }
 
428
  .source-original-link {
429
  display: inline-block;
430
  margin-top: 15px;
431
  color: var(--primary);
432
  }
 
433
  .notes-text {
434
  font-weight: 600;
435
  color: #f39c12;
@@ -438,24 +507,28 @@
438
  border-left: 3px solid #f39c12;
439
  padding-left: 10px;
440
  }
 
441
  .sources-title {
442
  font-weight: 600;
443
  color: #3f37c9;
444
  margin-top: 15px;
445
  margin-bottom: 8px;
446
  }
 
447
  .note-item {
448
  padding-left: 20px;
449
  position: relative;
450
  margin-bottom: 6px;
451
  font-style: italic;
452
  }
 
453
  .note-item:before {
454
  content: "•";
455
  position: absolute;
456
  left: 8px;
457
  color: #f39c12;
458
  }
 
459
  .source-reference {
460
  display: inline-block;
461
  background: rgba(67, 97, 238, 0.1);
@@ -465,51 +538,52 @@
465
  font-size: 0.9em;
466
  margin-right: 5px;
467
  }
 
468
  /* Новые стили */
469
  .d-none {
470
  display: none !important;
471
  }
472
-
473
  .log-text {
474
  line-height: 1.6;
475
  padding: 10px 0;
476
  white-space: pre-wrap;
477
  }
478
-
479
  .processing-steps {
480
  padding: 10px 0;
481
  font-size: 0.95em;
482
  }
483
-
484
  .step-item {
485
  margin-bottom: 8px;
486
  display: flex;
487
  align-items: flex-start;
488
  }
489
-
490
  .step-icon {
491
  margin-right: 10px;
492
  color: var(--primary);
493
  min-width: 20px;
494
  }
495
-
496
  .step-text {
497
  flex: 1;
498
  }
499
-
500
  .step-active {
501
  font-weight: 600;
502
  color: var(--secondary);
503
  }
504
 
505
- /* Новые стили для анимированного лоадера */
506
  .step-loader {
507
  display: flex;
508
  gap: 4px;
509
  height: 20px;
510
  align-items: center;
511
  }
512
-
513
  .step-loader .dot {
514
  width: 8px;
515
  height: 8px;
@@ -517,51 +591,37 @@
517
  background-color: var(--primary);
518
  animation: loader-bounce 1.4s infinite ease-in-out both;
519
  }
520
-
521
  .step-loader .dot:nth-child(1) {
522
  animation-delay: -0.32s;
523
  }
524
-
525
  .step-loader .dot:nth-child(2) {
526
  animation-delay: -0.16s;
527
  }
528
 
529
- .source-ref {
530
- display: inline-block;
531
- background: rgba(67, 97, 238, 0.15);
532
- color: var(--primary);
533
- width: 24px;
534
- height: 24px;
535
- border-radius: 50%;
536
- text-align: center;
537
- line-height: 24px;
538
- font-size: 12px;
539
- cursor: pointer;
540
- margin: 0 2px;
541
- vertical-align: super;
542
- transition: var(--transition);
543
- }
544
- .source-ref:hover {
545
- background: rgba(67, 97, 238, 0.25);
546
- transform: scale(1.1);
547
- }
548
-
549
  @keyframes loader-bounce {
550
- 0%, 80%, 100% {
 
 
 
551
  transform: scale(0);
552
  }
 
553
  40% {
554
  transform: scale(1);
555
  }
556
  }
557
-
558
  @media (max-width: 768px) {
559
  .app-container {
560
  grid-template-columns: 1fr;
561
  }
 
562
  .chat-container {
563
  height: 60vh;
564
  }
 
565
  .log-container {
566
  height: 30vh;
567
  }
@@ -640,7 +700,7 @@
640
  let currentBotMessage = null;
641
  let processingLog = [];
642
  let responseBuffer = '';
643
-
644
  // Новые переменные
645
  let currentLogMessage = null;
646
  let processingStep = 0;
@@ -723,45 +783,45 @@
723
  chatContainer.scrollTop = chatContainer.scrollHeight;
724
  return messageDiv;
725
  }
726
-
727
  // Обновление шагов процесса
728
  function updateProcessingSteps(stepIndex) {
729
- for (let i = 0; i < processingSteps.length; i++) {
730
- const stepElement = document.getElementById(`step-${i}`);
731
- if (!stepElement) continue;
732
-
733
- const loader = stepElement.querySelector('.step-loader');
734
- const iconCircle = stepElement.querySelector('.fa-circle');
735
- const iconCheck = stepElement.querySelector('.fa-check-circle');
736
- const text = stepElement.querySelector('.step-text');
737
-
738
- // Если элементы не найдены, пропускаем шаг
739
- if (!loader || !iconCircle || !text) continue;
740
-
741
- if (i < stepIndex) {
742
- // Завершенные шаги
743
- loader.classList.add('d-none');
744
- iconCircle.classList.add('d-none');
745
- if (iconCheck) iconCheck.classList.remove('d-none');
746
- text.classList.remove('step-active');
747
- text.textContent = processingSteps[i];
748
- } else if (i === stepIndex) {
749
- // Текущий шаг
750
- loader.classList.remove('d-none');
751
- iconCircle.classList.add('d-none');
752
- if (iconCheck) iconCheck.classList.add('d-none');
753
- text.classList.add('step-active');
754
- text.textContent = processingSteps[i] + '...';
755
- } else {
756
- // Будущие шаги
757
- loader.classList.add('d-none');
758
- iconCircle.classList.remove('d-none');
759
- if (iconCheck) iconCheck.classList.add('d-none');
760
- text.classList.remove('step-active');
761
- text.textContent = processingSteps[i];
762
- }
763
- }
764
- }
765
  // Показ следующего шага обработки
766
  function showNextProcessingStep() {
767
  processingStep++;
@@ -819,43 +879,25 @@
819
  `;
820
  chatContainer.appendChild(messageDiv);
821
  chatContainer.scrollTop = chatContainer.scrollHeight;
822
- setupSourceReferences(); // Добавьте эту строку
823
-
824
  return messageDiv;
825
  }
826
  // Форматирование контента
827
  function formatContent(content) {
828
- // Заменяем разделы на стилизованные блоки
829
- content = content.replace(/\*\*Проблема:\*\*/g,
830
- '<div class="problem-text">Проблема:</div>');
831
- content = content.replace(/\*\*Решение:\*\*/g,
832
- '<div class="solution-text">Решение:</div>');
833
- content = content.replace(/\*\*Примечания:\*\*/g,
834
- '<div class="notes-text">Примечания:</div>');
835
- content = content.replace(/\*\*Источники:\*\*/g,
836
- '<div class="sources-title">Источники:</div>');
837
- content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
838
-
839
- // Обработка ссылок на источники в тексте [1]
840
- content = content.replace(/\[(\d+)\]/g,
841
- '<sup class="source-ref" data-source="$1">[$1]</sup>');
842
-
843
- return content;
844
- }
845
-
846
- // Добавьте этот код после функции formatContent
847
- function setupSourceReferences() {
848
- document.querySelectorAll('.source-ref').forEach(ref => {
849
- ref.addEventListener('click', function(e) {
850
- e.preventDefault();
851
- const sourceIndex = parseInt(this.dataset.source) - 1;
852
- if (currentSources[sourceIndex]) {
853
- showSourceDetails(sourceIndex);
854
  }
855
- });
856
- ref.title = "Нажмите для просмотра источника";
857
- });
858
- }
859
  // Добавление ссылок на источники
860
  function addSourceLinks() {
861
  if (!currentBotMessage || currentSources.length === 0) return;
@@ -887,31 +929,30 @@ function setupSourceReferences() {
887
  }
888
  // Показ деталей источника
889
  function showSourceDetails(sourceIndex) {
890
- const source = currentSources[sourceIndex];
891
- if (!source) return;
892
-
893
- const modal = document.createElement('div');
894
- modal.className = 'source-modal';
895
- modal.innerHTML = `
896
- <div class="source-modal-content">
897
- <div class="source-modal-header">
898
- <h3>${source.title || 'Источник информации'}</h3>
899
- <button class="source-close">&times;</button>
900
- </div>
901
- <div class="source-modal-body">
902
- <p><strong>URL:</strong> <a href="${source.url}" target="_blank">${source.url}</a></p>
903
- <div class="mt-3">
904
- <h4>Содержимое:</h4>
905
- <div class="source-content">${source.content ? escapeHtml(source.content) : 'Содержимое недоступно'}</div>
 
 
 
 
 
906
  </div>
907
  </div>
908
- <div class="source-modal-footer">
909
- <button id="open-source" class="btn btn-primary">Открыть источник</button>
910
- <button id="close-modal" class="btn btn-secondary">Закрыть</button>
911
- </div>
912
- </div>
913
- `;
914
-
915
  document.body.appendChild(modal);
916
  // Обработчики событий
917
  modal.querySelector('.source-close').addEventListener('click', () => modal.remove());
@@ -944,44 +985,44 @@ function setupSourceReferences() {
944
  statusIndicator.classList.add('warning');
945
  statusIndicator.classList.remove('success');
946
  eventSource = new EventSource('/stream');
947
- eventSource.onmessage = function(event) {
948
- try {
949
- const data = JSON.parse(event.data);
950
- const { type, content } = data;
951
- if (type === 'log' || type === 'processing_log') {
952
- addLog(content, 'info');
953
- }
954
- else if (type === 'response_end') {
955
- // ТОЛЬКО ЗДЕСЬ ОБРАБАТЫВАЕМ ОТВЕТ
956
- responseBuffer = content; // Перезаписываем буфер
957
- currentBotMessage = convertToMessage(responseBuffer);
958
- addSourceLinks();
959
- toggleInputArea(true);
960
- }
961
- else if (type === 'sources') {
962
- try {
963
- currentSources = JSON.parse(content);
964
- } catch (e) {
965
- console.error('Error parsing sources:', e);
966
- addLog('Ошибка обработки источников', 'error');
967
- }
968
- }
969
- else if (type === 'done') {
970
- isProcessing = false;
971
- statusText.textContent = 'Подключено';
972
- statusDot.style.background = 'var(--success)';
973
- statusIndicator.classList.add('success');
974
- statusIndicator.classList.remove('warning');
975
- if (eventSource) {
976
- eventSource.close();
977
- eventSource = null;
978
- }
979
- }
980
- } catch (e) {
981
- console.error('Error processing event:', e);
982
- addLog(`Ошибка обработки данных: ${e.message}`, 'error');
983
- }
984
- };
985
  eventSource.onerror = function () {
986
  addLog("⚠️ Поток данных прерван", 'error');
987
  statusText.textContent = 'Ошибка соединения';
@@ -1062,7 +1103,7 @@ function setupSourceReferences() {
1062
  <div class="message-content text-danger">${errorMessage}</div>
1063
  `;
1064
  }
1065
-
1066
  // Восстанавливаем статус
1067
  setTimeout(() => {
1068
  statusText.textContent = 'Подключено';
 
20
  --danger: #e74c3c;
21
  --transition: all 0.3s ease;
22
  }
23
+
24
  * {
25
  margin: 0;
26
  padding: 0;
27
  box-sizing: border-box;
28
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
29
  }
30
+
31
  body {
32
  background: linear-gradient(135deg, #f5f7fa 0%, #e4e7f1 100%);
33
  color: var(--dark);
34
  min-height: 100vh;
35
  padding: 20px;
36
  }
37
+
38
  .app-container {
39
  max-width: 1200px;
40
  margin: 0 auto;
 
42
  grid-template-columns: 1fr 300px;
43
  gap: 20px;
44
  }
45
+
46
  .card {
47
  background: rgba(255, 255, 255, 0.92);
48
  border-radius: 16px;
 
52
  overflow: hidden;
53
  transition: var(--transition);
54
  }
55
+
56
  .card:hover {
57
  box-shadow: 0 12px 40px rgba(31, 38, 135, 0.15);
58
  }
59
+
60
  .card-header {
61
  background: white;
62
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
 
66
  align-items: center;
67
  gap: 12px;
68
  }
69
+
70
  .card-body {
71
  padding: 0;
72
  }
73
+
74
  .chat-container {
75
  height: calc(100vh - 200px);
76
  display: flex;
 
78
  padding: 20px;
79
  overflow-y: auto;
80
  }
81
+
82
  .message {
83
  max-width: 80%;
84
  padding: 16px 20px;
 
90
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.03);
91
  will-change: transform, opacity;
92
  }
93
+
94
  @keyframes fadeIn {
95
  from {
96
  opacity: 0;
97
  transform: translateY(10px);
98
  }
99
+
100
  to {
101
  opacity: 1;
102
  transform: translateY(0);
103
  }
104
  }
105
+
106
  .user-message {
107
  background: var(--primary);
108
  color: white;
109
  align-self: flex-end;
110
  border-bottom-right-radius: 4px;
111
  }
112
+
113
  .bot-message {
114
  background: white;
115
  border: 1px solid var(--gray);
116
  align-self: flex-start;
117
  border-bottom-left-radius: 4px;
118
  }
119
+
120
  .message-header {
121
  font-size: 12px;
122
  opacity: 0.8;
 
125
  align-items: center;
126
  gap: 6px;
127
  }
128
+
129
  .input-area {
130
  padding: 20px;
131
  border-top: 1px solid rgba(0, 0, 0, 0.05);
132
  background: white;
133
  transition: var(--transition);
134
  }
135
+
136
  .input-group {
137
  display: flex;
138
  gap: 10px;
139
  }
140
+
141
  #user-input {
142
  flex: 1;
143
  border: 1px solid var(--gray);
 
147
  transition: var(--transition);
148
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
149
  }
150
+
151
  #user-input:focus {
152
  border-color: var(--primary-light);
153
  box-shadow: 0 0 0 3px rgba(67, 97, 238, 0.15);
154
  outline: none;
155
  }
156
+
157
  #send-btn {
158
  background: var(--primary);
159
  color: white;
 
167
  justify-content: center;
168
  box-shadow: 0 4px 6px rgba(67, 97, 238, 0.2);
169
  }
170
+
171
  #send-btn:hover {
172
  background: var(--secondary);
173
  transform: translateY(-1px);
174
  }
175
+
176
  #send-btn:active {
177
  transform: translateY(1px);
178
  }
179
+
180
  .typing-indicator {
181
  display: flex;
182
  gap: 6px;
183
  padding: 20px;
184
  align-items: center;
185
  }
186
+
187
  .typing-text {
188
  margin-left: 10px;
189
  font-style: italic;
190
  color: #6c757d;
191
  }
192
+
193
  .typing-dot {
194
  width: 10px;
195
  height: 10px;
 
197
  border-radius: 50%;
198
  animation: bounce 1.4s infinite ease-in-out both;
199
  }
200
+
201
  .typing-dot:nth-child(1) {
202
  animation-delay: -0.32s;
203
  }
204
+
205
  .typing-dot:nth-child(2) {
206
  animation-delay: -0.16s;
207
  }
208
+
209
  @keyframes bounce {
210
+
211
  0%,
212
  80%,
213
  100% {
214
  transform: scale(0);
215
  }
216
+
217
  40% {
218
  transform: scale(1);
219
  }
220
  }
221
+
222
  .log-entry {
223
  padding: 14px 20px;
224
  border-bottom: 1px solid rgba(0, 0, 0, 0.03);
 
226
  line-height: 1.5;
227
  animation: fadeIn 0.3s ease;
228
  }
229
+
230
  .log-entry:last-child {
231
  border-bottom: none;
232
  }
233
+
234
  .log-timestamp {
235
  font-size: 11px;
236
  opacity: 0.6;
237
  margin-right: 8px;
238
  }
239
+
240
  .log-info {
241
  color: var(--primary);
242
  }
243
+
244
  .log-success {
245
  color: var(--success);
246
  }
247
+
248
  .log-warning {
249
  color: var(--warning);
250
  }
251
+
252
  .log-error {
253
  color: var(--danger);
254
  }
255
+
256
  .problem-text {
257
  font-weight: 600;
258
  color: var(--primary);
259
  margin-bottom: 8px;
260
  }
261
+
262
  .solution-text {
263
  font-weight: 600;
264
  color: var(--secondary);
265
  margin-bottom: 8px;
266
  }
267
+
268
  .solution-step {
269
  padding-left: 20px;
270
  position: relative;
271
  margin-bottom: 6px;
272
  }
273
+
274
  .solution-step:before {
275
  content: "•";
276
  position: absolute;
 
278
  color: var(--primary);
279
  font-weight: bold;
280
  }
281
+
282
  .sources-badge {
283
  display: inline-flex;
284
  align-items: center;
 
292
  cursor: pointer;
293
  transition: var(--transition);
294
  }
295
+
296
  .sources-badge:hover {
297
  background: rgba(67, 97, 238, 0.15);
298
  }
299
+
300
  .status-indicator {
301
  display: inline-flex;
302
  align-items: center;
 
307
  background: rgba(46, 204, 113, 0.1);
308
  color: var(--success);
309
  }
310
+
311
  .status-indicator.offline {
312
  background: rgba(231, 76, 60, 0.1);
313
  color: var(--danger);
314
  }
315
+
316
  .status-dot {
317
  width: 8px;
318
  height: 8px;
319
  border-radius: 50%;
320
  background: var(--success);
321
  }
322
+
323
  .status-indicator.offline .status-dot {
324
  background: var(--danger);
325
  }
326
+
327
  .logo {
328
  display: flex;
329
  align-items: center;
 
332
  font-size: 20px;
333
  color: var(--dark);
334
  }
335
+
336
  .logo-icon {
337
  width: 36px;
338
  height: 36px;
 
343
  justify-content: center;
344
  color: white;
345
  }
346
+
347
  .source-item {
348
  padding: 12px 16px;
349
  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
350
  transition: var(--transition);
351
  }
352
+
353
  .source-item:hover {
354
  background: rgba(67, 97, 238, 0.03);
355
  }
356
+
357
  .source-title {
358
  font-weight: 500;
359
  margin-bottom: 4px;
360
  display: block;
361
  }
362
+
363
  .source-url {
364
  font-size: 13px;
365
  color: #6c757d;
 
368
  overflow: hidden;
369
  text-overflow: ellipsis;
370
  }
371
+
372
  .empty-state {
373
  display: flex;
374
  flex-direction: column;
 
378
  padding: 40px 20px;
379
  color: #6c757d;
380
  }
381
+
382
  .empty-state i {
383
  font-size: 48px;
384
  margin-bottom: 20px;
385
  color: #dee2e6;
386
  }
387
+
388
  .empty-state h4 {
389
  font-weight: 500;
390
  margin-bottom: 10px;
391
  color: #495057;
392
  }
393
+
394
  .example-badge {
395
  display: inline-block;
396
  background: white;
 
401
  font-size: 14px;
402
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.03);
403
  }
404
+
405
  .log-container {
406
  height: calc(100vh - 200px);
407
  overflow-y: auto;
408
  }
409
+
410
  .highlight {
411
  background-color: #fff9c4;
412
  padding: 2px 4px;
413
  border-radius: 4px;
414
  font-weight: 600;
415
  }
416
+
417
  .message-content {
418
  white-space: pre-wrap;
419
  word-wrap: break-word;
420
  line-height: 1.6;
421
  }
422
+
423
  .source-link {
424
  color: #4361ee;
425
  text-decoration: underline;
 
427
  margin-left: 5px;
428
  font-size: 0.9em;
429
  }
430
+
431
  .source-modal {
432
  position: fixed;
433
  top: 0;
 
440
  justify-content: center;
441
  z-index: 1000;
442
  }
443
+
444
  .source-modal-content {
445
  background: white;
446
  border-radius: 16px;
 
451
  display: flex;
452
  flex-direction: column;
453
  }
454
+
455
  .source-modal-header {
456
  padding: 20px;
457
  background: var(--primary);
 
460
  justify-content: space-between;
461
  align-items: center;
462
  }
463
+
464
  .source-modal-body {
465
  padding: 20px;
466
  overflow-y: auto;
467
  flex-grow: 1;
468
  }
469
+
470
  .source-modal-footer {
471
  padding: 15px 20px;
472
  background: var(--gray);
 
474
  justify-content: flex-end;
475
  gap: 10px;
476
  }
477
+
478
  .source-close {
479
  background: none;
480
  border: none;
 
482
  font-size: 24px;
483
  cursor: pointer;
484
  }
485
+
486
  .source-content {
487
  line-height: 1.6;
488
  max-height: 50vh;
 
492
  border-radius: 8px;
493
  background: #fafafa;
494
  }
495
+
496
  .source-original-link {
497
  display: inline-block;
498
  margin-top: 15px;
499
  color: var(--primary);
500
  }
501
+
502
  .notes-text {
503
  font-weight: 600;
504
  color: #f39c12;
 
507
  border-left: 3px solid #f39c12;
508
  padding-left: 10px;
509
  }
510
+
511
  .sources-title {
512
  font-weight: 600;
513
  color: #3f37c9;
514
  margin-top: 15px;
515
  margin-bottom: 8px;
516
  }
517
+
518
  .note-item {
519
  padding-left: 20px;
520
  position: relative;
521
  margin-bottom: 6px;
522
  font-style: italic;
523
  }
524
+
525
  .note-item:before {
526
  content: "•";
527
  position: absolute;
528
  left: 8px;
529
  color: #f39c12;
530
  }
531
+
532
  .source-reference {
533
  display: inline-block;
534
  background: rgba(67, 97, 238, 0.1);
 
538
  font-size: 0.9em;
539
  margin-right: 5px;
540
  }
541
+
542
  /* Новые стили */
543
  .d-none {
544
  display: none !important;
545
  }
546
+
547
  .log-text {
548
  line-height: 1.6;
549
  padding: 10px 0;
550
  white-space: pre-wrap;
551
  }
552
+
553
  .processing-steps {
554
  padding: 10px 0;
555
  font-size: 0.95em;
556
  }
557
+
558
  .step-item {
559
  margin-bottom: 8px;
560
  display: flex;
561
  align-items: flex-start;
562
  }
563
+
564
  .step-icon {
565
  margin-right: 10px;
566
  color: var(--primary);
567
  min-width: 20px;
568
  }
569
+
570
  .step-text {
571
  flex: 1;
572
  }
573
+
574
  .step-active {
575
  font-weight: 600;
576
  color: var(--secondary);
577
  }
578
 
579
+ /* Новые стили для анимированного лоадера */
580
  .step-loader {
581
  display: flex;
582
  gap: 4px;
583
  height: 20px;
584
  align-items: center;
585
  }
586
+
587
  .step-loader .dot {
588
  width: 8px;
589
  height: 8px;
 
591
  background-color: var(--primary);
592
  animation: loader-bounce 1.4s infinite ease-in-out both;
593
  }
594
+
595
  .step-loader .dot:nth-child(1) {
596
  animation-delay: -0.32s;
597
  }
598
+
599
  .step-loader .dot:nth-child(2) {
600
  animation-delay: -0.16s;
601
  }
602
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
603
  @keyframes loader-bounce {
604
+
605
+ 0%,
606
+ 80%,
607
+ 100% {
608
  transform: scale(0);
609
  }
610
+
611
  40% {
612
  transform: scale(1);
613
  }
614
  }
615
+
616
  @media (max-width: 768px) {
617
  .app-container {
618
  grid-template-columns: 1fr;
619
  }
620
+
621
  .chat-container {
622
  height: 60vh;
623
  }
624
+
625
  .log-container {
626
  height: 30vh;
627
  }
 
700
  let currentBotMessage = null;
701
  let processingLog = [];
702
  let responseBuffer = '';
703
+
704
  // Новые переменные
705
  let currentLogMessage = null;
706
  let processingStep = 0;
 
783
  chatContainer.scrollTop = chatContainer.scrollHeight;
784
  return messageDiv;
785
  }
786
+
787
  // Обновление шагов процесса
788
  function updateProcessingSteps(stepIndex) {
789
+ for (let i = 0; i < processingSteps.length; i++) {
790
+ const stepElement = document.getElementById(`step-${i}`);
791
+ if (!stepElement) continue;
792
+
793
+ const loader = stepElement.querySelector('.step-loader');
794
+ const iconCircle = stepElement.querySelector('.fa-circle');
795
+ const iconCheck = stepElement.querySelector('.fa-check-circle');
796
+ const text = stepElement.querySelector('.step-text');
797
+
798
+ // Если элементы не найдены, пропускаем шаг
799
+ if (!loader || !iconCircle || !text) continue;
800
+
801
+ if (i < stepIndex) {
802
+ // Завершенные шаги
803
+ loader.classList.add('d-none');
804
+ iconCircle.classList.add('d-none');
805
+ if (iconCheck) iconCheck.classList.remove('d-none');
806
+ text.classList.remove('step-active');
807
+ text.textContent = processingSteps[i];
808
+ } else if (i === stepIndex) {
809
+ // Текущий шаг
810
+ loader.classList.remove('d-none');
811
+ iconCircle.classList.add('d-none');
812
+ if (iconCheck) iconCheck.classList.add('d-none');
813
+ text.classList.add('step-active');
814
+ text.textContent = processingSteps[i] + '...';
815
+ } else {
816
+ // Будущие шаги
817
+ loader.classList.add('d-none');
818
+ iconCircle.classList.remove('d-none');
819
+ if (iconCheck) iconCheck.classList.add('d-none');
820
+ text.classList.remove('step-active');
821
+ text.textContent = processingSteps[i];
822
+ }
823
+ }
824
+ }
825
  // Показ следующего шага обработки
826
  function showNextProcessingStep() {
827
  processingStep++;
 
879
  `;
880
  chatContainer.appendChild(messageDiv);
881
  chatContainer.scrollTop = chatContainer.scrollHeight;
 
 
882
  return messageDiv;
883
  }
884
  // Форматирование контента
885
  function formatContent(content) {
886
+ // Заменяем разделы на стилизованные блоки
887
+ content = content.replace(/\*\*Проблема:\*\*/g,
888
+ '<div class="problem-text">Проблема:</div>');
889
+ content = content.replace(/\*\*Решение:\*\*/g,
890
+ '<div class="solution-text">Решение:</div>');
891
+ content = content.replace(/\*\*Примечания:\*\*/g,
892
+ '<div class="notes-text">Примечания:</div>');
893
+ content = content.replace(/\*\*Источники:\*\*/g,
894
+ '<div class="sources-title">Источники:</div>');
895
+ content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
896
+ // Обработка ссылок на источники
897
+ content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g,
898
+ '<a href="$2" target="_blank" class="source-link">$1</a>');
899
+ return content;
 
 
 
 
 
 
 
 
 
 
 
 
900
  }
 
 
 
 
901
  // Добавление ссылок на источники
902
  function addSourceLinks() {
903
  if (!currentBotMessage || currentSources.length === 0) return;
 
929
  }
930
  // Показ деталей источника
931
  function showSourceDetails(sourceIndex) {
932
+ const source = currentSources[sourceIndex];
933
+ if (!source) return;
934
+ // Создаем модальное окно
935
+ const modal = document.createElement('div');
936
+ modal.className = 'source-modal';
937
+ modal.innerHTML = `
938
+ <div class="source-modal-content">
939
+ <div class="source-modal-header">
940
+ <h3>${source.title || 'Источник информации'}</h3>
941
+ <button class="source-close">&times;</button>
942
+ </div>
943
+ <div class="source-modal-body">
944
+ <p><strong>URL:</strong> <a href="${source.url}" target="_blank">${source.url}</a></p>
945
+ <div class="mt-3">
946
+ <h4>Содержимое:</h4>
947
+ <div class="source-content">${source.content ? escapeHtml(source.content) : 'Содержимое недоступно'}</div>
948
+ </div>
949
+ </div>
950
+ <div class="source-modal-footer">
951
+ <button id="open-source" class="btn btn-primary">Открыть источник</button>
952
+ <button id="close-modal" class="btn btn-secondary">Закрыть</button>
953
  </div>
954
  </div>
955
+ `;
 
 
 
 
 
 
956
  document.body.appendChild(modal);
957
  // Обработчики событий
958
  modal.querySelector('.source-close').addEventListener('click', () => modal.remove());
 
985
  statusIndicator.classList.add('warning');
986
  statusIndicator.classList.remove('success');
987
  eventSource = new EventSource('/stream');
988
+ eventSource.onmessage = function (event) {
989
+ try {
990
+ const data = JSON.parse(event.data);
991
+ const { type, content } = data;
992
+ if (type === 'log' || type === 'processing_log') {
993
+ addLog(content, 'info');
994
+ }
995
+ else if (type === 'response_end') {
996
+ // ТОЛЬКО ЗДЕСЬ ОБРАБАТЫВАЕМ ОТВЕТ
997
+ responseBuffer = content; // Перезаписываем буфер
998
+ currentBotMessage = convertToMessage(responseBuffer);
999
+ addSourceLinks();
1000
+ toggleInputArea(true);
1001
+ }
1002
+ else if (type === 'sources') {
1003
+ try {
1004
+ currentSources = JSON.parse(content);
1005
+ } catch (e) {
1006
+ console.error('Error parsing sources:', e);
1007
+ addLog('Ошибка обработки источников', 'error');
1008
+ }
1009
+ }
1010
+ else if (type === 'done') {
1011
+ isProcessing = false;
1012
+ statusText.textContent = 'Подключено';
1013
+ statusDot.style.background = 'var(--success)';
1014
+ statusIndicator.classList.add('success');
1015
+ statusIndicator.classList.remove('warning');
1016
+ if (eventSource) {
1017
+ eventSource.close();
1018
+ eventSource = null;
1019
+ }
1020
+ }
1021
+ } catch (e) {
1022
+ console.error('Error processing event:', e);
1023
+ addLog(`Ошибка обработки данных: ${e.message}`, 'error');
1024
+ }
1025
+ };
1026
  eventSource.onerror = function () {
1027
  addLog("⚠️ Поток данных прерван", 'error');
1028
  statusText.textContent = 'Ошибка соединения';
 
1103
  <div class="message-content text-danger">${errorMessage}</div>
1104
  `;
1105
  }
1106
+
1107
  // Восстанавливаем статус
1108
  setTimeout(() => {
1109
  statusText.textContent = 'Подключено';