Fix settings sync
This commit is contained in:
parent
19b034e4ea
commit
a737c3125a
18
app/api.py
18
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"})
|
||||
|
|
@ -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. Проверка команд <start>...<stop>
|
||||
self._check_commands(s)
|
||||
try:
|
||||
if self.vs.is_running:
|
||||
obsidian_settings = self.get_obsidian_settings()
|
||||
# 1. Проверка команд <start>...<stop>
|
||||
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)
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"<b>Команда:</b> {text}<br><b>Ответ:</b> {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}")
|
||||
print(f"❌🎤 Ошибка обработки команды: {e}")
|
||||
|
|
@ -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("🏁 Сессия транскрибации завершена")
|
||||
print("🏁🎤 Сессия транскрибации завершена")
|
||||
6
run.py
6
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
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user