Compare commits
3 Commits
cbd3d40e39
...
2b1743c206
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1743c206 | ||
|
|
48879fb849 | ||
|
|
c12fd8e1c1 |
@@ -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: <app>/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).
|
||||
|
||||
|
||||
@@ -330,7 +330,8 @@ def import_learned_workflow(workflow_id: str):
|
||||
|
||||
# Extraire et sauvegarder le screenshot d'ancre si présent
|
||||
anchor_b64 = params.pop("_anchor_image_base64", None)
|
||||
params.pop("_anchor_bbox", None)
|
||||
# NE PAS supprimer _anchor_bbox : on le conserve dans params pour que le frontend puisse lire x_pct/y_pct
|
||||
# et afficher la zone ciblée, au lieu de le jeter et de créer une bbox factice.
|
||||
if anchor_b64:
|
||||
try:
|
||||
from services.anchor_image_service import (
|
||||
@@ -344,6 +345,8 @@ def import_learned_workflow(workflow_id: str):
|
||||
anchor_b64 = anchor_b64.split(',', 1)[1]
|
||||
img_data = b64mod.b64decode(anchor_b64)
|
||||
img = Image.open(BytesIO(img_data))
|
||||
# Fallback sécurisé pour le service de crop si _anchor_bbox n'a pas le format attendu,
|
||||
# mais les données x_pct/y_pct restent intactes dans params pour le frontend.
|
||||
bbox = {
|
||||
"x": 0, "y": 0,
|
||||
"width": img.width, "height": img.height
|
||||
|
||||
@@ -2800,7 +2800,10 @@
|
||||
|
||||
// Utiliser le proxy du dashboard pour éviter les problèmes CORS
|
||||
const STREAMING_BASE = '/api/streaming';
|
||||
const VWB_IMPORT_URL = 'http://localhost:5002/api/workflows/import-core';
|
||||
// Construire VWB_IMPORT_URL dynamiquement à partir de l'origine actuelle (ex: http://192.168.1.45:5001 -> http://192.168.1.45:5002)
|
||||
// pour éviter le hardcoded localhost et permettre les tests depuis la VM/poste via l'IP du banc.
|
||||
const VWB_BASE = window.location.origin.replace(/:\d+$/, ':5002');
|
||||
const VWB_IMPORT_URL = `${VWB_BASE}/api/workflows/import-core`;
|
||||
|
||||
async function refreshStreaming() {
|
||||
await Promise.all([
|
||||
|
||||
Reference in New Issue
Block a user