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)
|
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)
|
# 1. Создаем экземпляр (обязательно укажите путь к вашей модели Vosk)
|
||||||
voice_inst = VoiceService(model_path=r"D:\\Work\\Software_Development\\Projects\\vosk\\vosk-model-small-ru-0.22")
|
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,
|
'interval': 5,
|
||||||
'context_limit': 3000,
|
'context_limit': 3000,
|
||||||
'voice_model': 'gemini-2.0-flash',
|
'voice_model': 'gemini-2.0-flash',
|
||||||
|
|
@ -383,14 +383,17 @@ current_settings = {
|
||||||
'voice_context_limit': 3000
|
'voice_context_limit': 3000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
obsidian_settings_fetched = False
|
||||||
|
|
||||||
@api.route('/api/voice/status', methods=['GET'])
|
@api.route('/api/voice/status', methods=['GET'])
|
||||||
def get_voice_status():
|
def get_voice_status():
|
||||||
# 2. Вызываем метод у экземпляра voice_inst, а не у модуля
|
# 2. Вызываем метод у экземпляра voice_inst, а не у модуля
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"transcription": voice_inst.get_last_chars(3000),
|
"transcription": voice_inst.get_last_chars(3000),
|
||||||
"response1": voice_inst.responses["regular"],
|
"response1": voice_inst.responses["regular"],
|
||||||
"response2": voice_inst.responses["commands"][-5:],
|
"response2": voice_inst.responses["commands"][:VOICE_COMMANDS_RESPONSE_TO_STORE],
|
||||||
"is_running": voice_inst.is_running
|
"is_running": voice_inst.is_running,
|
||||||
|
"obsidian_settings_fetched": obsidian_settings_fetched
|
||||||
})
|
})
|
||||||
|
|
||||||
@api.route('/api/voice/control', methods=['POST'])
|
@api.route('/api/voice/control', methods=['POST'])
|
||||||
|
|
@ -403,9 +406,10 @@ def control_voice():
|
||||||
|
|
||||||
@api.route('/api/settings/sync', methods=['POST'])
|
@api.route('/api/settings/sync', methods=['POST'])
|
||||||
def sync_settings():
|
def sync_settings():
|
||||||
global current_settings
|
global current_obsidian_settings, obsidian_settings_fetched
|
||||||
|
obsidian_settings_fetched = True
|
||||||
data = request.json
|
data = request.json
|
||||||
if data:
|
if data:
|
||||||
current_settings.update(data)
|
current_obsidian_settings.update(data)
|
||||||
print(f"⚙️ Settings synced: {current_settings}")
|
print(f"✅⚙️ Settings synced: {current_obsidian_settings}")
|
||||||
return jsonify({"status": "synced"})
|
return jsonify({"status": "synced"})
|
||||||
|
|
@ -148,36 +148,39 @@ class HeartbeatMonitor:
|
||||||
|
|
||||||
class VoiceHeartbeatMonitor(threading.Thread):
|
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)
|
super().__init__(daemon=True)
|
||||||
self.vs = voice_instance # Тот самый экземпляр из api.py
|
self.vs = voice_instance # Тот самый экземпляр из api.py
|
||||||
self.generator = generator
|
self.generator = generator
|
||||||
self.get_settings = settings_fn # Функция, возвращающая текущий конфиг
|
self.get_obsidian_settings = get_obsidian_settings_fn # Функция, возвращающая текущий конфиг
|
||||||
self.last_regular_run = time.time()
|
self.last_regular_run = time.time()
|
||||||
self.last_command_time = ""
|
self.last_command_time = ""
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
while True:
|
while True:
|
||||||
if self.vs.is_running:
|
try:
|
||||||
s = self.get_settings()
|
if self.vs.is_running:
|
||||||
# 1. Проверка команд <start>...<stop>
|
obsidian_settings = self.get_obsidian_settings()
|
||||||
self._check_commands(s)
|
# 1. Проверка команд <start>...<stop>
|
||||||
|
self._check_commands(obsidian_settings)
|
||||||
|
|
||||||
# 2. Регулярный запрос
|
# 2. Регулярный запрос
|
||||||
if time.time() - self.last_regular_run > s['interval'] * 60:
|
if time.time() - self.last_regular_run > obsidian_settings['interval'] * 60:
|
||||||
self._run_regular_analysis(s)
|
self._run_regular_analysis(obsidian_settings)
|
||||||
self.last_regular_run = time.time()
|
self.last_regular_run = time.time()
|
||||||
time.sleep(10)
|
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
|
import re
|
||||||
# Чистим таймстемпы Vosk для LLM (удаляем паттерны [00:00:00])
|
# Чистим таймстемпы Vosk для LLM (удаляем паттерны [00:00:00])
|
||||||
clean_text = re.sub(r'\[.*?\]', '', raw_text)
|
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)
|
matches = re.findall(pattern, clean_text, re.DOTALL)
|
||||||
|
|
||||||
for match in matches:
|
for match in matches:
|
||||||
|
|
@ -191,7 +194,7 @@ class VoiceHeartbeatMonitor(threading.Thread):
|
||||||
print(f"🎤 New Voice Command detected: {cmd}")
|
print(f"🎤 New Voice Command detected: {cmd}")
|
||||||
|
|
||||||
# ОТПРАВЛЯЕМ В LLM через TitleGenerator
|
# ОТПРАВЛЯЕМ В 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(
|
self.generator.add_voice_task_to_queue(
|
||||||
cmd,
|
cmd,
|
||||||
prompt
|
prompt
|
||||||
|
|
@ -199,13 +202,13 @@ class VoiceHeartbeatMonitor(threading.Thread):
|
||||||
|
|
||||||
self._processed_hashes.add(cmd_hash)
|
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):
|
def _run_regular_analysis(self, obsidian_settings):
|
||||||
raw_text = self.vs.get_last_chars(s['voice_context_limit'])
|
raw_text = self.vs.get_last_chars(obsidian_settings['voice_context_limit'])
|
||||||
if not raw_text.strip(): return
|
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'
|
# Добавим новый тип 'voice_regular' вTitleGenerator аналогично 'voice_command'
|
||||||
self.generator.add_voice_regular_task(raw_text, prompt)
|
self.generator.add_voice_regular_task(raw_text, prompt)
|
||||||
|
|
@ -44,6 +44,19 @@ MODELS: Dict[str, Dict[str, Any]] = {
|
||||||
"stream": True,
|
"stream": True,
|
||||||
"capabilities": ["vision"],
|
"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": {
|
"gemini-2.5-flash-r": {
|
||||||
"name": "gemini-2.5-flash-r",
|
"name": "gemini-2.5-flash-r",
|
||||||
"provider": "openai", # Изменено на "openai"
|
"provider": "openai", # Изменено на "openai"
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@ from llm_client import get_llm
|
||||||
from langchain_core.messages import SystemMessage, HumanMessage
|
from langchain_core.messages import SystemMessage, HumanMessage
|
||||||
import os
|
import os
|
||||||
|
|
||||||
DEFAULT_SUMMARIZATION_LLM_NAME = "mistral-small-latest" # "mistral-small-latest" #"gemini-2.0-flash-r"
|
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-3.0-flash-openrouter"
|
DEFAULT_VOICE_LLM_NAME = "gemini-2.0-flash-lite" # "gemini-3.0-flash-openrouter"
|
||||||
|
|
||||||
MAX_TITLE_GENERATION_CONTENT_LENGTH = 5000 # Максимальное количество символов для генерации заголовков
|
MAX_TITLE_GENERATION_CONTENT_LENGTH = 5000 # Максимальное количество символов для генерации заголовков
|
||||||
|
|
||||||
|
from app.voice_service import VOICE_COMMANDS_RESPONSE_TO_STORE
|
||||||
|
|
||||||
class TitleGenerator:
|
class TitleGenerator:
|
||||||
"""Сервис для асинхронной генерации заголовков."""
|
"""Сервис для асинхронной генерации заголовков."""
|
||||||
|
|
||||||
|
|
@ -374,7 +376,7 @@ class TitleGenerator:
|
||||||
# 3. Добавьте метод обработки регулярной задачи
|
# 3. Добавьте метод обработки регулярной задачи
|
||||||
def _process_voice_regular(self, text, prompt):
|
def _process_voice_regular(self, text, prompt):
|
||||||
try:
|
try:
|
||||||
print(f"📡 Анализ транскрибации (Prompt 1)...")
|
print(f"🎤 Анализ транскрибации (Prompt 1)...")
|
||||||
llm_messages = [
|
llm_messages = [
|
||||||
SystemMessage(content=prompt),
|
SystemMessage(content=prompt),
|
||||||
HumanMessage(content=f"Последний фрагмент диалога для анализа: {text}")
|
HumanMessage(content=f"Последний фрагмент диалога для анализа: {text}")
|
||||||
|
|
@ -383,14 +385,14 @@ class TitleGenerator:
|
||||||
|
|
||||||
import app.api as api
|
import app.api as api
|
||||||
api.voice_inst.responses["regular"] = response # Записываем в Response 1
|
api.voice_inst.responses["regular"] = response # Записываем в Response 1
|
||||||
print(f"✅ Регулярный анализ завершен.")
|
print(f"✅🎤 Регулярный анализ голосового ввода завершен.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка регулярного анализа: {e}")
|
print(f"❌🎤 Ошибка регулярного анализа: {e}")
|
||||||
|
|
||||||
# 4. Поправьте _process_voice_command (запись команды в Response 2)
|
# 4. Поправьте _process_voice_command (запись команды в Response 2)
|
||||||
def _process_voice_command(self, text, prompt):
|
def _process_voice_command(self, text, prompt):
|
||||||
try:
|
try:
|
||||||
print(f"🤖 Обработка голосовой команды (Prompt 2)...")
|
print(f"🎤 Обработка голосовой команды (Prompt 2)...")
|
||||||
llm_messages = [
|
llm_messages = [
|
||||||
SystemMessage(content=prompt),
|
SystemMessage(content=prompt),
|
||||||
HumanMessage(content=f"Выполни команду из транскрибации: {text}")
|
HumanMessage(content=f"Выполни команду из транскрибации: {text}")
|
||||||
|
|
@ -400,12 +402,12 @@ class TitleGenerator:
|
||||||
import app.api as api
|
import app.api as api
|
||||||
# Форматируем для вкладки Response 2
|
# Форматируем для вкладки Response 2
|
||||||
formatted_res = f"<b>Команда:</b> {text}<br><b>Ответ:</b> {response}"
|
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)
|
api.voice_inst.responses["commands"].pop(0)
|
||||||
|
|
||||||
print(f"✅ Команда обработана.")
|
print(f"✅🎤 Голосовая команда обработана.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Ошибка обработки команды: {e}")
|
print(f"❌🎤 Ошибка обработки команды: {e}")
|
||||||
|
|
@ -7,6 +7,8 @@ import numpy as np
|
||||||
from vosk import Model, KaldiRecognizer
|
from vosk import Model, KaldiRecognizer
|
||||||
import sounddevice as sd
|
import sounddevice as sd
|
||||||
|
|
||||||
|
VOICE_COMMANDS_RESPONSE_TO_STORE = 15
|
||||||
|
|
||||||
class VoiceService:
|
class VoiceService:
|
||||||
def __init__(self, model_path, samplerate=48000):
|
def __init__(self, model_path, samplerate=48000):
|
||||||
self.model_path = model_path
|
self.model_path = model_path
|
||||||
|
|
@ -126,12 +128,12 @@ class VoiceService:
|
||||||
channels=1,
|
channels=1,
|
||||||
callback=create_callback(rec_pcs, "PCs")):
|
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():
|
while not self._stop_event.is_set():
|
||||||
self._stop_event.wait(1.0) # Экономнее чем sleep
|
self._stop_event.wait(1.0) # Экономнее чем sleep
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"🔥 Критическая ошибка транскрибации: {e}")
|
print(f"❌🎤 Критическая ошибка транскрибации: {e}")
|
||||||
finally:
|
finally:
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
print("🏁 Сессия транскрибации завершена")
|
print("🏁🎤 Сессия транскрибации завершена")
|
||||||
6
run.py
6
run.py
|
|
@ -12,7 +12,7 @@ logger = logging.getLogger(__name__)
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# Добавляем папку app в Python path
|
# Добавляем папку app в Python path
|
||||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'app'))
|
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.heartbeat_monitor import HeartbeatMonitor
|
||||||
from app.workflows import graph_history_manager
|
from app.workflows import graph_history_manager
|
||||||
|
|
||||||
|
|
@ -38,13 +38,13 @@ if __name__ == "__main__":
|
||||||
v_monitor = VoiceHeartbeatMonitor(
|
v_monitor = VoiceHeartbeatMonitor(
|
||||||
voice_inst,
|
voice_inst,
|
||||||
graph_history_manager.title_generator,
|
graph_history_manager.title_generator,
|
||||||
lambda: current_settings # Теперь берет настройки из api.py
|
lambda: current_obsidian_settings # Теперь берет настройки из api.py
|
||||||
)
|
)
|
||||||
v_monitor.start()
|
v_monitor.start()
|
||||||
|
|
||||||
|
|
||||||
api.run(
|
api.run(
|
||||||
debug=True,
|
debug=False, # True - тогда всё по 2 раза запускает
|
||||||
port=5000,
|
port=5000,
|
||||||
host='localhost',
|
host='localhost',
|
||||||
threaded=True
|
threaded=True
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user