diff --git a/JiboTools/JiboTools/.gitignore b/JiboTools/JiboTools/.gitignore
new file mode 100644
index 0000000..ca8b61e
--- /dev/null
+++ b/JiboTools/JiboTools/.gitignore
@@ -0,0 +1,84 @@
+# This file is used to ignore files which are generated
+# ----------------------------------------------------------------------------
+
+*~
+*.autosave
+*.a
+*.core
+*.moc
+*.o
+*.obj
+*.orig
+*.rej
+*.so
+*.so.*
+*_pch.h.cpp
+*_resource.rc
+*.qm
+.#*
+*.*#
+core
+!core/
+tags
+.DS_Store
+.directory
+*.debug
+Makefile*
+*.prl
+*.app
+moc_*.cpp
+ui_*.h
+qrc_*.cpp
+Thumbs.db
+*.res
+*.rc
+/.qmake.cache
+/.qmake.stash
+**/.qmlls.ini
+
+# qtcreator generated files
+*.pro.user*
+*.qbs.user*
+CMakeLists.txt.user*
+
+# xemacs temporary files
+*.flc
+
+# Vim temporary files
+.*.swp
+
+# Visual Studio generated files
+*.ib_pdb_index
+*.idb
+*.ilk
+*.pdb
+*.sln
+*.suo
+*.vcproj
+*vcproj.*.*.user
+*.ncb
+*.sdf
+*.opensdf
+*.vcxproj
+*vcxproj.*
+
+# MinGW generated files
+*.Debug
+*.Release
+
+# Python byte code
+*.pyc
+
+# Binaries
+# --------
+*.dll
+*.exe
+
+# Directories with generated files
+.moc/
+.obj/
+.pch/
+.rcc/
+.uic/
+/build*/
+/.qtcreator/
diff --git a/JiboTools/JiboTools/form.ui b/JiboTools/JiboTools/form.ui
new file mode 100644
index 0000000..f6b9cb5
--- /dev/null
+++ b/JiboTools/JiboTools/form.ui
@@ -0,0 +1,643 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 782
+ 649
+
+
+
+ Jibo Tools
+
+
+ QTabWidget::TabShape::Rounded
+
+
+
+
+ 12
+
+
+ 14
+
+
+ 14
+
+
+ 14
+
+
+ 14
+
+ -
+
+
+ 0
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+
+ Jibo
+
+
+
+ 14
+
+
-
+
+
+
+ 420
+ 0
+
+
+
+ QFrame::Shape::StyledPanel
+
+
+ QFrame::Shadow::Raised
+
+
+
+ 10
+
+
+ 18
+
+
+ 18
+
+
+ 18
+
+
+ 18
+
+
-
+
+
+
+ 12
+ true
+
+
+
+ Config
+
+
+
+ -
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 380
+ 478
+
+
+
+
+ 14
+
+
-
+
+
+ Preview
+
+
+
-
+
+
+ Override
+
+
+
+ -
+
+
+
+
+
+ true
+
+
+
+ -
+
+
+ Connected
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+ -
+
+
+ Home Assistant
+
+
+
-
+
+
+ Enabled
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ Server IP
+
+
+
+ -
+
+
+ Home Assistant host
+
+
+
+
+
+
+ -
+
+
+ AI Provider
+
+
+
-
+
+
+ Enabled
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ Provider
+
+
+
+ -
+
+
+ -
+
+
+ API endpoint
+
+
+
+ -
+
+
+ http://...
+
+
+
+ -
+
+
+ API key
+
+
+
+ -
+
+
+ QLineEdit::EchoMode::Password
+
+
+
+ -
+
+
+ Tokens used
+
+
+
+ -
+
+
+ -1
+
+
+
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+ -
+
+
+ QFrame::Shape::StyledPanel
+
+
+ QFrame::Shadow::Raised
+
+
+
+ 12
+
+
+ 18
+
+
+ 18
+
+
+ 18
+
+
+ 18
+
+
-
+
+
-
+
+
+ Connect
+
+
+ false
+
+
+
+ -
+
+
+
+
+
+
+
+
+ e.g 192.168.1.54
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 12
+ true
+ false
+
+
+
+ Connect Your Jibo
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+
+ 260
+ 260
+
+
+
+
+ 260
+ 260
+
+
+
+
+
+
+ Qt::AlignmentFlag::AlignCenter
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+ QSizePolicy::Policy::Minimum
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ false
+
+
+ Robot Settings
+
+
+ false
+
+
+
+ -
+
+
+ false
+
+
+ true
+
+
+ Reboot
+
+
+ Reboot
+
+
+
+
+
+
+
+
+
+
+ Update
+
+
+
+ 12
+
+ -
+
+
+ QFrame::Shape::StyledPanel
+
+
+ QFrame::Shadow::Raised
+
+
+
+ 12
+
+
+ 18
+
+
+ 18
+
+
+ 18
+
+
+ 18
+
+
-
+
+
+ Installer and updater remain available via CLI. Use the buttons below to launch their GUIs.
+
+
+ true
+
+
+
+ -
+
+
+ Qt::Orientation::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ 12
+
+
-
+
+
+ Install
+
+
+
+ -
+
+
+ Check for updates
+
+
+
+
+
+
+
+
+
+
+
+
+ Skills
+
+
+ -
+
+
+ Coming soon.
+
+
+
+
+
+
+
+ SSH
+
+
+ -
+
+
+ Coming soon.
+
+
+
+
+
+
+
+ FTP
+
+
+ -
+
+
+ Coming soon.
+
+
+
+
+
+
+
+ Status
+
+
+
+ 10
+
+ -
+
+
+ 10
+
+
-
+
+
+
+ 10
+ 10
+
+
+
+
+ 10
+ 10
+
+
+
+
+
+
+
+ -
+
+
+ No Jibo IP configured
+
+
+ true
+
+
+
+
+
+
+
+
+
+ Robot OS
+
+
+ -
+
+
+ Coming soon.
+
+
+
+
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
diff --git a/JiboTools/JiboTools/gui/Assets/Jibo/Archive.zip b/JiboTools/JiboTools/gui/Assets/Jibo/Archive.zip
new file mode 100644
index 0000000..ef52089
Binary files /dev/null and b/JiboTools/JiboTools/gui/Assets/Jibo/Archive.zip differ
diff --git a/JiboTools/JiboTools/gui/Assets/Jibo/JiboFaceForward.png b/JiboTools/JiboTools/gui/Assets/Jibo/JiboFaceForward.png
new file mode 100644
index 0000000..a97a45b
Binary files /dev/null and b/JiboTools/JiboTools/gui/Assets/Jibo/JiboFaceForward.png differ
diff --git a/JiboTools/JiboTools/gui/Assets/Jibo/NoJiboConnected.png b/JiboTools/JiboTools/gui/Assets/Jibo/NoJiboConnected.png
new file mode 100644
index 0000000..2af7541
Binary files /dev/null and b/JiboTools/JiboTools/gui/Assets/Jibo/NoJiboConnected.png differ
diff --git a/gui/__init__.py b/JiboTools/JiboTools/gui/__init__.py
similarity index 100%
rename from gui/__init__.py
rename to JiboTools/JiboTools/gui/__init__.py
diff --git a/JiboTools/JiboTools/gui/assets/jibo.kra b/JiboTools/JiboTools/gui/assets/jibo.kra
new file mode 100644
index 0000000..2130a7f
Binary files /dev/null and b/JiboTools/JiboTools/gui/assets/jibo.kra differ
diff --git a/gui/assets/jibo.svg b/JiboTools/JiboTools/gui/assets/jibo.svg
similarity index 100%
rename from gui/assets/jibo.svg
rename to JiboTools/JiboTools/gui/assets/jibo.svg
diff --git a/JiboTools/JiboTools/gui/installer_gui.py b/JiboTools/JiboTools/gui/installer_gui.py
new file mode 100644
index 0000000..e3d3112
--- /dev/null
+++ b/JiboTools/JiboTools/gui/installer_gui.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+import sys
+from PySide6.QtWidgets import QApplication
+
+from .tool_runner_window import ToolRunnerWindow
+
+
+def main() -> int:
+ app = QApplication(sys.argv)
+ win = ToolRunnerWindow(title="Installer", script="jibo_automod.py")
+ win.show()
+ return int(app.exec())
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/JiboTools/JiboTools/gui/main_panel.py b/JiboTools/JiboTools/gui/main_panel.py
new file mode 100644
index 0000000..49427a1
--- /dev/null
+++ b/JiboTools/JiboTools/gui/main_panel.py
@@ -0,0 +1,318 @@
+from __future__ import annotations
+
+import json
+import subprocess
+import sys
+from pathlib import Path
+from typing import Optional
+
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QPixmap
+from PySide6.QtWidgets import (
+ QApplication,
+ QCheckBox,
+ QComboBox,
+ QFrame,
+ QHBoxLayout,
+ QLabel,
+ QLineEdit,
+ QPushButton,
+ QTabWidget,
+)
+
+from .process_runner import resolve_python, resolve_python_invocation
+from .ui_loader import load_ui, require_child
+
+
+def _set_dot(label: QLabel, color: str) -> None:
+ label.setStyleSheet(
+ "QLabel {"
+ f"background-color: {color};"
+ "border-radius: 5px;"
+ "}"
+ )
+
+
+class MainWindowController:
+ def __init__(self) -> None:
+ project_root = Path(__file__).resolve().parents[1]
+ ui_path = project_root / "form.ui"
+ self.window = load_ui(ui_path)
+ if not hasattr(self.window, "setWindowTitle"):
+ raise RuntimeError("form.ui must have a QMainWindow root")
+
+ self._ssh_client: Optional[object] = None
+ self._identity: Optional[dict] = None
+ self._connecting = False
+
+ # Tabs + connection pill
+ self.tab_widget = require_child(self.window, "tabWidget", QTabWidget)
+ self.connection_pill, self.conn_dot, self.conn_text = self._create_connection_pill()
+ self.tab_widget.setCornerWidget(self.connection_pill, Qt.TopRightCorner)
+
+ # Jibo/config
+ self.jibo_ip = require_child(self.window, "JiboIpField", QLineEdit)
+ self.connect_button = require_child(self.window, "TryToConnect", QPushButton)
+ self.jibo_title = require_child(self.window, "jiboTitle", QLabel)
+ self.override_check = require_child(self.window, "overrideCheck", QCheckBox)
+ self.preview_connected_check = require_child(self.window, "previewConnectedCheck", QCheckBox)
+
+ self.ha_enable = require_child(self.window, "haEnableCheck", QCheckBox)
+ self.ha_server_ip = require_child(self.window, "haServerIpField", QLineEdit)
+
+ self.ai_enable = require_child(self.window, "aiEnableCheck", QCheckBox)
+ self.ai_provider = require_child(self.window, "aiProviderCombo", QComboBox)
+ self.ai_endpoint = require_child(self.window, "aiEndpointField", QLineEdit)
+ self.ai_key = require_child(self.window, "aiKeyField", QLineEdit)
+ self.tokens_used = require_child(self.window, "tokensUsedLabel", QLabel)
+
+ # Jibo card controls
+ self.robot_settings_button = require_child(self.window, "RobotSettings", QPushButton)
+ self.robot_action_combo = require_child(self.window, "comboBox", QComboBox)
+ self.jibo_image = require_child(self.window, "jiboImage", QLabel)
+
+ # Update page
+ self.install_button = require_child(self.window, "installButton", QPushButton)
+ self.check_updates_button = require_child(self.window, "checkUpdatesButton", QPushButton)
+
+ # Status page
+ self.status_dot = require_child(self.window, "statusDot", QLabel)
+ self.status_text = require_child(self.window, "statusText", QLabel)
+
+ self._configure_ui()
+ self._wire_signals()
+ self._sync_enabled()
+ self._sync_all()
+
+ @property
+ def host(self) -> str:
+ return self.jibo_ip.text().strip()
+
+ @property
+ def session_connected(self) -> bool:
+ return self._ssh_client is not None
+
+ def effective_connected(self) -> bool:
+ """Effective connection state for visuals.
+
+ The Preview override is kept for UI testing, but the real connect/
+ disconnect state comes from an active SSH session.
+ """
+
+ if self.override_check.isChecked():
+ return self.preview_connected_check.isChecked()
+ return self.session_connected
+
+ def _configure_ui(self) -> None:
+ # Simple styling, roughly matching the previous QML look.
+ self.connection_pill.setStyleSheet(
+ "QFrame#connectionPill {"
+ "background-color: #f6f6f6;"
+ "border: 1px solid #e4e4e4;"
+ "border-radius: 14px;"
+ "}"
+ )
+
+ # Provider choices
+ self.ai_provider.clear()
+ self.ai_provider.addItems(["Self-hosted", "OpenAI", "Other"])
+
+ # Robot controls start disabled until connected.
+ self.robot_settings_button.setEnabled(False)
+ self.robot_action_combo.setEnabled(False)
+
+ # Defaults
+ self.tokens_used.setText("-1")
+ self.connect_button.setText("Connect")
+ self.jibo_title.setText("Connect Your Jibo")
+
+ def _wire_signals(self) -> None:
+ self.connect_button.clicked.connect(self._toggle_connection)
+
+ self.override_check.toggled.connect(self._sync_enabled)
+ self.preview_connected_check.toggled.connect(self._sync_all)
+ self.override_check.toggled.connect(self._sync_all)
+
+ self.ha_enable.toggled.connect(self._sync_enabled)
+ self.ai_enable.toggled.connect(self._sync_enabled)
+
+ self.install_button.clicked.connect(self._launch_installer)
+ self.check_updates_button.clicked.connect(self._launch_updater)
+
+ def _sync_enabled(self) -> None:
+ self.preview_connected_check.setEnabled(self.override_check.isChecked())
+ self.ha_server_ip.setEnabled(self.ha_enable.isChecked())
+
+ ai_enabled = self.ai_enable.isChecked()
+ self.ai_provider.setEnabled(ai_enabled)
+ self.ai_endpoint.setEnabled(ai_enabled)
+ self.ai_key.setEnabled(ai_enabled)
+
+ # Connection button enabled unless a connect attempt is in progress.
+ self.connect_button.setEnabled(not self._connecting)
+
+ def _sync_all(self) -> None:
+ host = self.host
+ connected = self.session_connected
+ visual_connected = self.effective_connected()
+
+ if connected:
+ title = (self._identity or {}).get("name") or "Connected"
+ else:
+ title = "Connect Your Jibo"
+ self.jibo_title.setText(title)
+
+ self.robot_settings_button.setEnabled(connected)
+ self.robot_action_combo.setEnabled(connected)
+ self.connect_button.setText("Disconnect" if connected else "Connect")
+
+ dot_color = "#2ecc71" if connected else ("#e67e22" if host else "#bdc3c7")
+ _set_dot(self.conn_dot, dot_color)
+ _set_dot(self.status_dot, dot_color)
+
+ if connected:
+ self.conn_text.setText("Connected")
+ else:
+ self.conn_text.setText("Disconnected" if host else "No IP")
+
+ if connected:
+ self.status_text.setText(f"Connected via SSH to {host}" if host else "Connected via SSH")
+ elif host:
+ self.status_text.setText(f"Disconnected ({host})")
+ else:
+ self.status_text.setText("No Jibo IP configured")
+
+ # Image swap
+ assets = Path(__file__).resolve().parent / "Assets" / "Jibo"
+ img_path = assets / ("JiboFaceForward.png" if visual_connected else "NoJiboConnected.png")
+ pm = QPixmap(str(img_path))
+ if not pm.isNull():
+ pm = pm.scaled(
+ self.jibo_image.size(),
+ Qt.AspectRatioMode.KeepAspectRatio,
+ Qt.TransformationMode.SmoothTransformation,
+ )
+ self.jibo_image.setPixmap(pm)
+
+ def _launch_installer(self) -> None:
+ program, prefix = resolve_python_invocation()
+ subprocess.Popen(
+ [program, *prefix, "-m", "gui.installer_gui"],
+ cwd=str(Path(__file__).resolve().parents[1]),
+ )
+
+ def _launch_updater(self) -> None:
+ program, prefix = resolve_python_invocation()
+ subprocess.Popen(
+ [program, *prefix, "-m", "gui.updater_gui"],
+ cwd=str(Path(__file__).resolve().parents[1]),
+ )
+
+ def _disconnect(self) -> None:
+ try:
+ if self._ssh_client is not None:
+ self._ssh_client.close()
+ finally:
+ self._ssh_client = None
+ self._identity = None
+ self._sync_all()
+
+ def _toggle_connection(self) -> None:
+ if self.session_connected:
+ self._disconnect()
+ return
+
+ host = self.host
+ if not host:
+ self.status_text.setText("Enter a Jibo IP address")
+ return
+
+ self._connecting = True
+ self._sync_enabled()
+ self.status_text.setText(f"Connecting to {host}...")
+
+ try:
+ import paramiko # type: ignore
+ except Exception:
+ self._connecting = False
+ self._sync_enabled()
+ self.status_text.setText("Paramiko not installed; install requirements")
+ return
+
+ try:
+ client = paramiko.SSHClient()
+ client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+ client.connect(
+ hostname=host,
+ username="root",
+ password="jibo",
+ look_for_keys=False,
+ allow_agent=False,
+ timeout=10,
+ banner_timeout=10,
+ auth_timeout=10,
+ )
+
+ sftp = client.open_sftp()
+ try:
+ with sftp.open("/var/jibo/identity.json", "r") as f:
+ raw = f.read()
+ finally:
+ sftp.close()
+
+ if isinstance(raw, bytes):
+ raw_text = raw.decode("utf-8", errors="replace")
+ else:
+ raw_text = str(raw)
+
+ identity = json.loads(raw_text)
+
+ # Success: store session.
+ self._ssh_client = client
+ self._identity = identity if isinstance(identity, dict) else None
+ self.status_text.setText(f"Connected via SSH to {host}")
+ except Exception as e:
+ try:
+ client.close()
+ except Exception:
+ pass
+ self.status_text.setText(f"Connect failed: {e}")
+ finally:
+ self._connecting = False
+ self._sync_enabled()
+ self._sync_all()
+
+ def _create_connection_pill(self) -> tuple[QFrame, QLabel, QLabel]:
+ pill = QFrame()
+ pill.setObjectName("connectionPill")
+
+ layout = QHBoxLayout(pill)
+ layout.setContentsMargins(10, 4, 10, 4)
+ layout.setSpacing(6)
+
+ dot = QLabel()
+ dot.setObjectName("connDot")
+ dot.setFixedSize(10, 10)
+
+ text = QLabel("No IP")
+ text.setObjectName("connText")
+
+ layout.addWidget(dot)
+ layout.addWidget(text)
+
+ # Keep it tight on the tab bar.
+ pill.setSizePolicy(pill.sizePolicy().horizontalPolicy(), pill.sizePolicy().verticalPolicy())
+ pill.setMinimumHeight(28)
+ return pill, dot, text
+
+
+def main() -> int:
+ _ = resolve_python # keep import stable (used elsewhere)
+ app = QApplication(sys.argv)
+ ctrl = MainWindowController()
+ ctrl.window.show()
+ return int(app.exec())
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/gui/process_runner.py b/JiboTools/JiboTools/gui/process_runner.py
similarity index 86%
rename from gui/process_runner.py
rename to JiboTools/JiboTools/gui/process_runner.py
index 4db3ac8..6676d72 100644
--- a/gui/process_runner.py
+++ b/JiboTools/JiboTools/gui/process_runner.py
@@ -12,7 +12,24 @@ from typing import Optional
from PySide6.QtCore import QObject, QProcess, QTimer, Signal, Slot, Property
-REPO_ROOT = Path(__file__).resolve().parents[1]
+def _find_repo_root(start: Path) -> Path:
+ """Find the outer JiboAutoMod repo root.
+
+ The Qt Creator project lives under JiboTools/JiboTools, while the CLI tools
+ (jibo_updater.py, jibo_automod.py) usually live in the outer repo root.
+ """
+
+ cur = start.resolve()
+ for _ in range(6):
+ if (cur / "jibo_updater.py").exists() and (cur / "jibo_automod.py").exists():
+ return cur
+ if cur.parent == cur:
+ break
+ cur = cur.parent
+ return start.resolve().parents[1]
+
+
+REPO_ROOT = _find_repo_root(Path(__file__).resolve())
def resolve_python_invocation() -> tuple[str, list[str]]:
@@ -28,13 +45,20 @@ def resolve_python_invocation() -> tuple[str, list[str]]:
if venv_py.exists():
return (str(venv_py), [])
+ # Prefer the current interpreter when running inside a venv (e.g. Qt Creator).
+ try:
+ if sys.executable and Path(sys.executable).exists():
+ return (sys.executable, [])
+ except Exception:
+ pass
+
if os.name == "nt" and shutil.which("py"):
return ("py", ["-3"])
if shutil.which("python3"):
return ("python3", [])
- return (sys.executable or "python", [])
+ return ("python", [])
def resolve_python() -> str:
diff --git a/gui/terminal_helper.py b/JiboTools/JiboTools/gui/terminal_helper.py
similarity index 100%
rename from gui/terminal_helper.py
rename to JiboTools/JiboTools/gui/terminal_helper.py
diff --git a/JiboTools/JiboTools/gui/tool_runner.ui b/JiboTools/JiboTools/gui/tool_runner.ui
new file mode 100644
index 0000000..d95c938
--- /dev/null
+++ b/JiboTools/JiboTools/gui/tool_runner.ui
@@ -0,0 +1,148 @@
+
+
+ ToolRunnerWindow
+
+
+
+ 0
+ 0
+ 900
+ 560
+
+
+
+ Tool
+
+
+
+
+ 16
+
+
+ 16
+
+
+ 16
+
+
+ 16
+
+
+ 12
+
+ -
+
+
+ 10
+
+
-
+
+
+ Tool
+
+
+
+ 12
+ true
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Start
+
+
+
+ -
+
+
+ Open in terminal
+
+
+
+
+
+ -
+
+
+ 10
+
+
-
+
+
+ Jibo IP (required for updater)
+
+
+
+ -
+
+
+ Extra arguments (optional)
+
+
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ 10
+
+
-
+
+
+ Idle
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Clear log
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/JiboTools/JiboTools/gui/tool_runner_window.py b/JiboTools/JiboTools/gui/tool_runner_window.py
new file mode 100644
index 0000000..40bbe9b
--- /dev/null
+++ b/JiboTools/JiboTools/gui/tool_runner_window.py
@@ -0,0 +1,123 @@
+from __future__ import annotations
+
+import shlex
+from pathlib import Path
+
+from PySide6.QtCore import QObject, Slot
+from PySide6.QtGui import QCloseEvent, QTextCursor
+from PySide6.QtWidgets import QMainWindow, QApplication, QLabel, QLineEdit, QPushButton, QPlainTextEdit
+
+from .process_runner import ProcessRunner, resolve_python_invocation
+from .terminal_helper import TerminalHelper
+from .ui_loader import load_ui, require_child
+
+
+class ToolRunnerWindow(QObject):
+ def __init__(self, *, title: str, script: str) -> None:
+ super().__init__()
+
+ self._script = script
+ self._is_updater = "jibo_updater.py" in script
+
+ ui_path = Path(__file__).resolve().parent / "tool_runner.ui"
+ self.window = load_ui(ui_path)
+ if not isinstance(self.window, QMainWindow):
+ raise RuntimeError("tool_runner.ui must have a QMainWindow as root")
+
+ self.window.setWindowTitle(title)
+
+ self.runner = ProcessRunner()
+ self.terminal = TerminalHelper()
+
+ self._title_label = require_child(self.window, "titleLabel", QLabel)
+ self._start_stop = require_child(self.window, "startStopButton", QPushButton)
+ self._open_terminal = require_child(self.window, "openTerminalButton", QPushButton)
+ self._host_field = require_child(self.window, "hostField", QLineEdit)
+ self._extra_args = require_child(self.window, "extraArgsField", QLineEdit)
+ self._log = require_child(self.window, "logEdit", QPlainTextEdit)
+ self._status = require_child(self.window, "statusLabel", QLabel)
+ self._clear = require_child(self.window, "clearLogButton", QPushButton)
+
+ self._title_label.setText(title)
+
+ self._host_field.setVisible(self._is_updater)
+
+ self._start_stop.clicked.connect(self._toggle)
+ self._open_terminal.clicked.connect(self._open_in_terminal)
+ self._clear.clicked.connect(lambda: self._log.setPlainText(""))
+
+ self.runner.outputAppended.connect(self._append_output)
+ self.runner.runningChanged.connect(self._sync_buttons)
+ self.runner.exitCodeChanged.connect(self._sync_status)
+
+ self._sync_buttons()
+ self._sync_status()
+
+ # Ensure the process is stopped when the window closes.
+ self.window.closeEvent = self._on_close # type: ignore[assignment]
+
+ def show(self) -> None:
+ self.window.show()
+
+ def _build_args(self) -> list[str]:
+ args: list[str] = [self._script]
+
+ if self._is_updater:
+ host = self._host_field.text().strip()
+ if host:
+ args += ["--ip", host]
+
+ extra = self._extra_args.text().strip()
+ if extra:
+ args += shlex.split(extra)
+
+ return args
+
+ @Slot()
+ def _toggle(self) -> None:
+ if self.runner.running:
+ self.runner.stop()
+ else:
+ program, prefix = resolve_python_invocation()
+ self.runner.start(program, [*prefix, *self._build_args()])
+
+ @Slot()
+ def _open_in_terminal(self) -> None:
+ program, prefix = resolve_python_invocation()
+ self.terminal.openTerminal(program, [*prefix, *self._build_args()])
+
+ @Slot(str)
+ def _append_output(self, chunk: str) -> None:
+ # Keep it simple: append and scroll to end.
+ self._log.moveCursor(QTextCursor.End)
+ self._log.insertPlainText(chunk)
+ self._log.moveCursor(QTextCursor.End)
+
+ def _sync_buttons(self) -> None:
+ running = self.runner.running
+ self._start_stop.setText("Stop" if running else "Start")
+ self._open_terminal.setEnabled(not running)
+
+ def _sync_status(self) -> None:
+ if self.runner.running:
+ self._status.setText("Running...")
+ return
+ code = self.runner.exitCode
+ if code >= 0:
+ self._status.setText(f"Exit: {code}")
+ else:
+ self._status.setText("Idle")
+
+ def _on_close(self, event: QCloseEvent) -> None:
+ try:
+ self.runner.stop()
+ except Exception:
+ pass
+ event.accept()
+
+
+def run_tool_window(*, title: str, script: str) -> int:
+ app = QApplication.instance() or QApplication([])
+ win = ToolRunnerWindow(title=title, script=script)
+ win.show()
+ return int(app.exec())
diff --git a/JiboTools/JiboTools/gui/ui_loader.py b/JiboTools/JiboTools/gui/ui_loader.py
new file mode 100644
index 0000000..a5955f7
--- /dev/null
+++ b/JiboTools/JiboTools/gui/ui_loader.py
@@ -0,0 +1,34 @@
+from __future__ import annotations
+
+from pathlib import Path
+from typing import TypeVar, cast
+
+from PySide6.QtCore import QFile
+from PySide6.QtUiTools import QUiLoader
+
+
+T = TypeVar("T")
+
+
+def load_ui(ui_path: Path) -> object:
+ loader = QUiLoader()
+ file = QFile(str(ui_path))
+ if not file.open(QFile.ReadOnly):
+ raise RuntimeError(f"Failed to open UI file: {ui_path}")
+ try:
+ widget = loader.load(file)
+ finally:
+ file.close()
+
+ if widget is None:
+ raise RuntimeError(f"Failed to load UI: {ui_path}")
+
+ return widget
+
+
+def require_child(parent: object, name: str, typ: type[T]) -> T:
+ # Qt objects implement findChild; keep typing light.
+ child = parent.findChild(typ, name) # type: ignore[attr-defined]
+ if child is None:
+ raise RuntimeError(f"UI is missing required widget '{name}' ({typ.__name__})")
+ return cast(T, child)
diff --git a/JiboTools/JiboTools/gui/ui_tool_runner.py b/JiboTools/JiboTools/gui/ui_tool_runner.py
new file mode 100644
index 0000000..8bb3424
--- /dev/null
+++ b/JiboTools/JiboTools/gui/ui_tool_runner.py
@@ -0,0 +1,124 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'tool_runner.ui'
+##
+## Created by: Qt User Interface Compiler version 6.10.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QLineEdit,
+ QMainWindow, QPlainTextEdit, QPushButton, QSizePolicy,
+ QSpacerItem, QStatusBar, QVBoxLayout, QWidget)
+
+class Ui_ToolRunnerWindow(object):
+ def setupUi(self, ToolRunnerWindow):
+ if not ToolRunnerWindow.objectName():
+ ToolRunnerWindow.setObjectName(u"ToolRunnerWindow")
+ ToolRunnerWindow.resize(900, 560)
+ self.centralwidget = QWidget(ToolRunnerWindow)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.rootLayout = QVBoxLayout(self.centralwidget)
+ self.rootLayout.setSpacing(12)
+ self.rootLayout.setObjectName(u"rootLayout")
+ self.rootLayout.setContentsMargins(16, 16, 16, 16)
+ self.headerLayout = QHBoxLayout()
+ self.headerLayout.setSpacing(10)
+ self.headerLayout.setObjectName(u"headerLayout")
+ self.titleLabel = QLabel(self.centralwidget)
+ self.titleLabel.setObjectName(u"titleLabel")
+ font = QFont()
+ font.setPointSize(12)
+ font.setBold(True)
+ self.titleLabel.setFont(font)
+
+ self.headerLayout.addWidget(self.titleLabel)
+
+ self.headerSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.headerLayout.addItem(self.headerSpacer)
+
+ self.startStopButton = QPushButton(self.centralwidget)
+ self.startStopButton.setObjectName(u"startStopButton")
+
+ self.headerLayout.addWidget(self.startStopButton)
+
+ self.openTerminalButton = QPushButton(self.centralwidget)
+ self.openTerminalButton.setObjectName(u"openTerminalButton")
+
+ self.headerLayout.addWidget(self.openTerminalButton)
+
+
+ self.rootLayout.addLayout(self.headerLayout)
+
+ self.argsLayout = QHBoxLayout()
+ self.argsLayout.setSpacing(10)
+ self.argsLayout.setObjectName(u"argsLayout")
+ self.hostField = QLineEdit(self.centralwidget)
+ self.hostField.setObjectName(u"hostField")
+
+ self.argsLayout.addWidget(self.hostField)
+
+ self.extraArgsField = QLineEdit(self.centralwidget)
+ self.extraArgsField.setObjectName(u"extraArgsField")
+
+ self.argsLayout.addWidget(self.extraArgsField)
+
+
+ self.rootLayout.addLayout(self.argsLayout)
+
+ self.logEdit = QPlainTextEdit(self.centralwidget)
+ self.logEdit.setObjectName(u"logEdit")
+ self.logEdit.setReadOnly(True)
+
+ self.rootLayout.addWidget(self.logEdit)
+
+ self.footerLayout = QHBoxLayout()
+ self.footerLayout.setSpacing(10)
+ self.footerLayout.setObjectName(u"footerLayout")
+ self.statusLabel = QLabel(self.centralwidget)
+ self.statusLabel.setObjectName(u"statusLabel")
+
+ self.footerLayout.addWidget(self.statusLabel)
+
+ self.footerSpacer = QSpacerItem(40, 20, QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
+
+ self.footerLayout.addItem(self.footerSpacer)
+
+ self.clearLogButton = QPushButton(self.centralwidget)
+ self.clearLogButton.setObjectName(u"clearLogButton")
+
+ self.footerLayout.addWidget(self.clearLogButton)
+
+
+ self.rootLayout.addLayout(self.footerLayout)
+
+ ToolRunnerWindow.setCentralWidget(self.centralwidget)
+ self.statusbar = QStatusBar(ToolRunnerWindow)
+ self.statusbar.setObjectName(u"statusbar")
+ ToolRunnerWindow.setStatusBar(self.statusbar)
+
+ self.retranslateUi(ToolRunnerWindow)
+
+ QMetaObject.connectSlotsByName(ToolRunnerWindow)
+ # setupUi
+
+ def retranslateUi(self, ToolRunnerWindow):
+ ToolRunnerWindow.setWindowTitle(QCoreApplication.translate("ToolRunnerWindow", u"Tool", None))
+ self.titleLabel.setText(QCoreApplication.translate("ToolRunnerWindow", u"Tool", None))
+ self.startStopButton.setText(QCoreApplication.translate("ToolRunnerWindow", u"Start", None))
+ self.openTerminalButton.setText(QCoreApplication.translate("ToolRunnerWindow", u"Open in terminal", None))
+ self.hostField.setPlaceholderText(QCoreApplication.translate("ToolRunnerWindow", u"Jibo IP (required for updater)", None))
+ self.extraArgsField.setPlaceholderText(QCoreApplication.translate("ToolRunnerWindow", u"Extra arguments (optional)", None))
+ self.statusLabel.setText(QCoreApplication.translate("ToolRunnerWindow", u"Idle", None))
+ self.clearLogButton.setText(QCoreApplication.translate("ToolRunnerWindow", u"Clear log", None))
+ # retranslateUi
+
diff --git a/JiboTools/JiboTools/gui/updater_gui.py b/JiboTools/JiboTools/gui/updater_gui.py
new file mode 100644
index 0000000..15035e5
--- /dev/null
+++ b/JiboTools/JiboTools/gui/updater_gui.py
@@ -0,0 +1,17 @@
+from __future__ import annotations
+
+import sys
+from PySide6.QtWidgets import QApplication
+
+from .tool_runner_window import ToolRunnerWindow
+
+
+def main() -> int:
+ app = QApplication(sys.argv)
+ win = ToolRunnerWindow(title="Updater", script="jibo_updater.py")
+ win.show()
+ return int(app.exec())
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/JiboTools/JiboTools/main_panel.py b/JiboTools/JiboTools/main_panel.py
new file mode 100644
index 0000000..6ce11f9
--- /dev/null
+++ b/JiboTools/JiboTools/main_panel.py
@@ -0,0 +1,18 @@
+"""Qt Creator entrypoint.
+
+This project is intended to run cleanly from Qt Creator. The UI is implemented
+with Qt Widgets loaded from `.ui` files at runtime (see gui/main_panel.py and
+form.ui).
+"""
+
+from __future__ import annotations
+
+
+def main() -> int:
+ from gui.main_panel import main as widgets_main
+
+ return int(widgets_main())
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/JiboTools/JiboTools/pyproject.toml b/JiboTools/JiboTools/pyproject.toml
new file mode 100644
index 0000000..6563692
--- /dev/null
+++ b/JiboTools/JiboTools/pyproject.toml
@@ -0,0 +1,19 @@
+[project]
+name = "JiboTools"
+
+[tool.pyside6-project]
+files = [
+ "main_panel.py",
+ "form.ui",
+ "gui/__init__.py",
+ "gui/main_panel.py",
+ "gui/installer_gui.py",
+ "gui/updater_gui.py",
+ "gui/process_runner.py",
+ "gui/terminal_helper.py",
+ "gui/tool_runner.ui",
+ "gui/tool_runner_window.py",
+ "gui/ui_loader.py",
+ "gui/Assets/Jibo/JiboFaceForward.png",
+ "gui/Assets/Jibo/NoJiboConnected.png",
+]
diff --git a/JiboTools/JiboTools/requirements.txt b/JiboTools/JiboTools/requirements.txt
new file mode 100644
index 0000000..c2f3583
--- /dev/null
+++ b/JiboTools/JiboTools/requirements.txt
@@ -0,0 +1,2 @@
+PySide6
+paramiko
diff --git a/JiboTools/JiboTools/ui_form.py b/JiboTools/JiboTools/ui_form.py
new file mode 100644
index 0000000..8899ea1
--- /dev/null
+++ b/JiboTools/JiboTools/ui_form.py
@@ -0,0 +1,444 @@
+# -*- coding: utf-8 -*-
+
+################################################################################
+## Form generated from reading UI file 'form.ui'
+##
+## Created by: Qt User Interface Compiler version 6.10.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
+
+from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
+ QMetaObject, QObject, QPoint, QRect,
+ QSize, QTime, QUrl, Qt)
+from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
+ QFont, QFontDatabase, QGradient, QIcon,
+ QImage, QKeySequence, QLinearGradient, QPainter,
+ QPalette, QPixmap, QRadialGradient, QTransform)
+from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QFormLayout,
+ QFrame, QGroupBox, QHBoxLayout, QLabel,
+ QLineEdit, QMainWindow, QPushButton, QScrollArea,
+ QSizePolicy, QSpacerItem, QStatusBar, QTabWidget,
+ QVBoxLayout, QWidget)
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ if not MainWindow.objectName():
+ MainWindow.setObjectName(u"MainWindow")
+ MainWindow.resize(782, 649)
+ MainWindow.setTabShape(QTabWidget.TabShape.Rounded)
+ self.centralwidget = QWidget(MainWindow)
+ self.centralwidget.setObjectName(u"centralwidget")
+ self.rootLayout = QVBoxLayout(self.centralwidget)
+ self.rootLayout.setSpacing(12)
+ self.rootLayout.setObjectName(u"rootLayout")
+ self.rootLayout.setContentsMargins(14, 14, 14, 14)
+ self.tabWidget = QTabWidget(self.centralwidget)
+ self.tabWidget.setObjectName(u"tabWidget")
+ self.tabWidget.setDocumentMode(True)
+ self.tabWidget.setTabsClosable(False)
+ self.tabWidget.setMovable(False)
+ self.tabJibo = QWidget()
+ self.tabJibo.setObjectName(u"tabJibo")
+ self.jiboPageLayout = QHBoxLayout(self.tabJibo)
+ self.jiboPageLayout.setSpacing(14)
+ self.jiboPageLayout.setObjectName(u"jiboPageLayout")
+ self.configFrame = QFrame(self.tabJibo)
+ self.configFrame.setObjectName(u"configFrame")
+ self.configFrame.setMinimumSize(QSize(420, 0))
+ self.configFrame.setFrameShape(QFrame.Shape.StyledPanel)
+ self.configFrame.setFrameShadow(QFrame.Shadow.Raised)
+ self.configLayout = QVBoxLayout(self.configFrame)
+ self.configLayout.setSpacing(10)
+ self.configLayout.setObjectName(u"configLayout")
+ self.configLayout.setContentsMargins(18, 18, 18, 18)
+ self.configTitle = QLabel(self.configFrame)
+ self.configTitle.setObjectName(u"configTitle")
+ font = QFont()
+ font.setPointSize(12)
+ font.setBold(True)
+ self.configTitle.setFont(font)
+
+ self.configLayout.addWidget(self.configTitle)
+
+ self.configScroll = QScrollArea(self.configFrame)
+ self.configScroll.setObjectName(u"configScroll")
+ self.configScroll.setWidgetResizable(True)
+ self.configScrollContents = QWidget()
+ self.configScrollContents.setObjectName(u"configScrollContents")
+ self.configScrollContents.setGeometry(QRect(0, 0, 380, 478))
+ self.configScrollLayout = QVBoxLayout(self.configScrollContents)
+ self.configScrollLayout.setSpacing(14)
+ self.configScrollLayout.setObjectName(u"configScrollLayout")
+ self.groupPreview = QGroupBox(self.configScrollContents)
+ self.groupPreview.setObjectName(u"groupPreview")
+ self.formPreview = QFormLayout(self.groupPreview)
+ self.formPreview.setObjectName(u"formPreview")
+ self.labelOverride = QLabel(self.groupPreview)
+ self.labelOverride.setObjectName(u"labelOverride")
+
+ self.formPreview.setWidget(0, QFormLayout.ItemRole.LabelRole, self.labelOverride)
+
+ self.overrideCheck = QCheckBox(self.groupPreview)
+ self.overrideCheck.setObjectName(u"overrideCheck")
+ self.overrideCheck.setChecked(True)
+
+ self.formPreview.setWidget(0, QFormLayout.ItemRole.FieldRole, self.overrideCheck)
+
+ self.labelPreviewConnected = QLabel(self.groupPreview)
+ self.labelPreviewConnected.setObjectName(u"labelPreviewConnected")
+
+ self.formPreview.setWidget(1, QFormLayout.ItemRole.LabelRole, self.labelPreviewConnected)
+
+ self.previewConnectedCheck = QCheckBox(self.groupPreview)
+ self.previewConnectedCheck.setObjectName(u"previewConnectedCheck")
+
+ self.formPreview.setWidget(1, QFormLayout.ItemRole.FieldRole, self.previewConnectedCheck)
+
+
+ self.configScrollLayout.addWidget(self.groupPreview)
+
+ self.groupHomeAssistant = QGroupBox(self.configScrollContents)
+ self.groupHomeAssistant.setObjectName(u"groupHomeAssistant")
+ self.formHomeAssistant = QFormLayout(self.groupHomeAssistant)
+ self.formHomeAssistant.setObjectName(u"formHomeAssistant")
+ self.labelHaEnable = QLabel(self.groupHomeAssistant)
+ self.labelHaEnable.setObjectName(u"labelHaEnable")
+
+ self.formHomeAssistant.setWidget(0, QFormLayout.ItemRole.LabelRole, self.labelHaEnable)
+
+ self.haEnableCheck = QCheckBox(self.groupHomeAssistant)
+ self.haEnableCheck.setObjectName(u"haEnableCheck")
+
+ self.formHomeAssistant.setWidget(0, QFormLayout.ItemRole.FieldRole, self.haEnableCheck)
+
+ self.labelHaServerIp = QLabel(self.groupHomeAssistant)
+ self.labelHaServerIp.setObjectName(u"labelHaServerIp")
+
+ self.formHomeAssistant.setWidget(1, QFormLayout.ItemRole.LabelRole, self.labelHaServerIp)
+
+ self.haServerIpField = QLineEdit(self.groupHomeAssistant)
+ self.haServerIpField.setObjectName(u"haServerIpField")
+
+ self.formHomeAssistant.setWidget(1, QFormLayout.ItemRole.FieldRole, self.haServerIpField)
+
+
+ self.configScrollLayout.addWidget(self.groupHomeAssistant)
+
+ self.groupAiProvider = QGroupBox(self.configScrollContents)
+ self.groupAiProvider.setObjectName(u"groupAiProvider")
+ self.formAiProvider = QFormLayout(self.groupAiProvider)
+ self.formAiProvider.setObjectName(u"formAiProvider")
+ self.labelAiEnable = QLabel(self.groupAiProvider)
+ self.labelAiEnable.setObjectName(u"labelAiEnable")
+
+ self.formAiProvider.setWidget(0, QFormLayout.ItemRole.LabelRole, self.labelAiEnable)
+
+ self.aiEnableCheck = QCheckBox(self.groupAiProvider)
+ self.aiEnableCheck.setObjectName(u"aiEnableCheck")
+
+ self.formAiProvider.setWidget(0, QFormLayout.ItemRole.FieldRole, self.aiEnableCheck)
+
+ self.labelAiProvider = QLabel(self.groupAiProvider)
+ self.labelAiProvider.setObjectName(u"labelAiProvider")
+
+ self.formAiProvider.setWidget(1, QFormLayout.ItemRole.LabelRole, self.labelAiProvider)
+
+ self.aiProviderCombo = QComboBox(self.groupAiProvider)
+ self.aiProviderCombo.setObjectName(u"aiProviderCombo")
+
+ self.formAiProvider.setWidget(1, QFormLayout.ItemRole.FieldRole, self.aiProviderCombo)
+
+ self.labelAiEndpoint = QLabel(self.groupAiProvider)
+ self.labelAiEndpoint.setObjectName(u"labelAiEndpoint")
+
+ self.formAiProvider.setWidget(2, QFormLayout.ItemRole.LabelRole, self.labelAiEndpoint)
+
+ self.aiEndpointField = QLineEdit(self.groupAiProvider)
+ self.aiEndpointField.setObjectName(u"aiEndpointField")
+
+ self.formAiProvider.setWidget(2, QFormLayout.ItemRole.FieldRole, self.aiEndpointField)
+
+ self.labelAiKey = QLabel(self.groupAiProvider)
+ self.labelAiKey.setObjectName(u"labelAiKey")
+
+ self.formAiProvider.setWidget(3, QFormLayout.ItemRole.LabelRole, self.labelAiKey)
+
+ self.aiKeyField = QLineEdit(self.groupAiProvider)
+ self.aiKeyField.setObjectName(u"aiKeyField")
+ self.aiKeyField.setEchoMode(QLineEdit.EchoMode.Password)
+
+ self.formAiProvider.setWidget(3, QFormLayout.ItemRole.FieldRole, self.aiKeyField)
+
+ self.labelTokens = QLabel(self.groupAiProvider)
+ self.labelTokens.setObjectName(u"labelTokens")
+
+ self.formAiProvider.setWidget(4, QFormLayout.ItemRole.LabelRole, self.labelTokens)
+
+ self.tokensUsedLabel = QLabel(self.groupAiProvider)
+ self.tokensUsedLabel.setObjectName(u"tokensUsedLabel")
+
+ self.formAiProvider.setWidget(4, QFormLayout.ItemRole.FieldRole, self.tokensUsedLabel)
+
+
+ self.configScrollLayout.addWidget(self.groupAiProvider)
+
+ self.configBottomSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.configScrollLayout.addItem(self.configBottomSpacer)
+
+ self.configScroll.setWidget(self.configScrollContents)
+
+ self.configLayout.addWidget(self.configScroll)
+
+
+ self.jiboPageLayout.addWidget(self.configFrame)
+
+ self.jiboCardFrame = QFrame(self.tabJibo)
+ self.jiboCardFrame.setObjectName(u"jiboCardFrame")
+ self.jiboCardFrame.setFrameShape(QFrame.Shape.StyledPanel)
+ self.jiboCardFrame.setFrameShadow(QFrame.Shadow.Raised)
+ self.jiboCardLayout = QVBoxLayout(self.jiboCardFrame)
+ self.jiboCardLayout.setSpacing(12)
+ self.jiboCardLayout.setObjectName(u"jiboCardLayout")
+ self.jiboCardLayout.setContentsMargins(18, 18, 18, 18)
+ self.IpConfig = QHBoxLayout()
+ self.IpConfig.setObjectName(u"IpConfig")
+ self.TryToConnect = QPushButton(self.jiboCardFrame)
+ self.TryToConnect.setObjectName(u"TryToConnect")
+ self.TryToConnect.setCheckable(False)
+
+ self.IpConfig.addWidget(self.TryToConnect, 0, Qt.AlignmentFlag.AlignRight)
+
+ self.JiboIpField = QLineEdit(self.jiboCardFrame)
+ self.JiboIpField.setObjectName(u"JiboIpField")
+
+ self.IpConfig.addWidget(self.JiboIpField)
+
+
+ self.jiboCardLayout.addLayout(self.IpConfig)
+
+ self.jiboHeaderLayout = QHBoxLayout()
+ self.jiboHeaderLayout.setObjectName(u"jiboHeaderLayout")
+ self.jiboTitle = QLabel(self.jiboCardFrame)
+ self.jiboTitle.setObjectName(u"jiboTitle")
+ font1 = QFont()
+ font1.setPointSize(12)
+ font1.setBold(True)
+ font1.setUnderline(False)
+ self.jiboTitle.setFont(font1)
+ self.jiboTitle.setAlignment(Qt.AlignmentFlag.AlignCenter)
+
+ self.jiboHeaderLayout.addWidget(self.jiboTitle)
+
+
+ self.jiboCardLayout.addLayout(self.jiboHeaderLayout)
+
+ self.jiboTopSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.jiboCardLayout.addItem(self.jiboTopSpacer)
+
+ self.jiboImage = QLabel(self.jiboCardFrame)
+ self.jiboImage.setObjectName(u"jiboImage")
+ self.jiboImage.setMinimumSize(QSize(260, 260))
+ self.jiboImage.setMaximumSize(QSize(260, 260))
+ self.jiboImage.setAlignment(Qt.AlignmentFlag.AlignCenter)
+
+ self.jiboCardLayout.addWidget(self.jiboImage)
+
+ self.jiboBottomSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
+
+ self.jiboCardLayout.addItem(self.jiboBottomSpacer)
+
+ self.RobotSettings = QPushButton(self.jiboCardFrame)
+ self.RobotSettings.setObjectName(u"RobotSettings")
+ self.RobotSettings.setEnabled(False)
+ self.RobotSettings.setFlat(False)
+
+ self.jiboCardLayout.addWidget(self.RobotSettings)
+
+ self.comboBox = QComboBox(self.jiboCardFrame)
+ self.comboBox.setObjectName(u"comboBox")
+ self.comboBox.setEnabled(False)
+ self.comboBox.setEditable(True)
+
+ self.jiboCardLayout.addWidget(self.comboBox)
+
+
+ self.jiboPageLayout.addWidget(self.jiboCardFrame)
+
+ self.tabWidget.addTab(self.tabJibo, "")
+ self.tabUpdate = QWidget()
+ self.tabUpdate.setObjectName(u"tabUpdate")
+ self.updatePageLayout = QVBoxLayout(self.tabUpdate)
+ self.updatePageLayout.setSpacing(12)
+ self.updatePageLayout.setObjectName(u"updatePageLayout")
+ self.updateFrame = QFrame(self.tabUpdate)
+ self.updateFrame.setObjectName(u"updateFrame")
+ self.updateFrame.setFrameShape(QFrame.Shape.StyledPanel)
+ self.updateFrame.setFrameShadow(QFrame.Shadow.Raised)
+ self.updateFrameLayout = QVBoxLayout(self.updateFrame)
+ self.updateFrameLayout.setSpacing(12)
+ self.updateFrameLayout.setObjectName(u"updateFrameLayout")
+ self.updateFrameLayout.setContentsMargins(18, 18, 18, 18)
+ self.updateInfoText = QLabel(self.updateFrame)
+ self.updateInfoText.setObjectName(u"updateInfoText")
+ self.updateInfoText.setWordWrap(True)
+
+ self.updateFrameLayout.addWidget(self.updateInfoText)
+
+ self.updateSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding)
+
+ self.updateFrameLayout.addItem(self.updateSpacer)
+
+ self.updateButtonsLayout = QHBoxLayout()
+ self.updateButtonsLayout.setSpacing(12)
+ self.updateButtonsLayout.setObjectName(u"updateButtonsLayout")
+ self.installButton = QPushButton(self.updateFrame)
+ self.installButton.setObjectName(u"installButton")
+
+ self.updateButtonsLayout.addWidget(self.installButton)
+
+ self.checkUpdatesButton = QPushButton(self.updateFrame)
+ self.checkUpdatesButton.setObjectName(u"checkUpdatesButton")
+
+ self.updateButtonsLayout.addWidget(self.checkUpdatesButton)
+
+
+ self.updateFrameLayout.addLayout(self.updateButtonsLayout)
+
+
+ self.updatePageLayout.addWidget(self.updateFrame)
+
+ self.tabWidget.addTab(self.tabUpdate, "")
+ self.tabSkills = QWidget()
+ self.tabSkills.setObjectName(u"tabSkills")
+ self.skillsLayout = QVBoxLayout(self.tabSkills)
+ self.skillsLayout.setObjectName(u"skillsLayout")
+ self.skillsComingSoon = QLabel(self.tabSkills)
+ self.skillsComingSoon.setObjectName(u"skillsComingSoon")
+
+ self.skillsLayout.addWidget(self.skillsComingSoon)
+
+ self.tabWidget.addTab(self.tabSkills, "")
+ self.tabSsh = QWidget()
+ self.tabSsh.setObjectName(u"tabSsh")
+ self.sshLayout = QVBoxLayout(self.tabSsh)
+ self.sshLayout.setObjectName(u"sshLayout")
+ self.sshComingSoon = QLabel(self.tabSsh)
+ self.sshComingSoon.setObjectName(u"sshComingSoon")
+
+ self.sshLayout.addWidget(self.sshComingSoon)
+
+ self.tabWidget.addTab(self.tabSsh, "")
+ self.tabFtp = QWidget()
+ self.tabFtp.setObjectName(u"tabFtp")
+ self.ftpLayout = QVBoxLayout(self.tabFtp)
+ self.ftpLayout.setObjectName(u"ftpLayout")
+ self.ftpComingSoon = QLabel(self.tabFtp)
+ self.ftpComingSoon.setObjectName(u"ftpComingSoon")
+
+ self.ftpLayout.addWidget(self.ftpComingSoon)
+
+ self.tabWidget.addTab(self.tabFtp, "")
+ self.tabStatus = QWidget()
+ self.tabStatus.setObjectName(u"tabStatus")
+ self.statusLayout = QVBoxLayout(self.tabStatus)
+ self.statusLayout.setSpacing(10)
+ self.statusLayout.setObjectName(u"statusLayout")
+ self.statusRow = QHBoxLayout()
+ self.statusRow.setSpacing(10)
+ self.statusRow.setObjectName(u"statusRow")
+ self.statusDot = QLabel(self.tabStatus)
+ self.statusDot.setObjectName(u"statusDot")
+ self.statusDot.setMinimumSize(QSize(10, 10))
+ self.statusDot.setMaximumSize(QSize(10, 10))
+
+ self.statusRow.addWidget(self.statusDot)
+
+ self.statusText = QLabel(self.tabStatus)
+ self.statusText.setObjectName(u"statusText")
+ self.statusText.setWordWrap(True)
+
+ self.statusRow.addWidget(self.statusText)
+
+
+ self.statusLayout.addLayout(self.statusRow)
+
+ self.tabWidget.addTab(self.tabStatus, "")
+ self.tabRobotOs = QWidget()
+ self.tabRobotOs.setObjectName(u"tabRobotOs")
+ self.robotOsLayout = QVBoxLayout(self.tabRobotOs)
+ self.robotOsLayout.setObjectName(u"robotOsLayout")
+ self.robotOsComingSoon = QLabel(self.tabRobotOs)
+ self.robotOsComingSoon.setObjectName(u"robotOsComingSoon")
+
+ self.robotOsLayout.addWidget(self.robotOsComingSoon)
+
+ self.tabWidget.addTab(self.tabRobotOs, "")
+
+ self.rootLayout.addWidget(self.tabWidget)
+
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.statusbar = QStatusBar(MainWindow)
+ self.statusbar.setObjectName(u"statusbar")
+ self.statusbar.setSizeGripEnabled(False)
+ MainWindow.setStatusBar(self.statusbar)
+
+ self.retranslateUi(MainWindow)
+
+ self.tabWidget.setCurrentIndex(0)
+
+
+ QMetaObject.connectSlotsByName(MainWindow)
+ # setupUi
+
+ def retranslateUi(self, MainWindow):
+ MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Jibo Tools", None))
+ self.configTitle.setText(QCoreApplication.translate("MainWindow", u"Config", None))
+ self.groupPreview.setTitle(QCoreApplication.translate("MainWindow", u"Preview", None))
+ self.labelOverride.setText(QCoreApplication.translate("MainWindow", u"Override", None))
+ self.overrideCheck.setText("")
+ self.labelPreviewConnected.setText(QCoreApplication.translate("MainWindow", u"Connected", None))
+ self.previewConnectedCheck.setText("")
+ self.groupHomeAssistant.setTitle(QCoreApplication.translate("MainWindow", u"Home Assistant", None))
+ self.labelHaEnable.setText(QCoreApplication.translate("MainWindow", u"Enabled", None))
+ self.haEnableCheck.setText("")
+ self.labelHaServerIp.setText(QCoreApplication.translate("MainWindow", u"Server IP", None))
+ self.haServerIpField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Home Assistant host", None))
+ self.groupAiProvider.setTitle(QCoreApplication.translate("MainWindow", u"AI Provider", None))
+ self.labelAiEnable.setText(QCoreApplication.translate("MainWindow", u"Enabled", None))
+ self.aiEnableCheck.setText("")
+ self.labelAiProvider.setText(QCoreApplication.translate("MainWindow", u"Provider", None))
+ self.labelAiEndpoint.setText(QCoreApplication.translate("MainWindow", u"API endpoint", None))
+ self.aiEndpointField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"http://...", None))
+ self.labelAiKey.setText(QCoreApplication.translate("MainWindow", u"API key", None))
+ self.labelTokens.setText(QCoreApplication.translate("MainWindow", u"Tokens used", None))
+ self.tokensUsedLabel.setText(QCoreApplication.translate("MainWindow", u"-1", None))
+ self.TryToConnect.setText(QCoreApplication.translate("MainWindow", u"Connect", None))
+ self.JiboIpField.setInputMask("")
+ self.JiboIpField.setText("")
+ self.JiboIpField.setPlaceholderText(QCoreApplication.translate("MainWindow", u"e.g 192.168.1.54", None))
+ self.jiboTitle.setText(QCoreApplication.translate("MainWindow", u"Connect Your Jibo", None))
+ self.jiboImage.setText("")
+ self.RobotSettings.setText(QCoreApplication.translate("MainWindow", u"Robot Settings", None))
+ self.comboBox.setCurrentText(QCoreApplication.translate("MainWindow", u"Reboot", None))
+ self.comboBox.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Reboot", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabJibo), QCoreApplication.translate("MainWindow", u"Jibo", None))
+ self.updateInfoText.setText(QCoreApplication.translate("MainWindow", u"Installer and updater remain available via CLI. Use the buttons below to launch their GUIs.", None))
+ self.installButton.setText(QCoreApplication.translate("MainWindow", u"Install", None))
+ self.checkUpdatesButton.setText(QCoreApplication.translate("MainWindow", u"Check for updates", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabUpdate), QCoreApplication.translate("MainWindow", u"Update", None))
+ self.skillsComingSoon.setText(QCoreApplication.translate("MainWindow", u"Coming soon.", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabSkills), QCoreApplication.translate("MainWindow", u"Skills", None))
+ self.sshComingSoon.setText(QCoreApplication.translate("MainWindow", u"Coming soon.", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabSsh), QCoreApplication.translate("MainWindow", u"SSH", None))
+ self.ftpComingSoon.setText(QCoreApplication.translate("MainWindow", u"Coming soon.", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabFtp), QCoreApplication.translate("MainWindow", u"FTP", None))
+ self.statusDot.setText("")
+ self.statusText.setText(QCoreApplication.translate("MainWindow", u"No Jibo IP configured", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabStatus), QCoreApplication.translate("MainWindow", u"Status", None))
+ self.robotOsComingSoon.setText(QCoreApplication.translate("MainWindow", u"Coming soon.", None))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.tabRobotOs), QCoreApplication.translate("MainWindow", u"Robot OS", None))
+ # retranslateUi
+
diff --git a/gui/__pycache__/__init__.cpython-313.pyc b/gui/__pycache__/__init__.cpython-313.pyc
deleted file mode 100644
index 55f94b3..0000000
Binary files a/gui/__pycache__/__init__.cpython-313.pyc and /dev/null differ
diff --git a/gui/__pycache__/installer_gui.cpython-313.pyc b/gui/__pycache__/installer_gui.cpython-313.pyc
deleted file mode 100644
index a808dae..0000000
Binary files a/gui/__pycache__/installer_gui.cpython-313.pyc and /dev/null differ
diff --git a/gui/__pycache__/main_panel.cpython-313.pyc b/gui/__pycache__/main_panel.cpython-313.pyc
deleted file mode 100644
index 8c53627..0000000
Binary files a/gui/__pycache__/main_panel.cpython-313.pyc and /dev/null differ
diff --git a/gui/__pycache__/process_runner.cpython-313.pyc b/gui/__pycache__/process_runner.cpython-313.pyc
deleted file mode 100644
index cd7c1d7..0000000
Binary files a/gui/__pycache__/process_runner.cpython-313.pyc and /dev/null differ
diff --git a/gui/__pycache__/terminal_helper.cpython-313.pyc b/gui/__pycache__/terminal_helper.cpython-313.pyc
deleted file mode 100644
index ccac640..0000000
Binary files a/gui/__pycache__/terminal_helper.cpython-313.pyc and /dev/null differ
diff --git a/gui/__pycache__/updater_gui.cpython-313.pyc b/gui/__pycache__/updater_gui.cpython-313.pyc
deleted file mode 100644
index 0639224..0000000
Binary files a/gui/__pycache__/updater_gui.cpython-313.pyc and /dev/null differ
diff --git a/gui/installer_gui.py b/gui/installer_gui.py
deleted file mode 100644
index 87f0d9d..0000000
--- a/gui/installer_gui.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from __future__ import annotations
-
-import sys
-from pathlib import Path
-
-from PySide6.QtCore import QUrl
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQml import QQmlApplicationEngine
-
-from .process_runner import ProcessRunner, resolve_python
-from .terminal_helper import TerminalHelper
-
-
-def main() -> int:
- app = QGuiApplication(sys.argv)
-
- engine = QQmlApplicationEngine()
-
- runner = ProcessRunner()
- terminal = TerminalHelper()
- engine.rootContext().setContextProperty("runner", runner)
- engine.rootContext().setContextProperty("terminal", terminal)
- engine.rootContext().setContextProperty("pyExec", resolve_python())
- engine.rootContext().setContextProperty("toolScript", "jibo_automod.py")
- engine.rootContext().setContextProperty("toolTitle", "Installer")
-
- qml_path = Path(__file__).resolve().parent / "qml" / "ToolRunner.qml"
- engine.load(QUrl.fromLocalFile(str(qml_path)))
-
- if not engine.rootObjects():
- return 1
-
- return app.exec()
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/gui/main_panel.py b/gui/main_panel.py
deleted file mode 100644
index af4aaec..0000000
--- a/gui/main_panel.py
+++ /dev/null
@@ -1,59 +0,0 @@
-from __future__ import annotations
-
-import sys
-from pathlib import Path
-
-from PySide6.QtCore import QUrl, QObject, Slot
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQml import QQmlApplicationEngine
-
-from .process_runner import ConnectionMonitor, resolve_python, resolve_python_invocation
-
-
-class Launcher(QObject):
- def __init__(self, python_program: str, python_prefix: list[str]) -> None:
- super().__init__()
- self._python_program = python_program
- self._python_prefix = list(python_prefix)
-
- @Slot()
- def launchInstaller(self) -> None:
- # Start installer GUI in a separate process.
- import subprocess
- subprocess.Popen(
- [self._python_program, *self._python_prefix, "-m", "gui.installer_gui"],
- cwd=str(Path(__file__).resolve().parents[1]),
- )
-
- @Slot()
- def launchUpdater(self) -> None:
- import subprocess
- subprocess.Popen(
- [self._python_program, *self._python_prefix, "-m", "gui.updater_gui"],
- cwd=str(Path(__file__).resolve().parents[1]),
- )
-
-
-def main() -> int:
- app = QGuiApplication(sys.argv)
-
- engine = QQmlApplicationEngine()
-
- conn = ConnectionMonitor()
- py_program, py_prefix = resolve_python_invocation()
- py_exec = resolve_python()
- engine.rootContext().setContextProperty("conn", conn)
- engine.rootContext().setContextProperty("pyExec", py_exec)
- engine.rootContext().setContextProperty("launcher", Launcher(py_program, py_prefix))
-
- qml_path = Path(__file__).resolve().parent / "qml" / "MainPanel.qml"
- engine.load(QUrl.fromLocalFile(str(qml_path)))
-
- if not engine.rootObjects():
- return 1
-
- return app.exec()
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/gui/qml/MainPanel.qml b/gui/qml/MainPanel.qml
deleted file mode 100644
index 0f2dd7b..0000000
--- a/gui/qml/MainPanel.qml
+++ /dev/null
@@ -1,168 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-import QtQuick.Window
-
-ApplicationWindow {
- id: win
- width: 880
- height: 520
- visible: true
- title: "Jibo Tools"
-
- property string host: hostField.text.trim()
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 18
- spacing: 14
-
- RowLayout {
- Layout.fillWidth: true
- spacing: 12
-
- Text {
- text: "Connection"
- font.pixelSize: 18
- font.bold: true
- }
-
- Rectangle {
- width: 10
- height: 10
- radius: 5
- color: conn.connected ? "#2ecc71" : (host.length > 0 ? "#e67e22" : "#bdc3c7")
- Layout.alignment: Qt.AlignVCenter
- }
-
- Text {
- text: conn.connected ? "SSH reachable" : (host.length > 0 ? "Not reachable" : "No IP")
- color: "#555"
- Layout.alignment: Qt.AlignVCenter
- }
-
- Item { Layout.fillWidth: true }
-
- TextField {
- id: hostField
- placeholderText: "Jibo IP (e.g. 192.168.1.50)"
- Layout.preferredWidth: 280
- onTextChanged: conn.host = text
- }
- }
-
- RowLayout {
- Layout.fillWidth: true
- Layout.fillHeight: true
- spacing: 16
-
- Rectangle {
- Layout.preferredWidth: 320
- Layout.fillHeight: true
- radius: 14
- color: "#f6f6f6"
- border.color: "#e4e4e4"
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 18
- spacing: 10
-
- Text {
- text: "Your Jibo"
- font.pixelSize: 18
- font.bold: true
- }
-
- Item { Layout.fillHeight: true }
-
- Rectangle {
- id: jiboCard
- Layout.alignment: Qt.AlignHCenter
- width: 240
- height: 240
- radius: 18
- color: "#ffffff"
- border.color: "#e4e4e4"
-
- Image {
- anchors.centerIn: parent
- width: 200
- height: 200
- source: "../assets/jibo.svg"
- fillMode: Image.PreserveAspectFit
- }
-
- MouseArea {
- anchors.fill: parent
- onClicked: {
- // Best-effort: open Chrome remote devices page.
- Qt.openUrlExternally("chrome://inspect/#devices")
- }
- }
- }
-
- Item { Layout.fillHeight: true }
-
- Text {
- text: "Click Jibo to open chrome://inspect"
- color: "#666"
- Layout.alignment: Qt.AlignHCenter
- }
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.fillHeight: true
- radius: 14
- color: "#f6f6f6"
- border.color: "#e4e4e4"
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 18
- spacing: 12
-
- Text {
- text: "Actions"
- font.pixelSize: 18
- font.bold: true
- }
-
- Text {
- text: "Installer and updater remain available via CLI.\nUse the buttons below to launch their GUIs."
- color: "#555"
- wrapMode: Text.WordWrap
- Layout.fillWidth: true
- }
-
- Item { Layout.fillHeight: true }
-
- RowLayout {
- Layout.fillWidth: true
- spacing: 12
-
- Button {
- Layout.fillWidth: true
- text: "Install"
- enabled: true
- onClicked: {
- launcher.launchInstaller()
- }
- }
-
- Button {
- Layout.fillWidth: true
- text: "Check for updates"
- enabled: true
- onClicked: {
- launcher.launchUpdater()
- }
- }
- }
- }
- }
- }
- }
-}
diff --git a/gui/qml/ToolRunner.qml b/gui/qml/ToolRunner.qml
deleted file mode 100644
index 72d3e84..0000000
--- a/gui/qml/ToolRunner.qml
+++ /dev/null
@@ -1,142 +0,0 @@
-import QtQuick
-import QtQuick.Controls
-import QtQuick.Layouts
-
-ApplicationWindow {
- id: win
- width: 900
- height: 560
- visible: true
- title: (typeof toolTitle === "string" ? toolTitle : "Tool")
-
- property string script: (typeof toolScript === "string" ? toolScript : "")
- property bool isUpdater: script.indexOf("jibo_updater.py") >= 0
-
- function buildArgs() {
- var args = []
- args.push(script)
-
- if (isUpdater) {
- var h = hostField.text.trim()
- if (h.length > 0) {
- args.push("--ip")
- args.push(h)
- }
- }
-
- var extra = extraArgs.text.trim()
- if (extra.length > 0) {
- // naive split (keeps GUI minimal)
- var parts = extra.split(/\s+/)
- for (var i=0; i 0) args.push(parts[i])
- }
- }
-
- return args
- }
-
- ColumnLayout {
- anchors.fill: parent
- anchors.margins: 16
- spacing: 12
-
- RowLayout {
- Layout.fillWidth: true
- spacing: 10
-
- Text {
- text: title
- font.pixelSize: 18
- font.bold: true
- }
-
- Item { Layout.fillWidth: true }
-
- Button {
- text: runner.running ? "Stop" : "Start"
- onClicked: {
- if (runner.running) {
- runner.stop()
- } else {
- runner.start(pyExec, buildArgs())
- }
- }
- }
-
- Button {
- text: "Open in terminal"
- enabled: !runner.running
- onClicked: {
- terminal.openTerminal(pyExec, buildArgs())
- }
- }
- }
-
- RowLayout {
- Layout.fillWidth: true
- spacing: 10
-
- TextField {
- id: hostField
- visible: isUpdater
- placeholderText: "Jibo IP (required for updater)"
- Layout.preferredWidth: 260
- }
-
- TextField {
- id: extraArgs
- placeholderText: "Extra arguments (optional)"
- Layout.fillWidth: true
- }
- }
-
- Rectangle {
- Layout.fillWidth: true
- Layout.fillHeight: true
- radius: 12
- color: "#0f0f0f"
-
- ScrollView {
- anchors.fill: parent
- anchors.margins: 10
- clip: true
-
- TextArea {
- id: log
- readOnly: true
- wrapMode: TextArea.Wrap
- color: "#e8e8e8"
- font.family: "monospace"
- background: null
- text: ""
- }
- }
- }
-
- RowLayout {
- Layout.fillWidth: true
- spacing: 10
-
- Text {
- text: runner.running ? "Running..." : (runner.exitCode >= 0 ? ("Exit: " + runner.exitCode) : "Idle")
- color: "#666"
- }
-
- Item { Layout.fillWidth: true }
-
- Button {
- text: "Clear log"
- onClicked: log.text = ""
- }
- }
- }
-
- Connections {
- target: runner
- function onOutputAppended(chunk) {
- log.text += chunk
- log.cursorPosition = log.length
- }
- }
-}
diff --git a/gui/updater_gui.py b/gui/updater_gui.py
deleted file mode 100644
index 9e52e5d..0000000
--- a/gui/updater_gui.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from __future__ import annotations
-
-import sys
-from pathlib import Path
-
-from PySide6.QtCore import QUrl
-from PySide6.QtGui import QGuiApplication
-from PySide6.QtQml import QQmlApplicationEngine
-
-from .process_runner import ProcessRunner, resolve_python
-from .terminal_helper import TerminalHelper
-
-
-def main() -> int:
- app = QGuiApplication(sys.argv)
-
- engine = QQmlApplicationEngine()
-
- runner = ProcessRunner()
- terminal = TerminalHelper()
- engine.rootContext().setContextProperty("runner", runner)
- engine.rootContext().setContextProperty("terminal", terminal)
- engine.rootContext().setContextProperty("pyExec", resolve_python())
- engine.rootContext().setContextProperty("toolScript", "jibo_updater.py")
- engine.rootContext().setContextProperty("toolTitle", "Updater")
-
- qml_path = Path(__file__).resolve().parent / "qml" / "ToolRunner.qml"
- engine.load(QUrl.fromLocalFile(str(qml_path)))
-
- if not engine.rootObjects():
- return 1
-
- return app.exec()
-
-
-if __name__ == "__main__":
- raise SystemExit(main())