diff --git a/agent_v0/agent_v1/ui/chat_window.py b/agent_v0/agent_v1/ui/chat_window.py index bb9805970..422b6cc31 100644 --- a/agent_v0/agent_v1/ui/chat_window.py +++ b/agent_v0/agent_v1/ui/chat_window.py @@ -5,6 +5,9 @@ Fenetre de chat Lea integree au systray — version tkinter native. Remplace l'approche Edge browser par une vraie fenetre tkinter integree. Design professionnel, theme clair, ancree en bas a droite de l'ecran. Tourne dans son propre thread daemon pour ne pas bloquer pystray. + +Le runtime Python embedded Windows ne contient pas toujours Tcl/Tk. Dans ce +cas, le menu "Discuter avec Lea" ouvre le chat DGX dans le navigateur. """ import logging @@ -13,6 +16,8 @@ import math import threading import time from datetime import datetime +from pathlib import Path +from urllib.parse import urlparse from typing import Any, Callable, Dict, Optional logger = logging.getLogger(__name__) @@ -219,7 +224,10 @@ class ChatWindow: def toggle(self) -> None: """Afficher/masquer la fenetre de chat.""" - if self._destroyed or self._root is None: + if self._destroyed: + return + if self._root is None: + self._open_browser_fallback() return if self._visible: self.hide() @@ -228,7 +236,10 @@ class ChatWindow: def show(self) -> None: """Afficher la fenetre.""" - if self._destroyed or self._root is None: + if self._destroyed: + return + if self._root is None: + self._open_browser_fallback() return self._root.after(0, self._do_show) @@ -257,6 +268,79 @@ class ChatWindow: """Mettre a jour le client serveur (appele si cree apres la fenetre).""" self._server_client = server_client + def _chat_url(self) -> str: + """Retourne l'URL web du chat, derivee de la config serveur.""" + configured_url = self._chat_url_from_server_url(self._configured_server_url()) + if self._server_client is not None: + chat_base = getattr(self._server_client, "_chat_base", None) + if chat_base: + chat_base = str(chat_base).rstrip("/") + if not self._is_local_url(chat_base): + return chat_base + if configured_url: + return configured_url + + if configured_url: + return configured_url + + host = (self._server_host or "localhost").strip() + if host.startswith(("http://", "https://")): + parsed = urlparse(host) + scheme = parsed.scheme or "http" + hostname = parsed.hostname or "localhost" + return f"{scheme}://{hostname}:{self._chat_port}" + + return f"http://{host}:{self._chat_port}" + + @staticmethod + def _is_local_url(url: str) -> bool: + try: + host = urlparse(url).hostname + except Exception: + return False + return host in {"localhost", "127.0.0.1", "::1"} + + def _chat_url_from_server_url(self, server_url: Optional[str]) -> Optional[str]: + if not server_url: + return None + try: + parsed = urlparse(server_url.strip()) + except Exception: + return None + if not parsed.hostname or parsed.hostname in {"localhost", "127.0.0.1", "::1"}: + return None + scheme = parsed.scheme or "http" + return f"{scheme}://{parsed.hostname}:{self._chat_port}" + + def _configured_server_url(self) -> Optional[str]: + env_url = os.environ.get("RPA_SERVER_URL", "").strip() + if env_url: + return env_url + + try: + # Installed layout: /agent_v1/ui/chat_window.py. + for parent in Path(__file__).resolve().parents: + cfg = parent / "config.txt" + if cfg.exists(): + for line in cfg.read_text(encoding="utf-8", errors="ignore").splitlines(): + if line.startswith("RPA_SERVER_URL="): + return line.split("=", 1)[1].strip() + except Exception: + logger.debug("Lecture config.txt pour chat_url impossible", exc_info=True) + return None + + def _open_browser_fallback(self) -> None: + """Fallback POC quand tkinter est absent du Python embedded.""" + url = self._chat_url() + try: + import webbrowser + if webbrowser.open(url, new=1): + logger.info("ChatWindow indisponible, chat ouvert dans le navigateur: %s", url) + else: + logger.warning("ChatWindow indisponible, ouverture navigateur refusee: %s", url) + except Exception as exc: + logger.error("Impossible d'ouvrir le chat dans le navigateur (%s): %s", url, exc) + def _on_shared_state_change(self, state) -> None: """Callback appele quand l'etat partage change (depuis le systray ou ailleurs).