diff --git a/app/core/ai.py b/app/core/ai.py index 5be899c..4d0c35b 100644 --- a/app/core/ai.py +++ b/app/core/ai.py @@ -7,6 +7,7 @@ import requests import re from .config import PERPLEXITY_API_KEY, PERPLEXITY_MODEL, PERPLEXITY_API_URL +_HTTP = requests.Session() # Системный промпт (инструкция) для AI. # Задает личность ассистента: имя "Александр", стиль общения, краткость. @@ -53,7 +54,7 @@ def _send_request(messages, max_tokens, temperature, error_text): } try: - response = requests.post( + response = _HTTP.post( PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15 # Уменьшаем таймаут ) response.raise_for_status() # Проверка на ошибки HTTP (4xx, 5xx) @@ -134,16 +135,16 @@ def ask_ai_stream(messages_history: list): } try: - response = requests.post( + response = _HTTP.post( PERPLEXITY_API_URL, headers=headers, json=payload, timeout=15, stream=True # Уменьшаем таймаут ) response.raise_for_status() import json - for line in response.iter_lines(): + for line in response.iter_lines(decode_unicode=True): if line: - line_text = line.decode("utf-8") + line_text = line if line_text.startswith("data: "): data_str = line_text[6:] # Skip "data: " if data_str == "[DONE]": diff --git a/app/features/weather.py b/app/features/weather.py index 9196e55..fc0e105 100644 --- a/app/features/weather.py +++ b/app/features/weather.py @@ -7,6 +7,7 @@ import requests from datetime import datetime from ..core.config import WEATHER_LAT, WEATHER_LON, WEATHER_CITY +_HTTP = requests.Session() def get_wmo_description(code: int) -> str: """Decodes WMO weather code to Russian description.""" codes = { @@ -348,7 +349,7 @@ def get_coordinates_by_city(city_name: str) -> tuple: "format": "json" } - response = requests.get(geocode_url, params=params, timeout=10) + response = _HTTP.get(geocode_url, params=params, timeout=10) response.raise_for_status() data = response.json() @@ -403,7 +404,7 @@ def get_weather_report(requested_city: str = None) -> str: } try: - response = requests.get(url, params=params, timeout=10) + response = _HTTP.get(url, params=params, timeout=10) response.raise_for_status() data = response.json() diff --git a/app/main.py b/app/main.py index 8208d58..50e1899 100644 --- a/app/main.py +++ b/app/main.py @@ -58,6 +58,120 @@ from .features.weather import get_weather_report from .features.music import get_music_controller from .features.cities_game import get_cities_game +_TRANSLATION_COMMANDS = [ + ("переведи на английский с русского", "ru", "en"), + ("переведи на русский с английского", "en", "ru"), + ("переведи на английский язык с русского", "ru", "en"), + ("переведи на русский язык с английского", "en", "ru"), + ("переведи с русского на английский", "ru", "en"), + ("переведи с русского в английский", "ru", "en"), + ("переведи с английского на русский", "en", "ru"), + ("переведи с английского в русский", "en", "ru"), + ("переведи с русского языка", "ru", "en"), + ("переведи с английского языка", "en", "ru"), + ("переведи на английский язык", "ru", "en"), + ("переведи на русский язык", "en", "ru"), + ("переведи на английский", "ru", "en"), + ("переведи на русский", "en", "ru"), + ("переведи с английского", "en", "ru"), + ("переведи с русского", "ru", "en"), + ("как по-английски", "ru", "en"), + ("как по английски", "ru", "en"), + ("как по-русски", "en", "ru"), + ("как по русски", "en", "ru"), + ("translate to english from russian", "ru", "en"), + ("translate to russian from english", "en", "ru"), + ("translate from russian to english", "ru", "en"), + ("translate from english to russian", "en", "ru"), + ("translate into english", "ru", "en"), + ("translate into russian", "en", "ru"), + ("translate to english", "ru", "en"), + ("translate to russian", "en", "ru"), + ("translate from english", "en", "ru"), + ("translate from russian", "ru", "en"), +] +_TRANSLATION_COMMANDS_SORTED = sorted( + _TRANSLATION_COMMANDS, key=lambda item: len(item[0]), reverse=True +) + +_REPEAT_PHRASES = { + "еще раз", + "повтори", + "скажи еще раз", + "что ты сказал", + "повтори пожалуйста", + "александр еще раз", + "еще раз александр", + "александр повтори", + "повтори александр", +} + +_WEATHER_TRIGGERS = ( + "погода", + "погоду", + "что на улице", + "какая температура", + "сколько градусов", + "холодно ли", + "жарко ли", + "нужен ли зонт", + "брать ли зонт", + "прогноз погоды", + "че там на улице", + "что там на улице", + "как на улице", + "как на улице-то", +) + +_CITY_INVALID_WORDS = { + "этом", + "том", + "той", + "тут", + "здесь", + "там", + "всё", + "все", + "всей", + "всего", + "всем", + "всеми", + "городе", + "город", + "село", + "деревня", + "посёлок", + "аул", + "станция", + "область", + "район", + "край", + "республика", +} + +_CITY_PATTERNS = [ + re.compile( + r"в\s+городе\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", + re.IGNORECASE, + ), + re.compile( + r"в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", + re.IGNORECASE, + ), + re.compile( + r"погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", + re.IGNORECASE, + ), + re.compile( + r"погода\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)\s+(?:какая|сейчас|там)", + re.IGNORECASE, + ), + re.compile( + r"(?:какая|как)\s+погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", + re.IGNORECASE, + ), +] + def signal_handler(sig, frame): """ Обработчик сигнала Ctrl+C. @@ -87,42 +201,7 @@ def parse_translation_request(text: str): # Список префиксов команд перевода и соответствующих направлений языков. # Важно: более длинные префиксы должны проверяться первыми (например, # "переведи с русского на английский" не должен схватиться как "переведи с русского"). - commands = [ - ("переведи на английский с русского", "ru", "en"), - ("переведи на русский с английского", "en", "ru"), - ("переведи на английский язык с русского", "ru", "en"), - ("переведи на русский язык с английского", "en", "ru"), - ("переведи с русского на английский", "ru", "en"), - ("переведи с русского в английский", "ru", "en"), - ("переведи с английского на русский", "en", "ru"), - ("переведи с английского в русский", "en", "ru"), - ("переведи с русского языка", "ru", "en"), - ("переведи с английского языка", "en", "ru"), - ("переведи на английский язык", "ru", "en"), - ("переведи на русский язык", "en", "ru"), - ("переведи на английский", "ru", "en"), - ("переведи на русский", "en", "ru"), - ("переведи с английского", "en", "ru"), - ("переведи с русского", "ru", "en"), - ("как по-английски", "ru", "en"), - ("как по английски", "ru", "en"), - ("как по-русски", "en", "ru"), - ("как по русски", "en", "ru"), - ("translate to english from russian", "ru", "en"), - ("translate to russian from english", "en", "ru"), - ("translate from russian to english", "ru", "en"), - ("translate from english to russian", "en", "ru"), - ("translate into english", "ru", "en"), - ("translate into russian", "en", "ru"), - ("translate to english", "ru", "en"), - ("translate to russian", "en", "ru"), - ("translate from english", "en", "ru"), - ("translate from russian", "ru", "en"), - ] - - for prefix, source_lang, target_lang in sorted( - commands, key=lambda item: len(item[0]), reverse=True - ): + for prefix, source_lang, target_lang in _TRANSLATION_COMMANDS_SORTED: if text_lower.startswith(prefix): # Отрезаем команду (префикс), оставляем только текст для перевода rest = text[len(prefix) :].strip() @@ -284,19 +363,8 @@ def main(): # Проверка на команду "Повтори" / "Еще раз" user_text_lower = user_text.lower().strip() - repeat_phrases = [ - "еще раз", - "повтори", - "скажи еще раз", - "что ты сказал", - "повтори пожалуйста", - "александр еще раз", - "еще раз александр", - "александр повтори", - "повтори александр", - ] # Проверяем точное совпадение или если фраза начинается с "повтори" (но не "повтори за мной") - if user_text_lower in repeat_phrases or ( + if user_text_lower in _REPEAT_PHRASES or ( user_text_lower.startswith("повтори") and "за мной" not in user_text_lower ): @@ -358,49 +426,28 @@ def main(): continue # Проверка команды "Погода" - weather_triggers = [ - "погода", - "погоду", - "что на улице", - "какая температура", - "сколько градусов", - "холодно ли", - "жарко ли", - "нужен ли зонт", - "брать ли зонт", - "прогноз погоды", - "че там на улице", - "что там на улице", - "как на улице", - "как на улице-то", - ] - # Проверяем, содержит ли запрос информацию о конкретном городе requested_city = None user_text_lower = user_text.lower() # Проверяем наличие упоминания города в запросе (например, "погода в Нью-Йорке", "какая погода в Москве") - import re - city_patterns = [ - r"в\s+городе\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "в городе Волгоград" - r"в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "в Нью-Йорке" - улучшенный паттерн для составных названий - r"погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "погода в Москве" - улучшенный паттерн - r"погода\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)\s+(?:какая|сейчас|там)", # "погода Москва какая" - r"(?:какая|как)\s+погода\s+в\s+([а-яёa-z]+[-\s]*[а-яёa-z]*(?:[-\s]+[а-яёa-z]+)*)", # "какая погода в Москве" - ] - - for pattern in city_patterns: - match = re.search(pattern, user_text_lower, re.IGNORECASE) + for pattern in _CITY_PATTERNS: + match = pattern.search(user_text_lower) if match: potential_city = match.group(1).strip() # Проверяем, что это не местоимение или другое слово, а реально название города - invalid_words = ["этом", "том", "той", "тут", "здесь", "там", "всё", "все", "всей", "всего", "всем", "всеми", "городе", "город", "село", "деревня", "посёлок", "аул", "станция", "область", "район", "край", "республика"] - if potential_city and len(potential_city) > 1 and not any(word in potential_city for word in invalid_words): + if ( + potential_city + and len(potential_city) > 1 + and not any(word in potential_city for word in _CITY_INVALID_WORDS) + ): requested_city = potential_city.title() # Приводим к формату "Нью-Йорк", "Москва" break # Проверяем, содержит ли запрос одну из погодных команд - has_weather_trigger = any(trigger in user_text_lower for trigger in weather_triggers) + has_weather_trigger = any( + trigger in user_text_lower for trigger in _WEATHER_TRIGGERS + ) if has_weather_trigger: from .features.weather import get_weather_report