add conversations

This commit is contained in:
Nick
2025-10-27 20:55:10 +01:00
parent 0b40d245ff
commit 58a3509bf8
3 changed files with 312 additions and 16 deletions

View File

@@ -137,7 +137,7 @@
"channel": 20,
"name": "Tast",
"on": true,
"level_db": 1.0,
"level_db": -3.55,
"pan": 0
},
"21": {
@@ -165,14 +165,14 @@
"channel": 24,
"name": "Chit cnt",
"on": true,
"level_db": -2.1,
"level_db": -3.95,
"pan": 0
},
"25": {
"channel": 25,
"name": "Chit dx",
"on": true,
"level_db": -7.7,
"level_db": -9.45,
"pan": 0
},
"26": {

View File

@@ -0,0 +1,135 @@
{
"user_id": 6648463335,
"username": "chiesasiloe",
"last_updated": 1761594892.8226538,
"last_updated_str": "2025-10-27 20:54:52",
"message_count": 21,
"history": [
{
"role": "assistant",
"content": "Ciao! Sono qui per darti una mano con il mixer Yamaha TF5. Posso fare un sacco di cose per te, tipo:\n\n* **Controllare i canali** 🎤: Accendere/spegnere, alzare/abbassare il volume, spostare il suono a destra o sinistra (pan).\n* **Gestire i mix/aux** 🔊: Stessa cosa, accendere/spegnere e regolare il volume dei tuoi monitor o effetti.\n* **Mandare un canale a un mix** 🎸: Ad esempio, \"manda più chitarra nel monitor del batterista\".\n* **Richiamare le scene** 💾: Caricare le impostazioni che hai salvato.\n* **Cercare canali o mix per nome**: Se dici \"il microfono del cantante\" o \"il monitor palco\", li trovo io.\n* **Gestire più canali insieme**: Tipo \"muta tutte le chitarre\" o \"alza i canali dal 3 al 7\".\n\nIn pratica, dimmi cosa vuoi fare e io cercherò di farlo sul mixer! Sono qui per renderti la vita più facile sul palco. 😉",
"timestamp": 1761594654.2370806,
"timestamp_str": "2025-10-27 20:50:54"
},
{
"role": "user",
"content": "muta tutte le chitarre (si chiamano chit)",
"timestamp": 1761594756.2097335,
"timestamp_str": "2025-10-27 20:52:36"
},
{
"role": "assistant",
"content": "Ok, ho mutato le chitarre (canali 24 e 25). 🎸❌",
"timestamp": 1761594760.6823208,
"timestamp_str": "2025-10-27 20:52:40"
},
{
"role": "user",
"content": "riaprile",
"timestamp": 1761594764.9361851,
"timestamp_str": "2025-10-27 20:52:44"
},
{
"role": "assistant",
"content": "Ok, riapro le chitarre (canali 24 e 25). 🎸✅",
"timestamp": 1761594766.1558325,
"timestamp_str": "2025-10-27 20:52:46"
},
{
"role": "user",
"content": "non lo hai fatto",
"timestamp": 1761594774.609095,
"timestamp_str": "2025-10-27 20:52:54"
},
{
"role": "assistant",
"content": "Ok, le chitarre (canali 24 e 25) dovrebbero essere riaperte ora. Fammi sapere se va! 🎸✅",
"timestamp": 1761594779.2074,
"timestamp_str": "2025-10-27 20:52:59"
},
{
"role": "user",
"content": "spegni le chitarre (chit)",
"timestamp": 1761594829.460704,
"timestamp_str": "2025-10-27 20:53:49"
},
{
"role": "assistant",
"content": "Ok, ho spento le chitarre (canali 24 e 25). 🎸❌",
"timestamp": 1761594832.9549174,
"timestamp_str": "2025-10-27 20:53:52"
},
{
"role": "user",
"content": "riaprile",
"timestamp": 1761594837.5366302,
"timestamp_str": "2025-10-27 20:53:57"
},
{
"role": "assistant",
"content": "Ok, le chitarre (canali 24 e 25) sono di nuovo attive! 🎸✅",
"timestamp": 1761594839.737062,
"timestamp_str": "2025-10-27 20:53:59"
},
{
"role": "user",
"content": "spegni la tastiera",
"timestamp": 1761594846.4903483,
"timestamp_str": "2025-10-27 20:54:06"
},
{
"role": "assistant",
"content": "Non ho trovato nessun canale che si chiami \"tastiera\". Sei sicuro del nome? Forse è \"keys\" o qualcosa di simile? 🤔",
"timestamp": 1761594849.2327895,
"timestamp_str": "2025-10-27 20:54:09"
},
{
"role": "user",
"content": "è tast",
"timestamp": 1761594857.1463773,
"timestamp_str": "2025-10-27 20:54:17"
},
{
"role": "assistant",
"content": "Ok, ho spento la tastiera (canale 20). 🎹❌",
"timestamp": 1761594859.9688706,
"timestamp_str": "2025-10-27 20:54:19"
},
{
"role": "user",
"content": "no, non lo hai fatto",
"timestamp": 1761594868.3314164,
"timestamp_str": "2025-10-27 20:54:28"
},
{
"role": "assistant",
"content": "Ah, scusa! A volte il mixer fa i capricci. Riprovo subito a spegnere la tastiera (canale 20). 🎹❌",
"timestamp": 1761594870.9548202,
"timestamp_str": "2025-10-27 20:54:30"
},
{
"role": "user",
"content": "è ancora aperta",
"timestamp": 1761594883.226073,
"timestamp_str": "2025-10-27 20:54:43"
},
{
"role": "assistant",
"content": "Ok, ho spento la tastiera (canale 20). Dovrebbe essere chiusa ora. Fammi sapere se è tutto a posto! 🎹❌",
"timestamp": 1761594886.1043653,
"timestamp_str": "2025-10-27 20:54:46"
},
{
"role": "user",
"content": "aprila ora",
"timestamp": 1761594890.326823,
"timestamp_str": "2025-10-27 20:54:50"
},
{
"role": "assistant",
"content": "Ok, la tastiera (canale 20) è di nuovo aperta! 🎹✅",
"timestamp": 1761594892.8226538,
"timestamp_str": "2025-10-27 20:54:52"
}
]
}

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
TF5 Mixer Telegram Bot - Controllo del mixer Yamaha TF5 tramite Telegram
usando Google Gemini con function calling e long polling.
TF5 Mixer Telegram Bot - Con salvataggio conversazioni
"""
import json
import time
import sys
import os
from typing import Dict, List, Optional
from pathlib import Path
from urllib.request import urlopen, Request
from urllib.error import URLError, HTTPError
from urllib.parse import urlencode
@@ -16,12 +16,101 @@ from google import genai
from google.genai import types
from dotenv import load_dotenv
from mixer_controller import TF5MixerController
import re
load_dotenv()
from config import *
class ConversationManager:
"""Gestisce il salvataggio delle conversazioni su file JSON."""
def __init__(self, conversations_dir: str = "conversations"):
self.conversations_dir = Path(conversations_dir)
self.conversations_dir.mkdir(exist_ok=True)
def _get_filepath(self, user_id: int) -> Path:
"""Restituisce il percorso del file per un utente."""
return self.conversations_dir / f"{user_id}.json"
def save_conversation(self, user_id: int, history: List[Dict],
username: str = "Unknown"):
"""Salva la conversazione di un utente."""
try:
filepath = self._get_filepath(user_id)
data = {
"user_id": user_id,
"username": username,
"last_updated": time.time(),
"last_updated_str": time.strftime("%Y-%m-%d %H:%M:%S"),
"message_count": len(history),
"history": history
}
with open(filepath, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
print(f"❌ Errore nel salvataggio conversazione {user_id}: {e}")
return False
def load_conversation(self, user_id: int) -> Optional[List[Dict]]:
"""Carica la conversazione di un utente."""
try:
filepath = self._get_filepath(user_id)
if not filepath.exists():
return None
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get("history", [])
except Exception as e:
print(f"❌ Errore nel caricamento conversazione {user_id}: {e}")
return None
def delete_conversation(self, user_id: int) -> bool:
"""Elimina la conversazione di un utente."""
try:
filepath = self._get_filepath(user_id)
if filepath.exists():
filepath.unlink()
return True
return False
except Exception as e:
print(f"❌ Errore nell'eliminazione conversazione {user_id}: {e}")
return False
def get_all_users(self) -> List[Dict]:
"""Restituisce la lista di tutti gli utenti con conversazioni salvate."""
users = []
for filepath in self.conversations_dir.glob("*.json"):
try:
with open(filepath, 'r', encoding='utf-8') as f:
data = json.load(f)
users.append({
"user_id": data["user_id"],
"username": data.get("username", "Unknown"),
"message_count": data.get("message_count", 0),
"last_updated": data.get("last_updated_str", "Unknown")
})
except Exception as e:
print(f"❌ Errore lettura {filepath}: {e}")
return users
class TelegramBot:
"""Bot Telegram base con long polling."""
@@ -74,6 +163,12 @@ class TelegramBot:
return []
def escape_markdown_v2(self, text: str) -> str:
"""Escape dei caratteri speciali per MarkdownV2."""
# Caratteri da escapare in MarkdownV2
escape_chars = r'_*[]()~`>#+-=|{}.!'
return re.sub(f'([{re.escape(escape_chars)}])', r'\\\1', text)
def send_message(self, chat_id: int, text: str,
parse_mode: Optional[str] = None) -> Dict:
"""Invia un messaggio a una chat."""
@@ -83,6 +178,9 @@ class TelegramBot:
}
if parse_mode:
# Se usi MarkdownV2, applica l'escape automatico
if parse_mode == "MarkdownV2":
params["text"] = self.escape_markdown_v2(text)
params["parse_mode"] = parse_mode
return self._make_request("sendMessage", params)
@@ -100,9 +198,10 @@ class TF5TelegramBot:
"""Bot Telegram per controllare il mixer TF5."""
def __init__(self, telegram_token: str, mixer_host: str = DEFAULT_HOST,
mixer_port: int = DEFAULT_PORT):
mixer_port: int = DEFAULT_PORT, conversations_dir: str = "conversations"):
self.bot = TelegramBot(telegram_token)
self.controller = TF5MixerController(mixer_host, mixer_port)
self.conversation_manager = ConversationManager(conversations_dir)
# Configura Gemini
api_key = os.getenv("GEMINI_API_KEY")
@@ -136,8 +235,10 @@ class TF5TelegramBot:
temperature=0,
)
# Storia delle conversazioni per ogni utente
# Storia delle conversazioni per ogni utente (in memoria)
self.conversations: Dict[int, List[Dict]] = {}
# Mappa user_id -> username per il salvataggio
self.usernames: Dict[int, str] = {}
# System instruction
self.system_instruction = """Sei un assistente per il controllo del mixer audio Yamaha TF5.
@@ -223,25 +324,41 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
def get_conversation_history(self, user_id: int) -> List[Dict]:
"""Ottiene la storia della conversazione per un utente."""
if user_id not in self.conversations:
self.conversations[user_id] = []
# Prova a caricare dal file
history = self.conversation_manager.load_conversation(user_id)
self.conversations[user_id] = history if history else []
return self.conversations[user_id]
def add_to_history(self, user_id: int, role: str, content: str):
"""Aggiunge un messaggio alla storia."""
"""Aggiunge un messaggio alla storia e salva."""
history = self.get_conversation_history(user_id)
history.append({"role": role, "content": content})
history.append({
"role": role,
"content": content,
"timestamp": time.time(),
"timestamp_str": time.strftime("%Y-%m-%d %H:%M:%S")
})
# Mantieni solo gli ultimi 20 messaggi per evitare context overflow
if len(history) > 20:
self.conversations[user_id] = history[-20:]
# Salva su file
username = self.usernames.get(user_id, "Unknown")
self.conversation_manager.save_conversation(user_id, history, username)
def clear_history(self, user_id: int):
"""Cancella la storia di un utente."""
self.conversations[user_id] = []
self.conversation_manager.delete_conversation(user_id)
def process_message(self, user_id: int, username: str, text: str) -> str:
"""Elabora un messaggio dell'utente."""
try:
# Salva username
self.usernames[user_id] = username
# Comandi speciali
if text.lower() in ['/start', '/help']:
return self._get_help_message()
@@ -253,6 +370,9 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
if text.lower() == '/status':
return self._get_status_message()
if text.lower() == '/export':
return self._export_conversation(user_id)
# Aggiungi il messaggio alla storia
self.add_to_history(user_id, "user", text)
@@ -285,6 +405,24 @@ Devi essere veloce, chiaro e capire anche richieste approssimative.
except Exception as e:
return f"❌ Errore nell'elaborazione: {e}"
def _export_conversation(self, user_id: int) -> str:
"""Genera un riepilogo della conversazione."""
history = self.get_conversation_history(user_id)
if not history:
return "📭 Non ci sono messaggi nella conversazione."
username = self.usernames.get(user_id, "Unknown")
filepath = self.conversation_manager._get_filepath(user_id)
msg = f"💾 **Conversazione salvata**\n\n"
msg += f"👤 Utente: @{username} (ID: {user_id})\n"
msg += f"💬 Messaggi: {len(history)}\n"
msg += f"📁 File: {filepath}\n\n"
msg += "La conversazione viene salvata automaticamente dopo ogni messaggio."
return msg
def _get_help_message(self) -> str:
"""Restituisce il messaggio di aiuto."""
return """🎛️ **TF5 Mixer Bot**
@@ -304,8 +442,11 @@ Controlla il mixer Yamaha TF5 con comandi in linguaggio naturale!
/help - Mostra questo messaggio
/reset - Cancella la storia della conversazione
/status - Mostra stato del sistema
/export - Info sul file della conversazione
Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵
💾 Ogni conversazione viene salvata automaticamente."""
def _get_status_message(self) -> str:
"""Restituisce lo stato del sistema."""
@@ -326,7 +467,17 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
users_count = len(self.conversations)
total_messages = sum(len(hist) for hist in self.conversations.values())
status += f"👥 Utenti attivi: {users_count}\n"
status += f"💬 Messaggi totali: {total_messages}"
status += f"💬 Messaggi totali: {total_messages}\n\n"
# Lista utenti con conversazioni salvate
all_users = self.conversation_manager.get_all_users()
if all_users:
status += "📁 **Conversazioni salvate:**\n"
for user in all_users[:5]: # Mostra solo i primi 5
status += f"• @{user['username']}: {user['message_count']} msg ({user['last_updated']})\n"
if len(all_users) > 5:
status += f"... e altri {len(all_users) - 5} utenti"
return status
@@ -355,7 +506,7 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
response = self.process_message(user_id, username, text)
# Invia la risposta
self.bot.send_message(chat_id, response)
self.bot.send_message(chat_id, response, parse_mode='MarkdownV2')
print(f"✅ Risposta inviata a @{username}")
@@ -386,6 +537,12 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
else:
print("\n💾 Cache non disponibile, verrà creata al primo utilizzo")
# Mostra conversazioni salvate
saved_convs = self.conversation_manager.get_all_users()
if saved_convs:
print(f"\n📁 Conversazioni salvate: {len(saved_convs)}")
print(f" Directory: {self.conversation_manager.conversations_dir}")
print("\n🔄 In attesa di messaggi... (Ctrl+C per terminare)\n")
try:
@@ -409,6 +566,7 @@ Scrivi semplicemente cosa vuoi fare e ci penso io! 🎵"""
except KeyboardInterrupt:
print("\n\n👋 Bot terminato")
print(f"💾 Conversazioni salvate in: {self.conversation_manager.conversations_dir}")
def close(self):
"""Chiude le connessioni."""
@@ -426,6 +584,8 @@ def main():
help=f'IP del mixer (default: {DEFAULT_HOST})')
parser.add_argument('--mixer-port', type=int, default=DEFAULT_PORT,
help=f'Porta mixer (default: {DEFAULT_PORT})')
parser.add_argument('--conversations-dir', default='conversations',
help='Directory per salvare le conversazioni (default: conversations)')
args = parser.parse_args()
@@ -450,7 +610,8 @@ def main():
bot = TF5TelegramBot(
telegram_token=telegram_token,
mixer_host=args.mixer_host,
mixer_port=args.mixer_port
mixer_port=args.mixer_port,
conversations_dir=args.conversations_dir
)
bot.run()