It looks like you're encountering several Pylint errors and warnings in your Python script that utilizes PyQt5 for the GUI application. I'll help you understand each of these messages and provide guidance on how to resolve them.

Overview of the Issues
E0611: No name 'X' in module 'Y'
Pylint is reporting that it cannot find certain names (classes/functions) in the modules you're importing from, such as PyQt5.QtWidgets, PyQt5.QtCore, etc.
W0611: Unused import 'X'
Pylint is indicating that you've imported some modules or names that you are not using in your code.
W0201: Attribute 'X' defined outside init
Pylint warns when attributes are added to objects outside of the init method.
W0718: Catching too general exception 'Exception'
This warning suggests that you are catching a very broad exception, which is generally discouraged.
C0114/C0115/C0116: Missing docstrings
Pylint encourages the use of docstrings (documentation strings) to describe modules, classes, and functions.
C0301: Line too long
Lines in your code exceed the recommended length (usually 100 characters).
C0411/C0412: Import order issues
Pylint checks for the proper grouping and ordering of import statements.
C0103: Invalid method name
Pylint enforces naming conventions (e.g., snake_case for methods and functions).
Now, let's address each of these issues step by step.

E0611: No name 'X' in module 'Y'
Error Messages:
No name 'QApplication' in module 'PyQt5.QtWidgets'
No name 'QMainWindow' in module 'PyQt5.QtWidgets'
No name 'QWidget' in module 'PyQt5.QtWidgets'
...
Cause:
Pylint is unable to find the specified names in the imported modules. However, you are importing valid classes from PyQt5, so the issue is likely with how Pylint is configured in your environment.

Solution:
Ensure PyQt5 is Installed Correctly:
Run pip show PyQt5 to verify that PyQt5 is installed in your current Python environment.
If it's not installed, install it using pip install PyQt5.
Check Python Interpreter in Your IDE:
Ensure that your IDE (e.g., VSCode, PyCharm) is configured to use the correct Python interpreter where PyQt5 is installed.
Update Pylint and Astroid:
Pylint relies on the astroid library to build an abstract syntax tree of your code.
Sometimes, outdated versions can cause false positives.
Update both packages:

pip install --upgrade pylint astroid
Adjust Pylint Configuration:
Pylint might have trouble inferring imported modules, especially with complex libraries like PyQt5.
You can add the following to your Pylint configuration (usually in a .pylintrc file) to ignore these errors:

[TYPECHECK]
ignored-modules=PyQt5
Alternatively, you can locally disable the specific message in your code:

from PyQt5.QtWidgets import QApplication # pylint: disable=E0611
Use PyQt5 Stubs for Improved Static Analysis:
Install the PyQt5-stubs package to provide type hints and improve code analysis.

pip install PyQt5-stubs
This may help Pylint better understand the modules and classes available in PyQt5.
Example Update:

At the top of your script
pylint: disable=E0611
from PyQt5.QtWidgets import (
QApplication, QFontComboBox, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QTextEdit, QLineEdit, QPushButton, QLabel, QFileDialog,
QMessageBox, QDialog, QScrollArea, QFrame, QAction, QMenu, QInputDialog,
QColorDialog, QFontDialog, QComboBox, QMenuBar
)
2. W0611: Unused import 'X'
Warning Messages:

Unused QLineEdit imported from PyQt5.QtWidgets
Unused QMenu imported from PyQt5.QtWidgets
Unused QMenuBar imported from PyQt5.QtWidgets
...
Cause:
You have imported modules or classes that are not used anywhere in your code.

Solution:
Remove Unused Imports:
Go through your import statements and remove any classes or functions that are not used.
This helps keep your code clean and reduces unnecessary dependencies.
Example Update:
If you are not using QLineEdit, QMenu, QMenuBar:

from PyQt5.QtWidgets import (
QApplication, QFontComboBox, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QTextEdit, QPushButton, QLabel, QFileDialog,
QMessageBox, QDialog, QScrollArea, QFrame, QAction, QInputDialog,
QColorDialog, QFontDialog, QComboBox
)
3. W0201: Attribute 'X' defined outside init
Warning Messages:

Attribute 'language_action' defined outside init
Attribute 'current_language' defined outside init
Attribute 'thread' defined outside init
Attribute 'worker' defined outside init
Cause:
Pylint is warning that you're defining or assigning to instance attributes outside of the init method, which can lead to maintenance issues or bugs if not carefully managed.

Solution:
Define All Instance Attributes in init:
Move the definitions of these attributes into the init method.
If they are conditionally assigned, set them to None initially.
Example Update:

class ChatBot(QMainWindow):
def init(self):
super().init()
# Existing code ...


    # Initialize attributes
    self.language_action = None
    self.current_language = self.settings.get('language', 'en')
    self.thread = None
    self.worker = None

    self.init_ui()

def create_menu(self):
    # Assign language_action
    self.language_action = QAction(self.tr("Сменить язык"), self)
    # Rest of the method ...

def change_language(self):
    # Use self.current_language as needed
    # ...
W0718: Catching too general exception 'Exception'
Warning Messages:
Catching too general exception Exception
Cause:
Catching the base Exception class can make debugging difficult, as it can catch unexpected exceptions, including system exit calls or keyboard interrupts.

Solution:
Catch Specific Exceptions:
Replace except Exception as e: with more specific exceptions.
Only catch the exceptions you expect and can handle appropriately.
Example Update:
Suppose you're catching exceptions during file operations:

Original code
try:
with open(file_path, "w", encoding="utf-8") as file:
# Write to file
except Exception as e:
QMessageBox.critical(self, self.tr("Ошибка"), self.tr(f"Не удалось сохранить чат:\n{e}"))
Update to catch specific exceptions:

import errno

try:
with open(file_path, "w", encoding="utf-8") as file:
# Write to file
except OSError as e:
if e.errno == errno.EACCES:
error_message = "Permission denied."
elif e.errno == errno.ENOENT:
error_message = "No such file or directory."
else:
error_message = str(e)
QMessageBox.critical(self, self.tr("Ошибка"), self.tr(f"Не удалось сохранить чат:\n{error_message}"))
If you're unsure which exceptions might occur, you may consult the documentation or use multiple except blocks.

Missing Docstrings (C0114/C0115/C0116)
Messages:
Missing module docstring
Missing class docstring
Missing function or method docstring
Cause:
Pylint encourages the use of docstrings to describe what your modules, classes, and functions do, which improves code readability and maintainability.

Solution:
Add Docstrings:
At the top of your module, add a docstring explaining the purpose of the script.
For each class and method, add a docstring that describes its functionality.
Example Update:

"""This module provides a GUI chat application using PyQt5, interacting with an API."""

class ChatBot(QMainWindow):
"""Main chat bot application window."""


def __init__(self):
    """Initialize the chat bot application."""
    super().__init__()
    # ...

def change_language(self):
    """Change the interface language."""
    # ...
C0301: Line too long
Messages:
Line too long (110/100)
Line too long (140/100)
...
Cause:
Lines exceed the maximum allowed length (default is 100 characters).

Solution:
Wrap Long Lines:
Break down long lines into multiple lines.
Use parentheses for implicit line continuation.
Example Update:

Before
self.chat_area.setStyleSheet(f"""QTextEdit {{ background-color: {bg_color}; border: 1px solid #ccc; border-radius: 5px; padding: 10px; }}""")

After
self.chat_area.setStyleSheet(
f"""
QTextEdit {{
background-color: {bg_color};
border: 1px solid #ccc;
border-radius: 5px;
padding: 10px;
}}
"""
)
7. C0411/C0412: Import order issues
Messages:

standard import "import os" should be placed before "import requests"
Imports from package PyQt5 are not grouped
Cause:
Pylint suggests a standard ordering of imports:

Standard library imports
Related third-party imports
Local application/library-specific imports
It also suggests that imports from the same package should be grouped together.

Solution:
Reorder and Group Imports:
Place all standard library imports at the top.
Group imports from the same package.
Example Update:

import sys
import os
import re
import json

import requests
from PyQt5.QtWidgets import (
QApplication, QFontComboBox, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
QPushButton, QLabel, QFileDialog, QMessageBox,
QDialog, QScrollArea, QFrame, QAction, QInputDialog, QColorDialog, QFontDialog, QComboBox
)
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QThread
from PyQt5.QtGui import QFont, QIcon, QTextCursor

from gradio_client import Client
from pygments import highlight
from pygments.lexers import (
PythonLexer, JavascriptLexer, JsonLexer, TextLexer
)
from pygments.formatters import HtmlFormatter
from PyQt5.QtPrintSupport import QPrinter
8. C0103: Invalid method name
Message:

Method name "eventFilter" doesn't conform to snake_case naming style
Cause:
Pylint enforces PEP 8 naming conventions, which recommend that method and function names should be in snake_case.

However, in PyQt5, the eventFilter method is an overridden method from the base class, and its name must match the original.

Solution:
Add Naming Convention Exceptions:
Since eventFilter is an overridden method, you can tell Pylint to ignore this warning.
Add pylint: disable=invalid-name as a comment.
Example Update:

def eventFilter(self, obj, event): # pylint: disable=invalid-name
"""Handle events for the input field."""
# ...
Additional Recommendations
Use Virtual Environments
Ensure you're working within a virtual environment to manage your project's dependencies. This prevents conflicts between different projects and ensures that Pylint and other tools see the correct packages.

Configure Pylint for Your Project
Create a .pylintrc file in your project's root directory to customize Pylint's behavior. You can disable certain warnings or adjust formatting options as needed.

Example .pylintrc snippet:

[MESSAGES CONTROL]
disable=C0114,C0115,C0116 # Disable missing docstring warnings for brevity

[FORMAT]
max-line-length=120 # Adjust the max line length to your preference
Improve Exception Handling
Be cautious when handling exceptions. Always aim to catch specific exceptions and handle them appropriately. Broad exceptions can mask errors and make debugging more difficult.

Clean Up Imports
Regularly review your imports to ensure you're only importing what you need. Tools like autoflake can help remove unused imports automatically.

Follow PEP 8 Guidelines
Consider using tools like black or autopep8 to format your code according to PEP 8 standards automatically.

Summary
By addressing the issues reported by Pylint, you'll improve the quality, readability, and maintainability of your code. Start by ensuring that your environment is correctly set up and that all necessary packages are installed. Then, systematically go through each warning and error, applying the recommended changes.

If you continue to experience issues after making these adjustments, please provide additional details, and I'll be happy to assist further.
```python
import sys
import re
import requests
import json
import os
from PyQt5.QtWidgets import (
    QApplication, QFontComboBox, QMainWindow, QWidget, QVBoxLayout,
    QHBoxLayout, QTextEdit, QPushButton, QLabel, QFileDialog,
    QMessageBox, QDialog, QScrollArea, QFrame, QAction, QInputDialog,
    QColorDialog, QFontDialog, QComboBox
)
from PyQt5.QtCore import Qt, QObject, pyqtSignal, QThread
from PyQt5.QtGui import QFont, QIcon, QTextCursor
from gradio_client import Client
from pygments import highlight
from pygments.lexers import PythonLexer, JavascriptLexer, JsonLexer, TextLexer
from pygments.formatters import HtmlFormatter
from PyQt5.QtPrintSupport import QPrinter

# Инициализация клиента для API
client = Client("yuntian-deng/o1mini")

# Карта поддерживаемых лексеров для подсветки синтаксиса
LEXER_MAP = {
    'python': PythonLexer(),
    'javascript': JavascriptLexer(),
    'json': JsonLexer(),
    # Добавьте другие языки здесь
}


def highlight_code(text, language='python'):
    """
    Подсвечивает синтаксис кода.
    Поддерживаемые языки можно расширять, добавляя их в LEXER_MAP.
    """
    lexer = LEXER_MAP.get(language.lower(), TextLexer())

    formatter = HtmlFormatter(style="friendly")
    highlighted = highlight(text, lexer, formatter)
    return highlighted


class Worker(QObject):
    """
    Рабочий объект для выполнения API-запросов в отдельном потоке.
    """
    finished = pyqtSignal(str)
    error = pyqtSignal(str)

    def __init__(self, message, chat_history, chat_counter):
        super().__init__()
        self.message = message
        self.chat_history = chat_history
        self.chat_counter = chat_counter

    def run(self):
        try:
            result = client.predict(
                inputs=self.message,
                top_p=1,
                temperature=1,
                chat_counter=self.chat_counter,
                chatbot=self.chat_history,
                api_name="/predict"
            )

            if len(result) < 4:
                raise ValueError("Некорректный формат ответа от API.")

            chatbot_history, new_chat_counter, status_code, reset_text = result

            if not isinstance(chatbot_history, (list, tuple)):
                raise TypeError(
                    "chatbot_history должно быть списком или кортежем.")

            if len(chatbot_history) == 0:
                chatbot_response = "Нет ответа от бота."
            else:
                latest_entry = chatbot_history[-1]
                if isinstance(latest_entry, (list, tuple)) and len(latest_entry) == 2:
                    chatbot_response = latest_entry[1]
                else:
                    chatbot_response = "Не удалось получить ответ бота."

            self.finished.emit(chatbot_response)

        except Exception as e:
            self.error.emit(str(e))


class ChatBot(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Чат-бот на основе API `yuntian-deng/o1mini`")
        self.setGeometry(100, 100, 900, 700)

        self.chat_history = []
        self.chat_counter = 0

        # Загрузка настроек
        self.load_settings()

        self.init_ui()

    def load_settings(self):
        """
        Загружает настройки из файла конфигурации.
        """
        self.config_path = 'config.json'
        if os.path.exists(self.config_path):
            try:
                with open(self.config_path, 'r', encoding='utf-8') as f:
                    self.settings = json.load(f)
            except Exception as e:
                QMessageBox.warning(self, self.tr("Ошибка"), self.tr(
                    f"Не удалось загрузить настройки:\n{e}"))
                self.settings = {}
        else:
            self.settings = {}

    def save_settings(self):
        """
        Сохраняет текущие настройки в файл конфигурации.
        """
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                json.dump(self.settings, f, ensure_ascii=False, indent=4)
        except Exception as e:
            QMessageBox.warning(self, self.tr("Ошибка"), self.tr(
                f"Не удалось сохранить настройки:\n{e}"))

    def init_ui(self):
        # Создаём центральный виджет и устанавливаем его
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        main_layout = QVBoxLayout(central_widget)

        # Меню
        self.create_menu()

        # Заголовок
        title = QLabel(self.tr("Чат-бот на основе API `yuntian-deng/o1mini`"))
        title.setFont(QFont("Arial", 20))
        title.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(title)

        # Область чата с прокруткой
        self.chat_area = QTextEdit()
        self.chat_area.setReadOnly(True)
        self.chat_area.setFont(QFont("Arial", 12))
        # Применение сохранённого цвета фона, если есть
        bg_color = self.settings.get('chat_bg_color', '#f9f9f9')
        self.chat_area.setStyleSheet(f"""
            QTextEdit {{
                background-color: {bg_color};
                border: 1px solid #ccc;
                border-radius: 5px;
                padding: 10px;
            }}
        """)

        scroll = QScrollArea()
        scroll.setWidgetResizable(True)
        scroll.setWidget(self.chat_area)
        scroll.setFrameStyle(QFrame.NoFrame)
        main_layout.addWidget(scroll)

        # Поле ввода сообщения и подсчёт символов и токенов
        input_container = QWidget()
        input_layout = QVBoxLayout()

        self.input_field = QTextEdit()
        self.input_field.setPlaceholderText(
            self.tr("Введите сообщение и нажмите Enter"))
        self.input_field.setFixedHeight(100)
        self.input_field.setFont(QFont("Arial", 12))
        self.input_field.setStyleSheet("""
            QTextEdit {
                padding: 8px;
                border: 1px solid #ccc;
                border-radius: 5px;
            }
        """)
        self.input_field.installEventFilter(self)
        input_layout.addWidget(self.input_field)

        counters_layout = QHBoxLayout()
        self.char_count_label = QLabel(self.tr("Символы: 0"))
        self.token_count_label = QLabel(self.tr("Токены: 0"))
        counters_layout.addWidget(self.char_count_label)
        counters_layout.addWidget(self.token_count_label)
        counters_layout.addStretch()
        input_layout.addLayout(counters_layout)

        input_container.setLayout(input_layout)
        main_layout.addWidget(input_container)

        # Кнопка отправки и индикатор статуса
        send_layout = QHBoxLayout()
        self.send_button = QPushButton(self.tr("Отправить"))
        self.send_button.setIcon(QIcon.fromTheme("send"))
        self.send_button.clicked.connect(self.send_message)
        send_layout.addWidget(self.send_button)

        self.status_label = QLabel("")
        self.status_label.setStyleSheet("color: green;")
        send_layout.addWidget(self.status_label)

        main_layout.addLayout(send_layout)

        # Кнопки дополнительных функций
        func_layout = QHBoxLayout()

        self.reset_btn = QPushButton(self.tr("Сбросить чат"))
        self.reset_btn.setIcon(QIcon.fromTheme("edit-clear"))
        self.reset_btn.clicked.connect(self.reset_chat)
        func_layout.addWidget(self.reset_btn)

        self.export_btn = QPushButton(self.tr("Экспортировать чат"))
        self.export_btn.setIcon(QIcon.fromTheme("document-save"))
        self.export_btn.clicked.connect(self.export_chat)
        func_layout.addWidget(self.export_btn)

        self.pdf_export_btn = QPushButton(self.tr("Экспортировать в PDF"))
        self.pdf_export_btn.setIcon(QIcon.fromTheme("document-print"))
        self.pdf_export_btn.clicked.connect(self.export_chat_to_pdf)
        func_layout.addWidget(self.pdf_export_btn)

        self.fact_btn = QPushButton(self.tr("Случайный факт"))
        self.fact_btn.setIcon(QIcon.fromTheme("information"))
        self.fact_btn.clicked.connect(self.get_random_fact)
        func_layout.addWidget(self.fact_btn)

        main_layout.addLayout(func_layout)

        # Информация о ошибках
        self.info_label = QLabel("")
        self.info_label.setStyleSheet("color: red;")
        main_layout.addWidget(self.info_label)

        # Применение стилей
        self.setStyleSheet("""
            QWidget {
                background-color: #e6e6e6;
            }
            QPushButton {
                padding: 8px 16px;
                border: none;
                background-color: #4CAF50;
                color: white;
                border-radius: 5px;
            }
            QPushButton:hover {
                background-color: #45a049;
            }
            QLabel {
                font-size: 12px;
            }
        """)

    def create_menu(self):
        """
        Создаёт меню для настроек и локализации.
        """
        # Настройки
        settings_menu = self.menuBar().addMenu(self.tr('Настройки'))
        settings_action = QAction(self.tr("Настройки"), self)
        settings_action.triggered.connect(self.open_settings)
        settings_menu.addAction(settings_action)

        # Язык интерфейса
        language_menu = self.menuBar().addMenu(self.tr('Язык'))
        self.language_action = QAction(self.tr("Сменить язык"), self)
        self.language_action.triggered.connect(self.change_language)
        language_menu.addAction(self.language_action)

    def change_language(self):
        """
        Меняет язык интерфейса.
        """
        languages = {
            'Русский': 'ru',
            'English': 'en'
        }
        lang, ok = QInputDialog.getItem(self, self.tr("Выберите язык"), self.tr(
            "Язык:"), list(languages.keys()), 0, False)
        if ok and lang:
            self.current_language = languages[lang]
            self.settings['language'] = self.current_language
            self.save_settings()
            QMessageBox.information(self, self.tr("Перезагрузка"), self.tr(
                "Изменения вступят в силу после перезагрузки приложения."))
            # Для полной смены языка необходимо перезапустить приложение
            # Здесь можно реализовать автоматический перезапуск или перезапись интерфейса

    def append_message(self, sender, message):
        """
        Добавляет сообщение в чат. Обрабатывает текст и блоки кода.
        """
        # Регулярное выражение для поиска блоков кода
        code_pattern = re.compile(r'```(\w+)?\n(.*?)```', re.DOTALL)
        parts = code_pattern.split(message)

        # Проход по частям сообщения
        for i in range(0, len(parts), 3):
            text = parts[i].strip()
            if text:
                formatted_text = self.format_text(sender, text)
                self.chat_area.append(formatted_text)

            # Проверяем наличие блока кода
            if i + 2 < len(parts):
                language = parts[i + 1] if parts[i + 1] else 'python'
                code = parts[i + 2].strip()
                highlighted_code = highlight_code(code, language)
                formatted_code = f"""
                <div style="background-color:#f4f4f4; padding:10px; border-radius:5px; position:relative;">
                    {highlighted_code}
                    <button onclick="copyToClipboard(this)" style="position:absolute; top:5px; right:5px; padding:2px 5px; font-size:10px;">{self.tr('Копировать')}</button>
                </div>
                <script>
                function copyToClipboard(button) {{
                    var code = button.parentElement.querySelector('pre').innerText;
                    navigator.clipboard.writeText(code).then(function() {{
                        button.textContent = '{self.tr('Скопировано!')}';
                        setTimeout(function() {{
                            button.textContent = '{self.tr('Копировать')}';
                        }}, 2000);
                    }}, function(err) {{
                        alert('{self.tr('Не удалось скопировать код')}');
                    }});
                }}
                </script>
                """
                self.chat_area.append(formatted_code)
                # TODO: Реализовать интерактивное выполнение кода или расширенные возможности редактирования

        self.chat_area.moveCursor(QTextCursor.End)

    def format_text(self, sender, text):
        """
        Форматирует текстовое сообщение.
        """
        if sender == self.tr("Вы"):
            align = 'right'
            color = '#0084ff'  # Синий для пользователя
        else:
            align = 'left'
            color = '#333333'  # Тёмно-серый для бота

        formatted = f"<p style='text-align: {align};'><span style='color: {color};'><b>{sender}:</b> {self.escape_html(text)}</span></p>"
        return formatted

    def escape_html(self, text):
        """
        Экранирует специальные HTML символы для предотвращения XSS атак.
        """
        import html
        return html.escape(text)

    def update_counters(self):
        """
        Обновляет подсчёт символов и токенов.
        """
        text = self.input_field.toPlainText()
        char_count = len(text)
        token_count = char_count // 4  # 1 токен = 4 символа
        self.char_count_label.setText(self.tr(f"Символы: {char_count}"))
        self.token_count_label.setText(self.tr(f"Токены: {token_count}"))

    def send_message(self):
        """
        Обрабатывает отправку сообщения.
        """
        user_message = self.input_field.toPlainText().strip()
        if not user_message:
            return

        self.append_message(self.tr("Вы"), user_message)
        self.chat_history.append((user_message, ""))
        self.input_field.clear()
        self.update_counters()

        # Показываем статус "Бот набирает..."
        self.status_label.setText(self.tr("Бот набирает ответ..."))
        QApplication.processEvents()

        # Запускаем рабочий поток для асинхронного запроса
        self.thread = QThread()
        self.worker = Worker(
            user_message, self.chat_history, self.chat_counter)
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.run)
        self.worker.finished.connect(self.handle_response)
        self.worker.error.connect(self.handle_error)
        self.worker.finished.connect(self.thread.quit)
        self.worker.finished.connect(self.worker.deleteLater)
        self.thread.finished.connect(self.thread.deleteLater)
        self.thread.start()

    def handle_response(self, response):
        """
        Обрабатывает ответ от бота.
        """
        self.status_label.setText("")
        self.chat_counter += 1
        self.chat_history[-1] = (self.chat_history[-1][0], response)
        self.append_message(self.tr("Бот"), response)

    def handle_error(self, error_message):
        """
        Обрабатывает ошибку при запросе.
        """
        self.status_label.setText("")
        QMessageBox.critical(self, self.tr("Ошибка"), self.tr(
            f"Не удалось отправить сообщение:\n{error_message}"))

    def reset_chat(self):
        """
        Сбрасывает историю чата.
        """
        confirmation = QMessageBox.question(
            self, self.tr("Сбросить чат"),
            self.tr("Вы уверены, что хотите сбросить историю чата?"),
            QMessageBox.Yes | QMessageBox.No
        )
        if confirmation == QMessageBox.Yes:
            self.chat_history = []
            self.chat_counter = 0
            self.chat_area.clear()
            QMessageBox.information(self, self.tr(
                "Чат сброшен"), self.tr("История чата была сброшена."))

    def export_chat(self):
        """
        Экспортирует историю чата в файл HTML или TXT.
        """
        if not self.chat_history:
            QMessageBox.warning(self, self.tr("Экспорт"), self.tr(
                "Нет истории чата для экспорта."))
            return

        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getSaveFileName(
            self, self.tr("Сохранить чат"), "",
            self.tr("HTML файлы (*.html);;Текстовые файлы (*.txt)"), options=options
        )
        if file_path:
            try:
                if file_path.endswith('.html'):
                    with open(file_path, "w", encoding="utf-8") as file:
                        file.write(
                            "<html><head><meta charset='UTF-8'><title>Чат</title></head><body>")
                        for user_msg, bot_msg in self.chat_history:
                            file.write(
                                f"<p style='text-align: right;'><b>{self.tr('Вы')}:</b> {self.escape_html(user_msg)}</p>")
                            # Обработка возможных блоков кода в сообщении бота
                            code_pattern = re.compile(
                                r'```(\w+)?\n(.*?)```', re.DOTALL)
                            parts = code_pattern.split(bot_msg)
                            for i in range(0, len(parts), 3):
                                text = parts[i].strip()
                                if text:
                                    file.write(
                                        f"<p style='text-align: left;'><b>{self.tr('Бот')}:</b> {self.escape_html(text)}</p>")
                                if i + 2 < len(parts):
                                    language = parts[i +
                                                     1] if parts[i + 1] else 'python'
                                    code = parts[i + 2].strip()
                                    highlighted_code = highlight_code(
                                        code, language)
                                    file.write(
                                        f"<pre style='background-color:#f4f4f4; padding:10px; border-radius:5px;'>{highlighted_code}</pre>")
                        file.write("</body></html>")
                else:
                    with open(file_path, "w", encoding="utf-8") as file:
                        for user_msg, bot_msg in self.chat_history:
                            file.write(
                                f"{self.tr('Вы')}: {user_msg}\n{self.tr('Бот')}: {bot_msg}\n\n")
                QMessageBox.information(self, self.tr("Экспорт"), self.tr(
                    f"Чат успешно сохранен в {file_path}"))
            except Exception as e:
                QMessageBox.critical(self, self.tr("Ошибка"), self.tr(
                    f"Не удалось сохранить чат:\n{e}"))

    def export_chat_to_pdf(self):
        """
        Экспортирует историю чата в PDF с сохранением форматирования и подсветки кода.
        """
        if not self.chat_history:
            QMessageBox.warning(self, self.tr("Экспорт PDF"), self.tr(
                "Нет истории чата для экспорта."))
            return

        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getSaveFileName(
            self, self.tr("Сохранить чат в PDF"), "",
            self.tr("PDF файлы (*.pdf)"), options=options
        )
        if file_path:
            try:
                printer = QPrinter(QPrinter.HighResolution)
                printer.setOutputFormat(QPrinter.PdfFormat)
                printer.setOutputFileName(file_path)
                document = self.chat_area.document()
                document.print_(printer)
                QMessageBox.information(self, self.tr("Экспорт PDF"), self.tr(
                    f"Чат успешно сохранен в {file_path}"))
            except Exception as e:
                QMessageBox.critical(self, self.tr("Ошибка"), self.tr(
                    f"Не удалось сохранить PDF:\n{e}"))

    def get_random_fact(self):
        """
        Получает случайный факт и добавляет его в чат.
        """
        try:
            response = requests.get(
                "https://uselessfacts.jsph.pl/random.json?language=ru", timeout=5)
            if response.status_code == 200:
                fact = response.json().get("text", self.tr("Не удалось получить факт."))
            else:
                fact = self.tr("Ошибка при получении факта.")
        except Exception as e:
            fact = self.tr(f"Ошибка при получении факта: {e}")

        self.append_message(self.tr("Бот"), fact)

    def eventFilter(self, obj, event):
        """
        Обрабатывает события для поля ввода сообщения.
        Позволяет использовать Shift + Enter для вставки новой строки.
        """
        if obj == self.input_field:
            if event.type() == event.KeyPress:
                if event.key() in (Qt.Key_Return, Qt.Key_Enter):
                    if event.modifiers() == Qt.ShiftModifier:
                        cursor = self.input_field.textCursor()
                        cursor.insertBlock()
                        return True
                    else:
                        self.send_message()
                        return True
        return super().eventFilter(obj, event)

    def open_settings(self):
        """
        Открывает диалог настроек.
        """
        settings_dialog = SettingsDialog(self)
        if settings_dialog.exec_():
            # Применение сохранённых настроек
            bg_color = self.settings.get('chat_bg_color', '#f9f9f9')
            self.chat_area.setStyleSheet(f"""
                QTextEdit {{
                    background-color: {bg_color};
                    border: 1px solid #ccc;
                    border-radius: 5px;
                    padding: 10px;
                }}
            """)
            font = self.settings.get('chat_font', "Arial")
            font_size = self.settings.get('chat_font_size', 12)
            self.chat_area.setFont(QFont(font, font_size))
            self.save_settings()


class SettingsDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle(self.tr("Настройки"))
        self.setFixedSize(400, 300)
        self.parent = parent
        self.init_ui()

    def init_ui(self):
        layout = QVBoxLayout()

        # Выбор цвета чата
        color_btn = QPushButton(self.tr("Выбрать цвет фона чата"))
        color_btn.clicked.connect(self.choose_color)
        layout.addWidget(color_btn)

        # Выбор шрифта
        font_btn = QPushButton(self.tr("Выбрать шрифт чата"))
        font_btn.clicked.connect(self.choose_font)
        layout.addWidget(font_btn)

        # Дополнительные настройки
        font_label = QLabel(self.tr("Шрифт:"))
        layout.addWidget(font_label)

        self.font_combo = QFontComboBox()
        current_font = self.parent.settings.get('chat_font', "Arial")
        self.font_combo.setCurrentFont(QFont(current_font))
        layout.addWidget(self.font_combo)

        size_label = QLabel(self.tr("Размер шрифта:"))
        layout.addWidget(size_label)

        self.size_combo = QComboBox()
        self.size_combo.addItems([str(size) for size in range(8, 30, 2)])
        current_size = str(self.parent.settings.get('chat_font_size', 12))
        index = self.size_combo.findText(current_size)
        if index >= 0:
            self.size_combo.setCurrentIndex(index)
        layout.addWidget(self.size_combo)

        # Кнопки OK и Отмена
        btn_layout = QHBoxLayout()
        ok_btn = QPushButton(self.tr("OK"))
        ok_btn.clicked.connect(self.accept)
        cancel_btn = QPushButton(self.tr("Отмена"))
        cancel_btn.clicked.connect(self.reject)
        btn_layout.addWidget(ok_btn)
        btn_layout.addWidget(cancel_btn)
        layout.addLayout(btn_layout)

        self.setLayout(layout)

    def choose_color(self):
        color = QColorDialog.getColor()
        if color.isValid():
            self.parent.settings['chat_bg_color'] = color.name()

    def choose_font(self):
        font, ok = QFontDialog.getFont()
        if ok:
            self.parent.settings['chat_font'] = font.family()
            self.parent.settings['chat_font_size'] = font.pointSize()

    def accept(self):
        # Сохранение выбранных шрифта и размера
        selected_font = self.font_combo.currentFont().family()
        selected_size = int(self.size_combo.currentText())
        self.parent.settings['chat_font'] = selected_font
        self.parent.settings['chat_font_size'] = selected_size
        super().accept()


def main():
    app = QApplication(sys.argv)

    # Установка переводов (для полной локализации необходимо добавить файлы перевода)
    # Ниже приведен пример реализации, однако для полноценной поддержки языков потребуется использование Qt Linguist
    # и создание соответствующих `.qm` файлов.

    chatbot = ChatBot()
    chatbot.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
```