- Frontend v4 accessible sur réseau local (192.168.1.40) - Ports ouverts: 3002 (frontend), 5001 (backend), 5004 (dashboard) - Ollama GPU fonctionnel - Self-healing interactif - Dashboard confiance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
217 lines
7.7 KiB
Python
217 lines
7.7 KiB
Python
"""Main Window - Modern GUI connected to core"""
|
|
from PyQt5.QtWidgets import *
|
|
from PyQt5.QtCore import *
|
|
from PyQt5.QtGui import *
|
|
from .orchestrator import RPAOrchestrator
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class MainWindow(QMainWindow):
|
|
"""Modern GUI with orchestrator integration"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("RPA Vision V3")
|
|
self.setGeometry(100, 100, 800, 600)
|
|
|
|
# Create orchestrator
|
|
self.orchestrator = RPAOrchestrator()
|
|
self._connect_orchestrator()
|
|
|
|
self._init_ui()
|
|
logger.info("MainWindow initialized")
|
|
|
|
def _connect_orchestrator(self):
|
|
"""Connect orchestrator signals"""
|
|
self.orchestrator.log_message.connect(self.add_log)
|
|
self.orchestrator.status_updated.connect(self.update_status)
|
|
self.orchestrator.training_progress.connect(self.update_training_progress)
|
|
|
|
def _init_ui(self):
|
|
"""Initialize UI"""
|
|
central = QWidget()
|
|
self.setCentralWidget(central)
|
|
layout = QVBoxLayout(central)
|
|
|
|
layout.addWidget(self._create_header())
|
|
|
|
self.tabs = QTabWidget()
|
|
self.tabs.addTab(self._create_live_tab(), "🔴 Live")
|
|
self.tabs.addTab(self._create_workflows_tab(), "📊 Workflows")
|
|
self.tabs.addTab(self._create_training_tab(), "🎓 Training")
|
|
layout.addWidget(self.tabs)
|
|
|
|
layout.addWidget(self._create_controls())
|
|
|
|
def _create_header(self):
|
|
widget = QWidget()
|
|
layout = QHBoxLayout(widget)
|
|
|
|
self.status_label = QLabel("État: 🟢 OBSERVATION")
|
|
self.status_label.setStyleSheet("font-size: 14px; font-weight: bold;")
|
|
layout.addWidget(self.status_label)
|
|
layout.addStretch()
|
|
|
|
self.confidence_label = QLabel("Confiance: --")
|
|
layout.addWidget(self.confidence_label)
|
|
|
|
self.workflows_label = QLabel("Workflows: 0")
|
|
layout.addWidget(self.workflows_label)
|
|
|
|
return widget
|
|
|
|
def _create_live_tab(self):
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
|
|
self.logs_text = QTextEdit()
|
|
self.logs_text.setReadOnly(True)
|
|
self.logs_text.setStyleSheet("background: #1e1e1e; color: #d4d4d4; font-family: monospace;")
|
|
layout.addWidget(self.logs_text)
|
|
|
|
return widget
|
|
|
|
def _create_workflows_tab(self):
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
|
|
self.workflows_table = QTableWidget(0, 4)
|
|
self.workflows_table.setHorizontalHeaderLabels(["ID", "État", "Succès", "Exécutions"])
|
|
self.workflows_table.horizontalHeader().setStretchLastSection(True)
|
|
layout.addWidget(self.workflows_table)
|
|
|
|
refresh_btn = QPushButton("🔄 Refresh")
|
|
refresh_btn.clicked.connect(self.refresh_workflows)
|
|
layout.addWidget(refresh_btn)
|
|
|
|
return widget
|
|
|
|
def _create_training_tab(self):
|
|
widget = QWidget()
|
|
layout = QVBoxLayout(widget)
|
|
|
|
layout.addWidget(QLabel("Sessions collectées:"))
|
|
self.training_progress = QProgressBar()
|
|
self.training_progress.setMaximum(100)
|
|
layout.addWidget(self.training_progress)
|
|
|
|
self.training_stats = QTextEdit()
|
|
self.training_stats.setReadOnly(True)
|
|
self.training_stats.setMaximumHeight(200)
|
|
layout.addWidget(self.training_stats)
|
|
|
|
btn_layout = QHBoxLayout()
|
|
self.export_btn = QPushButton("📤 Export Data")
|
|
self.export_btn.clicked.connect(self.export_training_data)
|
|
|
|
self.train_btn = QPushButton("🎯 Train Model")
|
|
self.train_btn.clicked.connect(self.train_model)
|
|
|
|
btn_layout.addWidget(self.export_btn)
|
|
btn_layout.addWidget(self.train_btn)
|
|
layout.addLayout(btn_layout)
|
|
|
|
layout.addStretch()
|
|
return widget
|
|
|
|
def _create_controls(self):
|
|
widget = QWidget()
|
|
layout = QHBoxLayout(widget)
|
|
|
|
self.start_btn = QPushButton("▶ Start")
|
|
self.start_btn.setStyleSheet("background: #4CAF50; color: white; padding: 10px; font-size: 14px;")
|
|
self.start_btn.clicked.connect(self.on_start)
|
|
|
|
self.pause_btn = QPushButton("⏸ Pause")
|
|
self.pause_btn.setEnabled(False)
|
|
self.pause_btn.clicked.connect(self.on_pause)
|
|
|
|
self.stop_btn = QPushButton("⏹ Stop")
|
|
self.stop_btn.setEnabled(False)
|
|
self.stop_btn.clicked.connect(self.on_stop)
|
|
|
|
layout.addWidget(self.start_btn)
|
|
layout.addWidget(self.pause_btn)
|
|
layout.addWidget(self.stop_btn)
|
|
|
|
return widget
|
|
|
|
def on_start(self):
|
|
"""Start button clicked"""
|
|
self.orchestrator.start()
|
|
self.start_btn.setEnabled(False)
|
|
self.pause_btn.setEnabled(True)
|
|
self.stop_btn.setEnabled(True)
|
|
|
|
def on_pause(self):
|
|
"""Pause button clicked"""
|
|
if self.pause_btn.text() == "⏸ Pause":
|
|
self.orchestrator.pause()
|
|
self.pause_btn.setText("▶ Resume")
|
|
else:
|
|
self.orchestrator.resume()
|
|
self.pause_btn.setText("⏸ Pause")
|
|
|
|
def on_stop(self):
|
|
"""Stop button clicked"""
|
|
self.orchestrator.stop()
|
|
self.start_btn.setEnabled(True)
|
|
self.pause_btn.setEnabled(False)
|
|
self.stop_btn.setEnabled(False)
|
|
self.pause_btn.setText("⏸ Pause")
|
|
|
|
def add_log(self, message: str):
|
|
"""Add log message"""
|
|
from datetime import datetime
|
|
timestamp = datetime.now().strftime("%H:%M:%S")
|
|
self.logs_text.append(f"[{timestamp}] {message}")
|
|
|
|
def update_status(self, state: str, confidence: float, workflows: int):
|
|
"""Update header status"""
|
|
icons = {
|
|
"OBSERVATION": "🟢",
|
|
"COACHING": "🟡",
|
|
"AUTO_CANDIDATE": "🟠",
|
|
"AUTO_CONFIRMED": "🔴"
|
|
}
|
|
icon = icons.get(state, "⚪")
|
|
self.status_label.setText(f"État: {icon} {state}")
|
|
self.confidence_label.setText(f"Confiance: {confidence:.0%}")
|
|
self.workflows_label.setText(f"Workflows: {workflows}")
|
|
|
|
def update_training_progress(self, current: int, total: int):
|
|
"""Update training progress bar"""
|
|
self.training_progress.setValue(current)
|
|
self.training_progress.setMaximum(total)
|
|
|
|
def refresh_workflows(self):
|
|
"""Refresh workflows table"""
|
|
workflows = self.orchestrator.get_workflows()
|
|
self.workflows_table.setRowCount(len(workflows))
|
|
|
|
for i, wf in enumerate(workflows):
|
|
self.workflows_table.setItem(i, 0, QTableWidgetItem(wf['id']))
|
|
self.workflows_table.setItem(i, 1, QTableWidgetItem(wf['state']))
|
|
self.workflows_table.setItem(i, 2, QTableWidgetItem(f"{wf['success_rate']:.0%}"))
|
|
self.workflows_table.setItem(i, 3, QTableWidgetItem(str(wf['executions'])))
|
|
|
|
self.add_log(f"✓ Refreshed {len(workflows)} workflows")
|
|
|
|
def export_training_data(self):
|
|
"""Export training data"""
|
|
if self.orchestrator.export_training_data():
|
|
QMessageBox.information(self, "Success", "Training data exported successfully!")
|
|
|
|
def train_model(self):
|
|
"""Train model"""
|
|
reply = QMessageBox.question(
|
|
self, "Train Model",
|
|
"This will train a new model on collected data. Continue?",
|
|
QMessageBox.Yes | QMessageBox.No
|
|
)
|
|
|
|
if reply == QMessageBox.Yes:
|
|
if self.orchestrator.train_model():
|
|
QMessageBox.information(self, "Success", "Model trained successfully!")
|