Files
avatar/main.py
T
lucianoandClaude Fable 5.1 ff79832c30 AvatarPy: assistente personale con volto 3D, voce, memoria e plugin
Riscrittura in Python dell'assistente Avatar con interfaccia HUD (derivata da Mark LIV, CC BY-NC 4.0, vedi NOTICE.md).
Tre motori (Claude API, server locale OpenAI-compatibile, Claude Code), voce Kokoro/macOS, Whisper MLX,
avatar 3D con sincronizzazione labiale, memoria per categorie, allegati con OCR, monitor con avvisi,
plugin per Calendario, Mail, Promemoria, Note, Musica, app, Mac, timer, meteo, contatti, Messaggi,
file, Comandi Rapidi, browser, Telegram, WhatsApp (archivio e tempo reale).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-23 16:21:39 +02:00

95 lines
3.3 KiB
Python

"""AvatarPy: assistente personale con volto animato (interfaccia da Mark-LIV, motori propri)."""
from __future__ import annotations
import json
import platform
import sys
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(BASE_DIR))
from avatar.settings import CONFIG_DIR, Settings # noqa: E402
def ensure_identity_file() -> None:
"""L'interfaccia legge config/api_keys.json per nome, colore e stato di configurazione."""
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
f = CONFIG_DIR / "api_keys.json"
data: dict = {}
if f.exists():
try:
data = json.loads(f.read_text(encoding="utf-8"))
except Exception:
data = {}
changed = False
if not data.get("os_system"):
data["os_system"] = {"Darwin": "mac", "Windows": "windows"}.get(platform.system(), "linux")
changed = True
if not data.get("assistant_name"):
data["assistant_name"] = "Ava"
changed = True
if changed:
f.write_text(json.dumps(data, indent=4, ensure_ascii=False), encoding="utf-8")
def main() -> None:
ensure_identity_file()
settings = Settings()
try:
from PyQt6 import QtWebEngineWidgets # noqa: F401 (va importato prima della QApplication)
except Exception as err:
print(f"[avatar3d] WebEngine non disponibile: {err}")
from ui import JarvisUI # crea la QApplication
from avatar.assistant import Assistant
from avatar.settings_dialog import SettingsDialog
ui = JarvisUI(str(BASE_DIR / "core" / "face_model.obj"))
ui._win._log._ai_name_lc = ui.assistant_name.lower()
assistant = Assistant(ui, settings)
ui.on_text_command = assistant.handle_text
ui.on_interrupt = assistant.interrupt
ui.on_voice_change = lambda: assistant.reload_voice(from_customise=True)
ui.on_audio_device_change = assistant.reopen_audio
ui.on_push_to_talk = assistant.set_push_to_talk
ui.ptt_hold = assistant.ptt_hold
ui.on_wake_toggle = assistant.on_wake_toggle
ui.on_wake_manual = assistant.on_wake_manual
ui.wake_get_state = assistant.wake_get_state
from avatar.plugins import registry
from core import confirm
confirm.bind(ui.show_confirm, ui.hide_confirm, ui.write_log)
registry.ctx = {"say": assistant.say, "log": ui.write_log, "confirm": confirm.request, "player": ui}
ui.get_plugins = registry.list_for_ui
ui.get_plugin_settings = lambda: []
ui.request_say = assistant.say
# Questi due sono attributi della finestra, non della facciata JarvisUI.
ui._win.on_new_conversation = assistant.reset_conversation
def open_settings() -> None:
SettingsDialog(settings, assistant.reconfigure, parent=ui._win).exec()
ui._win.on_engine_settings = open_settings
if "--smoke" in sys.argv:
from PyQt6.QtCore import QTimer
QTimer.singleShot(3000, lambda: (print("SMOKE OK"), ui._app.quit()))
else:
if not settings.ready:
from PyQt6.QtCore import QTimer
QTimer.singleShot(800, open_settings)
assistant.start()
try:
ui.root.mainloop()
finally:
try:
from avatar import whatsapp_bridge as wb
wb.stop()
except Exception:
pass
if __name__ == "__main__":
main()