добавь кнопку выбора API (расположи ее в верхнем левой углу, перед кнопкой "Отправить запрос в ChatGPT-3.5") - при нажатии выводится список из возможных API: https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-beta
https://api-inference.huggingface.co/models/microsoft/DialoGPT-medium
https://api-inference.huggingface.co/models/microsoft/DialoGPT-large
https://api-inference.huggingface.co/models/gpt2
https://api-inference.huggingface.co/models/facebook/blenderbot-400M-distill,  в которую будет отправлять запрос: # coding: utf-8

import time
import re
import speech_recognition as sr
import pyttsx3
import yt_dlp as youtube_dl
import pygame
import imageio_ffmpeg as ffmpeg
from freeGPT import Client
from googletrans import Translator
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
import os
import logging
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.popup import Popup
from config import HUGGINGFACE_API_TOKEN


# Создаем путь для log файла в папке с приложением
log_path = os.path.join(os.path.dirname(__file__), 'app.log')

# Создаем и настраиваем логгер
logger = logging.getLogger()
logger.setLevel(logging.WARNING)  # Установите нужный уровень логов

# Create a file handler
file_handler = logging.FileHandler(log_path)
file_handler.setLevel(logging.WARNING)

# Create formatter and add it to the handlers
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)

# Add the file handler to the logger
logger.addHandler(file_handler)

# Check if running on Android
try:
    from android.permissions import request_permissions, Permission
    from android import api_version

    android_platform = True
except ImportError:
    android_platform = False


def safe_print(text):
    try:
        print(text)
    except UnicodeEncodeError:
        print(text.encode('utf-8').decode('utf-8'))


def initialize_tts_engine():
    return pyttsx3.init()


def speak(text, engine=None):
    if not engine:
        engine = initialize_tts_engine()
    engine.say(text)
    engine.runAndWait()
    engine.stop()


# Initialize speech recognizer
recognizer = sr.Recognizer()


def listen_command():
    with sr.Microphone() as source:
        safe_print("Listening for command…")
        audio = recognizer.listen(source)
        try:
            command = recognizer.recognize_google(audio, language="ru-RU")
            safe_print(f"Command heard: {command}")
            return command.lower()
        except sr.UnknownValueError:
            # speak("Я не понял ваш запрос")
            return ""
        except sr.RequestError:
            speak("Ошибка сервиса распознавания речи")
            return ""


def sanitize_filename(filename):
    return re.sub(r'[/?:"<>|]', '', filename)


def search_youtube(query, tts_engine=None):
    try:
        sanitized_query = sanitize_filename(query)
        directory = "mp3"

        if not os.path.exists(directory):
            os.makedirs(directory)

        youtube_dl_opts = {
            'format': 'bestaudio/best',
            'noplaylist': True,
            'quiet': True,
            'outtmpl': os.path.join(directory, f'{sanitized_query}.%(ext)s'),
            'ffmpeg_location': ffmpeg.get_ffmpeg_exe(),
            'postprocessors': [{
                'key': 'FFmpegExtractAudio',
                'preferredcodec': 'mp3',
                'preferredquality': '192',
            }],
        }

        file_path = os.path.join(directory, f"{sanitized_query}.mp3")

        if os.path.exists(file_path):
            speak(f"Файл уже существует. Проигрываю {sanitized_query}.", tts_engine)
            return file_path

        with youtube_dl.YoutubeDL(youtube_dl_opts) as ydl:
            info = ydl.extract_info(f"ytsearch1:{query}", download=False)
            video_info = info['entries'][0]
            ydl.download([f"ytsearch1:{query}"])
            while not os.path.exists(file_path):
                time.sleep(1)
            return file_path

    except Exception as e:
        # logger.error(f"Error in search_youtube: {e}")
        speak("Произошла ошибка при поиске песни", tts_engine)
        return None


def set_volume(level, tts_engine=None):
    try:
        level = int(level)
        if 0 <= level <= 10:
            pygame.mixer.music.set_volume(round(level / 10.0, 1))  # Округление и установка громкости
            if tts_engine:
                speak(f"Громкость установлена на {level}", tts_engine)
        else:
            if tts_engine:
                speak("Пожалуйста, укажите уровень громкости от 0 до 10.", tts_engine)
    except ValueError:
        if tts_engine:
            speak("Я не понял уровень громкости, укажите значение от 0 до 10.", tts_engine)


def volume_up(current_volume, tts_engine=None):
    new_volume = round(min(current_volume + 2, 10))
    pygame.mixer.music.set_volume(new_volume / 10.0)
    # if tts_engine:
    #     speak(f"Громкость увеличена до {new_volume}", tts_engine)


def volume_down(current_volume, tts_engine=None):
    new_volume = round(max(current_volume - 2, 0))
    pygame.mixer.music.set_volume(new_volume / 10.0)
    # Проговаривание замены уровня громкости
    # if tts_engine:
    #     speak(f"Громкость уменьшена до {new_volume}", tts_engine)


def seek_forward(tts_engine):
    if pygame.mixer.music.get_busy():
        current_pos = pygame.mixer.music.get_pos()
        new_pos = current_pos + 10000  # Skip forward by 10 seconds
        pygame.mixer.music.set_pos(new_pos / 1000.0)  # Set the new position
        # if tts_engine:
        #     speak("Перемотка на 10 секунд вперед", tts_engine)

def seek_backward(tts_engine):
    if pygame.mixer.music.get_busy():
        current_pos = pygame.mixer.music.get_pos()
        new_pos = max(0, current_pos - 10000)  # Skip backward by 10 seconds, but not less than zero
        pygame.mixer.music.set_pos(new_pos / 1000.0)  # Set the new position
        # if tts_engine:
        #     speak("Перемотка на 10 секунд назад", tts_engine)


commands = [
    "найди песню - для поиска и воспроизведения песни",
    "проигрывание - для продолжения воспроизведения",
    "пауза - для паузы воспроизведения",
    "включи - для воспроизведения песни",
    "найди или найти - для поиска новой песни",
    "перестань слушать или не слушай - для прекращения слушать команды",
    "слушай - для возобновления слушания команд",
    "громкость - от нуля до десяти",
    "команды - для воспроизведения этого списка команд",
    "спасибо, выход или завершить - для завершения программы"
]


def list_commands(tts_engine=None):
    speak("Доступные команды: " + ", ".join(commands), tts_engine)


def get_gpt_response(query, tts_engine=None):
    translator = Translator()
    try:
        translated_query = translator.translate(query, src='ru', dest='en').text.encode('utf-8').decode('unicode_escape')
        safe_print(f"Translated Query: {translated_query}")

        # Выполнение запроса к GPT-3.5 клиенту
        response = Client.create_completion("gpt3_5", translated_query)

        if response:
            safe_print(f"GPT Response: {response}")
            translated_resp = translator.translate(response, src='en', dest='ru').text
            return translated_resp
        else:
            # logger.error("Received a None response from GPT-3.5 client")
            return "Не удалось получить ответ от GPT-3.5"
    except Exception as e:
        # logger.error(f"Error in get_gpt_response: {e}")
        return f"Произошла ошибка: {e}"

def get_hugginface_response(query, tts_engine=None):
    import requests
    API_URL = "https://api-inference.huggingface.co/models/HuggingFaceH4/zephyr-7b-beta"
    # API_URL = "https://api-inference.huggingface.co/models/microsoft/DialoGPT-medium"
    # API_URL = "https://api-inference.huggingface.co/models/microsoft/DialoGPT-large"
    # API_URL = "https://api-inference.huggingface.co/models/gpt2"
    # API_URL = "https://api-inference.huggingface.co/models/facebook/blenderbot-400M-distill"
    headers = {"Authorization": f"Bearer {HUGGINGFACE_API_TOKEN}"}
    translator = Translator()
    try:
        translated_query = translator.translate(query, src='ru', dest='en').text.encode('utf-8').decode(
            'unicode_escape')
        safe_print(f"Translated Query: {translated_query}")

        # Выполнение запроса к API
        response = requests.post(API_URL, headers=headers, json={"inputs": translated_query})

        # Проверка успешного выполнения запроса
        if response.status_code == 200:
            gpt_response = response.json()
            safe_print(f"GPT Response: {gpt_response}")

            # Проверка и извлечение ответа
            if gpt_response and isinstance(gpt_response, list) and len(gpt_response) > 0 and 'generated_text' in \
                    gpt_response[0]:
                generated_text = gpt_response[0]['generated_text']
                translated_resp = translator.translate(generated_text, src='en', dest='ru').text
                return translated_resp
            else:
                return "Не удалось получить корректный ответ от API"
        else:
            print(f"Запрос не удался с кодом состояния: {response.status_code}")
            print(response.text)
            return "Произошла ошибка при выполнении запроса к API"
    except Exception as e:
        return f"Произошла ошибка: {e}"


text_to_number = {
    "ноль": 0, "один": 1, "два": 2, "три": 3, "четыре": 4, "пять": 5,
    "шесть": 6, "семь": 7, "восемь": 8, "девять": 9, "десять": 10
}


def get_number_from_text(text):
    pattern = r"\d+"
    match = re.search(pattern, text)
    if match:
        return int(match.group(0))
    else:
        return None


class MyApp(App):
    def build(self):
        pygame.init()
        pygame.mixer.init()

        if android_platform:
            self.request_android_permissions()

        self.current_song = None
        self.current_position = 0  # Добавлено для отслеживания текущей позиции
        self.listening = True
        self.tts_enabled = False  # Переключатель для проговаривания нажатий на кнопки
        self.tts_engine = initialize_tts_engine()

        self.layout = BoxLayout(orientation='vertical')

        # Создаем новый горизонтальный BoxLayout для кнопок в верхней части экрана
        top_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.1))

        self.text_query_button = Button(text='Отправить запрос в ChatGPT-3.5', size_hint=(0.8, 1))
        self.text_query_button.bind(on_release=self.process_text_query)
        top_layout.add_widget(self.text_query_button)

        self.toggle_tts_button = Button(text='TTS: Вкл', size_hint=(0.2, 1))
        self.toggle_tts_button.bind(on_release=self.toggle_tts)
        top_layout.add_widget(self.toggle_tts_button)

        self.layout.add_widget(top_layout)

        self.command_input = TextInput(size_hint=(1, 0.1), multiline=False, hint_text='Введите команду или запрос')
        self.layout.add_widget(self.command_input)

        self.result_output = TextInput(size_hint=(1, 0.5), readonly=True)
        self.layout.add_widget(self.result_output)

        # Создаем новый горизонтальный BoxLayout для кнопок в нижней части экрана
        find_open_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.1))

        self.find_song_button = Button(text='Найти песню', size_hint=(0.4, 1))
        self.find_song_button.bind(on_release=self.find_song_action)
        find_open_layout.add_widget(self.find_song_button)

        self.open_file_button = Button(text='Открыть файл', size_hint=(0.2, 1))
        self.open_file_button.bind(on_release=self.open_file_action)
        find_open_layout.add_widget(self.open_file_button)

        self.layout.add_widget(find_open_layout)

        # Upper button layout
        self.upper_button_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.1))
        upper_commands = ["назад", "проигрывание", "пауза", "вперед"]
        for command in upper_commands:
            btn = Button(text=command)
            btn.bind(on_release=self.button_action)
            self.upper_button_layout.add_widget(btn)

        # Lower button layout
        self.lower_button_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.1))
        lower_commands = ["громкость +", "громкость -", "команды", "выход"]
        for command in lower_commands:
            btn = Button(text=command)
            btn.bind(on_release=self.button_action)
            self.lower_button_layout.add_widget(btn)

        self.layout.add_widget(self.upper_button_layout)
        self.layout.add_widget(self.lower_button_layout)

        self.listen_button = Button(text='Голосовая команда', size_hint=(1, 0.1))
        self.listen_button.bind(on_release=self.listen_command_action)
        self.layout.add_widget(self.listen_button)
        self.result_output.text += "\nНажми 'Голосовая команда', доступные команды отображаются при нажатии на кнопку 'команды'. " \
                                   "\nЧтобы натйти песню нажми 'Найти песню'. " \
                                   "\nДля отправки запроса в ChatGPT - напиши запрос в поле и нажми кнопку отправить в ChatGPT-3.5."

        return self.layout

    def button_action(self, instance):
        print(f"Button {instance.text} pressed")
        command = instance.text
        self.result_output.text += f"\nВы (кнопка): {command}"

        # Проговаривание нажатия кнопки
        if self.tts_enabled:
            speak(f"Вы нажали {command}", self.tts_engine)

        if command == "громкость +":
            self.volume_up_button_action(instance)
        elif command == "громкость -":
            self.volume_down_button_action(instance)
        elif command == "вперед":
            seek_forward(self.tts_engine)
        elif command == "назад":
            seek_backward(self.tts_engine)
        else:
            self.execute_command(command)


    def request_android_permissions(self):
        def callback(permissions, results):
            if all(results):
                safe_print("Permissions granted")
            else:
                safe_print("Permissions denied")
                speak("Приложению необходимы все разрешения для корректной работы.", self.tts_engine)
                return

        permissions_to_request = [Permission.READ_EXTERNAL_STORAGE,
                                  Permission.WRITE_EXTERNAL_STORAGE,
                                  Permission.RECORD_AUDIO]
        request_permissions(permissions_to_request, callback)

    def toggle_tts(self, instance):
        self.tts_enabled = not self.tts_enabled
        status = "включено" if self.tts_enabled else "выключено"
        self.toggle_tts_button.text = f"TTS: {'Вкл' if self.tts_enabled else 'Выкл'}"
        self.result_output.text += f"\nПроговаривание нажатий {status}"
        speak(f"Проговаривание нажатий {status}", self.tts_engine)

    def open_file_action(self, instance):
        content = BoxLayout(orientation='vertical')
        filechooser = FileChooserListView(path='./mp3', filters=['*.mp3'])
        content.add_widget(filechooser)

        btn_layout = BoxLayout(orientation='horizontal', size_hint=(1, 0.1))

        ok_button = Button(text="OK")
        cancel_button = Button(text="Отмена")
        delete_button = Button(text="Удалить")

        btn_layout.add_widget(ok_button)
        btn_layout.add_widget(cancel_button)
        btn_layout.add_widget(delete_button)

        content.add_widget(btn_layout)

        popup = Popup(title='Выберите файл', content=content, size_hint=(0.9, 0.9))

        def on_ok_button_press(instance):
            selected_file = filechooser.selection[0]
            # Обработка выбранного файла
            popup.dismiss()

        def on_cancel_button_press(instance):
            popup.dismiss()

        def on_delete_button_press(instance):
            selected_file = filechooser.selection[0]
            # Обработка удаления выбранного файла
            os.remove(selected_file)  # Удаление файла
            filechooser.path = './mp3'  # Сбросить путь на текущую директорию
            filechooser._update_files()

        ok_button.bind(on_press=on_ok_button_press)
        cancel_button.bind(on_press=on_cancel_button_press)
        delete_button.bind(on_press=on_delete_button_press)

        popup.open()

        def on_file_selection(instance):
            selection = filechooser.selection
            popup.dismiss()
            if selection:
                self.play_selected_file(selection[0])

        ok_button.bind(on_release=on_file_selection)
        cancel_button.bind(on_release=popup.dismiss)

        popup.open()

    def play_selected_file(self, file_path):
        try:
            self.stop_playback()
            self.current_song = file_path
            self.current_position = 0  # Сбрасываем позицию
            pygame.mixer.music.load(self.current_song)
            pygame.mixer.music.play()
            self.result_output.text += f"\nПроигрываю файл {file_path}"
            if self.tts_enabled:
                speak(f"Проигрываю файл {file_path}", self.tts_engine)
        except Exception as e:
            # logger.error(f"Error playing selected file: {e}")
            self.result_output.text += f"\nОшибка воспроизведения файла {file_path}"
            if self.tts_enabled:
                speak(f"Ошибка воспроизведения файла {file_path}", self.tts_engine)

    def process_text_query(self, instance):
        query = self.command_input.text
        self.result_output.text += f"\nВы: {query}"
        self.command_input.text = ''  # clear the input after executing the command
        self.execute_command(query)

    def find_song_action(self, instance):
        query = self.command_input.text
        if query:
            command = f"найди песню {query}"
            self.result_output.text += f"\nВы (поиск песни): {query}"
            self.command_input.text = ''  # clear the input after executing the command
            self.execute_command(command)
        else:
            speak("Какую песню вы хотите услышать?", self.tts_engine)
            search_query = listen_command()
            safe_print(f"Search Query: {search_query}")

            if search_query:
                song_path = search_youtube(search_query, self.tts_engine)
                safe_print(f"Song Path: {song_path}")
                if song_path and os.path.exists(song_path):
                    self.current_song = song_path
                    self.current_position = 0  # Сбрасываем позицию
                    pygame.mixer.music.load(self.current_song)
                    pygame.mixer.music.play()
                    if self.tts_enabled:
                        speak(f"Проигрываю песню '{search_query}'", self.tts_engine)
                    self.result_output.text += f"\nПроигрываю песню '{search_query}'"
                else:
                    if self.tts_enabled:
                        speak("Не удалось найти песню.", self.tts_engine)

    def listen_command_action(self, instance):
        command = listen_command()
        self.result_output.text += f"\nВы (голосовая команда): {command}"
        self.execute_command(command)



    def stop_playback(self):
        if self.current_song:
            pygame.mixer.music.stop()
            max_attempts = 10  # Максимальное количество попыток
            attempts = 0
            while pygame.mixer.music.get_busy() and attempts < max_attempts:
                time.sleep(0.1)
                attempts += 1

            if pygame.mixer.music.get_busy():
                # logger.warning(f"Music did not stop after {max_attempts} attempts. File: {self.current_song}")
                self.result_output.text += "\nНе удалось остановить воспроизведение должным образом."
                return

            try:
                if os.path.exists(self.current_song):
                    os.remove(self.current_song)
                    self.result_output.text += "\nВоспроизведение остановлено и файл удален"
                self.current_song = None
                self.current_position = 0  # Сбрасываем позицию
            except Exception as e:
                # logger.error(f"Error stopping playback: {e}")
                self.result_output.text += f"\nОшибка приостановки воспроизведения: {e}"

    def volume_up_button_action(self, instance):
        current_volume = int(round(pygame.mixer.music.get_volume() * 10))
        volume_up(current_volume, self.tts_engine)
        self.result_output.text += f"\nГромкость увеличена до {current_volume + 2}"

    def volume_down_button_action(self, instance):
        current_volume = int(round(pygame.mixer.music.get_volume() * 10))
        volume_down(current_volume, self.tts_engine)
        self.result_output.text += f"\nГромкость уменьшена до {current_volume - 2}"

    def execute_command(self, command):
        if "перестань слушать" in command or "не слушай" in command:
            speak("Я перестаю слушать. Скажите 'Слушай', чтобы я снова начал слушать.", self.tts_engine)
            self.listening = False
            return

        if "слушай" in command:
            speak("Я снова слушаю.", self.tts_engine)
            self.listening = True
            return

        if "команды" in command:
            list_commands(self.tts_engine)
            self.result_output.text += "\nДоступные команды:\n"
            for c in commands:
                self.result_output.text += f"- {c}\n"
            self.result_output.text += "\nДоступные команды перечислены."
            return

        if "песню" in command or "песня" in command or "музыку" in command or "музыка" in command:
            speak("Какую песню вы хотите услышать?", self.tts_engine)
            search_query = listen_command()
            safe_print(f"Search Query: {search_query}")

            if search_query:
                song_path = search_youtube(search_query, self.tts_engine)
                safe_print(f"Song Path: {song_path}")
                self.result_output.text += "\nЗагружаю файл…"
                if song_path and os.path.exists(song_path):
                    if self.current_song:
                        pygame.mixer.music.stop()
                        while pygame.mixer.music.get_busy():
                            time.sleep(0.1)
                        if os.path.exists(self.current_song):
                            os.remove(self.current_song)
                    self.current_song = song_path
                    self.current_position = 0  # Сбрасываем позицию
                    pygame.mixer.music.load(self.current_song)
                    pygame.mixer.music.play()
                    speak(f"Проигрываю песню '{search_query}'", self.tts_engine)
                    self.result_output.text += f"\nПроигрываю песню '{search_query}'"
                else:
                    speak("Не удалось найти песню.", self.tts_engine)
            return

        if "проигрывание" in command:
            if self.current_song and not pygame.mixer.music.get_busy() and self.current_position > 0:
                pygame.mixer.music.load(self.current_song)
                pygame.mixer.music.play(start=self.current_position / 1000)  # Возобновить с сохраненной позиции
                self.result_output.text += "\nВоспроизведение продолжено"
            return

        if "пауза" in command:
            if self.current_song and pygame.mixer.music.get_busy():
                self.current_position = pygame.mixer.music.get_pos()
                pygame.mixer.music.stop()
                self.result_output.text += "\nВоспроизведение приостановлено"
            return

        if "включи" in command:
            if self.current_song:
                if not pygame.mixer.music.get_busy():
                    pygame.mixer.music.play()
                    self.result_output.text += "\nПроигрывание начато"
                else:
                    self.result_output.text += "\nПроигрывание уже идет"
            return

        if "найди" in command or "найти" in command:
            self.stop_playback()
            speak("Что вы хотите найти?", self.tts_engine)
            search_query = listen_command()
            safe_print(f"Search Query: {search_query}")

            if search_query:
                song_path = search_youtube(search_query, self.tts_engine)
                safe_print(f"Song Path: {song_path}")
                if song_path and os.path.exists(song_path):
                    self.current_song = song_path
                    self.current_position = 0  # Сбрасываем позицию
                    pygame.mixer.music.load(self.current_song)
                    pygame.mixer.music.play()
                    speak(f"Проигрываю песню '{search_query}'", self.tts_engine)
                    self.result_output.text += f"\nПроигрываю песню '{search_query}'"
                else:
                    speak("Не удалось найти песню.", self.tts_engine)
            return

        if "громкость" in command:
            parts = command.split()
            if len(parts) > 1:
                number_word = parts[1]
                level = get_number_from_text(number_word)
                if level is not None:
                    set_volume(level, self.tts_engine)
                    self.result_output.text += f"\nГромкость установлена на {level}"
                else:
                    speak("Пожалуйста, укажите уровень громкости от 0 до 10.", self.tts_engine)
            else:
                speak("Пожалуйста, укажите уровень громкости от 0 до 10.", self.tts_engine)
            return

        if "спасибо" in command or "выход" in command or "завершить" in command:
            speak("Завершаю работу. До свидания!", self.tts_engine)
            App.get_running_app().stop()
            return

            # Default behavior when no known command matches
        gpt_response = get_hugginface_response(command, self.tts_engine)
        # gpt_response = get_gpt_response(command, self.tts_engine)
        self.result_output.text += f"\nОтвет: {gpt_response}"
        speak(gpt_response, self.tts_engine)


if __name__ == "__main__":
    try:
        MyApp().run()
    except Exception as e:
        print(f"App crashed: {e}")
        # logger.error(f"App crashed: {e}")
    finally:
        pygame.quit()