Fixes associated to voice commands
This commit is contained in:
parent
606cef3040
commit
d9dc09bca9
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -8,4 +8,6 @@ __pycache__
|
|||
*.db
|
||||
*.db-journal
|
||||
|
||||
build/
|
||||
build/
|
||||
|
||||
audio-logs/
|
||||
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
|
|
@ -22,10 +22,24 @@
|
|||
"request": "launch",
|
||||
"python": "${workspaceFolder}/env/Scripts/python.exe",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/run-transcribe.py",
|
||||
"program": "${workspaceFolder}/run-transcribe-parsing.py",
|
||||
"justMyCode": true,
|
||||
"console": "integratedTerminal",
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "Python: Vosk Transcribe",
|
||||
"type": "debugpy",
|
||||
"request": "launch",
|
||||
"python": "${workspaceFolder}/env/Scripts/python.exe",
|
||||
"cwd": "${workspaceFolder}",
|
||||
"program": "${workspaceFolder}/run-transcribe-vosk.py",
|
||||
"justMyCode": false,
|
||||
"console": "integratedTerminal",
|
||||
"env": {
|
||||
"PYTHONUNBUFFERED": "1"
|
||||
},
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
42
app/api.py
42
app/api.py
|
|
@ -367,3 +367,45 @@ def regenerate_message():
|
|||
except Exception as e:
|
||||
print(f"Ошибка при регенерации сообщения: {e}")
|
||||
return jsonify({"error": str(e)}, 500)
|
||||
|
||||
|
||||
from app.voice_service import VoiceService
|
||||
|
||||
# 1. Создаем экземпляр (обязательно укажите путь к вашей модели Vosk)
|
||||
voice_inst = VoiceService(model_path=r"D:\\Work\\Software_Development\\Projects\\vosk\\vosk-model-small-ru-0.22")
|
||||
|
||||
current_settings = {
|
||||
'interval': 5,
|
||||
'context_limit': 3000,
|
||||
'voice_model': 'gemini-2.0-flash',
|
||||
'marker_start': 'старт',
|
||||
'marker_stop': 'стоп',
|
||||
'voice_context_limit': 3000
|
||||
}
|
||||
|
||||
@api.route('/api/voice/status', methods=['GET'])
|
||||
def get_voice_status():
|
||||
# 2. Вызываем метод у экземпляра voice_inst, а не у модуля
|
||||
return jsonify({
|
||||
"transcription": voice_inst.get_last_chars(3000),
|
||||
"response1": voice_inst.responses["regular"],
|
||||
"response2": voice_inst.responses["commands"][-5:],
|
||||
"is_running": voice_inst.is_running
|
||||
})
|
||||
|
||||
@api.route('/api/voice/control', methods=['POST'])
|
||||
def control_voice():
|
||||
action = request.json.get("action")
|
||||
if action == "start": voice_inst.start_session(False)
|
||||
elif action == "continue": voice_inst.start_session(True)
|
||||
elif action == "stop": voice_inst.stop_session()
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
@api.route('/api/settings/sync', methods=['POST'])
|
||||
def sync_settings():
|
||||
global current_settings
|
||||
data = request.json
|
||||
if data:
|
||||
current_settings.update(data)
|
||||
print(f"⚙️ Settings synced: {current_settings}")
|
||||
return jsonify({"status": "synced"})
|
||||
|
|
@ -12,10 +12,12 @@ logger.setLevel(logging.INFO)
|
|||
# Добавляем обработчик, если его еще нет (чтобы логи выводились в консоль)
|
||||
if not logger.handlers:
|
||||
handler = logging.StreamHandler()
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
logger.addHandler(handler)
|
||||
|
||||
|
||||
class HeartbeatMonitor:
|
||||
"""
|
||||
Мониторит доступность заданных внешних эндпоинтов путем периодических HTTP-запросов.
|
||||
|
|
@ -31,7 +33,9 @@ class HeartbeatMonitor:
|
|||
self._running = False
|
||||
self._thread = None
|
||||
self._last_check_results = {}
|
||||
logger.info(f"HeartbeatMonitor инициализирован с интервалом {interval_minutes} минут.")
|
||||
logger.info(
|
||||
f"HeartbeatMonitor инициализирован с интервалом {interval_minutes} минут."
|
||||
)
|
||||
logger.info(f"Мониторинг URL: {', '.join(self._target_urls)}")
|
||||
|
||||
# Вывод call stack
|
||||
|
|
@ -50,27 +54,50 @@ class HeartbeatMonitor:
|
|||
for url in self._target_urls:
|
||||
start_time = time.time()
|
||||
try:
|
||||
response = requests.get(url, timeout=10) # Таймаут 10 секунд
|
||||
response = requests.get(url,
|
||||
timeout=10) # Таймаут 10 секунд
|
||||
if 200 <= response.status_code < 300:
|
||||
status = {"healthy": True, "status_code": response.status_code, "message": "OK"}
|
||||
logger.info(f"✅ Heartbeat {url}: OK (HTTP {response.status_code})")
|
||||
status = {
|
||||
"healthy": True,
|
||||
"status_code": response.status_code,
|
||||
"message": "OK"
|
||||
}
|
||||
logger.info(
|
||||
f"✅ Heartbeat {url}: OK (HTTP {response.status_code})"
|
||||
)
|
||||
else:
|
||||
status = {"healthy": False, "status_code": response.status_code, "message": f"Non-2xx status code"}
|
||||
logger.warning(f"⚠️ Heartbeat {url}: Ошибка (HTTP {response.status_code})")
|
||||
status = {
|
||||
"healthy": False,
|
||||
"status_code": response.status_code,
|
||||
"message": f"Non-2xx status code"
|
||||
}
|
||||
logger.warning(
|
||||
f"⚠️ Heartbeat {url}: Ошибка (HTTP {response.status_code})"
|
||||
)
|
||||
overall_healthy = False
|
||||
except requests.exceptions.RequestException as e:
|
||||
status = {"healthy": False, "status_code": None, "message": f"Request failed: {e}"}
|
||||
status = {
|
||||
"healthy": False,
|
||||
"status_code": None,
|
||||
"message": f"Request failed: {e}"
|
||||
}
|
||||
logger.error(f"❌ Heartbeat {url}: Ошибка запроса: {e}")
|
||||
overall_healthy = False
|
||||
except Exception as e:
|
||||
status = {"healthy": False, "status_code": None, "message": f"Unexpected error: {e}"}
|
||||
logger.critical(f"🔥 Heartbeat {url}: Неожиданная ошибка: {e}")
|
||||
status = {
|
||||
"healthy": False,
|
||||
"status_code": None,
|
||||
"message": f"Unexpected error: {e}"
|
||||
}
|
||||
logger.critical(
|
||||
f"🔥 Heartbeat {url}: Неожиданная ошибка: {e}")
|
||||
overall_healthy = False
|
||||
finally:
|
||||
status["timestamp"] = datetime.datetime.now().isoformat()
|
||||
status["response_time_ms"] = int((time.time() - start_time) * 1000)
|
||||
status["response_time_ms"] = int(
|
||||
(time.time() - start_time) * 1000)
|
||||
current_results[url] = status
|
||||
|
||||
|
||||
self._last_check_results = current_results
|
||||
if overall_healthy:
|
||||
logger.info("✔ Все Heartbeat-проверки успешно пройдены.")
|
||||
|
|
@ -87,7 +114,8 @@ class HeartbeatMonitor:
|
|||
self._running = True
|
||||
# daemon=True позволяет приложению завершиться, даже если этот поток еще работает.
|
||||
# Python автоматически завершит daemon-потоки при выходе из основной программы.
|
||||
self._thread = threading.Thread(target=self._run_heartbeat_loop, daemon=True)
|
||||
self._thread = threading.Thread(target=self._run_heartbeat_loop,
|
||||
daemon=True)
|
||||
self._thread.start()
|
||||
logger.info("HeartbeatMonitor запущен.")
|
||||
else:
|
||||
|
|
@ -101,9 +129,12 @@ class HeartbeatMonitor:
|
|||
logger.info("Остановка HeartbeatMonitor...")
|
||||
self._running = False
|
||||
if self._thread and self._thread.is_alive():
|
||||
self._thread.join(timeout=5) # Даем потоку 5 секунд на завершение
|
||||
self._thread.join(
|
||||
timeout=5) # Даем потоку 5 секунд на завершение
|
||||
if self._thread.is_alive():
|
||||
logger.warning("HeartbeatMonitor поток не завершился в течение таймаута.")
|
||||
logger.warning(
|
||||
"HeartbeatMonitor поток не завершился в течение таймаута."
|
||||
)
|
||||
logger.info("HeartbeatMonitor остановлен.")
|
||||
else:
|
||||
logger.warning("HeartbeatMonitor не был запущен.")
|
||||
|
|
@ -113,3 +144,65 @@ class HeartbeatMonitor:
|
|||
Возвращает результаты последней проверки (для возможного использования внутри приложения).
|
||||
"""
|
||||
return self._last_check_results
|
||||
|
||||
|
||||
class VoiceHeartbeatMonitor(threading.Thread):
|
||||
|
||||
def __init__(self, voice_instance, generator, settings_fn):
|
||||
super().__init__(daemon=True)
|
||||
self.vs = voice_instance # Тот самый экземпляр из api.py
|
||||
self.generator = generator
|
||||
self.get_settings = settings_fn # Функция, возвращающая текущий конфиг
|
||||
self.last_regular_run = time.time()
|
||||
self.last_command_time = ""
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
if self.vs.is_running:
|
||||
s = self.get_settings()
|
||||
# 1. Проверка команд <start>...<stop>
|
||||
self._check_commands(s)
|
||||
|
||||
# 2. Регулярный запрос
|
||||
if time.time() - self.last_regular_run > s['interval'] * 60:
|
||||
self._run_regular_analysis(s)
|
||||
self.last_regular_run = time.time()
|
||||
time.sleep(10)
|
||||
|
||||
def _check_commands(self, s):
|
||||
# Получаем данные за последние секунды
|
||||
raw_text = self.vs.get_last_chars(s['context_limit'])
|
||||
|
||||
import re
|
||||
# Чистим таймстемпы Vosk для LLM (удаляем паттерны [00:00:00])
|
||||
clean_text = re.sub(r'\[.*?\]', '', raw_text)
|
||||
|
||||
pattern = f"{re.escape(s['marker_start'])}(.*?){re.escape(s['marker_stop'])}"
|
||||
matches = re.findall(pattern, clean_text, re.DOTALL)
|
||||
|
||||
for match in matches:
|
||||
cmd = match.strip()
|
||||
# Проверяем, не обрабатывали ли мы ЭТУ конкретную строку только что
|
||||
# Используем множество для хранения хешей обработанных команд
|
||||
if not hasattr(self, '_processed_hashes'): self._processed_hashes = set()
|
||||
|
||||
cmd_hash = hash(cmd)
|
||||
if cmd_hash not in self._processed_hashes:
|
||||
print(f"🎤 New Voice Command detected: {cmd}")
|
||||
|
||||
# ОТПРАВЛЯЕМ В LLM через TitleGenerator
|
||||
self.generator.add_voice_task_to_queue(
|
||||
cmd,
|
||||
s['prompt_commands'] # Это путь к .md файлу из настроек
|
||||
)
|
||||
|
||||
self._processed_hashes.add(cmd_hash)
|
||||
# Ограничиваем размер множества, чтобы не росло бесконечно
|
||||
if len(self._processed_hashes) > 100: self._processed_hashes.clear()
|
||||
|
||||
def _run_regular_analysis(self, s):
|
||||
raw_text = self.vs.get_last_chars(s['voice_context_limit'])
|
||||
if not raw_text.strip(): return
|
||||
|
||||
# Добавим новый тип 'voice_regular' вTitleGenerator аналогично 'voice_command'
|
||||
self.generator.add_voice_regular_task(raw_text, s['prompt_regular'])
|
||||
|
|
@ -88,6 +88,19 @@ MODELS: Dict[str, Dict[str, Any]] = {
|
|||
"stream": True,
|
||||
"capabilities": ["vision"],
|
||||
},
|
||||
"gemini-3.0-flash-openrouter": {
|
||||
"name": "google/gemini-3-flash-preview",
|
||||
"provider": "openai",
|
||||
"model_name": "google/gemini-3-flash-preview",
|
||||
"apiBase": "https://openrouter.ai/api/v1", # Добавлено /api/v1
|
||||
"apiKey":
|
||||
"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",
|
||||
"stream": True,
|
||||
"capabilities": ["vision", "reasoning"],
|
||||
"model_kwargs": {
|
||||
"include_reasoning": True
|
||||
}
|
||||
},
|
||||
"gemini-3.1-pro-preview-bh": {
|
||||
"name": "gemini-3.1-pro-preview",
|
||||
"provider": "openai", # Изменено на "openai"
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from typing import Any, Dict, Optional
|
|||
from nodes import DEFAULT_SUMMARIZATION_LLM_NAME
|
||||
from llm_client import get_llm
|
||||
from langchain_core.messages import SystemMessage, HumanMessage
|
||||
import os
|
||||
|
||||
MAX_TITLE_GENERATION_CONTENT_LENGTH = 5000 # Максимальное количество символов для генерации заголовков
|
||||
|
||||
|
|
@ -128,6 +129,11 @@ class TitleGenerator:
|
|||
self._generate_graph_title(item["graph_id"])
|
||||
elif item["item_type"] == "node":
|
||||
self._generate_node_title(item["graph_id"], item["node_id"])
|
||||
# ОБРАБОТКА ГОЛОСА
|
||||
elif item["item_type"] == "voice_command":
|
||||
self._process_voice_command(item["node_id"], item["graph_id"])
|
||||
elif item["item_type"] == "voice_regular":
|
||||
self._process_voice_regular(item["node_id"], item["graph_id"])
|
||||
else:
|
||||
# Если очередь пуста, ждем немного
|
||||
time.sleep(5)
|
||||
|
|
@ -337,4 +343,66 @@ class TitleGenerator:
|
|||
time.sleep(0.5)
|
||||
|
||||
if not ack_received.is_set():
|
||||
print(f"❌ Не удалось доставить {event_name} после {max_retries} попыток: {data}")
|
||||
print(f"❌ Не удалось доставить {event_name} после {max_retries} попыток: {data}")
|
||||
|
||||
|
||||
# VOICE
|
||||
|
||||
def add_voice_task_to_queue(self, command_text, prompt, priority=20):
|
||||
"""Добавляет задачу на обработку голосовой команды в очередь."""
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
# Используем node_id для хранения текста команды,
|
||||
# а item_type 'voice_command' для идентификации
|
||||
cursor.execute(
|
||||
"INSERT INTO title_generation_queue (item_type, graph_id, node_id, priority) VALUES (?, ?, ?, ?)",
|
||||
("voice_command", prompt, command_text, priority))
|
||||
conn.commit()
|
||||
|
||||
# 2. Добавьте метод постановки регулярной задачи
|
||||
def add_voice_regular_task(self, transcription_text, prompt, priority=15):
|
||||
with self._get_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"INSERT INTO title_generation_queue (item_type, graph_id, node_id, priority) VALUES (?, ?, ?, ?)",
|
||||
("voice_regular", prompt, transcription_text, priority))
|
||||
conn.commit()
|
||||
|
||||
# 3. Добавьте метод обработки регулярной задачи
|
||||
def _process_voice_regular(self, text, prompt):
|
||||
try:
|
||||
print(f"📡 Анализ транскрибации (Prompt 1)...")
|
||||
llm_messages = [
|
||||
SystemMessage(content=prompt),
|
||||
HumanMessage(content=f"Последний фрагмент диалога для анализа: {text}")
|
||||
]
|
||||
response = self.llm.invoke(llm_messages)
|
||||
|
||||
import app.api as api
|
||||
api.voice_inst.responses["regular"] = response # Записываем в Response 1
|
||||
print(f"✅ Регулярный анализ завершен.")
|
||||
except Exception as e:
|
||||
print(f"Ошибка регулярного анализа: {e}")
|
||||
|
||||
# 4. Поправьте _process_voice_command (запись команды в Response 2)
|
||||
def _process_voice_command(self, text, prompt):
|
||||
try:
|
||||
print(f"🤖 Обработка голосовой команды (Prompt 2)...")
|
||||
llm_messages = [
|
||||
SystemMessage(content=prompt),
|
||||
HumanMessage(content=f"Выполни команду из транскрибации: {text}")
|
||||
]
|
||||
response = self.llm.invoke(llm_messages)
|
||||
|
||||
import app.api as api
|
||||
# Форматируем для вкладки Response 2
|
||||
formatted_res = f"<b>Команда:</b> {text}<br><b>Ответ:</b> {response}"
|
||||
api.voice_inst.responses["commands"].append(formatted_res)
|
||||
|
||||
# Ограничиваем историю команд, чтобы не раздувать память
|
||||
if len(api.voice_inst.responses["commands"]) > 20:
|
||||
api.voice_inst.responses["commands"].pop(0)
|
||||
|
||||
print(f"✅ Команда обработана.")
|
||||
except Exception as e:
|
||||
print(f"Ошибка обработки команды: {e}")
|
||||
137
app/voice_service.py
Normal file
137
app/voice_service.py
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
import os
|
||||
import json
|
||||
import threading
|
||||
import datetime
|
||||
import time
|
||||
import numpy as np
|
||||
from vosk import Model, KaldiRecognizer
|
||||
import sounddevice as sd
|
||||
|
||||
class VoiceService:
|
||||
def __init__(self, model_path, samplerate=48000):
|
||||
self.model_path = model_path
|
||||
self.samplerate = samplerate
|
||||
self.model = Model(model_path)
|
||||
self.is_running = False
|
||||
self.current_file = None
|
||||
self.last_processed_timestamp = 0.0
|
||||
self.responses = {"regular": "", "commands": []}
|
||||
self.lock = threading.Lock()
|
||||
self._stop_event = threading.Event()
|
||||
|
||||
def get_last_chars(self, n):
|
||||
"""Метод для получения последних N символов лога."""
|
||||
if not self.current_file or not os.path.exists(self.current_file):
|
||||
return "Лог-файл пуст или не создан."
|
||||
|
||||
try:
|
||||
with self.lock:
|
||||
with open(self.current_file, "r", encoding="utf-8") as f:
|
||||
f.seek(0, os.SEEK_END)
|
||||
size = f.tell()
|
||||
# Читаем последние байты с запасом (UTF-8 до 4 байт на символ)
|
||||
f.seek(max(0, size - n * 4))
|
||||
content = f.read()
|
||||
return content[-n:]
|
||||
except Exception as e:
|
||||
return f"Ошибка чтения: {str(e)}"
|
||||
|
||||
def start_session(self, continue_last=False):
|
||||
if self.is_running: return
|
||||
|
||||
log_dir = "audio-logs"
|
||||
if not os.path.exists(log_dir): os.makedirs(log_dir)
|
||||
|
||||
if continue_last:
|
||||
files = sorted([f for f in os.listdir(log_dir) if f.endswith(".txt")])
|
||||
self.current_file = os.path.join(log_dir, files[-1]) if files else self._create_new_file(log_dir)
|
||||
else:
|
||||
self.current_file = self._create_new_file(log_dir)
|
||||
|
||||
self.is_running = True
|
||||
self._stop_event.clear()
|
||||
threading.Thread(target=self._run_transcription, daemon=True).start()
|
||||
|
||||
def _create_new_file(self, log_dir):
|
||||
name = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S.txt")
|
||||
path = os.path.join(log_dir, name)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
f.write(f"--- Session Start: {name} ---\n\n")
|
||||
return path
|
||||
|
||||
def stop_session(self):
|
||||
self.is_running = False
|
||||
self._stop_event.set()
|
||||
|
||||
def _run_transcription(self):
|
||||
# 1. Поиск устройств (строго перед запуском потока)
|
||||
id_gm, id_pcs = None, None
|
||||
devices = sd.query_devices()
|
||||
hostapis = sd.query_hostapis()
|
||||
mme_api_index = next((i for i, h in enumerate(hostapis) if h['name'] == 'MME'), 0)
|
||||
|
||||
for i, d in enumerate(devices):
|
||||
# Проверяем, что устройство относится к MME и содержит нужное имя
|
||||
if d['hostapi'] == mme_api_index:
|
||||
if "Voicemeeter Out B1" in d['name']: id_gm = i
|
||||
if "Voicemeeter Out B2" in d['name']: id_pcs = i
|
||||
|
||||
if id_gm is None or id_pcs is None:
|
||||
# Если MME не нашел, пробуем любой
|
||||
for i, d in enumerate(devices):
|
||||
if "Voicemeeter Out B1" in d['name']: id_gm = i
|
||||
if "Voicemeeter Out B2" in d['name']: id_pcs = i
|
||||
|
||||
# 2. Создаем рекогнайзеры ВНУТРИ потока
|
||||
rec_gm = KaldiRecognizer(self.model, self.samplerate)
|
||||
rec_pcs = KaldiRecognizer(self.model, self.samplerate)
|
||||
|
||||
# 3. Фабрика колбэков
|
||||
def create_callback(rec, role):
|
||||
def callback_func(indata, frames, time_info, status):
|
||||
if status:
|
||||
print(f"Audio Status ({role}): {status}")
|
||||
if np.max(np.abs(indata)) < 0.001:
|
||||
return
|
||||
|
||||
# Важно: indata уже в формате bytes для RawInputStream
|
||||
if rec.AcceptWaveform(bytes(indata)):
|
||||
res = json.loads(rec.Result())
|
||||
text = res.get('text', '')
|
||||
if text:
|
||||
now = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
|
||||
entry = f"[{now}] {role}: {text}\n"
|
||||
# Пишем в файл
|
||||
try:
|
||||
with self.lock:
|
||||
with open(self.current_file, "a", encoding="utf-8") as f:
|
||||
f.write(entry)
|
||||
except Exception as e:
|
||||
print(f"File write error: {e}")
|
||||
return callback_func
|
||||
|
||||
# 4. Запуск стримов
|
||||
try:
|
||||
# Используем те же параметры, что в рабочем демо
|
||||
with sd.RawInputStream(samplerate=self.samplerate,
|
||||
blocksize=8000,
|
||||
device=id_gm,
|
||||
dtype='int16',
|
||||
channels=1,
|
||||
callback=create_callback(rec_gm, "GM")), \
|
||||
sd.RawInputStream(samplerate=self.samplerate,
|
||||
blocksize=8000,
|
||||
device=id_pcs,
|
||||
dtype='int16',
|
||||
channels=1,
|
||||
callback=create_callback(rec_pcs, "PCs")):
|
||||
|
||||
print(f"✅ Транскрибация запущена (IDs: {id_gm}, {id_pcs})")
|
||||
while not self._stop_event.is_set():
|
||||
self._stop_event.wait(1.0) # Экономнее чем sleep
|
||||
|
||||
except Exception as e:
|
||||
print(f"🔥 Критическая ошибка транскрибации: {e}")
|
||||
finally:
|
||||
self.is_running = False
|
||||
print("🏁 Сессия транскрибации завершена")
|
||||
|
|
@ -12,4 +12,8 @@ flask_socketio
|
|||
|
||||
mistralai==1.9.10
|
||||
|
||||
selenium
|
||||
selenium
|
||||
|
||||
vosk==0.3.45
|
||||
sounddevice==0.5.1
|
||||
numpy==1.26.4
|
||||
14
run.py
14
run.py
|
|
@ -12,8 +12,9 @@ logger = logging.getLogger(__name__)
|
|||
if __name__ == "__main__":
|
||||
# Добавляем папку app в Python path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
||||
from app.api import api
|
||||
from app.api import api, voice_inst, current_settings
|
||||
from app.heartbeat_monitor import HeartbeatMonitor
|
||||
from app.workflows import graph_history_manager
|
||||
|
||||
logger.info("🚀 Запуск LLM Agent Backend сервера...")
|
||||
logger.info("📍 API будет доступно на: http://localhost:5000/api")
|
||||
|
|
@ -31,6 +32,17 @@ if __name__ == "__main__":
|
|||
atexit.register(heartbeat_monitor.stop)
|
||||
logger.info("HeartbeatMonitor запущен.")
|
||||
|
||||
|
||||
from app.heartbeat_monitor import VoiceHeartbeatMonitor
|
||||
|
||||
v_monitor = VoiceHeartbeatMonitor(
|
||||
voice_inst,
|
||||
graph_history_manager.title_generator,
|
||||
lambda: current_settings # Теперь берет настройки из api.py
|
||||
)
|
||||
v_monitor.start()
|
||||
|
||||
|
||||
api.run(
|
||||
debug=True,
|
||||
port=5000,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user