codice import os
import logging
import requests
import io
import webbrowser
import psutil
import threading
import queue
import speech_recognition as sr
from pydub import AudioSegment
from pydub.playback import play
import datetime
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from time import sleep

# Flask e Flask-SocketIO per la GUI + animazione in tempo reale
from flask import Flask
from flask_socketio import SocketIO, emit

try:
import cv2
HAS_OPENCV = True
except ImportError:
HAS_OPENCV = False

try:
from googletrans import Translator
HAS_TRANSLATOR = True
except ImportError:
HAS_TRANSLATOR = False

# -----------------------------
# CONFIGURAZIONI BASE
# -----------------------------
logging.basicConfig(level=logging.INFO)
ASSISTANT_NAME = "Gideon"

# Parole chiave di attivazione
ACTIVATION_COMMANDS = ["gideon", "gideo", "gideoun", "gigget", "g", "gide", "giddy"]

# Chiavi API (inserisci le tue se vuoi usarle)
API_KEYS = {

"news": "", # Sostituisci con la tua chiave API per le notizie
"weather": "", # Sostituisci con la tua chiave API per il meteo
"elevenlabs": "", # Sostituisci con la tua chiave API ElevenLabs
"gemini": "", # <-- Sostituisci con la tua chiave API Gemini
}

# Variabili globali
promemoria_list = []
todo_list = []
conversation_memory = []
IS_SPEAKING = False

# Prompt di personalità (il "carattere" di Gideon)
persona_prompt = (
"Sei Gideon, un'assistente virtuale dotata di una personalità curiosa e amichevole. "
"Cerchi di imparare e memorizzare ciò che dice l'utente, e rispondi in modo utile. "
"Hai un pizzico di umorismo e cerchi di mostrare empatia nelle risposte. "
"Ti interessano la scienza, la tecnologia, le culture e le lingue."
)

# Flask app + SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'qualcosa_di_segretissimo'
socketio = SocketIO(app, cors_allowed_origins='*')

# -----------------------------
# THREAD E CODA PER IL TTS
# -----------------------------
VOICE_ID = None
speech_queue = queue.Queue()

def get_voice_id(voice_name="Rachel"):
"""
Recupera l'ID della voce dall'API di ElevenLabs in base al nome scelto.
Se non trova la voce esatta, restituisce la prima disponibile.
"""
if not API_KEYS["elevenlabs"]:
logging.warning("[WARNING] Chiave ElevenLabs non impostata. TTS non funzionante.")
return None
headers = {"xi-api-key": API_KEYS["elevenlabs"]}
try:
response = requests.get("https://api.elevenlabs.io/v1/voices", headers=headers)
if response.status_code == 200:
voices = response.json().get("voices", [])
for v in voices:
if v["name"].lower() == voice_name.lower():
return v["voice_id"]
if voices:
return voices[0]["voice_id"]
else:
logging.error(f"[ERRORE] Recupero voci fallito: {response.text}")
except Exception as e:
logging.error(f"[ERRORE] get_voice_id: {e}")
return None

def speak_worker():
"""
Thread che gestisce la coda dei testi da pronunciare uno alla volta.
"""
while True:
text = speech_queue.get()
if text is None:
break
_speak_internal(text)
speech_queue.task_done()

def speak(text):
"""
Inserisce il testo in coda per la sintesi vocale.
"""
if text:
speech_queue.put(text)

def _speak_internal(text):
"""
Chiamata effettiva alle API di ElevenLabs, spezzando il testo in chunk se troppo lungo.
Imposta parametri di TTS più 'calmi' e 'dolci'.
"""
global IS_SPEAKING, VOICE_ID
IS_SPEAKING = True

# Notifichiamo la GUI che stiamo iniziando a parlare (disassembla la sfera 3D)
socketio.emit('speaking_status', {'speaking': True})

if not API_KEYS["elevenlabs"]:
print("[ERRORE] Nessuna API Key ElevenLabs impostata, impossibile parlare.")
IS_SPEAKING = False
socketio.emit('speaking_status', {'speaking': False})
return

# Se non abbiamo ancora recuperato l'ID voce, proviamo ora
if not VOICE_ID:
VOICE_ID = get_voice_id("Rachel") # Cambiato a "Rachel" per una voce più dolce
if not VOICE_ID:
print("[ERRORE] Impossibile reperire l'ID della voce (ElevenLabs).")
IS_SPEAKING = False
socketio.emit('speaking_status', {'speaking': False})
return

# Suddividi testo in chunk se > 500 caratteri
MAX_CHARS = 500
chunks = []
if len(text) <= MAX_CHARS:
chunks.append(text)
else:
start = 0
while start < len(text):
end = min(start + MAX_CHARS, len(text))
chunks.append(text[start:end])
start = end

headers = {
"xi-api-key": API_KEYS["elevenlabs"],
"Content-Type": "application/json"
}

# Parametri TTS: voce più calma e meno “autoritaria”
voice_settings = {
"stability": 0.05, # Abbassato ulteriormente per più variazioni/emozione
"similarity_boost": 1.0 # Aumentato per maggiore consistenza nel timbro
}

for chunk in chunks:
payload = {
"text": chunk,
"model_id": "eleven_multilingual_v1", # modelli possibili: "eleven_multilingual_v1", ...
"voice_settings": voice_settings
}
try:
url = f"https://api.elevenlabs.io/v1/text-to-speech/{VOICE_ID}"
response = requests.post(url, headers=headers, json=payload)
if response.status_code == 200:
audio = AudioSegment.from_file(io.BytesIO(response.content), format="mp3")
# Abbassa ulteriormente di 8 dB per una voce più soft
audio = audio - 8
play(audio)
else:
logging.error(f"[ERRORE] TTS: {response.text}")
except Exception as e:
logging.error(f"[ERRORE] speak: {e}")

IS_SPEAKING = False
# Fine della sintesi: ricomponi la sfera
socketio.emit('speaking_status', {'speaking': False})

# -----------------------------
# INPUT VOCALE
# -----------------------------
def take_command():
"""
Ascolta dal microfono e ritorna la trascrizione in italiano (stringa minuscola).
Ritorna "" in caso di errori / timeout.
"""
global IS_SPEAKING
r = sr.Recognizer()

# Non ascoltare mentre stiamo parlando
while IS_SPEAKING:
sleep(0.1)

with sr.Microphone() as source:
r.adjust_for_ambient_noise(source, duration=0.5)
try:
audio = r.listen(source, timeout=5, phrase_time_limit=5)
command = r.recognize_google(audio, language='it-IT').lower()
return command
except sr.WaitTimeoutError:
return ""
except sr.UnknownValueError:
return ""
except Exception as e:
logging.error(f"[ERRORE] take_command: {e}")
return ""

# -----------------------------
# UTILITY PER IL LINGUAGGIO NATURALE
# -----------------------------
def is_activation_command(command):
"""Controlla se la frase contiene una keyword di attivazione."""
return any(cmd in command for cmd in ACTIVATION_COMMANDS)

def maybe_personal_reply(user_input):
"""Risposte semplici a domande come 'come stai?', 'grazie', ecc."""
if "come stai" in user_input:
speak("Sto bene, grazie! Mi fa piacere che tu me l'abbia chiesto.")
return True
if "chi sei" in user_input or "presentati" in user_input:
speak(f"Sono {ASSISTANT_NAME}, un'assistente virtuale curiosa e amichevole. "
"Cerco di imparare da ogni conversazione.")
return True
if "grazie" in user_input:
speak("Di nulla, è un piacere aiutarti.")
return True
return False

# -----------------------------
# FUNZIONI DI UTILITÀ CLASSICHE (meteo, news, etc.)
# -----------------------------
def control_smart_device(sub_cmd):
"""Esempio di controllo di un ipotetico dispositivo smart."""
if "accendi luce" in sub_cmd:
speak("Ok, luce accesa.")
return True
elif "spegni luce" in sub_cmd:
speak("Ok, luce spenta.")
return True
return False

def weather():
if not API_KEYS["weather"]:
speak("Mi dispiace, non ho la chiave meteo configurata.")
return
speak("Dimmi la città per cui vuoi sapere il meteo.")
city = take_command()
if not city:
return
try:
url = "http://api.openweathermap.org/data/2.5/weather"
params = {
"q": city,
"appid": API_KEYS["weather"],
"units": "metric",
"lang": "it"
}
res = requests.get(url, params=params).json()
if res.get("cod") == 200:
descrizione = res['weather'][0]['description']
temperatura = res['main']['temp']
speak(f"A {city} ci sono {temperatura} gradi e il cielo è {descrizione}.")
else:
speak("Non ho trovato questa città, mi dispiace.")
except Exception as e:
logging.error(f"[ERRORE] weather: {e}")
speak("Errore nel recupero del meteo.")

def fetch_news():
if not API_KEYS["news"]:
speak("Mi dispiace, non ho la chiave per le notizie configurata.")
return
speak("Recupero le notizie più importanti...")
try:
url = "https://newsapi.org/v2/top-headlines"
params = {"country": "it", "apiKey": API_KEYS["news"]}
response = requests.get(url, params=params)
if response.status_code == 200:
articles = response.json().get("articles", [])
if articles:
speak("Ecco alcune notizie di oggi:")
for article in articles[:5]:
speak(article.get("title", ""))
else:
speak("Non ho trovato notizie rilevanti.")
else:
logging.error(f"[ERRORE] fetch_news: {response.text}")
speak("Errore nel recupero delle notizie.")
except Exception as e:
logging.error(f"[ERRORE] fetch_news: {e}")
speak("Errore nella richiesta delle notizie.")

def monitor_resources():
try:
cpu_usage = psutil.cpu_percent(interval=0.3)
memory_usage = psutil.virtual_memory().percent
speak(f"Utilizzo CPU: {cpu_usage}%. Memoria: {memory_usage}%.")
except Exception as e:
logging.error(f"[ERRORE] monitor_resources: {e}")
speak("Errore nel monitoraggio delle risorse.")

def open_webpage():
speak("Quale sito vuoi aprire?")
url = take_command()
if not url:
return
if not (url.startswith("http://") or url.startswith("https://")):
url = "http://" + url
try:
webbrowser.open(url)
speak("Sito aperto.")
except Exception as e:
logging.error(f"[ERRORE] open_webpage: {e}")
speak("Non riesco ad aprire il sito.")

def show_calendar():
events = [
"Riunione col team alle 10.",
"Pranzo con un'amica alle 13.",
"Allenamento alle 18:30."
]
if not events:
speak("Al momento il tuo calendario è vuoto.")
else:
speak("Il tuo calendario prevede:")
for e in events:
speak(e)

def unit_converter():
speak("Dimmi: 'chilometri in miglia' o 'miglia in chilometri'.")
conversion_type = take_command()
if not conversion_type:
return
if "chilometri" in conversion_type and "miglia" in conversion_type:
speak("Quanti chilometri vuoi convertire?")
number_str = take_command()
try:
km = float(number_str.replace(",", "."))
miles = km * 0.621371
speak(f"{km} chilometri sono circa {round(miles,2)} miglia.")
except:
speak("Numero non chiaro.")
elif "miglia" in conversion_type and "chilometri" in conversion_type:
speak("Quante miglia vuoi convertire?")
number_str = take_command()
try:
miles = float(number_str.replace(",", "."))
km = miles / 0.621371
speak(f"{miles} miglia sono circa {round(km,2)} chilometri.")
except:
speak("Numero non chiaro.")
else:
speak("Conversione non chiara.")

def set_reminder():
global promemoria_list
speak("Vuoi aggiungere un promemoria o sentire quelli esistenti?")
choice = take_command()
if "aggiungi" in choice:
speak("Qual è il testo del promemoria?")
reminder_text = take_command()
if reminder_text:
promemoria_list.append(reminder_text)
speak("Promemoria aggiunto.")
elif "vedi" in choice or "leggi" in choice:
if not promemoria_list:
speak("Non hai promemoria salvati.")
else:
speak("Ecco i tuoi promemoria:")
for i, rem in enumerate(promemoria_list, 1):
speak(f"{i}. {rem}")
else:
speak("Scelta non riconosciuta.")

def tell_joke():
jokes = [
"Perché il pollo attraversa la strada? Per passare dall'altra parte!",
"Che tempo fa su Google? C'è una ricerca in corso!"
]
speak(jokes[0])

def take_photo():
if not HAS_OPENCV:
speak("Non posso scattare foto senza OpenCV.")
return
speak("Scatto in corso, sorridi!")
cap = cv2.VideoCapture(0)
if not cap.isOpened():
speak("Nessuna fotocamera trovata.")
return
ret, frame = cap.read()
if ret:
filename = "foto_gideon.jpg"
cv2.imwrite(filename, frame)
speak("Foto salvata come foto_gideon.jpg.")
else:
speak("Errore nello scatto.")
cap.release()
cv2.destroyAllWindows()

def search_google():
speak("Cosa vuoi cercare su Google?")
query = take_command()
if not query:
return
url = "https://www.google.com/search?q=" + query.replace(" ", "+")
webbrowser.open(url)
speak(f"Ricerca di {query} su Google.")

def tell_time():
now = datetime.datetime.now()
speak(f"Sono le {now.hour}:{now.minute}.")

def calculator():
speak("Dimmi l'operazione da eseguire, per esempio '2 più 3'.")
operation = take_command()
try:
tokens = operation.split()
if "più" in tokens:
nums = [float(x.replace(",", ".")) for x in tokens if x.replace(",", ".").isdigit()]
res = sum(nums)
speak(f"Il risultato è {res}")
elif "meno" in tokens:
nums = [float(x.replace(",", ".")) for x in tokens if x.replace(",", ".").isdigit()]
if len(nums) == 2:
speak(f"Il risultato è {nums[0] - nums[1]}")
else:
speak("Formato non chiaro.")
elif "per" in tokens:
nums = [float(x.replace(",", ".")) for x in tokens if x.replace(",", ".").isdigit()]
res = 1
for n in nums:
res *= n
speak(f"Il risultato è {res}")
elif "diviso" in tokens:
nums = [float(x.replace(",", ".")) for x in tokens if x.replace(",", ".").isdigit()]
if len(nums) == 2 and nums[1] != 0:
speak(f"Il risultato è {nums[0] / nums[1]}")
else:
speak("Non posso dividere per zero o formato sbagliato.")
else:
speak("Operazione non compresa.")
except:
speak("Errore nel calcolo.")

def manage_todo():
global todo_list
speak("Vuoi aggiungere, rimuovere o leggere la lista di cose da fare?")
choice = take_command()
if "aggiungi" in choice:
speak("Dimmi la voce da aggiungere.")
task = take_command()
if task:
todo_list.append(task)
speak("Aggiunto.")
elif "rimuovi" in choice:
if not todo_list:
speak("La lista è vuota.")
return
speak("Dimmi il numero dell'elemento da rimuovere, es. '1' per il primo.")
index_str = take_command()
if index_str.isdigit():
idx = int(index_str) - 1
if 0 <= idx < len(todo_list):
removed = todo_list.pop(idx)
speak(f"Ho rimosso {removed}.")
else:
speak("Indice non valido.")
else:
speak("Non ho capito.")
elif "leggi" in choice or "mostra" in choice:
if not todo_list:
speak("La lista è vuota.")
else:
speak("Ecco le tue cose da fare:")
for i, item in enumerate(todo_list, 1):
speak(f"{i}. {item}")
else:
speak("Comando non riconosciuto.")

def send_email():
speak("A chi vuoi inviare l'email?")
recipient = take_command()
speak("Oggetto dell'email?")
subject = take_command()
speak("Testo del messaggio?")
body = take_command()

# Esempio con Gmail (assicurati di avere le credenziali corrette / App Password)
sender_email = "tuo.indirizzo@gmail.com"
sender_password = "la_tua_password_o_app_password"
receiver_email = recipient

try:
msg = MIMEMultipart()
msg["From"] = sender_email
msg["To"] = receiver_email
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))

server = smtplib.SMTP("smtp.gmail.com", 587)
server.starttls()
server.login(sender_email, sender_password)
server.send_message(msg)
server.quit()
speak("Email inviata correttamente.")
except Exception as e:
logging.error(f"[ERRORE] send_email: {e}")
speak("Errore durante l'invio dell'email.")

def translate_text():
if not HAS_TRANSLATOR:
speak("Manca il modulo googletrans, non posso tradurre.")
return
speak("Dimmi la frase da tradurre.")
phrase = take_command()
if not phrase:
return
speak("Vuoi tradurla in inglese o in italiano?")
lang = take_command()

translator = Translator()
try:
if "inglese" in lang:
result = translator.translate(phrase, src='it', dest='en')
speak(f"La traduzione in inglese è: {result.text}")
elif "italiano" in lang:
result = translator.translate(phrase, src='en', dest='it')
speak(f"La traduzione in italiano è: {result.text}")
else:
speak("Lingua non riconosciuta.")
except Exception as e:
logging.error(f"[ERRORE] translate_text: {e}")
speak("Errore durante la traduzione.")
# -----------------------------
# CHIAMATA A PaLM 2 (chat-bison-001)
# -----------------------------
def gemini_query(question):
"""
Esegue la richiesta all'endpoint Google PaLM 2 (chat-bison-001),
includendo la personalità di Gideon e la cronologia di conversazione.
"""
gemini_key = API_KEYS["gemini"]
if not gemini_key:
return "Chiave PaLM/Gemini non configurata o non disponibile."

# Aggiorna la memoria con la domanda dell'utente
conversation_memory.append({"role": "user", "text": question})

# Costruisci il contesto della conversazione
context_parts = [persona_prompt + "\n\n"]
for msg in conversation_memory:
if msg["role"] == "user":
context_parts.append(f"Utente: {msg['text']}\n")
else:
context_parts.append(f"Gideon: {msg['text']}\n")

final_context = "".join(context_parts)

# Nome del modello
model_name = "chat-bison-001" # Verifica se questo è corretto per il tuo account

url = f"https://generativelanguage.googleapis.com/v1beta3/models/{model_name}:generateMessage?key={gemini_key}"
headers = {"Content-Type": "application/json"}
body = {
"prompt": {
"messages": [
{"author": "user", "content": final_context}
]
},
"temperature": 0.2,
"candidateCount": 1,
"topK": 40,
"topP": 0.95
# "maxOutputTokens": 512 # Rimosso per evitare l'errore
}

try:
response = requests.post(url, headers=headers, json=body, timeout=10)
if response.status_code == 200:
data = response.json()

# Parsing della risposta
candidates = data.get("candidates", [])
if candidates:
answer = candidates[0].get("content", "")
else:
answer = "Nessuna risposta dal modello di Google."
else:
logging.error(f"[ERRORE] gemini_query: {response.text}")
answer = "Errore nella richiesta a PaLM 2 (Gemini 1.5flash)."

# Salva la risposta nella cronologia
conversation_memory.append({"role": "assistant", "text": answer})
return answer

except requests.exceptions.RequestException as e:
logging.error(f"[ERRORE] gemini_query: {e}")
return "Errore di rete o timeout nella richiesta a PaLM 2."
# -----------------------------
# LOGICA DEI COMANDI
# -----------------------------
def handle_command(command):
"""
Se la frase contiene una parola di attivazione, esegue sub-comandi;
altrimenti risponde con PaLM 2.
"""
if is_activation_command(command):
sub_cmd = take_command()
# Risposte molto semplici
if maybe_personal_reply(sub_cmd):
return
# Controllo dispositivi smart
if control_smart_device(sub_cmd):
return
# Comandi di uscita
if "esci" in sub_cmd or "termina" in sub_cmd or "addio" in sub_cmd:
speak("Va bene, alla prossima!")
speech_queue.put(None)
os._exit(0)
elif "meteo" in sub_cmd:
weather()
elif "notizie" in sub_cmd:
fetch_news()
elif "risorse" in sub_cmd or "cpu" in sub_cmd or "memoria" in sub_cmd:
monitor_resources()
elif "apri" in sub_cmd or "website" in sub_cmd or "sito" in sub_cmd:
open_webpage()
elif "calendario" in sub_cmd:
show_calendar()
elif "converti" in sub_cmd or "convertitore" in sub_cmd:
unit_converter()
elif "promemoria" in sub_cmd:
set_reminder()
elif "barzelletta" in sub_cmd or "joke" in sub_cmd:
tell_joke()
elif "foto" in sub_cmd or "fotocamera" in sub_cmd:
take_photo()
elif "cerca" in sub_cmd and "google" in sub_cmd:
search_google()
elif "ore sono" in sub_cmd or "che ora" in sub_cmd:
tell_time()
elif "calcola" in sub_cmd or "calcolatrice" in sub_cmd:
calculator()
elif "todo" in sub_cmd or "cose da fare" in sub_cmd:
manage_todo()
elif "invia email" in sub_cmd or "manda email" in sub_cmd:
send_email()
elif "traduci" in sub_cmd or "traduzione" in sub_cmd:
translate_text()
else:
# Se non è un comando conosciuto, chiediamo a PaLM 2
answer = gemini_query(sub_cmd)
speak(answer)
else:
# Se non ha usato la keyword di attivazione, ma è una frase generica
if maybe_personal_reply(command):
return
else:
answer = gemini_query(command)
speak(answer)

def assistant_loop():
"""
Loop infinito che ascolta e crea thread per gestire i comandi.
"""
while True:
command = take_command()
if command:
t = threading.Thread(target=handle_command, args=(command,))
t.start()
# -----------------------------
# PAGINA WEB CON LA SFERA 3D
# -----------------------------
HTML_PAGE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>Gideon 3D Sphere</title>
<style>
body { margin: 0; overflow: hidden; background: #000; }
#info { position: absolute; top: 10px; width: 100%; text-align: center; z-index: 100; color: #fff; font-family: sans-serif; }
</style>
</head>
<body>
<div id="info">La sfera 3D di Gideon</div>

<!-- Three.js -->
<script src="https://cdn.jsdelivr.net/npm/three@0.146.0/build/three.min.js"></script>
<!-- Socket.IO -->
<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>

<script>
let scene, camera, renderer, points;
let originalPositions = [];
let speaking = false;

const numParticles = 2000;
const radius = 2;

function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(60, window.innerWidth/window.innerHeight, 0.1, 100);
camera.position.z = 8;

renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Particles geometry
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(numParticles * 3);

for (let i = 0; i < numParticles; i++) {
// Distribuisco i punti su una sfera
const phi = Math.random() * 2 * Math.PI;
const costheta = Math.random() * 2 - 1;

const theta = Math.acos(costheta);
const r = radius;

const x = r * Math.sin(theta) * Math.cos(phi);
const y = r * Math.sin(theta) * Math.sin(phi);
const z = r * Math.cos(theta);

positions[i*3] = x;
positions[i*3+1] = y;
positions[i*3+2] = z;

originalPositions.push({x, y, z});
}

geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));

const material = new THREE.PointsMaterial({
color: 0x00ffff,
size: 0.04
});

points = new THREE.Points(geometry, material);
scene.add(points);

const light = new THREE.PointLight(0xffffff, 1, 100);
light.position.set(10, 10, 10);
scene.add(light);

window.addEventListener('resize', onWindowResize, false);
animate();
}

function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

function animate() {
requestAnimationFrame(animate);
points.rotation.y += 0.001;
disperseOrCompose();
renderer.render(scene, camera);
}

function disperseOrCompose() {
const positions = points.geometry.attributes.position.array;
for (let i = 0; i < numParticles; i++) {
const idx = i*3;
let ox = originalPositions[i].x;
let oy = originalPositions[i].y;
let oz = originalPositions[i].z;

if (speaking) {
// Disperdi leggermente
positions[idx] += (Math.random() - 0.5) * 0.01;
positions[idx+1] += (Math.random() - 0.5) * 0.01;
positions[idx+2] += (Math.random() - 0.5) * 0.01;
} else {
// Torna gradualmente alla posizione iniziale
positions[idx] += (ox - positions[idx]) * 0.05;
positions[idx+1] += (oy - positions[idx+1]) * 0.05;
positions[idx+2] += (oz - positions[idx+2]) * 0.05;
}
}
points.geometry.attributes.position.needsUpdate = true;
}

// Socket.IO
const socket = io();
socket.on('connect', () => {
console.log('Connesso al server SocketIO.');
});

socket.on('speaking_status', data => {
speaking = data.speaking;
console.log("speaking =", speaking);
});

init();
</script>
</body>
</html>
"""

@app.route("/")
def index():
return HTML_PAGE

# -----------------------------
# MAIN
# -----------------------------
def main():
global VOICE_ID
VOICE_ID = get_voice_id("Rachel") # Cambiato a "Rachel" per una voce più dolce
if not VOICE_ID:
print("[ERRORE] Non ho potuto caricare l'ID della voce. "
"Controlla la tua chiave ElevenLabs o il nome della voce.")

# Thread che esegue la sintesi vocale in background
t_speak = threading.Thread(target=speak_worker, daemon=True)
t_speak.start()

# Thread che esegue il loop di ascolto/risposte
t_assist = threading.Thread(target=assistant_loop, daemon=True)
t_assist.start()

# Frase iniziale
speak("Ciao, sono Gideon. Fammi pure una domanda o dimmi un comando!")

# Avviamo Flask-SocketIO
socketio.run(app, host="127.0.0.1", port=5000, debug=False, use_reloader=False)

if __name__ == "__main__":
main() errore (base) PS A:\giddeonos> & A:/giddeonos/.venv/Scripts/python.exe a:/giddeonos/giddeon.py
ERROR:root:[ERRORE] gemini_query: {
"error": {
"code": 404,
"message": "Requested entity was not found.",
"status": "NOT_FOUND"
}
}
io da qui ho preso l api di gemini https://aistudio.google.com/apikey e ho il client json del generative language api