From a737c3125a1031e4594f866732153bce568de50a Mon Sep 17 00:00:00 2001 From: dimitrievgs Date: Fri, 1 May 2026 20:29:50 +0300 Subject: [PATCH] Fix settings sync --- app/api.py | 18 +++++++++++------- app/heartbeat_monitor.py | 41 +++++++++++++++++++++------------------- app/llm_client.py | 13 +++++++++++++ app/title_generator.py | 22 +++++++++++---------- app/voice_service.py | 8 +++++--- run.py | 6 +++--- 6 files changed, 66 insertions(+), 42 deletions(-) diff --git a/app/api.py b/app/api.py index 82c3bd6..244c148 100644 --- a/app/api.py +++ b/app/api.py @@ -369,12 +369,12 @@ def regenerate_message(): return jsonify({"error": str(e)}, 500) -from app.voice_service import VoiceService +from app.voice_service import VoiceService, VOICE_COMMANDS_RESPONSE_TO_STORE # 1. Создаем экземпляр (обязательно укажите путь к вашей модели Vosk) voice_inst = VoiceService(model_path=r"D:\\Work\\Software_Development\\Projects\\vosk\\vosk-model-small-ru-0.22") -current_settings = { +current_obsidian_settings = { 'interval': 5, 'context_limit': 3000, 'voice_model': 'gemini-2.0-flash', @@ -383,14 +383,17 @@ current_settings = { 'voice_context_limit': 3000 } +obsidian_settings_fetched = False + @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 + "response2": voice_inst.responses["commands"][:VOICE_COMMANDS_RESPONSE_TO_STORE], + "is_running": voice_inst.is_running, + "obsidian_settings_fetched": obsidian_settings_fetched }) @api.route('/api/voice/control', methods=['POST']) @@ -403,9 +406,10 @@ def control_voice(): @api.route('/api/settings/sync', methods=['POST']) def sync_settings(): - global current_settings + global current_obsidian_settings, obsidian_settings_fetched + obsidian_settings_fetched = True data = request.json if data: - current_settings.update(data) - print(f"⚙️ Settings synced: {current_settings}") + current_obsidian_settings.update(data) + print(f"✅⚙️ Settings synced: {current_obsidian_settings}") return jsonify({"status": "synced"}) \ No newline at end of file diff --git a/app/heartbeat_monitor.py b/app/heartbeat_monitor.py index 7b8e417..5f3180b 100644 --- a/app/heartbeat_monitor.py +++ b/app/heartbeat_monitor.py @@ -148,36 +148,39 @@ class HeartbeatMonitor: class VoiceHeartbeatMonitor(threading.Thread): - def __init__(self, voice_instance, generator, settings_fn): + def __init__(self, voice_instance, generator, get_obsidian_settings_fn): super().__init__(daemon=True) self.vs = voice_instance # Тот самый экземпляр из api.py self.generator = generator - self.get_settings = settings_fn # Функция, возвращающая текущий конфиг + self.get_obsidian_settings = get_obsidian_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. Проверка команд ... - self._check_commands(s) + try: + if self.vs.is_running: + obsidian_settings = self.get_obsidian_settings() + # 1. Проверка команд ... + self._check_commands(obsidian_settings) - # 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) + # 2. Регулярный запрос + if time.time() - self.last_regular_run > obsidian_settings['interval'] * 60: + self._run_regular_analysis(obsidian_settings) + self.last_regular_run = time.time() + time.sleep(3) # каждые 3 секунды проверяем + except Exception as e: + logger.critical(f"❌🎤 Voice Heartbeat: Неожиданная ошибка: {e}") - def _check_commands(self, s): + def _check_commands(self, obsidian_settings): # Получаем данные за последние секунды - raw_text = self.vs.get_last_chars(s['context_limit']) + raw_text = self.vs.get_last_chars(obsidian_settings['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'])}" + pattern = f"{re.escape(obsidian_settings['marker_start'])}(.*?){re.escape(obsidian_settings['marker_stop'])}" matches = re.findall(pattern, clean_text, re.DOTALL) for match in matches: @@ -191,7 +194,7 @@ class VoiceHeartbeatMonitor(threading.Thread): print(f"🎤 New Voice Command detected: {cmd}") # ОТПРАВЛЯЕМ В LLM через TitleGenerator - prompt = f"{(s.get('systemPrompt') or '')}\n\n{(s.get('prompt_commands') or '')}" + prompt = f"{(obsidian_settings.get('systemPrompt') or '')}\n\n{(obsidian_settings.get('prompt_commands') or '')}" self.generator.add_voice_task_to_queue( cmd, prompt @@ -199,13 +202,13 @@ class VoiceHeartbeatMonitor(threading.Thread): self._processed_hashes.add(cmd_hash) # Ограничиваем размер множества, чтобы не росло бесконечно - if len(self._processed_hashes) > 100: self._processed_hashes.clear() + 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']) + def _run_regular_analysis(self, obsidian_settings): + raw_text = self.vs.get_last_chars(obsidian_settings['voice_context_limit']) if not raw_text.strip(): return - prompt = f"{(s.get('systemPrompt') or '')}\n\n{(s.get('prompt_regular') or '')}" + prompt = f"{(obsidian_settings.get('systemPrompt') or '')}\n\n{(obsidian_settings.get('prompt_regular') or '')}" # Добавим новый тип 'voice_regular' вTitleGenerator аналогично 'voice_command' self.generator.add_voice_regular_task(raw_text, prompt) \ No newline at end of file diff --git a/app/llm_client.py b/app/llm_client.py index dd34f8a..15c71bc 100644 --- a/app/llm_client.py +++ b/app/llm_client.py @@ -44,6 +44,19 @@ MODELS: Dict[str, Dict[str, Any]] = { "stream": True, "capabilities": ["vision"], }, + "gemini-2.0-flash-lite": { + "name": "google/gemini-2.0-flash-lite-001", + "provider": "openai", + "model_name": "google/gemini-2.0-flash-lite-001", + "apiBase": "https://openrouter.ai/api/v1", # Добавлено /api/v1 + "apiKey": + "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", + "stream": True, + "capabilities": ["vision", "reasoning"], + "model_kwargs": { + "include_reasoning": True + } + }, "gemini-2.5-flash-r": { "name": "gemini-2.5-flash-r", "provider": "openai", # Изменено на "openai" diff --git a/app/title_generator.py b/app/title_generator.py index b612a1e..f06a2db 100644 --- a/app/title_generator.py +++ b/app/title_generator.py @@ -11,11 +11,13 @@ from llm_client import get_llm from langchain_core.messages import SystemMessage, HumanMessage import os -DEFAULT_SUMMARIZATION_LLM_NAME = "mistral-small-latest" # "mistral-small-latest" #"gemini-2.0-flash-r" -DEFAULT_VOICE_LLM_NAME = "gemini-3.0-flash-openrouter" +DEFAULT_SUMMARIZATION_LLM_NAME = "gemini-2.0-flash-lite" # "mistral-small-latest" # "mistral-small-latest" #"gemini-2.0-flash-r" +DEFAULT_VOICE_LLM_NAME = "gemini-2.0-flash-lite" # "gemini-3.0-flash-openrouter" MAX_TITLE_GENERATION_CONTENT_LENGTH = 5000 # Максимальное количество символов для генерации заголовков +from app.voice_service import VOICE_COMMANDS_RESPONSE_TO_STORE + class TitleGenerator: """Сервис для асинхронной генерации заголовков.""" @@ -374,7 +376,7 @@ class TitleGenerator: # 3. Добавьте метод обработки регулярной задачи def _process_voice_regular(self, text, prompt): try: - print(f"📡 Анализ транскрибации (Prompt 1)...") + print(f"🎤 Анализ транскрибации (Prompt 1)...") llm_messages = [ SystemMessage(content=prompt), HumanMessage(content=f"Последний фрагмент диалога для анализа: {text}") @@ -383,14 +385,14 @@ class TitleGenerator: import app.api as api api.voice_inst.responses["regular"] = response # Записываем в Response 1 - print(f"✅ Регулярный анализ завершен.") + print(f"✅🎤 Регулярный анализ голосового ввода завершен.") except Exception as e: - print(f"Ошибка регулярного анализа: {e}") + print(f"❌🎤 Ошибка регулярного анализа: {e}") # 4. Поправьте _process_voice_command (запись команды в Response 2) def _process_voice_command(self, text, prompt): try: - print(f"🤖 Обработка голосовой команды (Prompt 2)...") + print(f"🎤 Обработка голосовой команды (Prompt 2)...") llm_messages = [ SystemMessage(content=prompt), HumanMessage(content=f"Выполни команду из транскрибации: {text}") @@ -400,12 +402,12 @@ class TitleGenerator: import app.api as api # Форматируем для вкладки Response 2 formatted_res = f"Команда: {text}
Ответ: {response}" - api.voice_inst.responses["commands"].append(formatted_res) + api.voice_inst.responses["commands"].insert(0, formatted_res) # Ограничиваем историю команд, чтобы не раздувать память - if len(api.voice_inst.responses["commands"]) > 20: + if len(api.voice_inst.responses["commands"]) > VOICE_COMMANDS_RESPONSE_TO_STORE: api.voice_inst.responses["commands"].pop(0) - print(f"✅ Команда обработана.") + print(f"✅🎤 Голосовая команда обработана.") except Exception as e: - print(f"Ошибка обработки команды: {e}") \ No newline at end of file + print(f"❌🎤 Ошибка обработки команды: {e}") \ No newline at end of file diff --git a/app/voice_service.py b/app/voice_service.py index 09461df..6565125 100644 --- a/app/voice_service.py +++ b/app/voice_service.py @@ -7,6 +7,8 @@ import numpy as np from vosk import Model, KaldiRecognizer import sounddevice as sd +VOICE_COMMANDS_RESPONSE_TO_STORE = 15 + class VoiceService: def __init__(self, model_path, samplerate=48000): self.model_path = model_path @@ -126,12 +128,12 @@ class VoiceService: channels=1, callback=create_callback(rec_pcs, "PCs")): - print(f"✅ Транскрибация запущена (IDs: {id_gm}, {id_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}") + print(f"❌🎤 Критическая ошибка транскрибации: {e}") finally: self.is_running = False - print("🏁 Сессия транскрибации завершена") \ No newline at end of file + print("🏁🎤 Сессия транскрибации завершена") \ No newline at end of file diff --git a/run.py b/run.py index 03ea559..4a0c234 100644 --- a/run.py +++ b/run.py @@ -12,7 +12,7 @@ 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, voice_inst, current_settings + from app.api import api, voice_inst, current_obsidian_settings from app.heartbeat_monitor import HeartbeatMonitor from app.workflows import graph_history_manager @@ -38,13 +38,13 @@ if __name__ == "__main__": v_monitor = VoiceHeartbeatMonitor( voice_inst, graph_history_manager.title_generator, - lambda: current_settings # Теперь берет настройки из api.py + lambda: current_obsidian_settings # Теперь берет настройки из api.py ) v_monitor.start() api.run( - debug=True, + debug=False, # True - тогда всё по 2 раза запускает port=5000, host='localhost', threaded=True