Project structure:

DarkDataset/
├── static/
│   ├── css/
│   │   ├── account-tab.css
│   │   ├── base.css
│   │   ├── home-tab.css
│   │   ├── hotbar.css
│   │   ├── loading.css
│   │   ├── login-tab.css
│   │   ├── tab.css
│   │   └── windows.css
│   ├── icons/
│   │   ├── h_icon.webp
│   │   ├── profile_icon.webp
│   │   └── s_icon.webp
│   ├── js/
│   │   ├── account_manager.js
│   │   ├── account_page.js
│   │   ├── hotbar-manager.js
│   │   ├── login.js
│   │   └── main.js
├── templates/
│   ├── components/
│   │   ├── account_tab.html
│   │   ├── home_tab.html
│   │   ├── hotbar.html
│   │   ├── login_tab.html
│   │   └── windows.html
│   ├── account.html
│   ├── index.html
│   └── login.html
├── DatabaseManager.py
├── V2_GenerateProjectShort.py
├── database.db
├── main.py
└── requirements.txt


Project files:

static/js/account_manager.js
```js
// account_manager.js

var AccountManager = (function() {

    // Приватные функции для управления куки
    function setCookie(name, value, days) {
        var expires = "";
        if (days) {
            var date = new Date();
            date.setTime(date.getTime() + (days*24*60*60*1000));
            expires = "; expires=" + date.toUTCString();
        }
        // Кодируем значение перед установкой
        document.cookie = name + "=" + encodeURIComponent(value || "")  + expires + "; path=/";
    }

    function getCookie(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(';');
        for(var i=0;i < ca.length;i++) {
            var c = ca[i].trim(); // Используем trim() для удаления лишних пробелов
            if (c.indexOf(nameEQ) === 0) {
                // Декодируем значение перед возвратом
                return decodeURIComponent(c.substring(nameEQ.length,c.length));
            }
        }
        return null;
    }

    function eraseCookie(name) {
        document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;';
    }

    // Публичные функции
    return {

        // Установка токена в куки
        setToken: function(token, days=7) {
            setCookie('auth_token', token, days);
        },

        // Получение токена из куки
        getToken: function() {
            return getCookie('auth_token');
        },

        // Выход из аккаунта
        logout: function(callback) {
            $.ajax({
                url: '/api/logout',
                type: 'POST',
                success: function(response){
                    eraseCookie('auth_token'); // Если куки доступны
                    if(callback && typeof callback === 'function') {
                        callback(null, response);
                    }
                    // Перенаправляем пользователя на страницу входа или главную страницу
                    window.location.href = '/login';
                },
                error: function(xhr){
                    if(callback && typeof callback === 'function') {
                        if(xhr.responseJSON && xhr.responseJSON.message){
                            callback(xhr.responseJSON.message, null);
                        } else {
                            callback("Произошла ошибка при выходе из аккаунта.", null);
                        }
                    }
                }
            });
        },

        // Получение информации об аккаунте
        getAccountInfo: function(callback) {
            $.ajax({
                url: '/api/account',
                type: 'GET',
                // Куки отправляются автоматически, так как тот же домен
                // Если ваш API находится на другом домене, добавьте withCredentials: true
                success: function(response){
                    if(callback && typeof callback === 'function') {
                        callback(null, response);
                    }
                },
                error: function(xhr){
                    if(callback && typeof callback === 'function') {
                        if(xhr.responseJSON && xhr.responseJSON.message){
                            callback(xhr.responseJSON.message, null);
                        } else {
                            callback("Произошла ошибка при получении информации об аккаунте.", null);
                        }
                    }
                }
            });
        },

        // Проверка, авторизован ли пользователь
        isAuthenticated: function() {
            return !!this.getToken();
        }

    };

})();
```


static/js/account_page.js
```js
$(document).ready(function() {
    // Получаем информацию об аккаунте при загрузке страницы
    AccountManager.getAccountInfo(function(error, data){
        if(error){
            // Опционально: перенаправить на страницу входа
            window.location.href = '/login';
        } else {
            $('#account-title').text(data.username);
            $('#account-title').removeClass("loading")
        }
    });

    // Обработчик кнопки выхода
    $('#logout-btn').on('click', function(){
        AccountManager.logout();
    });
});
```


static/js/hotbar-manager.js
```js
$(document).ready(function() {
    $('#home-btn').on('click', function(){
        window.location.href = '/main';
    });

    $('#profile-btn').on('click', function(){
        window.location.href = '/account';
    });
});
```


static/js/login.js
```js
// login.js

$(document).ready(function() {

    // Обработчик кнопки логина
    $('#login-btn').on('click', function(e){
        e.preventDefault(); // Предотвращаем перезагрузку страницы

        var username = $('.login-field').val().trim();
        var password = $('.password-field').val().trim();

        if(username === "" || password === "") {
            alert("Пожалуйста, введите логин и пароль.");
            return;
        }

        $.ajax({
            url: '/api/login',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({username: username, password: password}),
            success: function(response){
                if(response.token){
                    // Используем AccountManager для сохранения токена
                    AccountManager.setToken(response.token, 7);
                    window.location.href = '/main';
                } else {
                    alert("Токен не получен. Повторите попытку.");
                }
            },
            error: function(xhr){
                if(xhr.responseJSON && xhr.responseJSON.message){
                    alert(xhr.responseJSON.message);
                } else {
                    alert("Произошла ошибка. Пожалуйста, попробуйте позже.");
                }
            }
        });
    });

    // Обработчик кнопки регистрации
    $('#register-btn').on('click', function(e){
        e.preventDefault(); // Предотвращаем перезагрузку страницы

        var username = $('.login-field').val().trim();
        var password = $('.password-field').val().trim();

        if(username === "" || password === "") {
            alert("Пожалуйста, введите логин и пароль.");
            return;
        }

        $.ajax({
            url: '/api/register',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({username: username, password: password}),
            success: function(response){
                alert(response.message);
                // Очистка полей и предложение войти
                $('.login-field').val('');
                $('.password-field').val('');
            },
            error: function(xhr){
                if(xhr.responseJSON && xhr.responseJSON.message){
                    alert(xhr.responseJSON.message);
                } else {
                    alert("Произошла ошибка. Пожалуйста, попробуйте позже.");
                }
            }
        });
    });

});
```


static/js/main.js
```js
$(document).ready(function() {
    // Получаем информацию об аккаунте при загрузке страницы
    AccountManager.getAccountInfo(function(error, data){
        if(error){
            // Опционально: перенаправить на страницу входа
            window.location.href = '/login';
        } else {
            $('#account-title').text("Yoy, " + data.username + "!");
            $('#account-title').removeClass("loading")
        }
    });

    // Обработчик кнопки выхода
    $('#logout-btn').on('click', function(){
        AccountManager.logout();
    });
});
```


templates/account.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DarkDataset</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto+Slab:wght@100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="../static/css/loading.css">
    <link rel="stylesheet" href="../static/css/base.css">
    <link rel="stylesheet" href="../static/css/hotbar.css">

    <link rel="stylesheet" href="../static/css/tab.css">
    <link rel="stylesheet" href="../static/css/account-tab.css">
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="../static/js/account_manager.js"></script>
<script src="../static/js/account_page.js"></script>

<div class="screen">
    {% include "components/hotbar.html" %}
    {% include "components/account_tab.html" %}
    <!--{% include "components/home_tab.html" %}-->
    <!--{% include "components/windows.html" %}-->
</div>
</body>
</html>
```


templates/index.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DarkDataset</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto+Slab:wght@100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="../static/css/loading.css">
    <link rel="stylesheet" href="../static/css/base.css">
    <link rel="stylesheet" href="../static/css/hotbar.css">
    <link rel="stylesheet" href="../static/css/windows.css">

    <link rel="stylesheet" href="../static/css/tab.css">
    <link rel="stylesheet" href="../static/css/home-tab.css">
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="../static/js/account_manager.js"></script>
<script src="../static/js/main.js"></script>

<div class="screen">
    {% include "components/hotbar.html" %}
    {% include "components/home_tab.html" %}
    <!--{% include "components/windows.html" %}-->
</div>
</body>
</html>
```


templates/login.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>DarkDataset</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto+Slab:wght@100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <link rel="stylesheet" href="../static/css/loading.css">
    <link rel="stylesheet" href="../static/css/base.css">

    <link rel="stylesheet" href="../static/css/tab.css">
    <link rel="stylesheet" href="../static/css/login-tab.css">
</head>
<body>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="../static/js/account_manager.js"></script>
<script src="../static/js/login.js"></script>
<div class="screen">
    {% include "components/login_tab.html" %}
    <!--{% include "components/home_tab.html" %}-->
    <!--{% include "components/windows.html" %}-->
</div>
</body>
</html>
```


templates/components/account_tab.html
```html
<div class="tab account-tab">
    <div class="account-tile">
        <div class="account-name loading" id="account-title">.....</div>
        <div class="account-details-container">
            <div class="account-projects-in">0</div>
            <div class="account-projects-host">0</div>
        </div>
    </div>
    <div class="account-actions">
        <button id="logout-btn">Logout</button>
    </div>
</div>
```


templates/components/home_tab.html
```html
<div class="tab home-tab">
    <div class="account-title loading" id="account-title">Yoy, ...!</div>
    <div class="tab-title">Your projects:</div>
    <div class="projects-container">

        <div class="project-container">
            <div class="project-title">Test project</div>
            <button class="project-action-btn continue-btn">Continue</button>
            <button class="project-action-btn edit-btn">Edit</button>
        </div>

    </div>
</div>
```


templates/components/hotbar.html
```html
<script src="../static/js/hotbar-manager.js"></script>
<div class="hotbar">
    <button class="hotbar-button" id="home-btn">
        <img src="../../static/icons/h_icon.webp">
    </button>
    <button class="hotbar-button" id="settings-btn">
        <img src="../../static/icons/s_icon.webp">
    </button>
    <button class="hotbar-button" id="profile-btn">
        <img src="../../static/icons/profile_icon.webp">
    </button>
</div>
```


templates/components/login_tab.html
```html
<div class="tab login-tab">
    <div class="tab-title">Login or register</div>
    <div class="login-boxes-container">
        <div class="login-box">
            <input class="login-field" placeholder="login">
            <input type="password" class="password-field" placeholder="password">
        </div>
        <div class="login-buttons">
            <button class="login-btn" id="login-btn">Login</button>
            or
            <button class="login-btn" id="register-btn">Register</button>
        </div>
    </div>
</div>
```


templates/components/windows.html
```html
<div class="windows-container">
    <div class="window">
        <div class="window-controls-container">
            <div class="window-title">Home</div>
            <div class="window-controls">
                <button class="close-btn"></button>
            </div>
        </div>
    </div>
</div>
```


main.py
```py
from flask import Flask, request, jsonify, render_template, make_response
from functools import wraps
import jwt
import datetime

from DatabaseManager import DatabaseManager

app = Flask(__name__)

# Секретный ключ для JWT
SECRET_KEY = 'your_secret_key'  # Замените на ваш секретный ключ
app.config['SECRET_KEY'] = SECRET_KEY

db_manager = DatabaseManager()

# Декоратор для защиты маршрутов, требующих аутентификации
def token_required(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        token = request.cookies.get('auth_token')

        if not token:
            return jsonify({'message': 'Токен отсутствует! Пожалуйста, войдите в систему.'}), 401

        try:
            data = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            current_user = db_manager.get_user_by_username(data['username'])
            if not current_user:
                raise Exception('Пользователь не найден')
        except jwt.ExpiredSignatureError:
            return jsonify({'message': 'Срок действия токена истек. Пожалуйста, войдите в систему снова.'}), 401
        except Exception as e:
            return jsonify({'message': 'Токен недействителен!'}), 401

        return f(current_user, *args, **kwargs)
    return decorated

# Эндпоинты API

# Регистрация пользователя
@app.route('/api/register', methods=['POST'])
def register():
    data = request.json
    username = data.get('username')
    password = data.get('password')
    if username and password:
        success = db_manager.create_account(username, password)
        if success:
            return jsonify({'message': 'Аккаунт успешно создан'}), 201
        else:
            return jsonify({'message': 'Пользователь с таким именем уже существует'}), 400
    else:
        return jsonify({'message': 'Требуются логин и пароль'}), 400

# Аутентификация пользователя
@app.route('/api/login', methods=['POST'])
def login_api():
    data = request.json
    username = data.get('username')
    password = data.get('password')
    if username and password:
        valid = db_manager.verify_account(username, password)
        if valid:
            # Генерация JWT токена
            token = jwt.encode({
                'username': username,
                'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)
            }, app.config['SECRET_KEY'], algorithm='HS256')

            # Создаем ответ и устанавливаем куки с токеном
            response = make_response(jsonify({'message': 'Вход выполнен успешно', 'token': token}))
            response.set_cookie('auth_token', token, httponly=True, samesite='Strict')  # Можно добавить 'secure=True' при использовании HTTPS
            return response, 200
        else:
            return jsonify({'message': 'Неверные учетные данные'}), 401
    else:
        return jsonify({'message': 'Требуются логин и пароль'}), 400

# Выход из аккаунта
@app.route('/api/logout', methods=['POST'])
def logout_api():
    response = make_response(jsonify({'message': 'Выход выполнен успешно'}))
    response.set_cookie('auth_token', '', expires=0)
    return response, 200

# Создание нового проекта
@app.route('/api/projects', methods=['POST'])
@token_required
def create_project(current_user):
    data = request.json
    author_id = current_user['id']
    user_ids = data.get('user_ids', [])
    project_data = data.get('data', {})
    project_id = db_manager.create_project(author_id, user_ids, project_data)
    return jsonify({'message': 'Проект создан', 'project_id': project_id}), 201

# Получение информации о проекте
@app.route('/api/projects/<int:project_id>', methods=['GET'])
@token_required
def get_project(current_user, project_id):
    project = db_manager.get_project(project_id)
    if project:
        # Проверка, имеет ли текущий пользователь доступ к проекту
        if project['author_id'] != current_user['id'] and current_user['id'] not in project['user_ids']:
            return jsonify({'message': 'Доступ запрещен'}), 403
        return jsonify(project), 200
    else:
        return jsonify({'message': 'Проект не найден'}), 404

# Обновление проекта
@app.route('/api/projects/<int:project_id>', methods=['PUT'])
@token_required
def update_project(current_user, project_id):
    project = db_manager.get_project(project_id)
    if not project:
        return jsonify({'message': 'Проект не найден'}), 404

    # Проверка, является ли текущий пользователь автором проекта
    if project['author_id'] != current_user['id']:
        return jsonify({'message': 'Только автор проекта может его обновлять'}), 403

    data = request.json
    user_ids = data.get('user_ids')
    project_data = data.get('data')
    db_manager.update_project(project_id, user_ids=user_ids, data=project_data)
    return jsonify({'message': 'Проект обновлен'}), 200

# Удаление проекта
@app.route('/api/projects/<int:project_id>', methods=['DELETE'])
@token_required
def delete_project(current_user, project_id):
    project = db_manager.get_project(project_id)
    if not project:
        return jsonify({'message': 'Проект не найден'}), 404

    # Проверка, является ли текущий пользователь автором проекта
    if project['author_id'] != current_user['id']:
        return jsonify({'message': 'Только автор проекта может его удалять'}), 403

    db_manager.delete_project(project_id)
    return jsonify({'message': 'Проект удален'}), 200

# Эндпоинт для получения информации об аккаунте
@app.route('/api/account', methods=['GET'])
@token_required
def get_account_info(current_user):
    print("get_account_info")
    return jsonify({
        'id': current_user['id'],
        'username': current_user['username']
        # Добавьте другие необходимые поля
    }), 200



# Главная страница
@app.route('/main')
def index():
    return render_template('index.html')

# Страница логина и регистрации
@app.route('/login')
def login():
    return render_template('login.html')

# Страница аккаунта
@app.route('/account')
def account():
    return render_template('account.html')


if __name__ == '__main__':
    app.run(debug=True, host="0.0.0.0", port=8081)
```


DatabaseManager.py
```py
import json
import os
import sqlite3

from werkzeug.security import generate_password_hash, check_password_hash

DATABASE = 'database.db'

class DatabaseManager:
    def __init__(self, db_path=DATABASE):
        # Проверяем, существует ли база данных, если нет - создаем
        if not os.path.exists(DATABASE):
            open(DATABASE, 'w').close()

        self.db_path = db_path
        self.create_tables()

    def create_connection(self):
        conn = sqlite3.connect(self.db_path)
        # Для возврата строк как словарей
        conn.row_factory = sqlite3.Row
        return conn

    def create_tables(self):
        conn = self.create_connection()
        cursor = conn.cursor()

        # Создаем таблицу accounts
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS accounts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                username TEXT UNIQUE NOT NULL,
                password_hash TEXT NOT NULL
            )
        ''')

        # Создаем таблицу projects
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS projects (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                author_id INTEGER NOT NULL,
                user_ids TEXT,
                data TEXT,
                FOREIGN KEY (author_id) REFERENCES accounts(id)
            )
        ''')

        conn.commit()
        conn.close()

    # Методы для работы с аккаунтами
    def create_account(self, username, password):
        password_hash = generate_password_hash(password)
        conn = self.create_connection()
        cursor = conn.cursor()
        try:
            cursor.execute('''
                INSERT INTO accounts (username, password_hash) VALUES (?, ?)
            ''', (username, password_hash))
            conn.commit()
            return True
        except sqlite3.IntegrityError:
            # Пользователь с таким именем уже существует
            return False
        finally:
            conn.close()

    def get_account(self, username):
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            SELECT * FROM accounts WHERE username = ?
        ''', (username,))
        account = cursor.fetchone()
        conn.close()
        return account

    def get_user_by_id(self, user_id):
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            SELECT * FROM accounts WHERE id = ?
        ''', (user_id,))
        account = cursor.fetchone()
        conn.close()
        return account

    def get_user_by_username(self, username):
        """
        Возвращает пользователя как словарь по имени пользователя.
        """
        account = self.get_account(username)
        if account:
            return {
                'id': account['id'],
                'username': account['username']
                # Добавьте другие поля по необходимости
            }
        return None

    def verify_account(self, username, password):
        account = self.get_account(username)
        if account:
            return check_password_hash(account['password_hash'], password)
        return False

    # Методы для работы с проектами
    def create_project(self, author_id, user_ids, data):
        user_ids_json = json.dumps(user_ids)
        data_json = json.dumps(data)
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            INSERT INTO projects (author_id, user_ids, data) VALUES (?, ?, ?)
        ''', (author_id, user_ids_json, data_json))
        conn.commit()
        project_id = cursor.lastrowid
        conn.close()
        return project_id

    def get_project(self, project_id):
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            SELECT * FROM projects WHERE id = ?
        ''', (project_id,))
        project = cursor.fetchone()
        conn.close()
        if project:
            project_dict = {
                'id': project['id'],
                'author_id': project['author_id'],
                'user_ids': json.loads(project['user_ids']) if project['user_ids'] else [],
                'data': json.loads(project['data']) if project['data'] else {}
            }
            return project_dict
        return None

    def update_project(self, project_id, user_ids=None, data=None):
        conn = self.create_connection()
        cursor = conn.cursor()
        if user_ids is not None:
            user_ids_json = json.dumps(user_ids)
            cursor.execute('''
                UPDATE projects SET user_ids = ? WHERE id = ?
            ''', (user_ids_json, project_id))
        if data is not None:
            data_json = json.dumps(data)
            cursor.execute('''
                UPDATE projects SET data = ? WHERE id = ?
            ''', (data_json, project_id))
        conn.commit()
        conn.close()

    def delete_project(self, project_id):
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            DELETE FROM projects WHERE id = ?
        ''', (project_id,))
        conn.commit()
        conn.close()
```


Привет, сделай так, чтобы на главной странице отображались бы реальные проекты пользователя, в которых он участвует и которыми владеет(функционал добавь в main.js).

На главной странице создай кнопку "New project", которая будет переправлять на `/new_project`.
Там будет форма для создания проекта - его название, добавленные изображения(туда в проект можно будет загружать изображения, большое количество), список маркировочных классов, цвет для каждого класса и прочее необходимое. И кнопка "Create", которая отобразит снизу прогрессбара с толщиной 2px белого цвета, показывающая процент загрузки изображений на сервер.
Добавь возможность указать просто абсолютный путь к папке с изображениями на самом сервере(я понимаю, что это не безопасно, но так надо. Это не обязательный режим).

Также, создай отдельную страницу `/workspace`, в которой и будет содержимое проекта при нажатии на кнопку `.continue-btn`. Как это должно быть:
Название проекта, имя пользователя.
Снизу основной контейнер. На этой странице сделай workspace.js файл, чтобы он был на ней главным.
В основном контейнере будет неразмеченное изображение из проекта.
Слева будет столбец с доступными классами в проекте.
Функционал разметки не добавляй, я это сделаю сам.
Добавь кнопку "Next", которая будет отправлять на сервере результат нашей маркировки изображения(но т.к. этот механизм пока не реализован, просто создай затычку. Это должно быть в формате YOLO).
Эндпоинт сервера должен обработать этот запрос для этого изображения и создать файл с разметкой в формате YOLO.
ВАЖНО, чтобы одно и то же изображение не попало к пользователям одновременно. Каждое изображение должно быть обработано только один раз.

Для получения текущего изображения для обработки используй API `/api/get_image` с информацией о проекте. Он должен вернуть как информацию о классах, изображении, так и порядковый номер изображения а также общее количество изображений в проекте.
Эту информацию нужно использовать, чтобы снизу страницы добавить прогрессбар, показывающий общий прогресс проекта.


Если ты не понял - это проект для создания датасета для модели сегментации изображений.

Пожалуйста, полностью выполни то, что я тебе написал, чтобы это работало в том же стиле, как сейчас написан проект