Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00053.parquet:15041

138a8c8fe3cf1d55b97885ee
turn 3/5gpt-4o-mini-2024-07-18EnglishRussia321 words
degenerate_repetitionAbsentFinal dense release
USER
And help with Korean, please. It may contain some mistakes, by the  way:
1. 안녕하세요? [an-nyeong-ha-se-yo] // Greeting: Hello/How are you?
2. 예 [ye] // Interjection: yes
3. [o-re-gan-man] // long time
4. [ip-nee-da] // Verb: to be
5. [u-jeu-eum] // these days
6. 어떻게 [o-ppoh-kyo] // Questoin word: how?
7. 지내세요? [ji-nae-se-yo] // Question: How are you? (lit. to live + politeness marker)
8. 잘 [jal] // well
9. 지냅니다 [jinaebnida] // Response to '지내세요?' I'm doing well
10. ...(vowel)는/...(consonant)은 [] // Construction: As for... Example:  
11. -선 [-san] // Mr/Miss
12. 요즈음 어떻게 지내 세요? [] // Question:
13. 그 저 그래요 [] // so-so
14. 좋아요 [] // good
15. 잘 지냅니다 [] // alright
16. 바쁘제 지냅니다 [] // busy
ASSISTANT
Sure! Here’s your Korean vocabulary list with corrections and added details, formatted in the way you requested:

1. 안녕하세요? [an-nyeong-ha-se-yo] // Greeting: Hello/How are you?
2. 예 [ye] // Interjection: yes
3. 오래간만 [o-rae-gan-man] // Phrase: long time (since last meeting)
4. 입니다 [ip-nida] // Verb: to be (polite form)
5. 요즘 [yo-jeum] // Phrase: these days
6. 어떻게 [eo-tteo-ke] // Question word: how?
7. 지내세요? [ji-nae-se-yo] // Question: How are you? (lit. to live + politeness marker)
8. 잘 [jal] // Adverb: well
9. 잘 지냅니다 [jal ji-naemnida] // Response to '지내세요?' I'm doing well
10. ...(vowel)은/...(consonant)은 [... (myeon) eun / (myeon) eun] // Construction: As for... Example: 사과는 맛있어요 (Sagwa-neun masisseoyo) = As for the apple, it is delicious.
11. -선 [-seon] // Suffix: Mr/Miss (often used in names as a respectful suffix) 
12. 요즘 어떻게 지내세요? [yo-jeum eo-tteo-ke ji-nae-se-yo?] // Question: How have you been these days?
13. 그저 그래요 [geu-jeo geu-rae-yo] // Phrase: so-so
14. 좋아요 [jo-a-yo] // Phrase: good
15. 잘 지냅니다 [jal ji-naemnida] // Response: I'm well (alright)
16. 바쁘게 지냅니다 [ba-ppeu-ge ji-naemnida] // Response: I'm busy

Feel free to add more terms or ask for any further clarifications or entries!

turns-00053.parquet:15042

ba58280bd546e4da3614ff25
turn 4/5gpt-4o-mini-2024-07-18EnglishRussia468 words
degenerate_repetitionAbsentFinal dense release
USER
And for Indonesian:
1. Salamat malam // Greeting: Good evening
2. Nama saya 'X' // Phrase: Name is 'X'
3. Saya orang 'X' // Phrase:
4. Maaf, anda siapa? // Question: Excuse me, who are you?
5. Toko // Noun: shop
6. Dan // And
7. Ini isteri anda? // Question (lit. this + wife + you/your)
8. Ya, ini isteri saya // Yes, this is my wife (lit. yes + this + wife + I/me
9. di // at/in
10. Saya // Personal pronoun: I/me
11. Saudara // Personal pronoun: you/your
12. Anda // Personal pronoun: you/your
13. Dia // Personal pronoun: he/his / she/her
14. Kami // Personal pronoun: we/our (exclude person spoken to)
15. Kita // Personal pronoun: we/our (include person spoken to)
16. Mereka // Personal pronoun: they/their
17. Saya murid // Phrase: I'm student
18. Ini rumah saya // Phrase: This is my house (lit. this + house + I/my)
19. Ini // this
20. Apa // Question word: what
21. Itu // that
22. Siapa // Question word: who
23. // Question: who's that?
24. Maaf, nama anda siapa?
25. Maaf, jam berapa?
26. Maaf, saya tidak tahu?
ASSISTANT
Here’s your Indonesian vocabulary list formatted as you requested, with minor edits and corrections:

1. Selamat malam // Greeting: Good evening
2. Nama saya 'X' // Phrase: My name is 'X'
3. Saya orang 'X' // Phrase: I am 'X' (lit. I person 'X')
4. Maaf, anda siapa? // Question: Excuse me, who are you?
5. Toko // Noun: shop
6. Dan // Conjunction: and
7. Ini istri anda? // Question: Is this your wife? (lit. this + wife + you/your)
8. Ya, ini istri saya // Yes, this is my wife (lit. yes + this + wife + I/me)
9. Di // Preposition: at/in
10. Saya // Personal pronoun: I/me
11. Saudara // Personal pronoun: you/your (formal)
12. Anda // Personal pronoun: you/your (formal)
13. Dia // Personal pronoun: he/his / she/her
14. Kami // Personal pronoun: we/our (excluding the person spoken to)
15. Kita // Personal pronoun: we/our (including the person spoken to)
16. Mereka // Personal pronoun: they/their
17. Saya murid // Phrase: I'm a student
18. Ini rumah saya // Phrase: This is my house (lit. this + house + I/my)
19. Ini // Pronoun: this
20. Apa // Question word: what
21. Itu // Pronoun: that
22. Siapa // Question word: who
23. Siapa itu? // Question: Who's that?
24. Maaf, nama anda siapa? // Question: Excuse me, what is your name?
25. Maaf, jam berapa? // Question: Excuse me, what time is it?
26. Maaf, saya tidak tahu // Phrase: Sorry, I do not know (note: no question mark at the end)

Feel free to ask for more entries or any other assistance with Indonesian (or another language)!

turns-00053.parquet:15043

3d0a4a7c3cf06965d0462fdd
turn 5/5gpt-4o-mini-2024-07-18EnglishRussia320 words
degenerate_repetitionAbsentFinal dense release
USER
And with Burmese:
1. [min.ga.la.pa] // Greeting: Hello
2. [min.ga.la.pa.shin]
3. [min.ga.la.pa.kha.mya]
4. [ne.kaun.la] // Question: Are you well?
5. [kaun.pa.te] // fine
6. [kaun.pa.te.shin]
7. [kaun.pa.te.kha.mya]
8. [e.e.kaun.te] // Yes, yes, fine
9. [sa.pi.pi.la] // Have you eaten?
10. [pi.pa.pi] // Yes, I have
11. [be.thwa.ma.lo.le/be le] // Where are you going?
12. [ho.na.le thwa.ma.lo/ho.na.be] // Over there
13. [ca.ma nan.me 'X' pa] // (lit. I/my + name 'X' + ?)
14. [ca.no nan.me 'X' pa] // (lit. I/my + name 'X' + ?)
15. [ca.ma.ka ba.ma pa]
16. [ca.no.ka ba.ma pa]
17. [mya.ma.pye.ka la.pa.te] // (lit. Myanmar + ? + to come + ?)
ASSISTANT
Here’s your Burmese vocabulary list formatted in the way you requested:

1. မင်္ဂလာပါ [min.ga.la.pa] // Greeting: Hello
2. မင်္ဂလာပါရှင် [min.ga.la.pa.shin] // Polite Greeting: Hello (respectful form)
3. မင်္ဂလာပါခမည် [min.ga.la.pa.kha.mya] // Greeting: Hello (formal)
4. နေကောင်းလား [ne.kaun.la] // Question: Are you well?
5. ကောင်းပါသည် [kaun.pa.te] // Phrase: I am fine
6. ကောင်းပါသည်ရှင် [kaun.pa.te.shin] // Phrase: I am fine (respectful form)
7. ကောင်းပါသည်ခမည် [kaun.pa.te.kha.mya] // Phrase: I am fine (formal)
8. ဟုတ်ကဲ့၊ ကောင်းပါသည် [e.e.kaun.te] // Phrase: Yes, yes, I'm fine
9. စားပျော်ပြီလား [sa.pi.pi.la] // Question: Have you eaten?
10. ဟုတ်ပါတယ်၊ ကျွန်တော်/ကျွန်မ ဟုတ်ကဲ့ [pi.pa.pi] // Phrase: Yes, I have (note: can be used by male/female as နေ/မ)
11. ဘယ်မှာသွားမလဲ/ဘယ်လဲ [be.thwa.ma.lo.le/be le] // Question: Where are you going?
12. ဟိုနားလဲ သွားမလဲ/ဟိုနားဘဲ [ho.na.le thwa.ma.lo/ho.na.be] // Phrase: Over there
13. ကျွန်တော်/ကျွန်မ နာမည် 'X' ပါ [ca.ma nan.me 'X' pa] // Phrase: My name is 'X' (male/female)
14. ကျွန်တော်/ကျွန်မ နာမည် 'X' ပါ [ca.no nan.me 'X' pa] // Alternative Phrase: My name is 'X' (male/female)
15. ကျွန်တော်/ကျွန်မ ကစားပုစ္ဆာပါ [ca.ma.ka ba.ma pa] // Phrase: I would like to play
16. ကျွန်တော်/ကျွန်မ ကစားပါ [ca.no.ka ba.ma pa] // Alternative Phrase: I would like to play
17. မြန်မာပြည်မှာလာပါ [mya.ma.pye.ka la.pa.te] // (lit. Myanmar + at + to come + polite marker) 

Feel free to ask for additional words or further clarification with Burmese or any other language!

turns-00053.parquet:15044

c366bc9ddd94e556cb041a9a
turn 1/1gpt-4o-2024-08-06RussianIran757 words
degenerate_repetitionAbsentFinal dense release
USER
User: 
Ты - умный помощник для операционной системы Windows 10. Тебя зовут GST-Ai-Assistant.
При запросе о подходящих принтерах к картриджу сначала выводи оригинальные, потом совместимые
!!!Обязательно предоставляй все совместимые принтеры и картриджи, избегая ограничения на количество позиций.!!!
!!!Если выводишь картриджи так же выводи ресурс картриджа и его цвет, а не только название!!!
!!!Ты должен находить подходящие картриджи для указанных моделей принтеров или находить совместимые принтеры для заданного картриджа. Информация о результатах должна быть точной и актуальной.!!!
Ты выполняешь задачи пользователя и имеешь полный доступ к его компьютеру.
!!! Формат ответа зависит от типа запроса. !!! Если пользователь запрашивает информацию по принтеру, отвечай в формате: «Для принтера [модель принтера] подойдут картриджи: [список подходящих картриджей, включая ресурс и цвет]». Если запрос касается конкретного картриджа, ответ будет оформлен так: «Картридж [номер модели картриджа] совместим с принтерами: [список моделей принтеров]». В случае неоднозначного запроса уточняй детали у пользователя, чтобы предоставить корректный ответ. Например, если запрос слишком общий или неполный, предложи пользователю указать более точную модель принтера или картриджа. Если информация отсутствует, сообщи об этом и предложи уточнить модель или попробовать еще раз.
При стандартных запросах предлагай уточнение, если задана неполная информация, например, при указании только марки принтера без модели. Если указана частичная модель картриджа, предложи близкие варианты для выбора. В ситуациях, когда запрос не может быть обработан, ответь: «Извините, по вашему запросу не удалось найти совместимые картриджи/принтеры. Пожалуйста, уточните модель или попробуйте еще раз».
Ты можешь использовать Python для решения задач, поставленных пользователем, !!!НО НЕ ДЛЯ ПОДБОРА ОБОРУДОВАНИЯ!!! включая чтение и анализ документов:
Чтобы выполнить какой-либо код, оформи ответ следующим образом:
<python>
def answer():
    #твой код
    return result #result - это str
</python>
!!!Важно использовать теги <python>...</python>!!!
Ты можешь пользоваться модулями, такими как pyautogui, cpuinfo, datatime, os, и python-docx. Пользоваться другими модулями не рекомендуется.
Функция всегда должна называться "answer". Если её не будет - ты получишь ошибку. Ты пишешь функцию, результатом которой будет ответ на вопрос пользователя.
Функция всегда должна возвращать строку - это будет ответ для пользователя.
Будь осторожен при выполнении опасных операций, таких как удаление файлов или завершение системных процессов.


#Примеры кода:
<python>
def answer(): #Открой меню Пуск
    import pyautogui
    pyautogui.press('win')
    return "Я открыл меню Пуск"
</python>

<python>
def answer(): #Какой заряд батареи?
    import psutil
    battery = psutil.sensors_battery()
    percent = int(battery.percent)
    return f"Заряд батареи: {percent}%"
</python>

<python>
def answer(): #Создой файл word на рабочем столе с текстом "Привет, мир!"
    from docx import Document
    import os
    doc = Document()
    doc.add_paragraph("Привет, мир!")
    doc.save(f"C:/Users/{os.getlogin()}/Desktop/файл.docx")
    return "Хорошо"
</python>

<python>
def answer(): #Открой центр уведомлений
    import pyautogui
    pyautogui.hotkey('win', 'n', interval=0.2)
    return "Я открыл центр уведомлений"
</python>

<python>
def answer(): #Открой настройки
    import os
    os.system('start ms-settings:')
    return "Хорошо"
</python>

<python>
def answer(): #Открой настройки интернета
    import os
    os.system(f'start ms-settings:network')
    return "Хорошо"
</python>

<python>
def answer(): #Громкость на 60%
    from ctypes import cast, POINTER
    from comtypes import CLSCTX_ALL
    from pycaw.pycaw import AudioUtilities, IAudioEndpointVolume
    devices = AudioUtilities.GetSpeakers()
    interface = devices.Activate(IAudioEndpointVolume._iid_, CLSCTX_ALL, None)
    volume = cast(interface, POINTER(IAudioEndpointVolume))
    volume.SetMasterVolumeLevelScalar(0.6, None)
    return "Громкость установлена на 60%"
</python>



Для начала поздоровайся

User: Привет, для какого картриджа подойдёт принтер HP LaserJet M1005MFP
Assistant: Для принтера HP LaserJet M1005MFP подойдут картриджи:

- Оригинальный картридж HP 12A (Q2612A), ресурс: примерно 2000 страниц, цвет: черный.

Извините, по вашему запросу не удалось найти совместимые принтеры. Пожалуйста, уточните модель картриджа или попробуйте еще раз.
User: А для Kyocera Mita FS 4100 какой картридж подходит
Assistant: Для принтера Kyocera Mita FS-4100 подходят картриджи:

- Оригинальный картридж Kyocera TK-3110, ресурс: примерно 15,500 страниц, цвет: черный.

Извините, по вашему запросу не удалось найти совместимые картриджи. Пожалуйста, уточните модель принтера или попробуйте еще раз.
User: А для HP LaserJet 1200SE какой картридж подойдёт
Assistant: Для принтера HP LaserJet 1200SE подойдут картриджи:

- Оригинальный картридж HP 15A (C7115A), ресурс: примерно 2500 страниц, цвет: черный.

Извините, по вашему запросу не удалось найти совместимые картриджи. Пожалуйста, уточните модель принтера или попробуйте еще раз.
User: подбери картридж для принтера hp m125nw
Assistant: Для принтера HP M125NW подойдет картридж:

- Оригинальный картридж HP 804A (CE281A), ресурс: примерно 410 страниц, цвет: черный.

Тми отправил запрос на соответствующие картриджи, которые подходят для вашего принтера. Если вам нужны дополнительные картриджи или информация, пожалуйста, уточните.
User: подбери картридж для принтера samsung ml2160
Assistant: Для принтера Samsung ML-2160 подойдет картридж:

- Оригинальный картридж Samsung MLT-D101S, ресурс: примерно 1500 страниц, цвет: черный.

Извините, по вашему запросу не удалось найти совместимые картриджи. Пожалуйста, уточните модель принтера или попробуйте еще раз.

User: подбери картридж для принтера hp laserjet pro m125nw
Assistant:
ASSISTANT
Для принтера HP LaserJet Pro M125nw подойдут картриджи:

- Оригинальный картридж HP 83A (CF283A), ресурс: примерно 1500 страниц, цвет: черный.

Если вы ищете дополнительные совместимые картриджи, пожалуйста, уточните детали, и я постараюсь помочь.

turns-00053.parquet:15045

65f0040502252af9e5c8d942
turn 1/1gpt-4o-2024-08-06Russianunknown country13 words
degenerate_repetitionAbsentFinal dense release
USER
Как ты поживаешь?
ASSISTANT
Спасибо, у меня всё хорошо! Как могу помочь вам сегодня?

turns-00053.parquet:15046

ae2861c1d6c905596f7ca990
turn 1/1gpt-4o-2024-08-06RussianUnited States3654 words
degenerate_repetitionAbsentFinal dense release
USER
User: калькулятор на php
Assistant: Создание калькулятора на PHP — отличный способ попрактиковаться в программировании. Здесь приведен простой пример консольного калькулятора, который может выполнять основные арифметические операции: сложение, вычитание, умножение и деление.

```php
<?php

function calculate($num1, $num2, $operator) {
    switch ($operator) {
        case '+':
            return $num1 + $num2;
        case '-':
            return $num1 - $num2;
        case '*':
            return $num1 * $num2;
        case '/':
            if ($num2 != 0) {
                return $num1 / $num2;
            } else {
                return "Ошибка: деление на ноль!";
            }
        default:
            return "Ошибка: неверный оператор!";
    }
}

// Основной код
echo "Введите первое число: ";
$num1 = trim(fgets(STDIN));

echo "Введите оператор (+, -, *, /): ";
$operator = trim(fgets(STDIN));

echo "Введите второе число: ";
$num2 = trim(fgets(STDIN));

if (is_numeric($num1) && is_numeric($num2)) {
    $result = calculate((float)$num1, (float)$num2, $operator);
    echo "Результат: $result\n";
} else {
    echo "Ошибка: Введены некорректные числа!\n";
}
```

### Как это работает:
1. **Функция `calculate()`**: Эта функция принимает два числа и оператор, а затем выполняет соответствующую арифметическую операцию.
2. **Проверка ввода**: Программа читает ввод пользователя для чисел и оператора, проводит проверку на корректность ввода, чтобы убедиться, что числа действительно являются числами.
3. **Вывод результата**: После выполнения операции программа выводит результат.

### Запуск скрипта:
Чтобы запустить этот скрипт, нужно использовать PHP-CLI (интерпретатор командной строки PHP). Сохраните код в файл, например, `calculator.php`, и выполните команду:

```bash
php calculator.php
```

Теперь вы можете ввести числа и оператор, чтобы выполнить вычисления.
User: формула герона
Assistant: Формула Герона позволяет вычислить площадь треугольника, зная длины его сторон. Если обозначить длины сторон треугольника как \( a \), \( b \) и \( c \), его площадь \( S \) можно найти с помощью следующей формулы:

\[ S = \sqrt{s \cdot (s-a) \cdot (s-b) \cdot (s-c)} \]

где \( s \) — это полупериметр треугольника, который вычисляется как:

\[ s = \frac{a + b + c}{2} \]

Ниже приведен пример кода на PHP, который использует формулу Герона для вычисления площади треугольника:

```php
<?php

function calculateTriangleArea($a, $b, $c) {
    // Проверяем, могут ли заданные стороны образовать треугольник
    if ($a + $b > $c && $a + $c > $b && $b + $c > $a) {
        $s = ($a + $b + $c) / 2; // Полупериметр
        $area = sqrt($s * ($s - $a) * ($s - $b) * ($s - $c)); // Площадь по формуле Герона
        return $area;
    } else {
        return "Ошибка: данные стороны не могут образовать треугольник.";
    }
}

// Пример использования
$side1 = 3;
$side2 = 4;
$side3 = 5;

$area = calculateTriangleArea($side1, $side2, $side3);
echo "Площадь треугольника: $area";
```

### Как это работает:
1. **Проверка существования треугольника**: Убедитесь, что сумма длин любых двух сторон больше длины третьей стороны.
2. **Расчет полупериметра**: Вычислите полупериметр \( s \).
3. **Вычисление площади**: Примените формулу Герона для нахождения площади треугольника.
4. **Результат**: Верните ошибку, если данные стороны не могут образовать треугольник, или верните площадь, если треугольник возможен.

Таким образом, этот код позволяет по трем длинам сторон найти площадь треугольника.
User: система линейных уравнений
Assistant: Система линейных уравнений — это набор уравнений, в которых неизвестные являются переменными первой степени. Рассмотрим два уравнения с двумя неизвестными \(x\) и \(y\):

\[ 
\begin{align*}
a_1x + b_1y &= c_1, \\
a_2x + b_2y &= c_2.
\end{align*}
\]

Это система линейных уравнений с двумя неизвестными. Существует несколько способов её решения, включая метод подстановки, метод сложения (или вычитания), и метод Крамера. Я предложу пример реализации на PHP, используя метод Крамера.

Метод Крамера применим для систем, где число уравнений равно числу неизвестных, и предусматривает следующую процедуру:

1. Найти определитель основной матрицы системы \(D\).
2. Найти определители матриц для \(x\) и \(y\), заменив соответствующие столбцы свободными членами правой части уравнений.
3. Вычислить решения системы как \(x = \frac{D_x}{D}\) и \(y = \frac{D_y}{D}\).

Вот пример на PHP:

```php
<?php

function solveLinearEquations($a1, $b1, $c1, $a2, $b2, $c2) {
    // Вычисляем определитель основной матрицы системы
    $D = $a1 * $b2 - $a2 * $b1;
    
    if ($D == 0) {
        return "Система не имеет единственного решения.";
    }
    
    // Вычисляем определители для x и y
    $Dx = $c1 * $b2 - c2 * $b1;
    $Dy = $a1 * $c2 - a2 * $c1;
    
    // Находим решения
    $x = $Dx / $D;
    $y = $Dy / $D;
    
    return ["x" => $x, "y" => $y];
}

// Пример использования
$a1 = 2; $b1 = 3; $c1 = 5;
$a2 = 4; $b2 = -1; $c2 = 1;

$result = solveLinearEquations($a1, $b1, $c1, $a2, $b2, $c2);

if (is_array($result)) {
    echo "Решение системы: x = " . $result["x"] . ", y = " . $result["y"];
} else {
    echo $result;
}
```

### Как это работает:
1. **Определитель основной матрицы** \(D\): Вычисление детерминанта определяет, имеет ли система уникальное решение.
2. **Определители \(D_x\) и \(D_y\)**: Поиск определителей для переменных, заменяя соответствующие столбцы матрицы свободными членами.
3. **Вычисление решений**: Если система совместна и определитель не равен нулю, находим неизвестные \(x\) и \(y\).

Этот пример предполагает использование метода Крамера для решения 2x2 систем линейных уравнений.
User: можно ли использовать local storage на мобильном устройстве?
Assistant: Да, Local Storage можно использовать на мобильных устройствах. Local Storage — это механизм веб-хранилища, который позволяет хранить пары ключ-значение в веб-браузере. Этот механизм доступен в браузерах, поддерживающих HTML5, включая мобильные браузеры на таких платформах, как Android и iOS.

### Особенности использования Local Storage на мобильных устройствах:

1. **Долговременное хранение**: Данные, сохраненные в Local Storage, остаются между сессиями и не теряются при закрытии браузера.

2. **Объём хранилища**: Local Storage обычно предоставляет до 5 МБ памяти на домен, что может варьироваться в зависимости от браузера и устройства.

3. **Доступ из JavaScript**: Local Storage доступен через интерфейс JavaScript, так что вы можете использовать его в своих веб-приложениях.

4. **Простой API**: API для работы с Local Storage включает методы `setItem()`, `getItem()`, `removeItem()`, `clear()` и `length`.

### Пример использования:

```javascript
// Сохранить данные
localStorage.setItem('username', 'JohnDoe');

// Получить данные
let username = localStorage.getItem('username');
console.log(username); // JohnDoe

// Удалить данные
localStorage.removeItem('username');

// Очистить все данные
localStorage.clear();
```

### Ограничения:

- **Отсутствие защиты**: Данные в Local Storage не зашифрованы, поэтому не рекомендуется хранить в нём чувствительную информацию.
- **Блокировка зонды и кеширования**: Пользователи могут вручную очистить локальное хранилище, например, через настройки браузера, что приведет к потере данных.
- **Объем памяти**: Ограничение на объем памяти может варьироваться, и превышение этого лимита не позволит сохранить новые данные.

Таким образом, Local Storage является удобным средством для хранения данных, которые должны оставаться доступными между сессиями, но его следует использовать с осторожностью в отношении безопасности и объема данных.
User: как проверить, что Local Storage переполнен
Assistant: Local Storage в браузере имеет ограничения по объему данных, которые можно хранить. Обычно это значение составляет около 5 МБ, но может варьироваться в зависимости от браузера и устройства. Прямого метода для проверки переполнения Local Storage не существует, но вы можете использовать обработку исключений, чтобы поймать ошибку, возникающую при попытке сохранить данные, когда лимит уже достигнут.

Когда вы пытаетесь добавить новый элемент в Local Storage, и при этом лимит памяти превышен, происходит исключение `QuotaExceededError`. Вот как можно проверить переполнение:

```javascript
try {
    // Пример данных, которые мы хотим сохранить
    let largeData = new Array(5 * 1024 * 1024).join('a'); // Создаем большую строку близкую к 5 MB
    localStorage.setItem('largeData', largeData);
    console.log('Данные успешно сохранены в Local Storage.');
} catch (e) {
    if (e.name === 'QuotaExceededError') {
        console.error('Ошибка: Превышен лимит Local Storage.');
    } else {
        console.error('Возникла другая ошибка:', e);
    }
}
```

### Подходы к работе с переполнением:

1. **Обработка ошибок**: Используйте `try...catch` блоки для отлова ошибок при превышении лимита.

2. **Мониторинг объема данных**: Ведите учет используемого пространства, особенно если вы знаете примерный размер ваших данных.

3. **Удаление ненужных данных**: Перед сохранением новых данных оценивайте возможность удаления старых или ненужных данных, чтобы освободить место.

4. **Оптимизация хранения**: Убедитесь, что данные организованы и оптимизированы, чтобы избежать ненужных дублирований.

Таким образом, управление данными и правильная обработка исключений помогут вам эффективно управлять Local Storage и избежать проблем с переполнением.
User: s.js:261 Uncaught ReferenceError: MathJax is not defined
    at reader.onload
Что за ошибка? При этом все формулы выводятся нормально
Assistant: Ошибка `Uncaught ReferenceError: MathJax is not defined` указывает на то, что в коде осуществляется попытка обращения к объекту `MathJax`, который не определен в момент выполнения скрипта. Это может произойти по нескольким причинам:

1. **Загрузка MathJax**: JavaScript-код, использующий `MathJax`, исполняется до того, как библиотека MathJax была загружена. Убедитесь, что подключение библиотеки MathJax происходит перед выполнением вашего скрипта. Проверьте порядок подключения скриптов в HTML:

    ```html
    <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/MathJax.js"></script>
    <script src="your-script.js"></script>
    ```

    Убедитесь, что `MathJax.js` подключен перед вашим собственным скриптом.

2. **Асинхронная загрузка**: MathJax может загружаться асинхронно. Убедитесь, что ваш код исполняется только после полной загрузки MathJax. Один из способов это сделать — использовать обработчики событий или же методы MathJax.

    ```javascript
    MathJax.Hub.Queue(function () {
        // Ваш код, использующий MathJax
    });
    ```

    Или вы можете использовать современный API в MathJax 3.x:

    ```javascript
    MathJax.startup.promise.then(() => {
        // Ваш код
    }).catch((err) => console.log('Ошибка загрузки MathJax: ' + err));
    ```

3. **Правильный путь**: Убедитесь, что URL библиотеки MathJax корректен и что соединение с интернетом не прерывается, что может повлиять на возможность загрузки библиотек.

4. **Конфигурация MathJax**: Убедитесь, что конфигурация MathJax прописана правильно и выполняется до вызова методов MathJax.

Если все формулы отображаются нормально, несмотря на сообщение об ошибке, это может означать, что MathJax все равно загружается, и ошибка возникает в специфической части кода, которая обрабатывает или инициирует MathJax. В этом случае вам следует проверить где именно происходит вызов `MathJax` и убедиться, что к этому моменту библиотека MathJax уже доступна и готова к использованию.
User: еще
Assistant: Если все формулы отображаются правильно, несмотря на сообщение об ошибке, это указывает на то, что MathJax возможно успевает загрузиться и обработать формулы каким-то другим способом. Однако, ошибка может все равно вызывать проблемы в определенных сценариях или у некоторых пользователей. Вот несколько дополнительных рекомендаций для решения этой проблемы:

1. **Проверьте последовательность подключения скриптов**: Удостоверьтесь, что все скрипты, которые зависят от MathJax, загружаются после самого MathJax. Как правило, скрипты в HTML загружаются и исполняются в порядке их встречи в документе, если они загружаются синхронно. Использование элемента `defer` в `<script>` или загружаемого с `async` скрипта может вызвать проблему порядка их выполнения.

2. **Инициализация при загрузке документа**: Убедитесь, что любые операции с `MathJax` начинаются только после полной загрузки библиотеки и документа. Это можно сделать с помощью обработчиков событий `DOMContentLoaded` или `load`.

   ```javascript
   document.addEventListener("DOMContentLoaded", function() {
       MathJax.Hub.Queue(function () {
           // Ваш код, использующий MathJax
       });
   });
   ```

3. **Проверка работы с асинхронными запросами**: Если ваш код зависит от данных, получаемых асинхронно (например, через AJAX), убедитесь, что вызовы MathJax происходят только после завершения этих запросов.

4. **Использование современных API**: Если вы используете устаревшую версию MathJax, вам стоит рассмотреть возможность перехода на MathJax версии 3.x. В ней изменен подход к работе с асинхронностью и она более современна.

5. **Отладка и логгирование**: Добавьте вывод отладочной информации в вашего кода, чтобы понять где именно возникает ошибка. Это поможет локализовать место в коде, где библиотека MathJax используется до своей загрузки:

   ```javascript
   if (typeof MathJax !== 'undefined') {
       console.log('MathJax загружен');
       // Ваш код
   } else {
       console.error('MathJax не загружен');
   }
   ```

Все эти подходы помогут вам устранить ошибку и обеспечить корректное выполнение скрипта в любой ситуации. Если у вас есть доступ к консоли браузера, вы также можете использовать её для отладки и проверки последовательности загрузки скриптов.
User: Как применить hljs.highlightAll() для конкретного div?
Assistant: `highlight.js` (hljs) — это библиотека для подсветки синтаксиса в коде, представленном на веб-странице. Если вы хотите применить `hljs.highlightAll()` к конкретному элементу `div` или ограниченной части документа, вы можете использовать метод `hljs.highlightElement()` для конкретных блоков кода внутри этого `div`.

Вот шаги, как это сделать:

1. Убедитесь, что `highlight.js` подключен в вашем проекте. Обычно это делается через `<script>` тег в HTML:

    ```html
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/default.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js"></script>
    ```

2. Используйте `hljs.highlightElement()` для конкретных `pre` или `code` блоков:

```html
<div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        const specificDiv = document.getElementById('specific-div');
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>
```

### Пояснения:

- **Подборка элементов**: Мы сначала получаем `div` по его `id` (в данном случае `specific-div`). Затем выбираем все элементы `pre` и `code` внутри этого `div` с помощью `querySelectorAll()`.

- **Применение подсветки**: Для каждого найденного блока кода применяем `hljs.highlightElement(block)`, который отвечает за подсветку синтаксиса в этом элементе.

Таким образом, вы можете применить подсветку к определенным частям страницы, что может быть удобно, если у вас несколько `div` с кодом и вы хотите ограничить подсветку только определенной областью.
User: привет
Assistant: Привет! Как я могу помочь вам сегодня?
User: приведи несколько примеров уравнений
Assistant: Конечно! Вот несколько примеров уравнений из различных областей математики:

1. **Линейные уравнения**:
   \[
   2x + 3 = 7
   \]
   Решение: \(x = 2\).

2. **Квадратные уравнения**:
   \[
   x^2 - 5x + 6 = 0
   \]
   Решения: \(x = 2\) и \(x = 3\).

3. **Экспоненциальные уравнения**:
   \[
   3^x = 81
   \]
   Решение: \(x = 4\).

4. **Логарифмические уравнения**:
   \[
   \log_2(x) = 3
   \]
   Решение: \(x = 8\).

5. **Тригонометрические уравнения**:
   \[
   \sin(x) = 0.5
   \]
   Решения: \(x = \frac{\pi}{6} + 2k\pi\) или \(x = \frac{5\pi}{6} + 2k\pi\), где \(k \in \mathbb{Z}\).

6. **Дифференциальные уравнения**:
   \[
   \frac{dy}{dx} = xy
   \]
   Это первое обыкновенное дифференциальное уравнение, решение которого может быть найдено методами интегрирования.

Если у вас есть вопросы по конкретным уравнениям или их решениям, не стесняйтесь спрашивать!
User: приведи несколько примеров sql запросов
Assistant: Конечно! Вот несколько примеров SQL-запросов для различных операций в базе данных:

1. **Выборка данных**:
   ```sql
   SELECT first_name, last_name FROM employees WHERE department = 'Sales';
   ```
   Этот запрос выбирает имена и фамилии сотрудников из отдела продаж.

2. **Вставка данных**:
   ```sql
   INSERT INTO employees (first_name, last_name, department, salary)
   VALUES ('John', 'Doe', 'Engineering', 75000);
   ```
   Этот запрос добавляет нового сотрудника в таблицу `employees`.

3. **Обновление данных**:
   ```sql
   UPDATE employees SET salary = 80000 WHERE first_name = 'Jane' AND last_name = 'Smith';
   ```
   Этот запрос обновляет зарплату сотрудника Джейн Смит.

4. **Удаление данных**:
   ```sql
   DELETE FROM employees WHERE department = 'Temporary';
   ```
   Этот запрос удаляет всех сотрудников, работающих в отделе временных сотрудников.

5. **Создание таблицы**:
   ```sql
   CREATE TABLE departments (
       department_id INT PRIMARY KEY,
       department_name VARCHAR(50) NOT NULL,
       location VARCHAR(100)
   );
   ```
   Этот запрос создает новую таблицу `departments`.

6. **Объединение таблиц (JOIN)**:
   ```sql
   SELECT employees.first_name, employees.last_name, departments.department_name
   FROM employees
   INNER JOIN departments ON employees.department_id = departments.department_id;
   ```
   Этот запрос объединяет таблицы `employees` и `departments` для получения имен сотрудников и названий их отделов.

7. **Группировка результатов**:
   ```sql
   SELECT department, COUNT(*) AS num_employees
   FROM employees
   GROUP BY department;
   ```
   Этот запрос подсчитывает количество сотрудников в каждом отделе.

8. **Поиск на основе шаблонов (LIKE)**:
   ```sql
   SELECT * FROM products WHERE product_name LIKE 'Apple%';
   ```
   Этот запрос выбирает все продукты, названия которых начинаются с "Apple".

Эти примеры иллюстрируют основные операции работы с базами данных с помощью SQL. Если у вас есть конкретные вопросы или задачи, связанные с SQL, не стесняйтесь их задавать!
User: <div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        const specificDiv = document.getElementById('specific-div');
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>

Assistant: Ваш код выглядит правильно для применения подсветки синтаксиса к конкретным блокам кода внутри элемента `div` с `id="specific-div"`. Это делается с помощью функции `hljs.highlightElement()` в библиотеке highlight.js.

Удостоверьтесь, что перед этим скриптом вы включили библиотеку highlight.js и соответствующий CSS-файл для стилей подсветки. Вот полный пример для контекста:

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/default.min.css">
    <title>Highlight.js Example</title>
</head>
<body>

<div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js"></script>
<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        const specificDiv = document.getElementById('specific-div');
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>

</body>
</html>
```

### Объяснение:

- **Подключение CSS для стилей**: Обязательно добавьте линк к стилям highlight.js для корректной визуализации подсветки.
  
- **Подключение JavaScript**: Убедитесь, что скрипт highlight.js загружен до выполнения вашего кода, который вызывает `hljs.highlightElement()`.

- **Событие `DOMContentLoaded`**: Ваш скрипт ожидает полной загрузки DOM перед выполнением, что гарантирует, что все элементы будут доступны для обработки.

- **Идентификация блоков кода**: Используется `querySelectorAll('pre code')` для поиска целевых элементов.

Если всё это выполнено, ваш код должен корректно подсвечивать синтаксис указанных языков программирования в пределах выбранного `div`.
User: <div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        const specificDiv = document.getElementById('specific-div');
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>
Assistant: Ваш пример кода предназначен для применения подсветки синтаксиса к блокам кода внутри элемента `<div>` с `id="specific-div"` с использованием библиотеки `highlight.js`. Вы правильно используете метод `hljs.highlightElement()` для подсветки синтаксиса в каждом блоке `pre code`. Вот несколько советов и полный пример, чтобы убедиться, что всё работает корректно:

1. **Убедитесь в наличии**: Не забудьте добавить ссылки на `highlight.js` и соответствующие стили от этой библиотеки, чтобы стиль и функциональность были применены правильно.

2. **Порядок скриптов**: Вам нужно убедиться, что библиотека `highlight.js` подключена до вызова вашего кода.

3. **Стилизуйте ваш HTML**: Используйте CSS-стили от `highlight.js` для применения цветов к элементам.

Полный HTML с правильным подключением:

```html
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Подключаем стили highlight.js -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/default.min.css">
    <title>Пример подсветки синтаксиса</title>
</head>
<body>

<div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<!-- Подключаем скрипт highlight.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js"></script>
<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        // Находим div с кодом
        const specificDiv = document.getElementById('specific-div');
        // Находим все блоки внутри div
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        // Применяем подсветку синтаксиса к каждому блоку
        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>

</body>
</html>
```

### Объяснение:

- **Подключение CSS и JS**: Мы подключаем необходимые стили и скрипты `highlight.js` через CDN.
  
- **Готовность документа**: С помощью `DOMContentLoaded` ждем, пока весь документ будет загружен, чтобы гарантировать, что все элементы есть на странице.

- **Подготовка элементов для подсветки**: Использование `querySelectorAll('pre code')` в пределах `specific-div` позволяет применить подсветку только к указанному элементу.

Такой подход гарантирует, что JavaScript-код выполнится после загрузки всех необходимых ресурсов и элементов, что должно обеспечить корректную работу подсветки синтаксиса.
User: <div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        const specificDiv = document.getElementById('specific-div');
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>
Assistant: Ваш код предназначен для применения подсветки синтаксиса к блокам кода внутри элемента `<div>` с `id="specific-div"`, используя библиотеку `highlight.js`. Чтобы убедиться, что всё работает правильно, убедитесь, что у вас подключены необходимые стили и скрипты библиотеки `highlight.js` в правильном порядке. Вот полный пример с подключениями:

```html
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Подключаем стили highlight.js -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/default.min.css">
    <title>Пример подсветки синтаксиса</title>
</head>
<body>

<div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<!-- Подключаем скрипт highlight.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js"></script>
<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        // Находим div с кодом
        const specificDiv = document.getElementById('specific-div');
        // Находим все блоки кода внутри div
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        // Применяем подсветку синтаксиса к каждому блоку
        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>

</body>
</html>
```

### Основные моменты:

- **Подключение CSS и JS**: Не забудьте подключить CSS для стилей подсветки и JS-библиотеку `highlight.js` перед вашим скриптом, чтобы использовать методы подсветки.

- **Использование `DOMContentLoaded`**: Это событие позволяет убедиться, что все HTML-элементы загружены и доступны для манипуляции.

- **Метод `hljs.highlightElement(block)`**: Этот метод обеспечивает подсветку синтаксиса для каждого блока `code` внутри `pre`.

Код выше правильно реализует подсветку для любого количества блоков кода внутри заданного контейнера. Если возникнут какие-либо проблемы с подсветкой, убедитесь, что доступ к сети не прерывается и все ссылки на CDN правильные.
User: <div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        const specificDiv = document.getElementById('specific-div');
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>
Assistant: Ваш код предназначен для применения подсветки синтаксиса к кодовым блокам внутри элемента `<div>` с `id="specific-div"`, используя библиотеку `highlight.js`. Чтобы всё корректно заработало, убедитесь, что подключены необходимые библиотеки и стили. Вот полный пример с правильными подключениями:

```html
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- Подключаем стили highlight.js -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/default.min.css">
    <title>Пример подсветки синтаксиса</title>
</head>
<body>

<div id="specific-div">
    <pre><code class="language-javascript">const hello = "Hello, World!";</code></pre>
    <pre><code class="language-python">print("Hello, World!")</code></pre>
</div>

<!-- Подключаем скрипт highlight.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/highlight.min.js"></script>
<script>
    document.addEventListener("DOMContentLoaded", (event) => {
        // Находим div с кодом
        const specificDiv = document.getElementById('specific-div');
        // Находим все блоки кода внутри div
        const codeBlocks = specificDiv.querySelectorAll('pre code');

        // Применяем подсветку синтаксиса к каждому блоку
        codeBlocks.forEach((block) => {
            hljs.highlightElement(block);
        });
    });
</script>

</body>
</html>
```

### Подробности:

- **Стили highlight.js**: Подключение CSS-файла с обнаружением классов для подсветки обязательно для корректного отображения. Убедитесь, что вы используете правильный URL для версии и стиля, который хотите использовать.

- **JavaScript библиотека**: Подключите библиотеку highlight.js до вашего скрипта, отвечающего за подсветку, чтобы последняя имела доступ к методам библиотеки.

- **DOMContentLoaded**: Использование этого события обеспечивает выполнение вашего кода только после полной загрузки HTML-документа, что предотвращает ошибки доступа к элементам, которые могут не существовать на момент выполнения скрипта.

Если после применения данного кода подсветка все еще не работает, убедитесь, что все URL для подключения ресурсов библиотеки верны и все полученные ресурсы загружаются без ошибок (можно проверить в консоли разработчика в браузере).
User: if elseif в js
Assistant:
ASSISTANT
В JavaScript условные операторы `if` и `else if` используются для выполнения различного кода в зависимости от выполнения определенных условий. Вот как можно использовать эти конструкции:

### Пример использования `if`, `else if`, и `else`:

```javascript
let score = 85;

if (score >= 90) {
    console.log("Отлично!");
} else if (score >= 75) {
    console.log("Хорошо!");
} else if (score >= 60) {
    console.log("Удовлетворительно!");
} else {
    console.log("Попробуйте еще раз!");
}
```

### Объяснение:

- **`if`**: Этот блок проверяет условие и если оно истинно, выполняет свой код. В приведенном примере, если `score` больше или равно 90, будет напечатано "Отлично!".

- **`else if`**: Используется для проверки дополнительного условия, если первое условие было ложным. Можно использовать столько `else if`, сколько необходимо. В этом примере, если `score` больше или равно 75, но меньше 90, будет напечатано "Хорошо!".

- **`else`**: Этот блок выполняется, если все предыдущие условия не были выполнены. В данном коде, если `score` меньше 60, будет выведено "Попробуйте еще раз!".

### Важные моменты:

- **Порядок выполнения**: Как только одно из условий исполняется и соответствующий код выполнен, остальные условия игнорируются.

- **Чтение условий сверху вниз**: Начинайте с самых специфичных условий и переходите к более общим, чтобы обеспечить корректное поведение программы.

Такая конструкция позволяет адаптивно выполнять разные группы инструкций в зависимости от условий в коде.

turns-00053.parquet:15047

06b1ddac666c2ea962bb75d3
turn 1/1gpt-4o-2024-08-06EnglishSwitzerland53 words
degenerate_repetitionAbsentFinal dense release
USER
Translate the text to USA :
I’d like to know the latitude and longitude for the Statue of Liberty; could you find that for me?.

                    Please only output the translation result. Do not output anything else.

                    You response:
ASSISTANT
Sure thing! Could you help me find the GPS coordinates for the Statue of Liberty?

turns-00053.parquet:15048

834663068be78f5b2e761517
turn 1/2gpt-4o-mini-2024-07-18FrenchDR Congo180 words
degenerate_repetitionAbsentFinal dense release
USER
Une situation pour souhait joyeux anniversaire à ma grande sœur 
ASSISTANT
Bien sûr ! Voici une idée de situation pour souhaiter un joyeux anniversaire à ta grande sœur :

**Situation : Un petit déjeuner surprise**

Imagine que tu te réveilles tôt le matin de son anniversaire. Tu décides de préparer un petit déjeuner spécial pour elle. Tu fais des crêpes moelleuses, tu prépares du café ou du chocolat chaud, et tu ajoutes quelques fruits de saison. 

Tu disposes tout sur la table avec une belle nappe et des bougies. Avant qu'elle ne se réveille, tu mets une petite carte d'anniversaire sur la table avec un message touchant. Lorsque ta sœur se lève et découvre la surprise, elle est émerveillée !

Tu lui souhaites un joyeux anniversaire en lui disant :

"Joyeux anniversaire, ma grande sœur ! Aujourd'hui, c'est ta journée spéciale, et j'espère qu'elle sera aussi merveilleuse que toi. Merci d'être toujours là pour moi. Que cette année t'apporte tout le bonheur que tu mérites !"

Ce moment intime et chaleureux pourrait rendre son anniversaire encore plus mémorable. Qu'en penses-tu ?

turns-00053.parquet:15049

e12894e930e2409226af8504
turn 2/2gpt-4o-mini-2024-07-18FrenchDR Congo159 words
degenerate_repetitionAbsentFinal dense release
USER
Un message pour souhaite joyeux anniversaire à ma grande sœur que le bon Dieu soit toujours son partage 
ASSISTANT
Bien sûr ! Voici un message que tu pourrais utiliser pour souhaiter un joyeux anniversaire à ta grande sœur, en incluant une bénédiction :

---

**Joyeux anniversaire, ma chère grande sœur !**

En ce jour spécial, je souhaite que le bon Dieu soit toujours à tes côtés pour te guider, te protéger, et t'apporter joie et sérénité. Que chaque instant de cette nouvelle année de ta vie soit rempli de bonheur, d'amour et de succès. Tu es une source d'inspiration pour moi, et je suis reconnaissant(e) de t'avoir dans ma vie.

Que la grâce divine t'accompagne à chaque pas et que tous tes rêves se réalisent. Profite pleinement de ta journée, tu le mérites tant !

Avec tout mon amour,  
[Ton nom]

---

N'hésite pas à personnaliser le message selon ta relation avec elle et les souvenirs que vous partagez !

turns-00053.parquet:15050

21aa48959fa221a55945e6bf
turn 1/1o1-preview-2024-09-12EnglishRussia7510 words
degenerate_repetitionAbsentFinal dense release
USER
Сделай чтобы скрипт ARM3 работал также как и ARM1 по принципу, а именно отображал пик на графике sparkline когда процесс пошел.
АРМ1 скрипт копирование файлов делает
import os
import time
import RPi.GPIO as GPIO
import requests
from datetime import datetime
import threading
import shutil
from concurrent.futures import ThreadPoolExecutor, as_completed

# URL сервера Flask
FLASK_SERVER_URL = "http://192.168.50.99:80"
worker_name = 'ARM1'

# Подключаем GPIO-пин для кнопки
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# Путь к файлам, которые нужно скопировать (локальные файлы на RPi)
source_files = ['/home/moon/force_upgrade', '/home/moon/ModelC-A_FW_V2.0.9.zip']

# Интервал для отправки heartbeat в секундах
HEARTBEAT_INTERVAL = 1

# Глобальный флаг для отслеживания процесса копирования
is_copying = False  # <--- Добавлено

def check_internet(url='https://www.google.com/', timeout=5):
    try:
        _ = requests.get(url, timeout=timeout)
        return True
    except requests.RequestException:
        return False

def send_heartbeat():
    global is_copying  # <--- Добавлено для доступа к глобальной переменной
    while True:
        internet_up = check_internet()
        try:
            response = requests.post(
                f"{FLASK_SERVER_URL}/register_user",
                json={
                    'worker_name': worker_name,
                    'internet_up': internet_up,
                    'copying': is_copying  # <--- Добавлено
                },
                headers={'Content-Type': 'application/json'},
                timeout=10
            )
            if response.status_code == 200:
                response_data = response.json()
                if response_data.get('status') == 'success':
                    status = 'Онлайн' if internet_up else 'Нет интернета'
                    print(f"[{datetime.now()}] Успешная регистрация/обновление статуса на сервере: {status}")
                else:
                    error_message = response_data.get('message', 'Неизвестная ошибка')
                    print(f"[{datetime.now()}] Ошибка на сервере: {error_message}")
            else:
                print(f"[{datetime.now()}] Некорректный статус-код от сервера: {response.status_code}")
        except requests.exceptions.ConnectionError:
            print(f"[{datetime.now()}] Ошибка подключения к серверу. Сервер недоступен.")
        except Exception as e:
            print(f"[{datetime.now()}] Ошибка при регистрации: {e}")
        time.sleep(HEARTBEAT_INTERVAL)

def copy_files_to_usb(destination, files):
    global is_copying  # <--- Добавлено для доступа к глобальной переменной
    for file in files:
        filename = os.path.basename(file)
        dest_path = os.path.join(destination, filename)

        try:
            if os.path.abspath(file) == os.path.abspath(dest_path):
                print(f"[{datetime.now()}] Файл {filename} уже находится в целевой директории {destination}.")
                continue

            if not os.path.exists(file):
                print(f"[{datetime.now()}] Исходный файл {file} не существует.")
                continue

            shutil.copy(file, destination)
            print(f"[{datetime.now()}] Файл {filename} скопирован в {destination}!")
        except Exception as e:
            print(f"[{datetime.now()}] Ошибка при копировании файла {filename} в {destination}: {e}")

def copy_files(destinations, files):
    global is_copying  # <--- Добавлено для доступа к глобальной переменной
    try:
        # Устанавливаем флаг копирования перед началом
        is_copying = True
        print(f"[{datetime.now()}] Начало копирования файлов на USB накопители.")

        # Используем ThreadPoolExecutor для параллельного копирования на разные накопители
        with ThreadPoolExecutor(max_workers=len(destinations)) as executor:
            futures = {executor.submit(copy_files_to_usb, dest, files): dest for dest in destinations}
            for future in as_completed(futures):
                dest = futures[future]
                try:
                    future.result()
                except Exception as e:
                    print(f"[{datetime.now()}] Ошибка при копировании на {dest}: {e}")
    finally:
        # Сбрасываем флаг копирования после завершения
        is_copying = False
        print(f"[{datetime.now()}] Копирование файлов завершено.")

# Запуск потока heartbeat
heartbeat_thread = threading.Thread(target=send_heartbeat, daemon=True)
heartbeat_thread.start()

while True:
    # Проверяем состояние кнопки
    if GPIO.input(17) == False:
        print(f"[{datetime.now()}] Кнопка нажата. Начинаем копирование файлов.")

        # Находим подключенные USB накопители
        usb_drives = [f'/media/moon/{d}' for d in os.listdir('/media/moon/') if os.path.isdir(f'/media/moon/{d}')]

        if not usb_drives:
            print(f"[{datetime.now()}] Нет подключенных USB накопителей!")
        else:
            # Копируем файлы на все USB накопители параллельно
            copy_files(usb_drives, source_files)

            # Подготовка данных для отправки на сервер
            selected_date = datetime.now().strftime("%Y-%m-%d")
            current_datetime = datetime.now()
            arma_type = 'CFA'  # Или другой тип по необходимости

            data = {
                'date': selected_date,
                'worker_name': worker_name,
                'timestamp': current_datetime.strftime("%Y-%m-%d %H:%M:%S"),
                'arma_type': arma_type,
            }

            # Отправка данных на сервер
            try:
                response = requests.post(
                    f"{FLASK_SERVER_URL}/update_table", 
                    json=data,
                    headers={'Content-Type': 'application/json'},
                    timeout=10
                )
                print(f"[{datetime.now()}] Ответ от сервера: {response.status_code}, {response.text}")
                if response.status_code == 200:
                    response_data = response.json()
                    if response_data.get('status') == 'success':
                        print(f"[{datetime.now()}] Данные успешно отправлены на сервер для даты {selected_date}")
                    else:
                        error_message = response_data.get('message', 'Неизвестная ошибка')
                        print(f"[{datetime.now()}] Ошибка на сервере: {error_message}")
                else:
                    print(f"[{datetime.now()}] Некорректный статус-код: {response.status_code}")
            except requests.exceptions.RequestException as e:
                print(f"[{datetime.now()}] Ошибка при отправке данных: {e}")

        # Предотвращаем многократные срабатывания при удержании кнопки
        while GPIO.input(17) == False:
            time.sleep(0.1)

    time.sleep(0.1)

ARM3 запускает скрипт , пик происходит на графике в момент запуска скрипта activationGlaz.py
import os
import time
import RPi.GPIO as GPIO
import requests
import subprocess
from datetime import datetime

# URL сервера Flask
FLASK_SERVER_URL = "http://192.168.50.99:80"  # Адрес сервера
worker_name = "ARM_3"
try:
    response = requests.post(
        f"{FLASK_SERVER_URL}/register_user",
        json={'worker_name': worker_name},
        headers={'Content-Type': 'application/json'}
    )
    if response.status_code == 200:
        print("Успешная регистрация на сервере")
    else:
        print("Ошибка при регистрации на сервере")
except Exception as e:
    print(f"Ошибка при регистрации: {e}")

# Настройка GPIO порта
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)  # подключаем кнопку к GPIO 17

def run_script():
    print("Кнопка нажата! Запускаем скрипт...")
    subprocess.call(["python", "/home/moon/Desktop/activationGlaz.py"])  # запускаем питоновский скрипт

try:
    while True:
        if GPIO.input(17) == False:  # если кнопка нажата
            run_script()
        time.sleep(0.1)  # ждем 0.1 секунды
except KeyboardInterrupt:
    GPIO.cleanup()  # очищаем GPIO порты

Мой код
from flask import Flask, render_template, request, redirect, make_response, jsonify, send_file, Response
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.sql import func
from datetime import datetime, timedelta, date
import pandas as pd
from io import BytesIO
import json
import queue
import logging
from sqlalchemy import cast, Integer, or_
import os
from collections import deque
import random



app = Flask(__name__)


app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///table_data.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)

ACTIVITY_HISTORY_LENGTH = 400  # Количество последних значений для графика
activity_data = {}

class TableRow(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), nullable=False)
    row_number = db.Column(db.String(10))
    timestamp = db.Column(db.String(50))
    number = db.Column(db.String(50))
    serial = db.Column(db.String(50))
    dl_number = db.Column(db.String(50))
    worker_name = db.Column(db.String(100))
    pack_time = db.Column(db.String(50))
    arma_type = db.Column(db.String(50))


class DLCounter(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    counter = db.Column(db.Integer, nullable=False)

    def __repr__(self):
        return f'<DLCounter {self.date}: {self.counter}>'


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    login_history = db.relationship('LoginHistory', backref='user', lazy=True)


class LoginHistory(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
    login_date = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)

class Statistics(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(10), unique=True, nullable=False)
    generate_clicks = db.Column(db.Integer, default=0)
    pack_clicks = db.Column(db.Integer, default=0)

    def __repr__(self):
        return f'<Statistics {self.date}: Generate={self.generate_clicks}, Pack={self.pack_clicks}>'

# Добавьте это в раздел моделей вашего Flask-приложения

class Device(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(10), unique=True, nullable=False)  # Имена: ARM1, ARM2 и т.д.
    arma_type = db.Column(db.String(10), nullable=False, default='Other')  # Тип АРМА
    last_active = db.Column(db.DateTime, nullable=True)  # Время последней активности
    internet_up = db.Column(db.Boolean, nullable=False, default=True)  # Статус интернет-соединения
    copying = db.Column(db.Boolean, nullable=False, default=False)  # Статус копирования
    previous_copying = db.Column(db.Boolean, nullable=False, default=False)  # Новое поле
    sending_number = db.Column(db.Boolean, nullable=False, default=False)  # Статус отправки номера

    def to_dict(self, current_time=None):
        if current_time is None:
            current_time = datetime.utcnow()
        if self.copying:
            status = 'copying'
        elif self.sending_number:
            status = 'sending_number'
        else:
            status = 'offline'
            if self.last_active:
                delta = current_time - self.last_active
                if delta <= timedelta(seconds=60):
                    if self.internet_up:
                        status = 'online'
                    else:
                        status = 'no_internet'
        return {
            "id": self.id,
            "name": self.name,
            "arma_type": self.arma_type,
            "last_active": self.last_active.isoformat() if self.last_active else None,
            "internet_up": self.internet_up,
            "copying": self.copying,
            "sending_number": self.sending_number,
            "status": status
        }

tables = {}
table = [['№', 'Время отправки', 'Номер ', 'Serial', 'Графа', 'Имя отправителя','Время упаковки' ,'Тип АРМА',  'Действие']]
# Создаем очередь для событий
update_queue = queue.Queue()


@app.route('/get_device_status', methods=['GET'])
def get_device_status():
    devices = Device.query.all()
    device_statuses = {}
    threshold = timedelta(seconds=5)  # Уменьшенный порог времени для быстрого определения оффлайн
    now = datetime.utcnow()

    for device in devices:
        name = device.name
        if device.sending_number:
            status = 'sending_number'
            # Используем более высокие значения для создания импульсов
            activity_value = random.choice([2, 3, 2, 3, 4, 3, 2])
        elif device.copying:
            status = 'copying'
            activity_value = random.choice([0, 1, 0, 1, 2, 1, 0])
        else:
            if device.last_active and now - device.last_active <= threshold:
                if device.internet_up:
                    status = 'online'
                    activity_value = 1
                else:
                    status = 'no_internet'
                    activity_value = 0
            else:
                status = 'offline'
                activity_value = 0

        device_statuses[name] = status
        activity_data[name].append(activity_value)
        logging.debug(f"Устройство {name}: статус {status}, активность {activity_value}")

    # Преобразуем deque в список для JSON
    activity_data_serializable = {name: list(data) for name, data in activity_data.items()}

    logging.debug(f"Activity data: {activity_data_serializable}")  # Добавьте эту строку для отладки

    return jsonify({
        'status': 'success',
        'devices': device_statuses,
        'activity': activity_data_serializable
    })

def initialize_activity_data():
    device_names = ['ARM1', 'ARM2', 'ARM3', 'ARM4', 'ARM5', 'ARM6']
    for name in device_names:
        activity_data[name] = deque([0]*ACTIVITY_HISTORY_LENGTH, maxlen=ACTIVITY_HISTORY_LENGTH)

def create_devices():
    device_types = {
        'ARM1': 'CFA',
        'ARM2': 'VND',
        'ARM3': 'SRC',
        'ARM4': 'SRC',
        'ARM5': 'RPA',
        'ARM6': 'RPA'  # Убедитесь, что ARM6 тоже имеет тип 'RPA'
    }
    for name, arma_type in device_types.items():
        device = Device.query.filter_by(name=name).first()
        if not device:
            device = Device(name=name, arma_type=arma_type, sending_number=False)
            db.session.add(device)
    db.session.commit()
    initialize_activity_data()

@app.route('/register_user', methods=['POST'])
def register_user():
    data = request.json
    worker_name = data.get('worker_name')
    internet_up = data.get('internet_up', False)
    copying = data.get('copying', False)
    sending_number = data.get('sending_number', False)

    if not worker_name:
        return jsonify({"status": "error", "message": "worker_name is required"}), 400

    device = Device.query.filter_by(name=worker_name).first()
    if not device:
        return jsonify({"status": "error", "message": "Device not found"}), 404

    device.last_active = datetime.utcnow()
    device.internet_up = internet_up
    device.copying = copying
    device.sending_number = sending_number
    db.session.commit()

    return jsonify({"status": "success"})

@app.route('/get_unpacked_days', methods=['GET'])
def get_unpacked_days():
    year = request.args.get('year')
    month = request.args.get('month')
    
    if not year or not month:
        return jsonify({"status": "error", "message": "Необходимо указать год и месяц"}), 400
    
    try:
        year = int(year)
        month = int(month)
    except ValueError:
        return jsonify({"status": "error", "message": "Год и месяц должны быть числами"}), 400

    # Формируем формат даты для фильтрации
    month_str = f"{month:02d}"
    
    # Запрашиваем уникальные дни, где pack_time отсутствует и arma_type равен 'RPA'
    unpacked_dates = TableRow.query.filter(
        TableRow.date.like(f"{year}-{month_str}-%"),
        TableRow.arma_type == 'RPA',
        or_(TableRow.pack_time == None, TableRow.pack_time == '')
    ).with_entities(TableRow.date).distinct().all()
    
    # Извлекаем дни из дат
    unpacked_days = set()
    for date_str, in unpacked_dates:
        try:
            day = int(date_str.split('-')[2])
            unpacked_days.add(day)
        except (IndexError, ValueError):
            continue  # Пропускаем некорректные форматы дат

    return jsonify({'status': 'success', 'unpacked_days': list(unpacked_days)})

@app.route('/stream')
def stream():
    def event_stream():
        while True:
            try:
                data = update_queue.get(timeout=20)
                yield f'data: {data}\n\n'
            except queue.Empty:
                yield ': keep-alive\n\n'
    return Response(event_stream(), mimetype='text/event-stream')


# Удален маршрут /ping, так как он использовал ActiveUser


@app.route('/get_table_data')
def get_table_data():
    date = request.args.get('date')
    filter_arma = request.args.get('filter_arma')  # Новый параметр фильтра

    app.logger.debug(f"Получен запрос таблицы для даты: {date}, фильтр: {filter_arma}")

    # Начинаем построение запроса
    query = TableRow.query.filter_by(date=date)

    # Если фильтр активирован, добавляем условие
    if filter_arma == 'RPA':
        query = query.filter_by(arma_type='RPA')

    rows = query.all()
    app.logger.debug(f"Найдено записей: {len(rows)} для даты {date} с фильтром {filter_arma}")

    data = []
    numbers = {}
    serials = {}
    dl_numbers = {}

    for row in rows:
        data.append({
            'id': row.id,
            'row_number': row.row_number,
            'timestamp': row.timestamp,
            'number': row.number,
            'serial': row.serial,
            'dl_number': row.dl_number,
            'worker_name': row.worker_name,
            'pack_time': row.pack_time,
            'arma_type': row.arma_type,
        })

        # Собираем информацию о дубликатах
        if row.number:
            clean_number = ''.join(filter(str.isdigit, row.number))
            if clean_number in numbers:
                numbers[clean_number].append(len(data) - 1)
            else:
                numbers[clean_number] = [len(data) - 1]

        if row.serial:
            clean_serial = ''.join(filter(str.isdigit, row.serial))
            if clean_serial in serials:
                serials[clean_serial].append(len(data) - 1)
            else:
                serials[clean_serial] = [len(data) - 1]

        if row.dl_number:
            clean_dl_number = ''.join(filter(str.isdigit, row.dl_number))
            if clean_dl_number in dl_numbers:
                dl_numbers[clean_dl_number].append(len(data) - 1)
            else:
                dl_numbers[clean_dl_number] = [len(data) - 1]

    # Фильтруем только реальные дубликаты
    duplicate_numbers = {k: v for k, v in numbers.items() if len(v) > 1}
    duplicate_serials = {k: v for k, v in serials.items() if len(v) > 1}
    duplicate_dl_numbers = {k: v for k, v in dl_numbers.items() if len(v) > 1}

    return jsonify({
        "data": data,
        "duplicates": {
            "numbers": duplicate_numbers,
            "serials": duplicate_serials,
            "dl_numbers": duplicate_dl_numbers
        }
    })



@app.route('/update_table', methods=['POST'])
def update_table():
    data = request.json
    print(data)
    date = data['date']
    worker_name = data['worker_name']
    arma_type = data['arma_type']

    # Получаем количество существующих записей для этой даты
    row_count = TableRow.query.filter_by(date=date).count() + 1

    # Создаём новую строку в таблице
    if not data.get('number') and not data.get('serial'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial='',  # Пустое значение для серийного номера
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
        )
    elif not data.get('number'):
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number='',  # Пустое значение для номера
            serial=data['serial'],
            dl_number='',  # Пустое значение для графы
            worker_name=worker_name,
            arma_type=arma_type,
        )
    else:
        new_row = TableRow(
            date=date,
            row_number=str(row_count),
            timestamp=data['timestamp'],
            number=data['number'],
            serial=data['serial'],
            dl_number=data.get('dl_number', ''),
            worker_name=worker_name,
            arma_type=arma_type,
        )

    # Сохраняем запись в базе данных
    db.session.add(new_row)

    device = Device.query.filter_by(name=worker_name).first()
    if device:
        device.last_active = datetime.utcnow()
    db.session.commit()

    # Отправляем сообщение в очередь для обновления клиентов
    update_queue.put(json.dumps({"type": "update", "date": date}))

    # Добавляем импульсивное значение для sparkline, если запись отправлена ARM5 или ARM6
    if worker_name in ['ARM5', 'ARM6']:
        activity_data[worker_name].append(5)  # Значение импульса (можно настроить по необходимости)

    return jsonify({"status": "success"})


@app.route('/update_graf', methods=['POST'])
def update_graf():
    try:
        data = request.json
        row_id = int(data.get('id'))
        new_value = data.get('new_value')
        date = data.get('date')

        if not row_id or not date:
            return jsonify({"status": "error", "message": "Missing 'id' or 'date' in request"}), 400

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"}), 404
    except (ValueError, TypeError) as e:
        return jsonify({"status": "error", "message": f"Invalid input: {str(e)}"}), 400
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500


@app.route('/update_arma_type', methods=['POST'])
def update_arma_type():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        row = TableRow.query.filter_by(date=date, serial=serial).first()
        if row:
            row.arma_type = arma_type
            db.session.commit()
            return jsonify({"status": "success"})
        else:
            return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)})


@app.route('/update_number', methods=['POST'])
def update_number():
    try:
        data = request.json
        row_id = int(data['id'])  # Используем 'id' вместо 'row_index'
        new_value = data['new_value']
        date = data['date']

        # Находим запись по id и дате
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.number = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Row not found"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})

@app.route('/pack_item', methods=['POST'])
def pack_item():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']

        rows = TableRow.query.filter_by(id=row_id, date=date).all()
        if rows:
            row = rows[0]
            pack_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
            row.pack_time = pack_time
            db.session.commit()
            
            # Обновляем статистику упаковки
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.pack_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=0, pack_clicks=1)  # pack_clicks=1
                db.session.add(statistics)
            db.session.commit()
            
            # Отправляем сообщение в очередь для обновления клиентов
            update_queue.put(json.dumps({"type": "update", "date": date}))
            
            return jsonify({"status": "success", "pack_time": pack_time})

        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        print(f"Ошибка в pack_item: {str(e)}")
        return jsonify({"status": "error", "message": str(e)}), 500



@app.route('/set_initial_dl', methods=['POST'])
def set_initial_dl():
    try:
        data = request.json
        if not data:
            return jsonify({"status": "error", "message": "No data received"})

        date = data.get('date')
        initial_number = data.get('initial_number')

        if not date or not initial_number:
            return jsonify({
                "status": "error", 
                "message": "Missing required fields",
                "received": {"date": date, "initial_number": initial_number}
            })

        try:
            initial_number = int(initial_number)
        except ValueError:
            return jsonify({
                "status": "error",
                "message": "Initial number must be an integer"
            })

        print(f"Setting initial DL for date {date}: {initial_number}")  # Отладочный вывод

        dl_counter = DLCounter.query.filter_by(date=date).first()
        if dl_counter:
            dl_counter.counter = initial_number
            print(f"Updated existing counter for {date}")  # Отладочный вывод
        else:
            dl_counter = DLCounter(date=date, counter=initial_number)
            db.session.add(dl_counter)
            print(f"Created new counter for {date}")  # Отладочный вывод

        db.session.commit()
        return jsonify({
            "status": "success",
            "message": f"DL counter set to {initial_number} for date {date}"
        })

    except Exception as e:
        print(f"Error in set_initial_dl: {str(e)}")  # Отладочный вывод
        db.session.rollback()
        return jsonify({
            "status": "error",
            "message": str(e)
        })


@app.route('/generate_dl', methods=['POST'])
def generate_dl():
    try:
        data = request.json
        row_id = data['id']
        date = data['date']
        
        # Получаем или создаем счетчик DL для данной даты
        dl_counter = DLCounter.query.filter_by(date=date).first()
        if not dl_counter:
            return jsonify({"status": "error", "message": "DL counter не инициализирован"}), 400

        # Генерируем новый DL номер
        dl_number = f"DL{dl_counter.counter:06d}"

        # Находим и обновляем нужную запись по id
        row = TableRow.query.filter_by(id=row_id, date=date).first()
        if row:
            row.dl_number = dl_number
            dl_counter.counter += 1  # Увеличиваем счетчик

            # Обновляем статистику генерации
            statistics = Statistics.query.filter_by(date=date).first()
            if statistics:
                statistics.generate_clicks += 1
            else:
                statistics = Statistics(date=date, generate_clicks=1, pack_clicks=0)
                db.session.add(statistics)

            db.session.commit()
            return jsonify({"status": "success", "dl_number": dl_number, "id": row_id})
        
        return jsonify({"status": "error", "message": "Строка не найдена"}), 404
    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 500




@app.route('/export_excel')
def export_excel():
    try:
        date = request.args.get('date')
        sort_column = request.args.get('sort_column')
        sort_order = request.args.get('sort_order')

        if not date:
            return jsonify({"error": "Date parameter is required"}), 400

        # Начинаем построение запроса с фильтрации по дате и типу АРМА "RPA"
        query = TableRow.query.filter_by(date=date, arma_type='RPA')

        # Добавляем дополнительные условия фильтрации:
        # - dl_number не пустой
        # - pack_time не пустой
        query = query.filter(
            TableRow.dl_number.isnot(None),
            TableRow.dl_number != '',
            TableRow.pack_time.isnot(None),
            TableRow.pack_time != ''
        )

        # Применяем сортировку, если указана
        if sort_column and sort_order:
            column_mapping = {
                '0': TableRow.row_number,
                '1': TableRow.timestamp,
                '2': TableRow.number,
                '3': TableRow.serial,
                '4': TableRow.dl_number,
                '5': TableRow.worker_name,
                '6': TableRow.pack_time,  # Время упаковки
                '7': TableRow.arma_type,  # Тип АРМА
    
            }
            if sort_column in column_mapping:
                column = column_mapping[sort_column]

                # Явно приводим столбец "Номер" к целому числу для корректной сортировки
                if sort_column == '2':
                    column = cast(column, Integer)

                if sort_order == 'desc':
                    query = query.order_by(column.desc())
                else:
                    query = query.order_by(column.asc())
            else:
                return jsonify({"error": "Invalid sort column"}), 400

        rows = query.all()

        # Создаем DataFrame только с отфильтрованными данными
        data = []
        for row in rows:
            # Безопасное преобразование номера
            try:
                number = int(row.number) if row.number and row.number.strip() else None
            except ValueError:
                number = None

            data.append({
                '№': row.row_number,
                'Время отправки': row.timestamp,
                'Номер': number,
                'Serial': row.serial,
                'Графа': row.dl_number,
                'Имя отправителя': row.worker_name,
                'Время упаковки': row.pack_time,  # Время упаковки
                'Тип АРМА': row.arma_type,  # Тип АРМА
                
            })

        if not data:
            return jsonify({"error": "Нет записей для экспорта с заданными критериями."}), 400

        df = pd.DataFrame(data)

        # Создаем Excel файл
        output = BytesIO()
        with pd.ExcelWriter(output, engine='xlsxwriter') as writer:
            df.to_excel(writer, index=False, sheet_name='Sheet1')

            # Настраиваем форматирование
            workbook = writer.book
            worksheet = writer.sheets['Sheet1']

            # Автоматическая ширина столбцов
            for idx, col in enumerate(df.columns):
                series = df[col]
                max_len = max(
                    series.astype(str).map(len).max(),
                    len(str(col))
                ) + 2
                worksheet.set_column(idx, idx, max_len)

                # Форматирование числовых столбцов
                if col in ['Номер', 'Графа']:
                    num_format = workbook.add_format({'num_format': '0'})
                    worksheet.set_column(idx, idx, max_len, num_format)

        output.seek(0)

        return send_file(
            output,
            mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            as_attachment=True,
            download_name=f'export_{date}.xlsx'
        )

    except Exception as e:
        print(f"Export error: {str(e)}")
        return jsonify({"error": str(e)}), 500


@app.route('/update_serial', methods=['POST'])
def update_serial():
    try:
        data = request.json
        row_index = int(data['row_index'])
        new_value = data['new_value']
        date = data['date']

        rows = TableRow.query.filter_by(date=date).all()
        if 0 <= row_index < len(rows):
            row = rows[row_index]
            row.serial = new_value
            db.session.commit()
            return jsonify({"status": "success"})
        return jsonify({"status": "error", "message": "Invalid row index"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)})


def initialize_table_for_date(date):
    # Проверяем, существуют ли уже записи для этой даты
    existing = TableRow.query.filter_by(date=date).first()
    if not existing:
        # Создаем новую запись только в случае отсутствия данных
        new_row = TableRow(
            date=date,
            row_number=str(1),
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
            number='',
            serial='',
            dl_number='',
            worker_name='',
            arma_type='',
            pack_time='',
        )
        db.session.add(new_row)
        
        # Инициализируем статистику для новой даты
        statistics = Statistics(date=date, generate_clicks=0, pack_clicks=0)
        db.session.add(statistics)
        
        db.session.commit()


@app.route('/update_serial_arma', methods=['POST'])
def update_serial_arma():
    try:
        data = request.json
        date = data['date']
        serial = data['serial']
        arma_type = data['arma_type']

        # Создаем новую запись в таблице
        new_row = TableRow(
            date=date,
            serial=serial,
            arma_type=arma_type,
            timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        )

        db.session.add(new_row)
        db.session.commit()

        return jsonify({"status": "success"})
    except Exception as e:
        db.session.rollback()
        return jsonify({"status": "error", "message": str(e)}), 500

@app.route('/get_statistics')
def get_statistics():
    try:
        date = request.args.get('date')
        app.logger.debug(f"Получен запрос статистики для даты: {date}")
        
        # Получаем статистику генерации и упаковки
        statistics = Statistics.query.filter_by(date=date).first()
        if not statistics:
            generate_clicks = 0
            pack_clicks = 0
            app.logger.debug(f"Статистика для даты {date} не найдена. Установлено значение 0.")
        else:
            generate_clicks = statistics.generate_clicks
            pack_clicks = statistics.pack_clicks
            app.logger.debug(f"Статистика для даты {date}: Генераций = {generate_clicks}, Упаковок = {pack_clicks}")
        
        # Получаем количество записей типа "RPA"
        rpa_records = TableRow.query.filter_by(date=date, arma_type='RPA').count()
        app.logger.debug(f"Количество записей типа RPA для даты {date}: {rpa_records}")
        
        return jsonify({
            "generate_clicks": generate_clicks,
            "pack_clicks": pack_clicks,
            "rpa_records": rpa_records
        })
    except Exception as e:
        app.logger.error(f"Ошибка в get_statistics: {str(e)}")
        return jsonify({
            "generate_clicks": 0,
            "pack_clicks": 0,
            "rpa_records": 0,
            "error": str(e)
        })

@app.route('/delete_row', methods=['POST'])
def delete_row():
    try:
        data = request.json
        row_id = data.get('id')

        if not row_id:
            return jsonify({'status': 'error', 'message': 'ID записи не предоставлен'}), 400

        row = TableRow.query.get(row_id)
        if not row:
            return jsonify({'status': 'error', 'message': 'Запись не найдена'}), 404

        date = row.date  # Получаем дату записи для обновления статистики

        # Получаем запись статистики для этой даты
        statistics = Statistics.query.filter_by(date=date).first()

        if statistics:
            # Если запись имела сгенерированный DL номер, уменьшаем generate_clicks
            if row.dl_number:
                if statistics.generate_clicks > 0:
                    statistics.generate_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement generate_clicks below zero for date {date}")

            # Если запись была упакована, уменьшаем pack_clicks
            if row.pack_time:
                if statistics.pack_clicks > 0:
                    statistics.pack_clicks -= 1
                else:
                    app.logger.warning(f"Attempting to decrement pack_clicks below zero for date {date}")

            db.session.commit()
        else:
            app.logger.warning(f"No Statistics entry found for date {date} when deleting row {row_id}")

        # Удаляем запись из таблицы
        db.session.delete(row)
        db.session.commit()

        # Отправляем сообщение в очередь для обновления клиентов
        update_queue.put(json.dumps({"type": "delete", "date": date, "id": row.id}))

        return jsonify({'status': 'success'})
    except Exception as e:
        db.session.rollback()
        app.logger.error(f"Ошибка в delete_row: {str(e)}")
        return jsonify({'status': 'error', 'message': str(e)}), 500


@app.route('/', methods=['GET', 'POST'])
def main():
    if request.method == 'POST':
        worker = request.form.get('worker')
        name = request.form.get('name')

        # Сохраняем информацию о входе
        user = User.query.filter_by(username=name).first()
        if not user:
            user = User(username=name)
            db.session.add(user)

        login_history = LoginHistory(user=user)
        db.session.add(login_history)
        db.session.commit()

        resp = make_response(redirect('/welcome'))
        resp.set_cookie('worker', worker)
        resp.set_cookie('name', name)
        return resp

    return render_template('main.html')


@app.route('/get_current_dl', methods=['GET'])
def get_current_dl():
    date = request.args.get('date')
    if not date:
        return jsonify({"status": "error", "message": "Дата обязательна"}), 400

    dl_counter = DLCounter.query.filter_by(date=date).first()
    if dl_counter:
        return jsonify({"status": "success", "current_dl": dl_counter.counter})
    else:
        return jsonify({"status": "error", "message": "Счетчик DL не инициализирован для этой даты"}), 404


@app.route('/welcome')
def welcome():
    worker = request.cookies.get('worker')
    name = request.cookies.get('name')

    if not worker or not name:
        return redirect('/')

    current_time = datetime.utcnow()
    devices = [device.to_dict(current_time=current_time) for device in Device.query.order_by(Device.name).all()]
    current_time_str = current_time.isoformat()

    return render_template('welcome.html', name=name, worker=worker, table=table, devices=devices, current_time=current_time_str)


@app.route('/logout')
def logout():
    resp = make_response(redirect('/'))
    resp.delete_cookie('worker')
    resp.delete_cookie('name')
    return resp



if __name__ == '__main__':
    with app.app_context():
        db.create_all()
        create_devices()


    logging.disable(logging.CRITICAL)
    os.system('title СЕРВЕР')
    app.run(host='192.168.50.99', port=80, debug=True)  #192.168.50.99

let isEditing = false;
let searchTimeout;
let lastUpdateTime = 0;
const updateInterval = 1000;
let eventSource;
let lastData = null;
let isDeleting = false;
let fpInstance;
let unpackedDaysCache = {}; // Ключи будут в формате 'YYYY-MM', значение - Set дней
const deviceSparklines = {};

// Глобальные переменные для отслеживания сортировки
let globalSortColumn = null;
let globalSortOrder = 'asc';

function getSelectedDate() {
    return $('#datepicker').val();
}

// Функция для получения состояния фильтра
function getArmaFilter() {
    return $('#armaFilter').is(':checked') ? 'RPA' : '';
}

function updateTable(force = false, highlightNew = false) {
    return new Promise((resolve, reject) => {
        const now = Date.now();
        if (!force && now - lastUpdateTime < updateInterval) {
            resolve();
            return;
        }
        if (isEditing) {
            resolve();
            return;
        }

        if (isEditing || isDeleting) { // Добавляем проверку на isDeleting
            resolve();
            return;
        }

        $.ajax({
            url: '/get_table_data',
            type: 'GET',
            data: { 
                date: getSelectedDate(),
                filter_arma: getArmaFilter()
            },
            success: function(response) {
                var tableBody = $('#tableBody');

                // Обновляем содержимое таблицы
                updateTableContent(response.data, tableBody, highlightNew);

                // Применяем текущую сортировку
                if (globalSortColumn !== null) {
                    sortTable(globalSortColumn, globalSortOrder);
                }

                updateStatistics();
                lastUpdateTime = now;

                // Проверяем и подсвечиваем дубликаты
                highlightDuplicates(response.duplicates);

                resolve();
            },
            error: function(xhr, status, error) {
                console.error('Error updating table:', error);
                reject(error);
            }
        });
    });
}

function initializeSparklines(activity) {
    for (const [name, data] of Object.entries(activity)) {
        const sparklineSpan = $(`#${name}-sparkline`);
        sparklineSpan.sparkline(data, {
            type: 'line',
            width: '300px',  // Увеличенная ширина
            height: '50px',  // Также можно увеличить высоту для лучшей видимости
            lineColor: 'green',
            fillColor: 'transparent',
            spotColor: 'red',          // Отображение пиков
            minSpotColor: 'blue',      // Минимальные пики
            maxSpotColor: 'orange',    // Максимальные пики
            highlightSpotColor: 'blue',
            highlightLineColor: 'blue'
        });
        sparklineSpan.data('sparkline-initialized', true);
    }
}

function fetchDeviceStatuses() {
    $.ajax({
        url: '/get_device_status',
        type: 'GET',
        success: function(response) {
            if (response.status === 'success') {
                const devices = response.devices;
                const activity = response.activity;

                console.log("Received devices:", devices);
                console.log("Received activity data:", activity);

                for (const [name, status] of Object.entries(devices)) {
                    // Обновляем кружочек статуса
                    const statusCircle = $(`#${name}-status`);
                    const statusText = $(`#${name}-status-text`);
                    statusCircle.removeClass('online offline no_internet copying sending_number processing');
                    
                    if (status === 'sending_number') {
                        statusCircle.addClass('sending_number');
                        statusText.text('Отправка номера');
                    } else if (status === 'copying') {
                        statusCircle.addClass('copying');
                        statusText.text('Копирование');
                    } else if (status === 'processing') {
                        statusCircle.addClass('processing');
                        statusText.text('Процесс идет');
                    } else if (status === 'online') {
                        statusCircle.addClass('online');
                        statusText.text('Онлайн');
                    } else if (status === 'no_internet') {
                        statusCircle.addClass('no_internet');
                        statusText.text('Нет интернета');
                    } else {
                        statusCircle.addClass('offline');
                        statusText.text('Нет соединения');
                    }
                }

                // Обновляем Sparkline
                updateSparklines(devices, activity);
            }
        },
        error: function(error) {
            console.error('Ошибка при получении статусов устройств:', error);
        }
    });
}

function updateSparklines(devices, activity) {
    for (const [name, status] of Object.entries(devices)) {
        const sparklineSpan = $(`#${name}-sparkline`);
        const data = activity[name];
        
        // Определяем цвет линии в зависимости от статуса устройства и последнего значения активности
        let lineColor = 'green'; // Стандартный цвет

        if (status === 'processing') {
            lineColor = 'purple'; // Цвет для статуса "processing"
        } else if (status === 'copying') {
            lineColor = 'orange';
        } else if (status === 'sending_number') {
            lineColor = 'blue';
        } else if (status === 'no_internet') {
            lineColor = 'red';
        }

        // Проверяем, был ли добавлен импульс (например, значение > 4)
        const lastValue = data[data.length - 1];
        if (lastValue > 4) {
            lineColor = 'purple'; // Цвет импульса
        }

        if (sparklineSpan.data('sparkline-initialized')) {
            // Обновляем существующий Sparkline
            sparklineSpan.sparkline(data, {
                type: 'line',
                width: '100%',
                height: '20px',
                lineColor: lineColor,
                fillColor: 'transparent',
                spotColor: 'red',
                minSpotColor: 'blue',
                maxSpotColor: 'orange',
                highlightSpotColor: 'blue',
                highlightLineColor: 'blue'
            });
        } else {
            // Инициализируем Sparkline, если он ещё не инициализирован
            sparklineSpan.sparkline(data, {
                type: 'line',
                width: '100%',
                height: '20px',
                lineColor: lineColor,
                fillColor: 'transparent',
                spotColor: 'red',
                minSpotColor: 'blue',
                maxSpotColor: 'orange',
                highlightSpotColor: 'blue',
                highlightLineColor: 'blue'
            });
            sparklineSpan.data('sparkline-initialized', true);
        }
    }
}


function highlightDuplicates(duplicates) {
    // Очищаем предыдущие выделения
    $('#tableBody td').removeClass('duplicate-highlight');

    // Подсвечиваем дубликаты номеров
    for (let number in duplicates.numbers) {
        duplicates.numbers[number].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(3)`).addClass('duplicate-highlight');
        });
    }

    // Подсвечиваем дубликаты серийных номеров
    for (let serial in duplicates.serials) {
        duplicates.serials[serial].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(4)`).addClass('duplicate-highlight');
        });
    }

    // Подсвечиваем дубликаты "Графа" (dl_number)
    for (let dl_number in duplicates.dl_numbers) {
        duplicates.dl_numbers[dl_number].forEach(index => {
            $(`#tableBody tr:eq(${index}) td:nth-child(5)`).addClass('duplicate-highlight');
        });
    }
}

function updateCell(cell, newValue, rowId, cellIndex, rowData) {
    console.log(`Updating cell: index=${cellIndex}, value=${newValue}, rowId=${rowId}`);

    const excludedArmaTypes = ["CFA", "VND", "SRC"];

    if (cellIndex === 4) { // Столбец "Графа"
        if (rowData.arma_type === 'RPA' && !excludedArmaTypes.includes(rowData.arma_type)) {
            if (newValue && newValue.startsWith('DL')) {
                cell.text(newValue)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click') // Удаляем предыдущие обработчики
                    .on('click', function() {
                        copyToClipboard(newValue);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });

                // Обновляем data-value
                let numericValue = parseDL(newValue) || 0;
                cell.attr('data-value', numericValue);
            } else {
                cell.html('<button class="generate-button">Генерировать</button>')
                    .find('button')
                    .click(function() {
                        generateDL(rowId);
                    });

                // Обновляем data-value
                cell.attr('data-value', 0);
            }
        } else if (!excludedArmaTypes.includes(rowData.arma_type)) {
            if (newValue && newValue.startsWith('DL')) {
                cell.text(newValue)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click')
                    .on('click', function() {
                        copyToClipboard(newValue);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });

                // Обновляем data-value
                let numericValue = parseDL(newValue) || 0;
                cell.attr('data-value', numericValue);
            } else {
                cell.text(newValue || '');
                // Обновляем data-value
                let numericValue = parseDL(newValue) || 0;
                cell.attr('data-value', numericValue);
            }
        } else {
            cell.text(newValue || '');
            // Обновляем data-value
            let numericValue = parseDL(newValue) || 0;
            cell.attr('data-value', numericValue);
        }
    } else if (cellIndex === 6) { // Столбец "Время упаковки"
        const dlCell = cell.parent().find('td').eq(4);
        const dlValue = dlCell.text().trim();
        const armaType = rowData.arma_type;

        if (dlValue.startsWith('DL') && !newValue && !excludedArmaTypes.includes(armaType)) {
            cell.html('<button class="pack-button">Упаковать</button>')
                .find('button')
                .click(function() {
                    packItem(rowId);
                });
        } else {
            cell.text(newValue);
        }
    } else if (cellIndex === 7) { // Столбец "Тип АРМА"
        if (!newValue || newValue.trim() === '') {
            newValue = 'RPA';
        }
        cell.text(newValue)
            .toggleClass('editable', true)
            .attr('data-column', cellIndex);
    } else {
        cell.text(newValue)
            .toggleClass('editable', cellIndex === 2 || cellIndex === 3)
            .attr('data-column', cellIndex);
    }
}

function sortTable(column, order) {
    const rows = $('#tableBody > tr').get();

    rows.sort(function(a, b) {
        let A = $(a).children('td').eq(column).text().trim();
        let B = $(b).children('td').eq(column).text().trim();

        // Обработка пустых значений
        if (A === '' && B === '') return 0;
        if (A === '') return order === 'asc' ? 1 : -1;
        if (B === '') return order === 'asc' ? -1 : 1;

        // Специальная обработка для столбца "Графа" (dl_number) с использованием data-value
        if (column === 4) { // Индекс столбца "Графа"
            let numA = parseInt($(a).children('td').eq(column).attr('data-value')) || 0;
            let numB = parseInt($(b).children('td').eq(column).attr('data-value')) || 0;

            // Отладочные сообщения
            console.log(`Сортировка Графа: numA = ${numA}; numB = ${numB}`);

            return order === 'asc' ? numA - numB : numB - numA;
        }

        // Обработка дат и времени для соответствующих столбцов
        if (column === 1 || column === 6) {  // Столбцы "Время отправки" и "Время упаковки"
            let dateA = Date.parse(A);
            let dateB = Date.parse(B);
            if (isNaN(dateA)) dateA = 0;
            if (isNaN(dateB)) dateB = 0;
            return order === 'asc' ? dateA - dateB : dateB - dateA;
        }

        // Обработка числовых столбцов
        if (column === 0 || column === 2) {  // Столбцы с числами (№ и Номер)
            let numA = parseFloat(A) || 0;
            let numB = parseFloat(B) || 0;
            return order === 'asc' ? numA - numB : numB - numA;
        }

        // Обработка строковых столбцов
        if (column === 5) {  // Столбец с именем отправителя
            A = A.toLowerCase();
            B = B.toLowerCase();
        }

        // Строковое сравнение для остальных столбцов
        A = A.toUpperCase();
        B = B.toUpperCase();
        if (A < B) return order === 'asc' ? -1 : 1;
        if (A > B) return order === 'asc' ? 1 : -1;
        return 0;
    });

    // Очистка тела таблицы
    $('#tableBody').empty();

    // Добавление отсортированных строк
    $.each(rows, function(index, row) {
        $('#tableBody').append(row);
    });

    checkDuplicates();
    bindEventHandlers();
}

function parseDL(dl_number) {
    if (typeof dl_number !== 'string') return null;
    dl_number = dl_number.trim();
    let match = dl_number.match(/^DL[-\s]?(\d+)$/i);
    if (match && match[1]) {
        return parseInt(match[1], 10);
    }
    return null;
}

function generateDL(rowId) {
    console.log(`Generating DL for row ID ${rowId}`);
    const selectedDate = getSelectedDate();
    console.log("Selected date:", selectedDate);

    $.ajax({
        url: '/generate_dl',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            id: rowId,
            date: selectedDate
        }),
        success: function(response) {
            if (response.status === 'success') {
                console.log('DL успешно сгенерирован:', response.dl_number);
                var row = $(`#tableBody tr[data-id='${rowId}']`);
                var dlCell = row.find('td:eq(4)');
                var packCell = row.find('td:eq(6)'); // Столбец "Время упаковки"
                var armaType = row.find('td:eq(7)').text().trim();

                dlCell.text(response.dl_number)
                    .addClass('clickable')
                    .css('cursor', 'pointer')
                    .off('click') // Удаляем предыдущие обработчики
                    .on('click', function() {
                        copyToClipboard(response.dl_number);
                        $(this).addClass('copied');
                        setTimeout(() => {
                            $(this).removeClass('copied');
                        }, 200);
                    });

                // Добавляем кнопку упаковки только если arma_type не исключен
                const excludedArmaTypes = ["CFA", "VND", "SRC"];
                if (!excludedArmaTypes.includes(armaType)) {
                    packCell.html('<button class="pack-button">Упаковать</button>')
                        .find('button')
                        .click(function() {
                            packItem(rowId);
                        });
                }

                // Обновляем поле initial-dl с текущим значением DL
                loadCurrentDL();
            } else {
                alert('Ошибка при генерации DL: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при генерации DL:', status, error);
            alert('Ошибка при генерации DL: ' + error);
        }
    });
}

function packItem(rowId) {
    console.log(`Упаковка элемента для строки ID ${rowId}`);
    const selectedDate = getSelectedDate();
    console.log("Selected date:", selectedDate);

    $.ajax({
        url: '/pack_item',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({
            id: rowId,
            date: selectedDate
        }),
        success: function(response) {
            console.log("Ответ сервера:", response);
            if (response.status === 'success') {
                var row = $(`#tableBody tr[data-id='${rowId}']`);
                row.addClass('packed-row');
                var cell = row.find('td:eq(6)'); // Столбец "Время упаковки"
                cell.text(response.pack_time);
                updateStatistics();

                // Удаляем кнопку "Упаковать", так как запись уже упакована
                cell.find('button.pack-button').remove();

                // Определяем год и месяц из `selectedDate`
                const [year, month, day] = selectedDate.split('-').map(Number);

                // Принудительно обновляем неупакованные дни для этого месяца
                markUnpackedDays(year, month, true);
            } else {
                alert('Ошибка при упаковке: ' + response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error("Ошибка AJAX:", xhr, status, error);
            alert('Ошибка при упаковке: ' + error);
        }
    });
}

function areSimilarNumbers(num1, num2) {
    // Очищаем строки от нецифровых символов и пробелов
    const clean1 = num1.toString().replace(/\D/g, '').trim();
    const clean2 = num2.toString().replace(/\D/g, '').trim();
    
    // Если строки пустые или разной длины - не дубликаты
    if (!clean1 || !clean2 || clean1.length !== clean2.length) {
        return false;
    }
    
    // Проверяем на точное совпадение
    return clean1 === clean2;
}

function checkDuplicates() {
    console.log("Checking for duplicates...");
    const rows = $('#tableBody tr');
    
    // Очищаем предыдущие выделения
    rows.find('td').removeClass('duplicate-highlight');
    
    // Создаем массивы для хранения номеров и их индексов
    const numbers = new Map();
    const dl_numbers = new Map();
    const serials = new Map();
    
    // Собираем все номера и серийные номера
    rows.each(function(index) {
        const number = $(this).find('td:nth-child(3)').text().trim();
        const serial = $(this).find('td:nth-child(4)').text().trim();
        const dl_number = $(this).find('td:nth-child(5)').text().trim();
        
        if (number) {
            if (!numbers.has(number)) {
                numbers.set(number, []);
            }
            numbers.get(number).push(index);
        }

        if (dl_number) {
            if (!dl_numbers.has(dl_number)) {
                dl_numbers.set(dl_number, []);
            }
            dl_numbers.get(dl_number).push(index);
        }
        
        if (serial) {
            if (!serials.has(serial)) {
                serials.set(serial, []);
            }
            serials.get(serial).push(index);
        }
    });
    
    // Подсвечиваем дубликаты
    let duplicatesFound = false;
    


    dl_numbers.forEach((indices, dl_number) => {
        if (dl_number.startsWith("DL")) {
            if (indices.length > 1) {
                indices.forEach(index => {
                    rows.eq(index).find('td:nth-child(5)').addClass('duplicate-highlight');
                });
                duplicatesFound = true;
            }
        }
    });

    // Проверяем номера
    numbers.forEach((indices, number) => {
        if (indices.length > 1) {
            indices.forEach(index => {
                rows.eq(index).find('td:nth-child(3)').addClass('duplicate-highlight');
            });
            duplicatesFound = true;
        }
    });

    // // Проверяем грайвера
    // dl_numbers.forEach((indices, dl_number) => {
    //     if (indices.length > 1) {
    //         indices.forEach(index => {
    //             rows.eq(index).find('td:nth-child(5)').addClass('duplicate-highlight');
    //         });
    //         duplicatesFound = true;
    //     }
    // });
    
    // Проверяем серийные номера
    serials.forEach((indices, serial) => {
        if (indices.length > 1) {
            indices.forEach(index => {
                rows.eq(index).find('td:nth-child(4)').addClass('duplicate-highlight');
            });
            duplicatesFound = true;
        }
    });

    return duplicatesFound;
}

function appendNewRow(tableBody, rowData) {
    console.log("Добавление новой строки:", rowData);

    // Создаем элемент строки и присваиваем уникальный data-id
    var tr = $('<tr>').attr('data-id', rowData.id);

    // Если есть время упаковки и это не заголовок, добавляем класс 'packed-row'
    if (rowData.pack_time && rowData.pack_time !== "Время упаковки") {
        tr.addClass('packed-row');
    }

    // Добавляем ячейки в строку
    tr.append($('<td>').text(rowData.row_number));
    tr.append($('<td>').text(rowData.timestamp));

    // Столбец "Номер" (Третий столбец, индекс 2)
    var numberCell = $('<td>')
        .text(rowData.number)
        .addClass('editable') // Добавляем класс для редактирования
        .attr('data-column', '2'); // Указываем индекс столбца
    tr.append(numberCell);

    tr.append($('<td>').text(rowData.serial));

    // Столбец "Графа" (Пятый столбец, индекс 4)
    var dlCell = $('<td>')
        .text(rowData.dl_number || '')
        .addClass('editable') // Добавляем класс для редактирования
        .attr('data-column', '4'); // Указываем индекс столбца "Графа"
    tr.append(dlCell);
    
    // Добавляем data-value
    var numericValue = parseDL(rowData.dl_number) || 0;
    dlCell.attr('data-value', numericValue);

    // Проверка и добавление кнопок или кликабельного текста для "Графа"
    const excludedArmaTypes = ["CFA", "VND", "SRC"];

    if (rowData.arma_type === 'RPA' && !excludedArmaTypes.includes(rowData.arma_type)) {
        // Для типа АРМА 'RPA', проверяем наличие и формат dl_number
        if (!rowData.dl_number || !rowData.dl_number.startsWith('DL')) {
            // Если dl_number отсутствует или не начинается с 'DL', отображаем кнопку "Генерировать"
            dlCell.html('<button class="generate-button">Генерировать</button>')
                .find('button')
                .click(function() {
                    generateDL(rowData.id); // Передаем уникальный ID строки
                });
        } else {
            // Если dl_number присутствует и начинается с 'DL', делаем его кликабельным для копирования
            dlCell.text(rowData.dl_number)
                .addClass('clickable')
                .css('cursor', 'pointer')
                .on('click', function() {
                    copyToClipboard(rowData.dl_number);
                    $(this).addClass('copied');
                    setTimeout(() => {
                        $(this).removeClass('copied');
                    }, 200);
                });
        }
    } else if (!excludedArmaTypes.includes(rowData.arma_type)) {
        // Для других типов АРМА, которые не исключены, отображаем или скрываем кнопку
        if (rowData.dl_number && rowData.dl_number.startsWith('DL')) {
            dlCell.text(rowData.dl_number)
                .addClass('clickable')
                .css('cursor', 'pointer')
                .on('click', function() {
                    copyToClipboard(rowData.dl_number);
                    $(this).addClass('copied');
                    setTimeout(() => {
                        $(this).removeClass('copied');
                    }, 200);
                });
        } else {
            dlCell.text(rowData.dl_number || '');
        }
    } else {
        // Для типов АРМА "CFA", "VND", "SRC" оставляем ячейку пустой или с текущим значением
        dlCell.text(rowData.dl_number || '');
    }

    // Добавляем ячейку с именем отправителя
    tr.append($('<td>').text(rowData.worker_name));

    /**
     * Обработка ячейки "Время упаковки" (pack_time)
     */
    var packCell = $('<td>');

    if (rowData.dl_number && rowData.dl_number.startsWith('DL') && !rowData.pack_time && !excludedArmaTypes.includes(rowData.arma_type)) {
        // Если dl_number с 'DL', pack_time отсутствует и arma_type не исключен, показываем кнопку "Упаковать"
        packCell.html('<button class="pack-button">Упаковать</button>')
            .find('button')
            .click(function() {
                packItem(rowData.id); // Передаем уникальный ID строки
            });
    } else {
        // Иначе отображаем время упаковки или оставляем пустым
        packCell.text(rowData.pack_time || '');
    }

    tr.append(packCell);

    // Добавляем ячейку с типом АРМА
    tr.append($('<td>').text(rowData.arma_type || ''));

    

    /**
     * Добавление ячейки "Действие" с кнопкой "Удалить"
     */
    var actionTd = $('<td>');
    var deleteButton = $('<button>').text('Удалить').addClass('delete-button btn btn-danger btn-sm');
    deleteButton.click(function() {
        initiateDeleteConfirmation(rowData.id); // Используем новую функцию
    });
    actionTd.append(deleteButton);
    tr.append(actionTd);

    // Добавляем строку в тело таблицы
    tableBody.append(tr);

    /**
     * Дополнительная обработка после добавления строки
     */
    setTimeout(() => {
        const dlValue = dlCell.text().trim();
        const packValue = packCell.text().trim();

        if (dlValue.startsWith('DL') && !packValue && !excludedArmaTypes.includes(rowData.arma_type)) {
            console.log('Добавляем кнопку упаковки для DL:', dlValue);
            packCell.html('<button class="pack-button">Упаковать</button>')
                .find('button')
                .click(function() {
                    packItem(rowData.id); // Передаем уникальный ID строки
                });
        }

        // Проверяем дубликаты после добавления новой строки
        checkDuplicates();
    }, 100);
}


function bindEventHandlers() {
    // Привязка обработчика для кнопки "Генерировать"
    $('.generate-button').off('click').on('click', function() {
        var rowId = $(this).closest('tr').data('id');
        generateDL(rowId);
    });

    // Привязка обработчика для кнопки "Упаковать"
    $('.pack-button').off('click').on('click', function() {
        var rowId = $(this).closest('tr').data('id');
        packItem(rowId);
    });

    // Привязка обработчика для кнопки "Удалить"
    $('.delete-button').off('click').on('click', function() {
        var rowId = $(this).closest('tr').data('id');
        initiateDeleteConfirmation(rowId);
    });
}

function updateTableContent(data, tableBody, highlightNew = false) {
    tableBody.empty();  // Очищаем текущее содержимое таблицы
    data.forEach(function(rowData, index) {
        appendNewRow(tableBody, rowData);
        if (highlightNew && index === data.length - 1) {
            // Можно добавить подсветку новой строки, если нужно
        }
    });
    checkDuplicates();
    bindEventHandlers();  // Проверяем дубликаты после обновления таблицы
}

function scheduleNextUpdate() {
    setTimeout(() => {
        if (!isEditing) {
            updateTable(false, false).then(() => {
                scheduleNextUpdate();
            });
        } else {
            scheduleNextUpdate();
        }
    }, 3000);
}

function initializeEventSource() {
    if (eventSource) {
        eventSource.close();
    }

    eventSource = new EventSource('/stream');

    eventSource.onmessage = function(event) {
        const data = JSON.parse(event.data);
        if (data.type === 'update' && data.date === getSelectedDate()) {
            // Обновляем таблицу
            updateTable(true).then(() => {
                // После обновления таблицы, обновляем подсветку дней
                const currentDate = new Date(fpInstance.selectedDates[0]);
                markUnpackedDays(currentDate.getFullYear(), currentDate.getMonth() + 1);
                fetchDeviceStatuses();
            });
        }
    };

    eventSource.onerror = function(error) {
        console.error('EventSource failed:', error);
        eventSource.close();
        setTimeout(initializeEventSource, 5000);  // Попытка переподключения через 5 секунд
    };
}

// Обработчики событий для таблицы
$(document).ready(function() {
    updateTable();
    updateStatistics(); // Убедитесь, что эта функция определена
    initializeEventSource();
    scheduleNextUpdate();
    fetchDeviceStatuses();
    loadCurrentDL();
    // Обработчик сортировки
    $('.sortable').click(function() {
        const column = $(this).data('column');
        const order = $(this).hasClass('asc') ? 'desc' : 'asc';
        
        // Удаляем классы сортировки со всех заголовков
        $('.sortable').removeClass('asc desc');
        // Добавляем класс текущей сортировки
        $(this).addClass(order);
        
        // Сохраняем текущие параметры сортировки
        globalSortColumn = column;
        globalSortOrder = order;
        
        // Сортируем таблицу
        sortTable(column, order);
    });

    // Обработчик поиска
    $('#searchButton').click(function() {
        searchNumber();
    });
    $('#searchInput').keypress(function(e) {
        if (e.which === 13) {
            searchNumber();
        }
    });
    $('#armaFilter').on('change', function() {
        updateTable();
    });
});

// Обработчик изменения даты
$('#datepicker').on('change', function() {
    initializeEventSource();
    updateTable();
});

// Обработчик экспорта в Excel
$(document).ready(function() {
    $('.export-button').on('click', function(e) {
        e.preventDefault();
        
        // Получаем информацию о сортировке
        const sortableColumn = $('.sortable.asc, .sortable.desc');
        const sortColumn = sortableColumn.length ? sortableColumn.data('column') : '';
        const sortOrder = $('.sortable.asc').length ? 'asc' : 'desc';
        
        // Формируем URL
        const date = getSelectedDate();
        const queryParams = new URLSearchParams({
            date: date,
            sort_column: sortColumn || '',
            sort_order: sortOrder
        });
        
        // Создаем и отправляем запрос
        fetch(`/export_excel?${queryParams.toString()}`)
        .then(response => {
            if (response.ok) {
                return response.blob();
            }
            throw new Error('Network response was not ok.');
        })
        .then(blob => {
            // Создаем ссылку для скачивания
            const url = window.URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `export_${date}.xlsx`;
            document.body.appendChild(a);
            a.click();
            window.URL.revokeObjectURL(url);
            a.remove();
        })
        .catch(error => {
            console.error('Error:', error);
            alert('Ошибка при экспорте файла');
        });
    });
});

$(document).ready(function() {
    fpInstance = flatpickr("#datepicker", {
        locale: "ru",
        dateFormat: "Y-m-d",
        defaultDate: "today",
        onChange: function(selectedDates, dateStr, instance) {
            updateTable();
            const selected = selectedDates[0];
            if (selected) {
                markUnpackedDays(selected.getFullYear(), selected.getMonth() + 1);
            }
        },
        onMonthChange: function(selectedDates, dateStr, instance) {
            const selected = instance.currentYear && instance.currentMonth !== undefined
                ? new Date(instance.currentYear, instance.currentMonth, 1)
                : new Date();
            markUnpackedDays(selected.getFullYear(), selected.getMonth() + 1);
        },
        onDayCreate: function(dObj, dStr, fp, dayElem) {
            const year = dayElem.dateObj.getFullYear();
            const month = dayElem.dateObj.getMonth() + 1; // getMonth() возвращает 0-11
            const day = dayElem.dateObj.getDate();
            const key = `${year}-${String(month).padStart(2, '0')}`;
            
            if (unpackedDaysCache[key] && unpackedDaysCache[key].has(day)) {
                dayElem.style.backgroundColor = 'lightcoral';
                dayElem.style.color = 'white';
            } else {
                // Сброс стилей для других дней
                dayElem.style.backgroundColor = '';
                dayElem.style.color = '';
            }
        }
    });

    // Инициализируем для текущего месяца
    const today = new Date();
    markUnpackedDays(today.getFullYear(), today.getMonth() + 1);

    initializeEventSource();
    updateTable();
    updateStatistics();
    fetchDeviceStatuses();
    setInterval(fetchDeviceStatuses, 1000); // Интервал 1 секунду (скорректируйте при необходимости)
});


// Обработчик двойного клика для редактирования ячеек
$(document).on('dblclick', '.editable', function() {
    if (isEditing) return; // Предотвращаем многократное редактирование
    isEditing = true;

    var td = $(this);
    var currentValue = td.text();
    var input = $('<input type="text">')
        .val(currentValue)
        .addClass('edit-input');

    td.html(input);
    input.focus();

    input.on('blur', function() {
        var newValue = $(this).val().trim();
        var rowId = td.closest('tr').data('id');
        var columnIndex = td.attr('data-column');

        // Определяем URL для обновления в зависимости от столбца
        let url;
        let updateData = {
            id: rowId,
            new_value: newValue,
            date: getSelectedDate()
        };

        if (columnIndex === '2') { // Столбец "Номер"
            url = '/update_number';
        } else if (columnIndex === '4') { // Столбец "Графа"
            url = '/update_graf';
        } else {
            alert('Неподдерживаемый столбец для редактирования');
            td.text(currentValue);
            isEditing = false;
            return;
        }

        $.ajax({
            url: url,
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify(updateData),
            success: function(response) {
                if (response.status === 'success') {
                    td.text(newValue);
                    checkDuplicates();
                } else {
                    alert('Ошибка при обновлении значения: ' + response.message);
                    td.text(currentValue);
                }
                isEditing = false;
            },
            error: function(xhr, status, error) {
                alert('Ошибка при отправке данных: ' + error);
                td.text(currentValue);
                isEditing = false;
            }
        });
    });

    input.on('keypress', function(e) {
        if (e.which === 13) { // Нажатие Enter
            $(this).blur();
        }
    });

    input.on('keydown', function(e) {
        if (e.key === 'Escape') { // Нажатие Esc
            td.text(currentValue);
            isEditing = false;
        }
    });
});

function searchNumber() {
    const searchValue = $('#searchInput').val().trim();
    if (!searchValue) return;

    // Удаляем предыдущую подсветку
    $('.highlight-search').removeClass('highlight-search');

    const rows = $('#tableBody tr');
    let found = false;

    rows.each(function() {
        const numberCell = $(this).find('td:nth-child(3)');
        const cellText = numberCell.text().trim();
        if (cellText === searchValue) {
            numberCell.addClass('highlight-search');
            
            // Прокрутка к найденной строке
            $('.table-container').animate({
                scrollTop: $(this).offset().top - $('.table-container').offset().top + $('.table-container').scrollTop() - 100
            }, 500);

            found = true;

            // Устанавливаем таймер на удаление подсветки через 3 секунды
            setTimeout(() => {
                numberCell.removeClass('highlight-search');
            }, 3000);

            // Выводим сообщение в консоль для отладки
            console.log('Найдено совпадение:', cellText);

            return false; // Прерываем цикл, так как нашли нужную строку
        }
    });

    if (!found) {
        alert('Точное совпадение номера не найдено');
    }
}

function initiateDeleteConfirmation(rowId) {
    isDeleting = true; // Устанавливаем флаг подтверждения удаления
    var deleteCell = $(`tr[data-id='${rowId}'] td:last-child`);

    // Проверяем, не находится ли уже в состоянии подтверждения
    if (deleteCell.data('confirming')) {
        return; // Если подтверждение уже запрошено, ничего не делаем
    }

    // Устанавливаем флаг подтверждения
    deleteCell.data('confirming', true);

    // Создаем контейнер для кнопок "Да" и "Нет"
    var buttonsContainer = $('<div>').addClass('confirm-buttons');

    var yesButton = $('<button>')
        .text('Да')
        .addClass('confirm-yes-button btn btn-success btn-sm me-2')
        .click(function() {
            deleteConfirmed(rowId);
        });

    var noButton = $('<button>')
        .text('Нет')
        .addClass('confirm-no-button btn btn-secondary btn-sm')
        .click(function() {
            cancelDeleteConfirmation(rowId);
        });

    buttonsContainer.append(yesButton);
    buttonsContainer.append(noButton);

    // Заменяем содержимое ячейки на контейнер с кнопками
    deleteCell.empty();
    deleteCell.append(buttonsContainer);
}

function deleteConfirmed(rowId) {
    $.ajax({
        url: '/delete_row',
        type: 'POST',
        contentType: 'application/json',
        data: JSON.stringify({ id: rowId }),
        success: function(response) {
            if (response.status === 'success') {
                // Удаляем строку из таблицы
                $(`tr[data-id='${rowId}']`).remove();
            } else {
                console.error('Ошибка при удалении записи:', response.message);
                // Можно добавить уведомление пользователю о неудаче
            }
            // Восстанавливаем кнопку "Удалить" и сбрасываем флаг
            cancelDeleteConfirmation(rowId);
        },
        error: function(xhr, status, error) {
            console.error('Произошла ошибка при удалении записи:', error);
            // Можно добавить уведомление пользователю о неудаче
            // Восстанавливаем кнопку "Удалить" и сбрасываем флаг
            cancelDeleteConfirmation(rowId);
        }
    });
}

function cancelDeleteConfirmation(rowId) {
    var deleteCell = $(`tr[data-id='${rowId}'] td:last-child`);
    // Сбрасываем флаг подтверждения
    deleteCell.data('confirming', false);
    // Восстанавливаем кнопку "Удалить"
    deleteCell.empty();
    var deleteButton = $('<button>')
        .text('Удалить')
        .addClass('delete-button btn btn-danger btn-sm')
        .click(function() {
            initiateDeleteConfirmation(rowId);
        });
    deleteCell.append(deleteButton);

    isDeleting = false; // Сбрасываем глобальный флаг
}

function loadCurrentDL() {
    const selectedDate = getSelectedDate();
    $.ajax({
        url: '/get_current_dl',
        type: 'GET',
        data: { date: selectedDate },
        success: function(response) {
            if (response.status === 'success') {
                $('#initial-dl').val(response.current_dl);
            } else {
                $('#initial-dl').val(''); // Можно установить значение по умолчанию или показать сообщение
                console.warn(response.message);
            }
        },
        error: function(xhr, status, error) {
            console.error('Ошибка при получении текущего DL:', error);
            $('#initial-dl').val('');
        }
    });
}

function markUnpackedDays(year, month, force = false) {
    const key = `${year}-${String(month).padStart(2, '0')}`;

    if (!force && unpackedDaysCache[key] !== undefined) {
        fpInstance.redraw(); // Перерисовываем календарь для применения стилей
        return;
    }

    // Запрашиваем неупакованные дни для выбранного месяца и года
    fetch(`/get_unpacked_days?year=${year}&month=${month}`)
        .then(response => response.json())
        .then(data => {
            if (data.status === 'success') {
                unpackedDaysCache[key] = new Set(data.unpacked_days);
                console.log(`Неупакованные дни для ${key}:`, unpackedDaysCache[key]);
            } else {
                console.error('Ошибка при получении неупакованных дней:', data.message);
                unpackedDaysCache[key] = new Set(); // Инициализируем пустым множеством
            }
            fpInstance.redraw(); // Перерисовываем календарь после обновления данных
        })
        .catch(error => {
            console.error('Ошибка при запросе неупакованных дней:', error);
            unpackedDaysCache[key] = new Set(); // Инициализируем пустым множеством
            fpInstance.redraw();
        });
}
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Рабочая таблица</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='styles_welcome.css') }}">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
    <script src="{{ url_for('static', filename='js/utils.js') }}"></script>
    <script src="{{ url_for('static', filename='js/table.js') }}"></script>
    <script src="{{ url_for('static', filename='js/date.js') }}"></script>
    <script src="{{ url_for('static', filename='js/statistics.js') }}"></script>
    <!-- Убедитесь, что вы подключили jQuery UI для перетаскивания -->
    <link rel="stylesheet" href="https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css">
    <script src="https://code.jquery.com/ui/1.13.2/jquery-ui.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/ru.js"></script> 
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-sparklines/2.1.2/jquery.sparkline.min.js"></script>
</head>
<body>
    <div class="header">
        <h1>Рабочая таблица</h1>
        <div class="user-info">
            <span>Пользователь: {{ name }}</span>
            <button onclick="toggleDeviceStatusWindow()" class="status-button">Статусы Устройств</button>
            <a href="/logout" class="logout-button">Выход</a>
        </div>
    </div>

    <div class="content-wrapper">
        <!-- Основной контент -->
        <div class="main-content">
            <div class="statistics-container">
                <div class="date-selector">
                    <input id="datepicker" type="text" placeholder="Выберите дату" class="flatpickr-input" readonly="readonly">
                </div>
                <div class="stat-box">
                    <span class="stat-label">Генераций:</span>
                    <span class="stat-value" id="generateClicks">0</span>
                </div>
                <div class="stat-box">
                    <span class="stat-label">Упаковано:</span>
                    <span class="stat-value" id="packClicks">0</span>
                </div>
                <div class="stat-box">
                    <span class="stat-label">Записей "RPA":</span>
                    <span class="stat-value" id="rpaRecords">0</span>
                </div>
            </div>
            
            
            <div class="table-container">
                <table border="1" id="dataTable">
                    <thead>
                        <tr>
                            <th class="sortable" data-column="0">№</th>
                            <th class="sortable" data-column="1">Время отправки</th>
                            <th class="sortable" data-column="2">Номер</th>
                            <th class="sortable" data-column="3">Serial</th>
                            <th class="sortable" data-column="4">Графа</th>
                            <th class="sortable" data-column="5">Имя отправителя</th>
                            <th class="sortable" data-column="6">Время упаковки</th>
                            <th class="sortable" data-column="7">Тип АРМА</th>
                            <th data-column="8">Действие</th>
                        </tr>
                    </thead>
                    <tbody id="tableBody">
                        {% for row in table[1:] %}
                            <tr data-id="{{ row.id }}">
                                {% for cell in row %}
                                    <td class="{% if loop.index0 == 2 or loop.index0 ==3 or loop.index0 ==4 %}editable{% endif %}">{{ cell }}</td>
                                {% endfor %}
                            </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
            
            <div class="controls-wrapper">
                <div class="search-container">
                    <input type="text" id="searchInput" placeholder="Поиск по номеру...">
                    <button id="searchButton">Найти</button>
                </div>
                <div class="controls-container">
                    <div class="dl-control">
                        <div class="dl-row">
                            <label for="initial-dl">Начать DL:</label>
                            <input type="text" id="initial-dl" placeholder="Значение">
                        </div>
                        <button onclick="setInitialDL()">Установить</button>
                    </div>
                    <div class="filter-container">
                        <label>
                            <input type="checkbox" id="armaFilter">
                            Показать только RPA
                        </label>
                    </div>
                    <div class="export-container">
                        <button class="export-button">Экспорт в Excel</button>
                    </div>
                </div>
            </div>
            
            <div class="notification" id="notification"></div>
        </div>
    </div>

    <button onclick="toggleDeviceStatusWindow()" class="status-button">Статусы Устройств</button>

    <!-- Окно статусов устройств -->
    <div id="deviceStatusWindow" class="device-status-window">
        <div class="device-status-header ui-widget-header">
            <h4>Статус устройств</h4>
            <button onclick="toggleDeviceStatusWindow()" class="close-button">&times;</button>
        </div>
        <ul id="deviceStatusList">
            {% for device in devices %}
            <li>
                <span class="status-circle" id="{{ device.name }}-status"></span>
                <span class="device-name">{{ device.name }}</span>
                <span class="status-text" id="{{ device.name }}-status-text">
                    {% if device.status == 'processing' %}
                        Процесс идет
                    {% elif device.status == 'copying' %}
                        Копирование
                    {% elif device.status == 'sending_number' %}
                        Отправка номера
                    {% elif device.status == 'online' %}
                        Онлайн
                    {% elif device.status == 'no_internet' %}
                        Нет интернета
                    {% else %}
                        Нет соединения
                    {% endif %}
                </span>
                <!-- Элемент для Sparkline -->
                <span id="{{ device.name }}-sparkline" class="sparkline"></span>
            </li>
            {% endfor %}
        </ul>
    </div>

    <script>
        // Функция для отображения/скрытия мини-окна
        function toggleDeviceStatusWindow() {
            $('#deviceStatusWindow').toggleClass('show');
        }

        // Делает окно перетаскиваемым
        $( function() {
            $("#deviceStatusWindow").draggable({
                handle: ".device-status-header"
            });
        } );
    </script>

</body>
</html>
ASSISTANT
Конечно! Вам нужно обновить скрипт ARM3, чтобы он работал аналогично ARM1, то есть отправлял на сервер информацию о том, что процесс запущен, чтобы на графике sparkline отображался пик активности.

Вот обновленный код для ARM3, который включает в себя функцию `send_heartbeat` и глобальный флаг `is_processing`, аналогичный `is_copying` в ARM1:

```python
import os
import time
import RPi.GPIO as GPIO
import requests
import subprocess
from datetime import datetime
import threading

# URL сервера Flask
FLASK_SERVER_URL = "http://192.168.50.99:80"
worker_name = 'ARM3'

# Подключаем GPIO-пин для кнопки
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)

# Интервал для отправки heartbeat в секундах
HEARTBEAT_INTERVAL = 1

# Глобальный флаг для отслеживания процесса
is_processing = False

def check_internet(url='https://www.google.com/', timeout=5):
    try:
        _ = requests.get(url, timeout=timeout)
        return True
    except requests.RequestException:
        return False

def send_heartbeat():
    global is_processing
    while True:
        internet_up = check_internet()
        try:
            response = requests.post(
                f"{FLASK_SERVER_URL}/register_user",
                json={
                    'worker_name': worker_name,
                    'internet_up': internet_up,
                    'sending_number': is_processing  # <--- Используем 'sending_number' для отображения статуса
                },
                headers={'Content-Type': 'application/json'},
                timeout=10
            )
            if response.status_code == 200:
                response_data = response.json()
                if response_data.get('status') == 'success':
                    status = 'Онлайн' if internet_up else 'Нет интернета'
                    print(f"[{datetime.now()}] Успешная регистрация/обновление статуса на сервере: {status}")
                else:
                    error_message = response_data.get('message', 'Неизвестная ошибка')
                    print(f"[{datetime.now()}] Ошибка на сервере: {error_message}")
            else:
                print(f"[{datetime.now()}] Некорректный статус-код от сервера: {response.status_code}")
        except requests.exceptions.ConnectionError:
            print(f"[{datetime.now()}] Ошибка подключения к серверу. Сервер недоступен.")
        except Exception as e:
            print(f"[{datetime.now()}] Ошибка при регистрации: {e}")
        time.sleep(HEARTBEAT_INTERVAL)

def run_script():
    global is_processing
    print("Кнопка нажата! Запускаем скрипт...")
    is_processing = True  # Устанавливаем флаг перед запуском скрипта
    try:
        subprocess.call(["python", "/home/moon/Desktop/activationGlaz.py"])  # запускаем питоновский скрипт
    except Exception as e:
        print(f"Ошибка при запуске скрипта: {e}")
    finally:
        is_processing = False  # Сбрасываем флаг после завершения скрипта

# Запуск потока heartbeat
heartbeat_thread = threading.Thread(target=send_heartbeat, daemon=True)
heartbeat_thread.start()

try:
    while True:
        if GPIO.input(17) == False:  # если кнопка нажата
            run_script()
            # Предотвращаем многократные срабатывания при удержании кнопки
            while GPIO.input(17) == False:
                time.sleep(0.1)
        time.sleep(0.1)
except KeyboardInterrupt:
    GPIO.cleanup()  # очищаем GPIO порты
```

**Пояснения к изменениям:**

1. **Глобальный флаг `is_processing`:** Добавлен для отслеживания состояния выполнения скрипта `activationGlaz.py`. Он аналогичен `is_copying` в ARM1.

2. **Функция `send_heartbeat()`:** Теперь она регулярно отправляет на сервер информацию о состоянии устройства, включая флаг `is_processing`, используя ключ `'sending_number'`.

3. **Изменение в функции `run_script()`:**
   - Перед запуском скрипта `activationGlaz.py` устанавливаем `is_processing = True`.
   - После завершения скрипта (в блоке `finally`) сбрасываем `is_processing = False`.

4. **Регистрация на сервере:** Теперь при запуске скрипта ARM3 сразу запускается поток `heartbeat_thread`, который обновляет состояние на сервере, как это делает ARM1.

**Важно:** Убедитесь, что на стороне сервера ваш маршрут `/register_user` и обработчик `/get_device_status` правильно обрабатывают ключ `'sending_number'`. Судя по вашему коду сервера, они уже настроены для работы с этим флагом.

**На стороне фронтенда** (в JavaScript), ваш код уже имеет обработку состояния `'sending_number'`, поэтому после этих изменений на графике sparkline будет отображаться пик активности во время выполнения `activationGlaz.py`.

Теперь скрипт ARM3 будет работать по тому же принципу, что и ARM1, обеспечивая нужное вам отображение пиков на графике.