ускоренная работа
This commit is contained in:
@@ -93,6 +93,63 @@ def ask_ai(messages_history: list) -> str:
|
||||
return response
|
||||
|
||||
|
||||
def ask_ai_stream(messages_history: list):
|
||||
"""
|
||||
Generator that yields chunks of the AI response as they arrive.
|
||||
"""
|
||||
if not messages_history:
|
||||
yield "Извините, я не расслышал вашу команду."
|
||||
return
|
||||
|
||||
# Log the last user message
|
||||
last_user_message = "Unknown"
|
||||
for msg in reversed(messages_history):
|
||||
if msg["role"] == "user":
|
||||
last_user_message = msg["content"]
|
||||
break
|
||||
print(f"🤖 Запрос к AI (Stream): {last_user_message}")
|
||||
|
||||
messages = [{"role": "system", "content": SYSTEM_PROMPT}] + list(messages_history)
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {PERPLEXITY_API_KEY}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
payload = {
|
||||
"model": PERPLEXITY_MODEL,
|
||||
"messages": messages,
|
||||
"max_tokens": 500,
|
||||
"temperature": 1.0,
|
||||
"stream": True, # Enable streaming
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
PERPLEXITY_API_URL, headers=headers, json=payload, timeout=30, stream=True
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
import json
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
line_text = line.decode("utf-8")
|
||||
if line_text.startswith("data: "):
|
||||
data_str = line_text[6:] # Skip "data: "
|
||||
if data_str == "[DONE]":
|
||||
break
|
||||
try:
|
||||
data_json = json.loads(data_str)
|
||||
content = data_json["choices"][0]["delta"].get("content", "")
|
||||
if content:
|
||||
yield content
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"❌ Streaming Error: {e}")
|
||||
yield "Произошла ошибка связи."
|
||||
|
||||
|
||||
def translate_text(text: str, source_lang: str, target_lang: str) -> str:
|
||||
"""
|
||||
Запрос к AI в режиме перевода.
|
||||
|
||||
27
app/core/audio_manager.py
Normal file
27
app/core/audio_manager.py
Normal file
@@ -0,0 +1,27 @@
|
||||
import pyaudio
|
||||
import threading
|
||||
|
||||
|
||||
class AudioManager:
|
||||
_instance = None
|
||||
_lock = threading.Lock()
|
||||
|
||||
def __new__(cls):
|
||||
with cls._lock:
|
||||
if cls._instance is None:
|
||||
cls._instance = super(AudioManager, cls).__new__(cls)
|
||||
cls._instance.pa = pyaudio.PyAudio()
|
||||
print("🔊 AudioManager: PyAudio initialized (Global)")
|
||||
return cls._instance
|
||||
|
||||
def get_pyaudio(self):
|
||||
return self.pa
|
||||
|
||||
def cleanup(self):
|
||||
if self.pa:
|
||||
self.pa.terminate()
|
||||
self.pa = None
|
||||
|
||||
|
||||
def get_audio_manager():
|
||||
return AudioManager()
|
||||
@@ -44,6 +44,14 @@ PREPOSITION_CASES = {
|
||||
"перед": "ablt",
|
||||
"за": "ablt",
|
||||
"между": "ablt",
|
||||
"около": "gent",
|
||||
"против": "gent",
|
||||
"вместо": "gent",
|
||||
"кроме": "gent",
|
||||
"из-за": "gent",
|
||||
"сквозь": "accs",
|
||||
"через": "accs",
|
||||
"про": "accs",
|
||||
}
|
||||
|
||||
# Соответствие падежей pymorphy и библиотеки num2words
|
||||
@@ -60,6 +68,13 @@ PYMORPHY_TO_NUM2WORDS = {
|
||||
"loc2": "prepositional",
|
||||
}
|
||||
|
||||
# Соответствие родов pymorphy и num2words
|
||||
PYMORPHY_TO_GENDER = {
|
||||
"masc": "m",
|
||||
"femn": "f",
|
||||
"neut": "n",
|
||||
}
|
||||
|
||||
# Названия месяцев в родительном падеже (для поиска дат в тексте)
|
||||
MONTHS_GENITIVE = [
|
||||
"января",
|
||||
@@ -123,6 +138,12 @@ def numbers_to_words(text: str) -> str:
|
||||
|
||||
nw_case = PYMORPHY_TO_NUM2WORDS.get(case_tag, "nominative")
|
||||
|
||||
# FIX: Pymorphy часто определяет "год" как accs (винительный), что для num2words
|
||||
# превращается в родительный (для одушевленных?), давая "2024 года".
|
||||
# Если предлога нет, принудительно ставим именительный.
|
||||
if not prep and year_word.lower().startswith("год"):
|
||||
nw_case = "nominative"
|
||||
|
||||
# Конвертируем число в порядковое числительное (тысяча девятьсот девяносто девятом)
|
||||
words = convert_number(
|
||||
year_str, context_type="ordinal", case=nw_case, gender="m"
|
||||
@@ -171,9 +192,9 @@ def numbers_to_words(text: str) -> str:
|
||||
prefix = f"{prep} " if prep else ""
|
||||
return f"{prefix}{words} {month_word}"
|
||||
|
||||
# Конкатенация regex для месяцев (ВАЖНО: month_regex должен быть вставлен в строку)
|
||||
# Конкатенация regex для месяцев (FIX: используем f-строку)
|
||||
text = re.sub(
|
||||
r"(?i)\b((?:с|к|до|от|на|по)\s+)?(\d{1,2})\s+({month_regex})\b",
|
||||
rf"(?i)\b((?:с|к|до|от|на|по)\s+)?(\d{{1,2}})\s+({month_regex})\b",
|
||||
replace_date_match,
|
||||
text,
|
||||
)
|
||||
@@ -182,20 +203,41 @@ def numbers_to_words(text: str) -> str:
|
||||
def replace_cardinal_match(match):
|
||||
prep = match.group(1)
|
||||
num_str = match.group(2)
|
||||
next_word = match.group(3)
|
||||
|
||||
case = "nominative"
|
||||
gender = "m"
|
||||
|
||||
if prep:
|
||||
morph_case = get_case_from_preposition(prep.strip())
|
||||
if morph_case:
|
||||
case = PYMORPHY_TO_NUM2WORDS.get(morph_case, "nominative")
|
||||
|
||||
words = convert_number(num_str, context_type="cardinal", case=case)
|
||||
# Если есть следующее слово, проверяем его род (для "2 минуты" -> "две")
|
||||
if next_word:
|
||||
word_clean = next_word.strip()
|
||||
parsed = morph.parse(word_clean)[0]
|
||||
if "NOUN" in parsed.tag:
|
||||
morph_gender = parsed.tag.gender
|
||||
gender = PYMORPHY_TO_GENDER.get(morph_gender, "m")
|
||||
|
||||
words = convert_number(
|
||||
num_str, context_type="cardinal", case=case, gender=gender
|
||||
)
|
||||
|
||||
# Если конвертация вернула пустую строку (сбой?), возвращаем цифры
|
||||
if not words:
|
||||
words = num_str
|
||||
|
||||
prefix = f"{prep} " if prep else ""
|
||||
# suffix removed (lookahead)
|
||||
return f"{prefix}{words}"
|
||||
|
||||
# Регулярка теперь захватывает (опционально) следующее слово для определения рода
|
||||
|
||||
preps_list = "|".join(map(re.escape, PREPOSITION_CASES.keys()))
|
||||
text = re.sub(
|
||||
r"(?i)\b((?:в|на|о|об|обо|при|у|от|до|из|с|со|без|для|вокруг|после|к|ко|по|над|под|перед|за|между)\s+)?(\d+(?:[.,]\d+)?)\b",
|
||||
rf"(?i)\b((?:{preps_list})\s+)?(\d+(?:[.,]\d+)?)(?=(\s+[а-яА-ЯёЁ]+))?\b",
|
||||
replace_cardinal_match,
|
||||
text,
|
||||
)
|
||||
@@ -234,13 +276,13 @@ def clean_response(text: str, language: str = "ru") -> str:
|
||||
# Удаление заголовков Markdown (# Header)
|
||||
text = re.sub(r"^#{1,6}\s*", "", text, flags=re.MULTILINE)
|
||||
|
||||
# Удаление картинок  -> удаляем полностью
|
||||
text = re.sub(r"!\x5B([^\x5D]*)\x5D\([^)]+\)", "", text)
|
||||
|
||||
# Удаление ссылок [text](url) -> оставляем только text
|
||||
# \x5B = [, \x5D = ]
|
||||
text = re.sub(r"\x5B([^\x5D]+)\x5D\([^)]+\)", r"\1", text)
|
||||
|
||||
# Удаление картинок  -> удаляем полностью
|
||||
text = re.sub(r"!\x5B([^\x5D]*)\x5D\([^)]+\)", "", text)
|
||||
|
||||
# Удаление inline кода `code`
|
||||
text = re.sub(r"`([^`]+)`", r"\1", text)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user