Project structure:

DarkDataset/
├── projects/
│   ├── 1/
│   │   ├── Test2/
│   │   │   └── VADIK.png
│   │   ├── aasddas/
│   │   │   ├── 109к.png
│   │   │   ├── AvatarV6_NEW.png
│   │   │   ├── JpPzA17TR8KgpoJcbyPKvw.jfif
│   │   │   ├── VADIM_ART3_HD.jpg
│   │   │   ├── VADIM_ART3_HD.txt
│   │   │   ├── anubis_map.jpg
│   │   │   ├── arsen_markaryan.png
│   │   │   ├── b2_icon.png
│   │   │   ├── black-road-texture_1160-806.avif
│   │   │   ├── cat-7021300_960_720.jpg
│   │   │   ├── cement-texture_1194-6422.png
│   │   │   ├── javascript_spizjenno.png
│   │   │   ├── output.jpg
│   │   │   ├── photo-of-a-tabby-cat-with-green-eyes-lying-on-a-br-JpPzA17TR8KgpoJcbyPKvw-1aA_wBBvSterO-4aPRRSRg.jpeg
│   │   │   ├── photo_2024-10-06_04-09-32.jpg
│   │   │   ├── photo_2024-10-06_04-09-32.txt
│   │   │   ├── sXxq7YSg.jpg
│   │   │   ├── v2_icon.png
│   │   │   ├── why_dedal_image.png
│   │   │   └── Вадим депутат.jpg
│   │   ├── test1/
│   │   │   ├── VADIM_ART3_HD.jpg
│   │   │   ├── arsen_markaryan.png
│   │   │   ├── arsen_markaryan.txt
│   │   │   ├── output.jpg
│   │   │   ├── output.txt
│   │   │   ├── sXxq7YSg.jpg
│   │   │   ├── sXxq7YSg.txt
│   │   │   ├── Вадим депутат.jpg
│   │   │   └── Вадим депутат.txt
├── static/
│   ├── css/
│   │   ├── account-tab.css
│   │   ├── base.css
│   │   ├── home-tab.css
│   │   ├── hotbar.css
│   │   ├── loading.css
│   │   ├── login-tab.css
│   │   ├── new_project.css
│   │   ├── tab.css
│   │   ├── windows.css
│   │   └── workspace.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
│   │   ├── new_project.js
│   │   └── workspace.js
├── templates/
│   ├── components/
│   │   ├── account_tab.html
│   │   ├── home_tab.html
│   │   ├── hotbar.html
│   │   ├── login_tab.html
│   │   └── windows.html
│   ├── account.html
│   ├── index.html
│   ├── login.html
│   ├── new_project.html
│   └── workspace.html
├── DatabaseManager.py
├── V2_GenerateProjectShort.py
├── database.db
├── main.py
└── requirements.txt


Project files:

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)
            )
        ''')

        # Создаем таблицу project_images
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS project_images (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                project_id INTEGER NOT NULL,
                image_path TEXT NOT NULL,
                status TEXT NOT NULL DEFAULT 'unannotated',
                assigned_to INTEGER,
                FOREIGN KEY (project_id) REFERENCES projects(id),
                FOREIGN KEY (assigned_to) 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, images):
        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))
        project_id = cursor.lastrowid

        # Добавляем изображения в таблицу project_images
        project_images = [(project_id, image_path) for image_path in images]
        cursor.executemany('''
            INSERT INTO project_images (project_id, image_path) VALUES (?, ?)
        ''', project_images)

        conn.commit()
        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 MAIN MAIN MAIN

    def get_projects_by_user(self, user_id):
        conn = self.create_connection()
        cursor = conn.cursor()

        # Получаем проекты, где пользователь является автором
        cursor.execute('''
            SELECT * FROM projects WHERE author_id = ?
        ''', (user_id,))
        authored_projects = cursor.fetchall()

        # Получаем проекты, где пользователь является участником
        cursor.execute('''
            SELECT * FROM projects WHERE user_ids IS NOT NULL
        ''')
        all_projects = cursor.fetchall()

        participating_projects = []
        for project in all_projects:
            user_ids = json.loads(project['user_ids']) if project['user_ids'] else []
            if user_id in user_ids:
                participating_projects.append(project)

        # Объединяем и исключаем дубли
        projects = {project['id']: {
            '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 {}
        } for project in authored_projects + participating_projects}

        conn.close()
        return list(projects.values())

    def get_next_image_for_annotation(self, project_id, user_id):
        conn = self.create_connection()
        cursor = conn.cursor()

        # Пытаемся получить изображение, назначенное пользователю
        cursor.execute('''
            SELECT * FROM project_images WHERE project_id = ? AND status = 'unannotated' AND assigned_to = ?
        ''', (project_id, user_id))
        image = cursor.fetchone()

        # Если нет, назначаем новое изображение пользователю
        if not image:
            cursor.execute('''
                SELECT * FROM project_images WHERE project_id = ? AND status = 'unannotated' AND (assigned_to IS NULL OR assigned_to = ?)
            ''', (project_id, None))
            image = cursor.fetchone()
            if image:
                cursor.execute('''
                    UPDATE project_images SET assigned_to = ? WHERE id = ?
                ''', (user_id, image['id']))
                conn.commit()
        conn.close()
        return image

    def get_project_progress(self, project_id):
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            SELECT COUNT(*) FROM project_images WHERE project_id = ?
        ''', (project_id,))
        total_images = cursor.fetchone()[0]

        cursor.execute('''
            SELECT COUNT(*) FROM project_images WHERE project_id = ? AND status = 'annotated'
        ''', (project_id,))
        annotated_images = cursor.fetchone()[0]
        conn.close()
        return total_images, annotated_images

    def get_project_image(self, image_id):
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            SELECT * FROM project_images WHERE id = ?
        ''', (image_id,))
        image = cursor.fetchone()
        conn.close()
        return image

    def update_image_status(self, image_id, status):
        conn = self.create_connection()
        cursor = conn.cursor()
        cursor.execute('''
            UPDATE project_images SET status = ? WHERE id = ?
        ''', (status, image_id))
        conn.commit()
        conn.close()
```


main.py
```py
import json
import os
import shutil

from flask import Flask, request, jsonify, render_template, make_response, send_file
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/<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')


# MAIN MAIN MAIN MAIN

@app.route('/new_project')
@token_required
def new_project(current_user):
    return render_template('new_project.html')

@app.route('/workspace')
@token_required
def workspace(current_user):
    project_id = request.args.get('project_id', type=int)
    if not project_id:
        return "Project ID is required", 400
    return render_template('workspace.html', project_id=project_id)

# Получение списка проектов пользователя
# Создание нового проекта
@app.route('/api/projects', methods=['POST'])
@token_required
def create_project(current_user):
    # Получаем данные из формы
    title = request.form.get('title')
    path = request.form.get('path')
    classes_json = request.form.get('classes')
    images = request.files.getlist('images')

    if not title:
        return jsonify({'message': 'Title is required'}), 400

    if not images and not path:
        return jsonify({'message': 'Please upload images or specify a path'}), 400

    # Парсируем классы
    try:
        classes = json.loads(classes_json)
    except:
        return jsonify({'message': 'Invalid classes data'}), 400

    # Обрабатываем изображения
    image_paths = []

    # Директория для сохранения изображений проекта
    project_dir = os.path.join('projects', str(current_user['id']), title)
    os.makedirs(project_dir, exist_ok=True)

    # Сохраняем загруженные изображения
    for image in images:
        filename = image.filename
        image_path = os.path.join(project_dir, filename)
        image.save(image_path)
        image_paths.append(image_path)

    # Если указан путь, используем изображения из него
    if path:
        if os.path.exists(path) and os.path.isdir(path):
            for root, dirs, files in os.walk(path):
                for file in files:
                    if file.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
                        file_path = os.path.join(root, file)
                        dest_path = os.path.join(project_dir, file)
                        shutil.copy2(file_path, dest_path)
                        image_paths.append(dest_path)
        else:
            return jsonify({'message': 'Invalid path specified'}), 400

    # Подготавливаем данные проекта
    project_data = {
        'title': title,
        'classes': classes,
        # Другие необходимые данные
    }

    # Создаем проект в базе данных
    author_id = current_user['id']
    user_ids = []  # Пока что только автор
    project_id = db_manager.create_project(author_id, user_ids, project_data, image_paths)
    return jsonify({'message': 'Проект создан', 'project_id': project_id}), 201

######
# Получение изображения для аннотации
@app.route('/api/get_image', methods=['GET'])
@token_required
def get_image(current_user):
    project_id = request.args.get('project_id', type=int)
    if not project_id:
        return jsonify({'message': 'Project ID is required'}), 400

    project = db_manager.get_project(project_id)
    if not project:
        return jsonify({'message': 'Project not found'}), 404

    # Проверяем доступ пользователя к проекту
    if project['author_id'] != current_user['id'] and current_user['id'] not in project['user_ids']:
        return jsonify({'message': 'Access denied'}), 403

    # Получаем следующее неразмеченное изображение
    image_info = db_manager.get_next_image_for_annotation(project_id, current_user['id'])

    if not image_info:
        return jsonify({'message': 'No more images to annotate'}), 200

    # Получаем прогресс проекта
    total_images, annotated_images = db_manager.get_project_progress(project_id)
    progress = (annotated_images / total_images) * 100 if total_images > 0 else 0

    image_url = '/api/project_image/{}'.format(image_info['id'])

    response_data = {
        'image_id': image_info['id'],
        'image_url': image_url,
        'classes': project['data']['classes'],
        'current': annotated_images + 1,  # Предполагая, что мы собираемся аннотировать это изображение
        'total': total_images,
        'progress': progress,
        'project_title': project['data']['title']
    }
    return jsonify(response_data), 200

def convert_to_yolo_format(annotation):
    yolo_annotations = []
    for ann in annotation:
        class_id = ann['class_id']
        x_center = ann['x_center']
        y_center = ann['y_center']
        width = ann['width']
        height = ann['height']
        yolo_annotations.append(f"{class_id} {x_center} {y_center} {width} {height}")
    return '\n'.join(yolo_annotations)

# Отправка аннотации для изображения
@app.route('/api/submit_annotation', methods=['POST'])
@token_required
def submit_annotation(current_user):
    data = request.json
    project_id = data.get('project_id')
    image_id = data.get('image_id')
    annotation = data.get('annotation')

    if not project_id or not image_id or annotation is None:
        return jsonify({'message': 'project_id, image_id and annotation are required'}), 400

    project = db_manager.get_project(project_id)
    if not project:
        return jsonify({'message': 'Project not found'}), 404

    # Проверяем доступ пользователя к проекту
    if project['author_id'] != current_user['id'] and current_user['id'] not in project['user_ids']:
        return jsonify({'message': 'Access denied'}), 403

    # Сохраняем аннотацию в формате YOLO
    image_info = db_manager.get_project_image(image_id)
    if not image_info:
        return jsonify({'message': 'Image not found'}), 404

    # Проверяем, что изображение назначено текущему пользователю
    if image_info['assigned_to'] != current_user['id']:
        return jsonify({'message': 'This image is not assigned to you'}), 403

    # Путь к изображению
    image_path = image_info['image_path']
    annotation_path = os.path.splitext(image_path)[0] + '.txt'

    # Преобразуем аннотацию в формат YOLO
    yolo_annotation = convert_to_yolo_format(annotation)

    with open(annotation_path, 'w') as f:
        f.write(yolo_annotation)

    # Обновляем статус изображения на "annotated"
    db_manager.update_image_status(image_id, 'annotated')

    return jsonify({'message': 'Annotation submitted'}), 200

# Сервис для безопасной выдачи изображений
@app.route('/api/project_image/<int:image_id>')
@token_required
def project_image(current_user, image_id):
    image_info = db_manager.get_project_image(image_id)
    if not image_info:
        return "Image not found", 404

    project = db_manager.get_project(image_info['project_id'])
    if not project:
        return "Project not found", 404

    # Проверяем доступ пользователя к проекту
    if project['author_id'] != current_user['id'] and current_user['id'] not in project['user_ids']:
        return "Access denied", 403

    return send_file(image_info['image_path'])

# Получение списка проектов пользователя
@app.route('/api/projects', methods=['GET'])
@token_required
def get_user_projects(current_user):
    projects = db_manager.get_projects_by_user(current_user['id'])
    return jsonify({'projects': projects}), 200
######



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

```


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("Hey, " + data.username + "!");
            $('#account-title').removeClass("loading")
        }
    });

    // Получаем проекты пользователя
    $.ajax({
        url: '/api/projects',
        type: 'GET',
        success: function(response){
            var projectsContainer = $('.projects-container');
            projectsContainer.empty(); // Очистить контейнер проектов

            if(response.projects.length === 0){
                projectsContainer.append('<div class="no-projects">У вас нет проектов.</div>');
            } else {
                response.projects.forEach(function(project){
                    var projectElement = `
                        <div class="project-container">
                            <div class="project-title">${project.data.title || 'Без названия'}</div>
                            <button class="project-action-btn continue-btn" data-project-id="${project.id}">Continue</button>
                            <!--<button class="project-action-btn edit-btn" data-project-id="${project.id}">Edit</button>-->
                        </div>
                    `;
                    projectsContainer.append(projectElement);
                });

                // Обработчик кнопок "Continue"
                $('.continue-btn').on('click', function(){
                    var projectId = $(this).data('project-id');
                    window.location.href = '/workspace?project_id=' + projectId;
                });

                // Обработчик кнопок "Edit"
                /*$('.edit-btn').on('click', function(){
                    var projectId = $(this).data('project-id');
                    window.location.href = '/edit_project?project_id=' + projectId;
                });*/
            }
        },
        error: function(xhr){
            console.error("Ошибка при получении проектов пользователя.");
            if(xhr.responseJSON && xhr.responseJSON.message){
                alert(xhr.responseJSON.message);
            } else {
                alert("Произошла ошибка. Пожалуйста, попробуйте позже.");
            }
        }
    });

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

    // Обработчик кнопки "New Project"
    $('#new-project-btn').on('click', function(){
        window.location.href = '/new_project';
    });
});
```


static/js/new_project.js
```js
$(document).ready(function() {
    // Обработчик добавления нового класса
    $('#add-class-btn').on('click', function(){
        var classItem = `
            <div class="class-item">
                <input type="text" name="class-name" placeholder="Class Name">
                <input type="color" name="class-color" value="#ff0000">
                <button type="button" class="remove-class-btn">Remove</button>
            </div>
        `;
        $('#classes-container').append(classItem);
    });

    // Обработчик удаления класса
    $(document).on('click', '.remove-class-btn', function(){
        $(this).closest('.class-item').remove();
    });

    // Обработчик формы создания проекта
    $('#new-project-form').on('submit', function(e){
        e.preventDefault();

        var title = $('#project-title').val().trim();
        var images = $('#project-images')[0].files;
        var path = $('#project-path').val().trim();

        if(title === ""){
            alert("Please enter a project title.");
            return;
        }

        if(images.length === 0 && path === ""){
            alert("Please upload images or specify a path.");
            return;
        }

        // Собираем классы
        var classes = [];
        $('.class-item').each(function(){
            var className = $(this).find('input[name="class-name"]').val().trim();
            var classColor = $(this).find('input[name="class-color"]').val();
            if(className !== ""){
                classes.push({
                    name: className,
                    color: classColor
                });
            }
        });

        if(classes.length === 0){
            alert("Please add at least one class.");
            return;
        }

        var formData = new FormData();
        formData.append('title', title);
        formData.append('path', path);
        formData.append('classes', JSON.stringify(classes));

        // Добавляем файлы изображений
        for(var i = 0; i < images.length; i++){
            formData.append('images', images[i]);
        }

        // Отправка запроса с прогрессом загрузки
        $.ajax({
            xhr: function() {
                var xhr = new window.XMLHttpRequest();
                xhr.upload.addEventListener("progress", function(evt){
                    if (evt.lengthComputable) {
                        var percentComplete = (evt.loaded / evt.total) * 100;
                        $('#upload-progress').show();
                        $('#progress-bar').val(percentComplete);
                        $('#progress-text').text(Math.round(percentComplete) + '% uploaded');
                    }
                }, false);
                return xhr;
            },
            url: '/api/projects',
            type: 'POST',
            data: formData,
            contentType: false,
            processData: false,
            success: function(response){
                alert("Project created successfully!");
                window.location.href = '/main';
            },
            error: function(xhr){
                if(xhr.responseJSON && xhr.responseJSON.message){
                    alert(xhr.responseJSON.message);
                } else {
                    alert("An error occurred. Please try again later.");
                }
            }
        });
    });
});
```


static/js/workspace.js
```js
$(document).ready(function () {
    var urlParams = new URLSearchParams(window.location.search);
    var projectId = urlParams.get('project_id');

    if (!projectId) {
        alert("Project ID is required.");
        window.location.href = '/main';
        return;
    }

    var currentImageId = null;
    var classes = [];
    var selectedClass = null;
    var isDrawing = false;
    var startX, startY;
    var annotationMode = false;

    var $imageContainer = $('.image-container');
    var imageContainerOffset;

    // Fetch account info
    AccountManager.getAccountInfo(function (error, data) {
        if (error) {
            window.location.href = '/login';
        } else {
            $('#username').text(data.username);
        }
    });

    // Fetch the image and project info
    $.ajax({
        url: '/api/get_image',
        type: 'GET',
        data: { project_id: projectId },
        success: function (response) {
            if (response.message) {
                alert(response.message);
                window.location.href = '/main';
                return;
            }

            $('#project-title').text(response.project_title || 'Project');
            $('#project-progress').val(response.progress);
            $('#progress-text').text('Image ' + response.current + ' of ' + response.total + ' (' + Math.round(response.progress) + '%)');

            // Set up classes
            classes = response.classes;
            var $classList = $('#class-list');
            $classList.empty();
            classes.forEach(function (cls, index) {
                var $classItem = $('<div class="class-item"></div>');
                $classItem.text(cls.name);
                $classItem.css('background-color', cls.color);
                $classItem.data('classIndex', index);
                $classList.append($classItem);
            });

            // Class selection handler
            $('.class-item').on('click', function () {
                selectedClass = classes[$(this).data('classIndex')];

                // Highlight selected class
                $('.class-item').removeClass('selected');
                $(this).addClass('selected');

                // Enter annotation mode
                annotationMode = true;
                $('#selected-class-name').text(selectedClass.name);
                $('#annotation-mode-indicator').removeClass('hidden');
            });

            // Load image
            currentImageId = response.image_id;
            var imageUrl = response.image_url;
            var $imageDisplay = $('#image-display');
            $imageDisplay.attr('src', imageUrl);

            // Set up image load handler
            $imageDisplay.on('load', function () {
                // Adjust image container dimensions
                $imageContainer.css({
                    'width': $imageDisplay.width(),
                    'height': $imageDisplay.height()
                });
                imageContainerOffset = $imageContainer.offset();
            });

            // Mouse events for drawing annotations
            $imageContainer.on('mousedown', function (e) {
                if (annotationMode && selectedClass) {
                    e.preventDefault();
                    isDrawing = true;
                    var offset = getMousePosition(e);
                    startX = offset.x;
                    startY = offset.y;

                    // Create a new annotation element
                    var $annotation = $('<div class="annotation"></div>');
                    $annotation.css({
                        'left': startX,
                        'top': startY,
                        'border-color': selectedClass.color,
                    });
                    $annotation.data('class', selectedClass);
                    $annotation.append('<div class="delete-handle">&times;</div>');
                    $annotation.on('mousedown', annotationMouseDown);

                    // Добавляем угловые точки ресайза
                    $annotation.append('<div class="resize-handle nw"></div>');
                    $annotation.append('<div class="resize-handle sw"></div>');
                    $annotation.append('<div class="resize-handle se"></div>');

                    // События для перетаскивания угловых точек
                    $annotation.find('.resize-handle').on('mousedown', resizeMouseDown);

                    // Add to image container
                    $imageContainer.append($annotation);

                    // Attach delete handler
                    $annotation.find('.delete-handle').on('click', function (e) {
                        e.stopPropagation();
                        $(this).parent('.annotation').remove();
                    });

                    // Store current annotation element
                    $imageContainer.data('currentAnnotation', $annotation);
                }
            });

            $(document).on('mousemove', function (e) {
                if (isDrawing) {
                    var offset = getMousePosition(e);
                    var currentX = offset.x;
                    var currentY = offset.y;
                    var width = currentX - startX;
                    var height = currentY - startY;

                    var $annotation = $imageContainer.data('currentAnnotation');
                    $annotation.css({
                        'width': Math.abs(width),
                        'height': Math.abs(height),
                        'left': width < 0 ? currentX : startX,
                        'top': height < 0 ? currentY : startY
                    });
                }
            });

            $(document).on('mouseup', function (e) {
                if (isDrawing) {
                    isDrawing = false;
                    $imageContainer.removeData('currentAnnotation');

                    // После установки маркера автоматически выходим из режима установки
                    annotationMode = false;
                    $('#annotation-mode-indicator').addClass('hidden');
                    $('.class-item').removeClass('selected');
                    selectedClass = null;
                }
            });

            /////////////
            ////////

            // Функция для ресайза
            function resizeMouseDown(e) {
                e.stopPropagation();
                e.preventDefault();

                var $handle = $(this);
                var $annotation = $handle.parent();
                var startX = e.pageX;
                var startY = e.pageY;
                var startWidth = $annotation.width();
                var startHeight = $annotation.height();
                var startLeft = parseInt($annotation.css('left'));
                var startTop = parseInt($annotation.css('top'));

                $(document).on('mousemove.resize', function (e) {
                    var dx = e.pageX - startX;
                    var dy = e.pageY - startY;

                    if ($handle.hasClass('nw')) {
                        $annotation.css({
                            'left': startLeft + dx,
                            'top': startTop + dy,
                            'width': startWidth - dx,
                            'height': startHeight - dy
                        });
                    } else if ($handle.hasClass('ne')) {
                        $annotation.css({
                            'top': startTop + dy,
                            'width': startWidth + dx,
                            'height': startHeight - dy
                        });
                    } else if ($handle.hasClass('sw')) {
                        $annotation.css({
                            'left': startLeft + dx,
                            'width': startWidth - dx,
                            'height': startHeight + dy
                        });
                    } else if ($handle.hasClass('se')) {
                        $annotation.css({
                            'width': startWidth + dx,
                            'height': startHeight + dy
                        });
                    }
                });

                $(document).on('mouseup.resize', function () {
                    $(document).off('.resize');
                });
            }
            //////
            ////////

            // Cancel annotation mode
            $('#cancel-annotation-btn').on('click', function () {
                annotationMode = false;
                $('#annotation-mode-indicator').addClass('hidden');
                $('.class-item').removeClass('selected');
                selectedClass = null;
            });
        },
        error: function (xhr) {
            alert("An error occurred while fetching the image.");
            window.location.href = '/main';
        }
    });

    // Helper to get the mouse position relative to the image container
    // Обновление функции getMousePosition
    function getMousePosition(e) {
        var parentOffset = $imageContainer.offset();
        var x = e.pageX - parentOffset.left;
        var y = e.pageY - parentOffset.top;

        return { x: x, y: y };
    }

    // Annotation movement and resizing
    function annotationMouseDown(e) {
        if ($(e.target).hasClass('delete-handle')) {
            return; // Ignore delete handle clicks
        }
        e.stopPropagation();

        var $annotation = $(this);
        var startOffset = $annotation.offset();
        var startX = e.pageX;
        var startY = e.pageY;

        $(document).on('mousemove.annotation', function (e) {
            var dx = e.pageX - startX;
            var dy = e.pageY - startY;
            $annotation.css({
                'left': startOffset.left + dx - imageContainerOffset.left,
                'top': startOffset.top + dy - imageContainerOffset.top
            });
        });

        $(document).on('mouseup.annotation', function () {
            $(document).off('.annotation');
        });
    }

    // Next button click handler
    $('#next-btn').on('click', function () {
        // Collect annotation data
        var annotations = [];
        $('.annotation').each(function () {
            var $annotation = $(this);
            var cls = $annotation.data('class');
            var left = parseInt($annotation.css('left'));
            var top = parseInt($annotation.css('top'));
            var width = $annotation.width();
            var height = $annotation.height();

            // Normalize coordinates relative to image dimensions
            var imageWidth = $imageContainer.width();
            var imageHeight = $imageContainer.height();

            var x_center = (left + width / 2) / imageWidth;
            var y_center = (top + height / 2) / imageHeight;
            var w = width / imageWidth;
            var h = height / imageHeight;

            annotations.push({
                class_id: classes.indexOf(cls),
                x_center: x_center,
                y_center: y_center,
                width: w,
                height: h
            });
        });

        // Send annotations to server
        $.ajax({
            url: '/api/submit_annotation',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({
                project_id: projectId,
                image_id: currentImageId,
                annotation: annotations
            }),
            success: function (response) {
                // Reload the page for the next image
                location.reload();
            },
            error: function (xhr) {
                alert("An error occurred while submitting the annotation.");
            }
        });
    });
    // Следим за движением мыши над изображением
    $imageContainer.on('mousemove', function (e) {
        var mousePos = getMousePosition(e);

        // Для каждой аннотации проверяем расстояние до ручек ресайза
        $('.annotation').each(function () {
            var $annotation = $(this);
            var annotationOffset = $annotation.position();
            var annotationWidth = $annotation.width();
            var annotationHeight = $annotation.height();

            // Массив ручек ресайза
            var handles = [
                { name: 'nw', x: annotationOffset.left, y: annotationOffset.top },
                { name: 'sw', x: annotationOffset.left, y: annotationOffset.top + annotationHeight },
                { name: 'se', x: annotationOffset.left + annotationWidth, y: annotationOffset.top + annotationHeight }
            ];

            // Проверяем каждую ручку
            $annotation.find('.resize-handle').each(function (index) {
                var $handle = $(this);
                var handlePos = handles[index];
                var distance = Math.hypot(mousePos.x - handlePos.x, mousePos.y - handlePos.y);

                if (distance <= 20) {
                    $handle.addClass('visible');
                } else {
                    $handle.removeClass('visible');
                }
            });
        });
    });

    // Когда курсор покидает область изображения, скрываем все ручки ресайза
    $imageContainer.on('mouseleave', function () {
        $('.resize-handle').removeClass('visible');
    });
});
```


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/new_project.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>New Project - 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/new_project.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/new_project.js"></script>
<div class="screen">
    {% include "components/hotbar.html" %}
    <div class="tab new-project-tab">
        <h2>Create New Project</h2>
        <form id="new-project-form">
            <div class="form-group">
                <label for="project-title">Project Title:</label>
                <input type="text" id="project-title" name="title" required>
            </div>
            <div class="form-group">
                <label for="project-images">Upload Images:</label>
                <input type="file" id="project-images" name="images" multiple accept="image/*">
            </div>
            <div class="form-group">
                <label for="project-path">Or specify absolute path to images folder (optional):</label>
                <input type="text" id="project-path" name="path">
            </div>
            <div class="form-group" id="classes-container">
                <label>Classes:</label>
                <div class="class-item">
                    <input type="text" name="class-name" placeholder="Class Name">
                    <input type="color" name="class-color" value="#ff0000">
                    <button type="button" class="remove-class-btn">Remove</button>
                </div>
            </div>
            <button type="button" id="add-class-btn">Add Class</button>
            <button type="submit" id="create-project-btn">Create</button>
        </form>
        <div id="upload-progress" style="display:none;">
            <progress id="progress-bar" value="0" max="100"></progress>
            <span id="progress-text"></span>
        </div>
    </div>
</div>
</body>
</html>
```


templates/workspace.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Project Workspace - 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/workspace.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/workspace.js"></script>
<div class="screen">
    {% include "components/hotbar.html" %}
    <div class="tab workspace-tab">
        <h2 id="project-title">Project Title</h2>
        <div id="username">Username</div>
        <div class="progress-container">
            <progress id="project-progress" value="0" max="100"></progress>
        </div>
        <div class="workspace-container">
            <div class="classes-list" id="class-list">
                <!-- Classes will be dynamically added here -->
            </div>
            <div class="image-container">
                <img id="image-display" alt="Annotation Image">
                <!-- Annotations will be added here as overlays -->
            </div>
        </div>
        <div id="annotation-mode-indicator" class="hidden">
            <span>Annotation Mode: <span id="selected-class-name"></span></span>
            <button id="cancel-annotation-btn">Cancel</button>
        </div>
        <button id="next-btn">Next</button>
    </div>
</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">Hey, ...!</div>
    <div class="tab-title">
        Your projects:
        <button id="new-project-btn">New Project</button>
    </div>
    <div class="projects-container">
        <!-- Здесь будут отображаться проекты -->
    </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>
```


Сделай пожалуйста так, чтобы в `.project-container`, если пользователь автор проекта, там была бы кнопка edit.
Она открывала бы страницу /edit_project.
Ты джолжен сделать отдельные эндпоинты, чтобы изменить название проекта, а также, что самое главное:
В странице /edit_project должен быть контейнер с display: grid, содержащий все изображения по страницам.
На каждой странице помещаяется не больше 20 изображений. Снизу должно быть написано количество страниц и на какой мы сейчас.
При наведении на изображение на нем должны появляться кнопки - кнопка "Delete". Если на нее нажать, то должен отправиться запрос на эндпоинт на удаление изображения из этого проекта.
Также, добавь кнопку "Delete Project"

Также, сделай, чтобы в интерфейсе создания проекта был написан его уникальный id. Его нельзя будет изменить. В /edit_project он будет написан в неизменяемом поле вместе с кнопкой копирования текста.
Этот id можнор будет использовать чтобы войти в чей-то проект.
Для этого, вместе с кнопкой `New Project`, создай кнопку `Join Project`. Откроется страница `/join`, в которой будет текстовое поле.
Как только в нем будет что-то меняться, то этот текст будет отправляться на API сервера с целью получить информацию об проекте: его название, автора, количество изображений, классы, прогресс.
Если вообще такой проект найдется конечно.

Как только все это отобразится снизу будет кнопка "Join", после чего нас вернут на `/main`.
У проекта, к которому мы присоединились(не наш), не будет кнопки "Edit", там будет кнопка "Leave".

САМОЕ ВАЖНОЕ: Сделай так, чтобы если один пользователь уже взял изображение(ему его дали) на разметку, то это же изображение не могло бы отправиться другому человеку, даже если запросы были отдновременно. Это очень важно, каждый пользователь должен обрабатывать только новое изображение.
Причем, это нужно реализовать так, что если человек не нажмет кнопку "Next", то это изображение не пропало бы, а могло бы показать тогда либо ему потом снова, либо моментально у другого человека(и оять же не более чем у одного человека)

Сделай пожалуйста это все полностью, чтобы работало как часы. Спасибо заранее!