base script
This commit is contained in:
651
mixer_agent.py
Normal file
651
mixer_agent.py
Normal file
@@ -0,0 +1,651 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
TF5 Mixer AI Agent - Controllo del mixer Yamaha TF5 tramite linguaggio naturale
|
||||
usando Google Gemini con function calling.
|
||||
"""
|
||||
import socket
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
# Configurazione mixer
|
||||
DEFAULT_HOST = "192.168.1.62"
|
||||
DEFAULT_PORT = 49280
|
||||
TF5_INPUT_CHANNELS = 40
|
||||
TF5_MIX_BUSSES = 20
|
||||
|
||||
# Configurazione cache
|
||||
CACHE_DIR = Path.home() / ".tf5_mixer_cache"
|
||||
CACHE_FILE = CACHE_DIR / "channels_cache.json"
|
||||
CACHE_DURATION = 3600 # 60 minuti in secondi
|
||||
|
||||
|
||||
class TF5MixerController:
|
||||
"""Controller per il mixer Yamaha TF5 con sistema di caching."""
|
||||
|
||||
def __init__(self, host=DEFAULT_HOST, port=DEFAULT_PORT):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self._ensure_cache_dir()
|
||||
self._cache = self._load_cache()
|
||||
|
||||
def _ensure_cache_dir(self):
|
||||
"""Crea la directory di cache se non esiste."""
|
||||
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _load_cache(self) -> dict:
|
||||
"""Carica la cache dal file."""
|
||||
if CACHE_FILE.exists():
|
||||
try:
|
||||
with open(CACHE_FILE, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Errore nel caricamento della cache: {e}")
|
||||
return {"channels": {}, "timestamp": 0}
|
||||
|
||||
def _save_cache(self):
|
||||
"""Salva la cache nel file."""
|
||||
try:
|
||||
with open(CACHE_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(self._cache, f, indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Errore nel salvataggio della cache: {e}")
|
||||
|
||||
def _is_cache_valid(self) -> bool:
|
||||
"""Verifica se la cache è ancora valida."""
|
||||
if not self._cache.get("channels"):
|
||||
return False
|
||||
cache_age = time.time() - self._cache.get("timestamp", 0)
|
||||
return cache_age < CACHE_DURATION
|
||||
|
||||
def _send_command(self, command: str) -> str:
|
||||
"""Invia un comando al mixer e restituisce la risposta."""
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.settimeout(5)
|
||||
s.connect((self.host, self.port))
|
||||
s.sendall((command + '\n').encode('utf-8'))
|
||||
response = s.recv(4096)
|
||||
s.close()
|
||||
return response.decode('utf-8', errors='ignore').strip()
|
||||
except socket.error as e:
|
||||
return f"Errore di connessione: {e}"
|
||||
|
||||
def _parse_name(self, response: str) -> str:
|
||||
"""Estrae il nome tra virgolette dalla risposta."""
|
||||
try:
|
||||
start = response.find('"') + 1
|
||||
end = response.rfind('"')
|
||||
if start > 0 and end > start:
|
||||
return response[start:end]
|
||||
return "Sconosciuto"
|
||||
except:
|
||||
return "Errore"
|
||||
|
||||
def _parse_value(self, response: str) -> str:
|
||||
"""Estrae l'ultimo valore da una risposta OK."""
|
||||
parts = response.split()
|
||||
if len(parts) > 0 and parts[0] == "OK":
|
||||
return parts[-1]
|
||||
return "N/A"
|
||||
|
||||
def refresh_cache(self) -> dict:
|
||||
"""Aggiorna la cache leggendo tutti i canali dal mixer.
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
print("🔄 Aggiornamento cache in corso...")
|
||||
channels_data = {}
|
||||
|
||||
for ch in range(1, TF5_INPUT_CHANNELS + 1):
|
||||
ch_idx = ch - 1
|
||||
|
||||
# Leggi nome
|
||||
resp_name = self._send_command(f"get MIXER:Current/InCh/Label/Name {ch_idx} 0")
|
||||
name = self._parse_name(resp_name)
|
||||
|
||||
# Leggi stato ON/OFF
|
||||
resp_on = self._send_command(f"get MIXER:Current/InCh/Fader/On {ch_idx} 0")
|
||||
is_on = self._parse_value(resp_on) == "1"
|
||||
|
||||
# Leggi livello
|
||||
resp_level = self._send_command(f"get MIXER:Current/InCh/Fader/Level {ch_idx} 0")
|
||||
level_raw = self._parse_value(resp_level)
|
||||
try:
|
||||
level_int = int(level_raw)
|
||||
level_db = level_int / 100.0 if level_int > -32768 else float('-inf')
|
||||
except:
|
||||
level_db = None
|
||||
|
||||
# Leggi pan
|
||||
resp_pan = self._send_command(f"get MIXER:Current/InCh/ToSt/Pan {ch_idx} 0")
|
||||
pan_raw = self._parse_value(resp_pan)
|
||||
try:
|
||||
pan_value = int(pan_raw)
|
||||
except:
|
||||
pan_value = None
|
||||
|
||||
channels_data[str(ch)] = {
|
||||
"channel": ch,
|
||||
"name": name,
|
||||
"on": is_on,
|
||||
"level_db": level_db,
|
||||
"pan": pan_value
|
||||
}
|
||||
|
||||
# Piccolo delay per non sovraccaricare il mixer
|
||||
time.sleep(0.05)
|
||||
|
||||
self._cache = {
|
||||
"channels": channels_data,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
self._save_cache()
|
||||
|
||||
print(f"✅ Cache aggiornata con {len(channels_data)} canali")
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Cache aggiornata con {len(channels_data)} canali",
|
||||
"channels_count": len(channels_data)
|
||||
}
|
||||
|
||||
def recall_scene(self, bank: str, scene_number: int) -> dict:
|
||||
"""Richiama una scena dal banco A o B.
|
||||
|
||||
Args:
|
||||
bank: Il banco della scena ('a' o 'b')
|
||||
scene_number: Il numero della scena (0-99)
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
if bank.lower() not in ['a', 'b']:
|
||||
return {"status": "error", "message": "Il banco deve essere 'a' o 'b'"}
|
||||
if not 0 <= scene_number <= 99:
|
||||
return {"status": "error", "message": "Il numero scena deve essere tra 0 e 99"}
|
||||
|
||||
command = f"ssrecall_ex scene_{bank.lower()} {scene_number}"
|
||||
response = self._send_command(command)
|
||||
|
||||
# Invalida la cache dopo il cambio scena
|
||||
self._cache["timestamp"] = 0
|
||||
|
||||
return {
|
||||
"status": "success" if "OK" in response else "error",
|
||||
"message": f"Scena {bank.upper()}{scene_number} richiamata. Cache invalidata.",
|
||||
"response": response
|
||||
}
|
||||
|
||||
def set_channel_level(self, channel: int, level_db: float) -> dict:
|
||||
"""Imposta il livello del fader di un canale in dB.
|
||||
|
||||
Args:
|
||||
channel: Numero del canale (1-40)
|
||||
level_db: Livello in dB (da -inf a +10.0)
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
if not 1 <= channel <= TF5_INPUT_CHANNELS:
|
||||
return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"}
|
||||
|
||||
# Converti dB in valore interno (moltiplicato per 100)
|
||||
if level_db <= -138:
|
||||
internal_value = -32768 # -inf
|
||||
else:
|
||||
internal_value = int(level_db * 100)
|
||||
|
||||
command = f"set MIXER:Current/InCh/Fader/Level {channel-1} 0 {internal_value}"
|
||||
response = self._send_command(command)
|
||||
|
||||
# Aggiorna cache locale
|
||||
if str(channel) in self._cache.get("channels", {}):
|
||||
self._cache["channels"][str(channel)]["level_db"] = level_db
|
||||
|
||||
return {
|
||||
"status": "success" if "OK" in response else "error",
|
||||
"message": f"Canale {channel} impostato a {level_db:+.1f} dB",
|
||||
"response": response
|
||||
}
|
||||
|
||||
def set_channel_on_off(self, channel: int, state: bool) -> dict:
|
||||
"""Accende o spegne un canale.
|
||||
|
||||
Args:
|
||||
channel: Numero del canale (1-40)
|
||||
state: True per accendere, False per spegnere
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
if not 1 <= channel <= TF5_INPUT_CHANNELS:
|
||||
return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"}
|
||||
|
||||
value = 1 if state else 0
|
||||
command = f"set MIXER:Current/InCh/Fader/On {channel-1} 0 {value}"
|
||||
response = self._send_command(command)
|
||||
|
||||
# Aggiorna cache locale
|
||||
if str(channel) in self._cache.get("channels", {}):
|
||||
self._cache["channels"][str(channel)]["on"] = state
|
||||
|
||||
return {
|
||||
"status": "success" if "OK" in response else "error",
|
||||
"message": f"Canale {channel} {'acceso' if state else 'spento'}",
|
||||
"response": response
|
||||
}
|
||||
|
||||
def set_channel_pan(self, channel: int, pan_value: int) -> dict:
|
||||
"""Imposta il pan di un canale.
|
||||
|
||||
Args:
|
||||
channel: Numero del canale (1-40)
|
||||
pan_value: Valore pan da -63 (sinistra) a +63 (destra), 0 è centro
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
if not 1 <= channel <= TF5_INPUT_CHANNELS:
|
||||
return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"}
|
||||
if not -63 <= pan_value <= 63:
|
||||
return {"status": "error", "message": "Il pan deve essere tra -63 e +63"}
|
||||
|
||||
command = f"set MIXER:Current/InCh/ToSt/Pan {channel-1} 0 {pan_value}"
|
||||
response = self._send_command(command)
|
||||
|
||||
# Aggiorna cache locale
|
||||
if str(channel) in self._cache.get("channels", {}):
|
||||
self._cache["channels"][str(channel)]["pan"] = pan_value
|
||||
|
||||
pan_desc = "centro"
|
||||
if pan_value < 0:
|
||||
pan_desc = f"sinistra {abs(pan_value)}"
|
||||
elif pan_value > 0:
|
||||
pan_desc = f"destra {pan_value}"
|
||||
|
||||
return {
|
||||
"status": "success" if "OK" in response else "error",
|
||||
"message": f"Canale {channel} pan impostato a {pan_desc}",
|
||||
"response": response
|
||||
}
|
||||
|
||||
def set_mix_level(self, mix_number: int, level_db: float) -> dict:
|
||||
"""Imposta il livello di un mix/aux.
|
||||
|
||||
Args:
|
||||
mix_number: Numero del mix (1-20)
|
||||
level_db: Livello in dB (da -inf a +10.0)
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
if not 1 <= mix_number <= TF5_MIX_BUSSES:
|
||||
return {"status": "error", "message": f"Il mix deve essere tra 1 e {TF5_MIX_BUSSES}"}
|
||||
|
||||
if level_db <= -138:
|
||||
internal_value = -32768
|
||||
else:
|
||||
internal_value = int(level_db * 100)
|
||||
|
||||
command = f"set MIXER:Current/Mix/Fader/Level {mix_number-1} 0 {internal_value}"
|
||||
response = self._send_command(command)
|
||||
return {
|
||||
"status": "success" if "OK" in response else "error",
|
||||
"message": f"Mix {mix_number} impostato a {level_db:+.1f} dB",
|
||||
"response": response
|
||||
}
|
||||
|
||||
def mute_multiple_channels(self, channels: List[int]) -> dict:
|
||||
"""Muta più canali contemporaneamente.
|
||||
|
||||
Args:
|
||||
channels: Lista di numeri di canale da mutare (es: [1, 2, 5, 8])
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
results = []
|
||||
for ch in channels:
|
||||
result = self.set_channel_on_off(ch, False)
|
||||
results.append(result)
|
||||
|
||||
success_count = sum(1 for r in results if r["status"] == "success")
|
||||
return {
|
||||
"status": "success" if success_count == len(channels) else "partial",
|
||||
"message": f"Mutati {success_count}/{len(channels)} canali: {channels}",
|
||||
"details": results
|
||||
}
|
||||
|
||||
def unmute_multiple_channels(self, channels: List[int]) -> dict:
|
||||
"""Riattiva più canali contemporaneamente.
|
||||
|
||||
Args:
|
||||
channels: Lista di numeri di canale da riattivare (es: [1, 2, 5, 8])
|
||||
|
||||
Returns:
|
||||
Un dizionario con lo stato dell'operazione
|
||||
"""
|
||||
results = []
|
||||
for ch in channels:
|
||||
result = self.set_channel_on_off(ch, True)
|
||||
results.append(result)
|
||||
|
||||
success_count = sum(1 for r in results if r["status"] == "success")
|
||||
return {
|
||||
"status": "success" if success_count == len(channels) else "partial",
|
||||
"message": f"Riattivati {success_count}/{len(channels)} canali: {channels}",
|
||||
"details": results
|
||||
}
|
||||
|
||||
def get_channel_info(self, channel: int, force_refresh: bool = False) -> dict:
|
||||
"""Legge le informazioni di un canale (nome, livello, stato, pan).
|
||||
Usa la cache se disponibile e valida.
|
||||
|
||||
Args:
|
||||
channel: Numero del canale (1-40)
|
||||
force_refresh: Se True, ignora la cache e legge dal mixer
|
||||
|
||||
Returns:
|
||||
Un dizionario con tutte le informazioni del canale
|
||||
"""
|
||||
if not 1 <= channel <= TF5_INPUT_CHANNELS:
|
||||
return {"status": "error", "message": f"Il canale deve essere tra 1 e {TF5_INPUT_CHANNELS}"}
|
||||
|
||||
# Usa cache se valida e non forzato il refresh
|
||||
if not force_refresh and self._is_cache_valid():
|
||||
cached_data = self._cache.get("channels", {}).get(str(channel))
|
||||
if cached_data:
|
||||
return {
|
||||
"status": "success",
|
||||
"source": "cache",
|
||||
**cached_data
|
||||
}
|
||||
|
||||
# Altrimenti leggi dal mixer
|
||||
ch_idx = channel - 1
|
||||
|
||||
resp_name = self._send_command(f"get MIXER:Current/InCh/Label/Name {ch_idx} 0")
|
||||
name = self._parse_name(resp_name)
|
||||
|
||||
resp_on = self._send_command(f"get MIXER:Current/InCh/Fader/On {ch_idx} 0")
|
||||
is_on = self._parse_value(resp_on) == "1"
|
||||
|
||||
resp_level = self._send_command(f"get MIXER:Current/InCh/Fader/Level {ch_idx} 0")
|
||||
level_raw = self._parse_value(resp_level)
|
||||
try:
|
||||
level_int = int(level_raw)
|
||||
level_db = level_int / 100.0 if level_int > -32768 else float('-inf')
|
||||
except:
|
||||
level_db = None
|
||||
|
||||
resp_pan = self._send_command(f"get MIXER:Current/InCh/ToSt/Pan {ch_idx} 0")
|
||||
pan_raw = self._parse_value(resp_pan)
|
||||
try:
|
||||
pan_value = int(pan_raw)
|
||||
except:
|
||||
pan_value = None
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"source": "mixer",
|
||||
"channel": channel,
|
||||
"name": name,
|
||||
"on": is_on,
|
||||
"level_db": level_db,
|
||||
"pan": pan_value
|
||||
}
|
||||
|
||||
def search_channels_by_name(self, search_term: str) -> dict:
|
||||
"""Cerca canali il cui nome contiene un determinato termine.
|
||||
Usa la cache per velocizzare la ricerca.
|
||||
|
||||
Args:
|
||||
search_term: Il termine da cercare nei nomi dei canali (case-insensitive)
|
||||
|
||||
Returns:
|
||||
Un dizionario con la lista dei canali trovati
|
||||
"""
|
||||
# Aggiorna cache se non valida
|
||||
if not self._is_cache_valid():
|
||||
self.refresh_cache()
|
||||
|
||||
search_lower = search_term.lower()
|
||||
found_channels = []
|
||||
|
||||
for ch_str, info in self._cache.get("channels", {}).items():
|
||||
if search_lower in info.get("name", "").lower():
|
||||
found_channels.append({
|
||||
"channel": info["channel"],
|
||||
"name": info["name"],
|
||||
"on": info["on"],
|
||||
"level_db": info["level_db"]
|
||||
})
|
||||
|
||||
# Ordina per numero canale
|
||||
found_channels.sort(key=lambda x: x["channel"])
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"search_term": search_term,
|
||||
"found_count": len(found_channels),
|
||||
"channels": found_channels,
|
||||
"message": f"Trovati {len(found_channels)} canali contenenti '{search_term}'"
|
||||
}
|
||||
|
||||
def get_all_channels_summary(self) -> dict:
|
||||
"""Ottiene un riepilogo di tutti i canali con nome e stato.
|
||||
Usa la cache per velocizzare.
|
||||
|
||||
Returns:
|
||||
Un dizionario con il riepilogo di tutti i canali
|
||||
"""
|
||||
# Aggiorna cache se non valida
|
||||
if not self._is_cache_valid():
|
||||
self.refresh_cache()
|
||||
|
||||
channels = []
|
||||
for ch_str, info in self._cache.get("channels", {}).items():
|
||||
channels.append({
|
||||
"channel": info["channel"],
|
||||
"name": info["name"],
|
||||
"on": info["on"]
|
||||
})
|
||||
|
||||
# Ordina per numero canale
|
||||
channels.sort(key=lambda x: x["channel"])
|
||||
|
||||
cache_age = time.time() - self._cache.get("timestamp", 0)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"total_channels": len(channels),
|
||||
"channels": channels,
|
||||
"cache_age_seconds": int(cache_age),
|
||||
"message": f"Riepilogo di {len(channels)} canali (cache: {int(cache_age)}s fa)"
|
||||
}
|
||||
|
||||
|
||||
class TF5AIAgent:
|
||||
"""Agente AI per controllare il mixer TF5 con linguaggio naturale."""
|
||||
|
||||
def __init__(self, mixer_host=DEFAULT_HOST, mixer_port=DEFAULT_PORT):
|
||||
self.controller = TF5MixerController(mixer_host, mixer_port)
|
||||
|
||||
# Configura il client Gemini
|
||||
api_key = 'AIzaSyCdjM5XE0hv9O1Ecx-uspvI8UInSzK1U9M'
|
||||
if not api_key:
|
||||
raise ValueError("GEMINI_API_KEY non trovata nelle variabili d'ambiente")
|
||||
|
||||
self.client = genai.Client(api_key=api_key)
|
||||
|
||||
# Configura gli strumenti con automatic function calling
|
||||
self.config = types.GenerateContentConfig(
|
||||
tools=[
|
||||
self.controller.recall_scene,
|
||||
self.controller.set_channel_level,
|
||||
self.controller.set_channel_on_off,
|
||||
self.controller.set_channel_pan,
|
||||
self.controller.set_mix_level,
|
||||
self.controller.mute_multiple_channels,
|
||||
self.controller.unmute_multiple_channels,
|
||||
self.controller.get_channel_info,
|
||||
self.controller.search_channels_by_name,
|
||||
self.controller.get_all_channels_summary,
|
||||
self.controller.refresh_cache,
|
||||
],
|
||||
temperature=0,
|
||||
)
|
||||
|
||||
# Messaggio di sistema per dare contesto all'AI
|
||||
self.system_instruction = """Sei un assistente esperto per il controllo di mixer audio Yamaha TF5.
|
||||
|
||||
Il mixer ha:
|
||||
- 40 canali di input (numerati da 1 a 40)
|
||||
- 20 mix/aux bus (numerati da 1 a 20)
|
||||
- Livelli fader espressi in dB (da -inf a +10.0 dB)
|
||||
- Pan da -63 (sinistra) a +63 (destra), 0 è centro
|
||||
- Scene salvate nei banchi A e B (numerate da 0 a 99)
|
||||
|
||||
IMPORTANTE - Sistema di Cache:
|
||||
- Le informazioni sui canali sono cachate per 5 minuti per evitare di sovraccaricare il mixer
|
||||
- Usa search_channels_by_name e get_all_channels_summary che usano automaticamente la cache
|
||||
- La cache viene invalidata automaticamente quando si richiama una scena
|
||||
- Puoi usare refresh_cache se l'utente chiede esplicitamente dati aggiornati
|
||||
|
||||
Quando l'utente fa una richiesta:
|
||||
1. Interpreta il linguaggio naturale e identifica l'azione richiesta
|
||||
2. Usa le funzioni disponibili per eseguire i comandi
|
||||
3. Conferma all'utente cosa hai fatto in modo chiaro e conciso
|
||||
4. Se una richiesta non è chiara, chiedi chiarimenti
|
||||
|
||||
Esempi di comandi che puoi gestire:
|
||||
- "Alza il canale 5 a -10 dB"
|
||||
- "Spegni i canali dal 10 al 15"
|
||||
- "Imposta il pan del canale 3 tutto a sinistra"
|
||||
- "Richiama la scena A5"
|
||||
- "Muta i canali 1, 3, 5 e 7"
|
||||
- "Quali canali sono associati ai vox?" (cerca nei nomi usando cache)
|
||||
- "Mostrami lo stato del canale 12"
|
||||
- "Dammi la lista di tutti i canali" (usa cache)
|
||||
- "Aggiorna i dati dal mixer" (refresh_cache)
|
||||
"""
|
||||
|
||||
def chat(self, user_message: str) -> str:
|
||||
"""Invia un messaggio all'agente e riceve la risposta.
|
||||
|
||||
Args:
|
||||
user_message: Il messaggio dell'utente in linguaggio naturale
|
||||
|
||||
Returns:
|
||||
La risposta dell'agente
|
||||
"""
|
||||
try:
|
||||
full_prompt = f"{self.system_instruction}\n\nUtente: {user_message}"
|
||||
|
||||
response = self.client.models.generate_content(
|
||||
model="gemini-2.5-flash",
|
||||
contents=full_prompt,
|
||||
config=self.config,
|
||||
)
|
||||
|
||||
return response.text
|
||||
except Exception as e:
|
||||
return f"Errore nell'elaborazione della richiesta: {e}"
|
||||
|
||||
def interactive_mode(self):
|
||||
"""Avvia una sessione interattiva con l'agente."""
|
||||
print("=" * 70)
|
||||
print("TF5 Mixer AI Agent - Controllo tramite linguaggio naturale")
|
||||
print("=" * 70)
|
||||
|
||||
# Mostra stato cache
|
||||
cache_age = time.time() - self.controller._cache.get("timestamp", 0)
|
||||
if self.controller._is_cache_valid():
|
||||
print(f"\n💾 Cache disponibile (età: {int(cache_age)}s)")
|
||||
else:
|
||||
print("\n💾 Cache non disponibile, verrà creata al primo utilizzo")
|
||||
|
||||
print("\nEsempi di comandi:")
|
||||
print(" - 'Alza il canale 5 a -10 dB'")
|
||||
print(" - 'Spegni i canali dal 1 al 5'")
|
||||
print(" - 'Richiama la scena A10'")
|
||||
print(" - 'Imposta il pan del canale 3 a sinistra'")
|
||||
print(" - 'Muta i canali 2, 4, 6 e 8'")
|
||||
print(" - 'Quali canali sono associati ai vox?'")
|
||||
print(" - 'Mostrami lo stato del canale 12'")
|
||||
print(" - 'Aggiorna i dati dal mixer'")
|
||||
print("\nDigita 'esci' o 'quit' per terminare\n")
|
||||
|
||||
while True:
|
||||
try:
|
||||
user_input = input("\n🎛️ Tu: ").strip()
|
||||
|
||||
if user_input.lower() in ['esci', 'quit', 'exit', 'q']:
|
||||
print("\n👋 Arrivederci!")
|
||||
break
|
||||
|
||||
if not user_input:
|
||||
continue
|
||||
|
||||
print("\n🤖 Agent: ", end="", flush=True)
|
||||
response = self.chat(user_input)
|
||||
print(response)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n👋 Arrivederci!")
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"\n❌ Errore: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Funzione principale."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='TF5 Mixer AI Agent - Controllo tramite linguaggio naturale'
|
||||
)
|
||||
parser.add_argument('--host', default=DEFAULT_HOST, help=f'IP del mixer (default: {DEFAULT_HOST})')
|
||||
parser.add_argument('--port', type=int, default=DEFAULT_PORT, help=f'Porta (default: {DEFAULT_PORT})')
|
||||
parser.add_argument('--message', '-m', help='Invia un singolo comando invece di avviare la modalità interattiva')
|
||||
parser.add_argument('--refresh-cache', action='store_true', help='Forza l\'aggiornamento della cache all\'avvio')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Verifica che la API key sia impostata
|
||||
if not 'AIzaSyCdjM5XE0hv9O1Ecx-uspvI8UInSzK1U9M':
|
||||
print("❌ Errore: GEMINI_API_KEY non trovata nelle variabili d'ambiente")
|
||||
print("\nPer impostare la chiave API:")
|
||||
print(" export GEMINI_API_KEY='la-tua-chiave-api'")
|
||||
print("\nOttieni una chiave API gratuita su: https://aistudio.google.com/apikey")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
agent = TF5AIAgent(args.host, args.port)
|
||||
|
||||
# Refresh cache se richiesto
|
||||
if args.refresh_cache:
|
||||
agent.controller.refresh_cache()
|
||||
|
||||
if args.message:
|
||||
# Modalità singolo comando
|
||||
print(f"🎛️ Comando: {args.message}")
|
||||
print(f"\n🤖 Risposta: {agent.chat(args.message)}")
|
||||
else:
|
||||
# Modalità interattiva
|
||||
agent.interactive_mode()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Errore fatale: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user