completely redone menu now using widgets instead of QML

This commit is contained in:
2026-03-18 02:07:35 +02:00
parent 8dfb15ac40
commit c6235093e1
32 changed files with 2017 additions and 445 deletions

84
JiboTools/JiboTools/.gitignore vendored Normal file
View File

@@ -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/

643
JiboTools/JiboTools/form.ui Normal file
View File

@@ -0,0 +1,643 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>782</width>
<height>649</height>
</rect>
</property>
<property name="windowTitle">
<string>Jibo Tools</string>
</property>
<property name="tabShape">
<enum>QTabWidget::TabShape::Rounded</enum>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="rootLayout">
<property name="spacing">
<number>12</number>
</property>
<property name="leftMargin">
<number>14</number>
</property>
<property name="topMargin">
<number>14</number>
</property>
<property name="rightMargin">
<number>14</number>
</property>
<property name="bottomMargin">
<number>14</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<property name="documentMode">
<bool>true</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
</property>
<property name="movable">
<bool>false</bool>
</property>
<widget class="QWidget" name="tabJibo">
<attribute name="title">
<string>Jibo</string>
</attribute>
<layout class="QHBoxLayout" name="jiboPageLayout">
<property name="spacing">
<number>14</number>
</property>
<item>
<widget class="QFrame" name="configFrame">
<property name="minimumSize">
<size>
<width>420</width>
<height>0</height>
</size>
</property>
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="configLayout">
<property name="spacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>18</number>
</property>
<property name="topMargin">
<number>18</number>
</property>
<property name="rightMargin">
<number>18</number>
</property>
<property name="bottomMargin">
<number>18</number>
</property>
<item>
<widget class="QLabel" name="configTitle">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Config</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="configScroll">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="configScrollContents">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>380</width>
<height>478</height>
</rect>
</property>
<layout class="QVBoxLayout" name="configScrollLayout">
<property name="spacing">
<number>14</number>
</property>
<item>
<widget class="QGroupBox" name="groupPreview">
<property name="title">
<string>Preview</string>
</property>
<layout class="QFormLayout" name="formPreview">
<item row="0" column="0">
<widget class="QLabel" name="labelOverride">
<property name="text">
<string>Override</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="overrideCheck">
<property name="text">
<string/>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelPreviewConnected">
<property name="text">
<string>Connected</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="previewConnectedCheck">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupHomeAssistant">
<property name="title">
<string>Home Assistant</string>
</property>
<layout class="QFormLayout" name="formHomeAssistant">
<item row="0" column="0">
<widget class="QLabel" name="labelHaEnable">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="haEnableCheck">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelHaServerIp">
<property name="text">
<string>Server IP</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="haServerIpField">
<property name="placeholderText">
<string>Home Assistant host</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupAiProvider">
<property name="title">
<string>AI Provider</string>
</property>
<layout class="QFormLayout" name="formAiProvider">
<item row="0" column="0">
<widget class="QLabel" name="labelAiEnable">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="aiEnableCheck">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelAiProvider">
<property name="text">
<string>Provider</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="aiProviderCombo"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="labelAiEndpoint">
<property name="text">
<string>API endpoint</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="aiEndpointField">
<property name="placeholderText">
<string>http://...</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="labelAiKey">
<property name="text">
<string>API key</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="aiKeyField">
<property name="echoMode">
<enum>QLineEdit::EchoMode::Password</enum>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="labelTokens">
<property name="text">
<string>Tokens used</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="tokensUsedLabel">
<property name="text">
<string>-1</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="configBottomSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="jiboCardFrame">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="jiboCardLayout">
<property name="spacing">
<number>12</number>
</property>
<property name="leftMargin">
<number>18</number>
</property>
<property name="topMargin">
<number>18</number>
</property>
<property name="rightMargin">
<number>18</number>
</property>
<property name="bottomMargin">
<number>18</number>
</property>
<item>
<layout class="QHBoxLayout" name="IpConfig">
<item alignment="Qt::AlignmentFlag::AlignRight">
<widget class="QPushButton" name="TryToConnect">
<property name="text">
<string>Connect</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="JiboIpField">
<property name="inputMask">
<string/>
</property>
<property name="text">
<string/>
</property>
<property name="placeholderText">
<string>e.g 192.168.1.54</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="jiboHeaderLayout">
<item>
<widget class="QLabel" name="jiboTitle">
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>true</bold>
<underline>false</underline>
</font>
</property>
<property name="text">
<string>Connect Your Jibo</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="jiboTopSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="jiboImage">
<property name="minimumSize">
<size>
<width>260</width>
<height>260</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>260</width>
<height>260</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="jiboBottomSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="RobotSettings">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Robot Settings</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="editable">
<bool>true</bool>
</property>
<property name="currentText">
<string>Reboot</string>
</property>
<property name="placeholderText">
<string>Reboot</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabUpdate">
<attribute name="title">
<string>Update</string>
</attribute>
<layout class="QVBoxLayout" name="updatePageLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QFrame" name="updateFrame">
<property name="frameShape">
<enum>QFrame::Shape::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Shadow::Raised</enum>
</property>
<layout class="QVBoxLayout" name="updateFrameLayout">
<property name="spacing">
<number>12</number>
</property>
<property name="leftMargin">
<number>18</number>
</property>
<property name="topMargin">
<number>18</number>
</property>
<property name="rightMargin">
<number>18</number>
</property>
<property name="bottomMargin">
<number>18</number>
</property>
<item>
<widget class="QLabel" name="updateInfoText">
<property name="text">
<string>Installer and updater remain available via CLI. Use the buttons below to launch their GUIs.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="updateSpacer">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="updateButtonsLayout">
<property name="spacing">
<number>12</number>
</property>
<item>
<widget class="QPushButton" name="installButton">
<property name="text">
<string>Install</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="checkUpdatesButton">
<property name="text">
<string>Check for updates</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabSkills">
<attribute name="title">
<string>Skills</string>
</attribute>
<layout class="QVBoxLayout" name="skillsLayout">
<item>
<widget class="QLabel" name="skillsComingSoon">
<property name="text">
<string>Coming soon.</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabSsh">
<attribute name="title">
<string>SSH</string>
</attribute>
<layout class="QVBoxLayout" name="sshLayout">
<item>
<widget class="QLabel" name="sshComingSoon">
<property name="text">
<string>Coming soon.</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabFtp">
<attribute name="title">
<string>FTP</string>
</attribute>
<layout class="QVBoxLayout" name="ftpLayout">
<item>
<widget class="QLabel" name="ftpComingSoon">
<property name="text">
<string>Coming soon.</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabStatus">
<attribute name="title">
<string>Status</string>
</attribute>
<layout class="QVBoxLayout" name="statusLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<layout class="QHBoxLayout" name="statusRow">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="statusDot">
<property name="minimumSize">
<size>
<width>10</width>
<height>10</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>10</width>
<height>10</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statusText">
<property name="text">
<string>No Jibo IP configured</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tabRobotOs">
<attribute name="title">
<string>Robot OS</string>
</attribute>
<layout class="QVBoxLayout" name="robotOsLayout">
<item>
<widget class="QLabel" name="robotOsComingSoon">
<property name="text">
<string>Coming soon.</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar">
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

Binary file not shown.

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 256 256">
<defs>
<linearGradient id="g" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#f2f2f2"/>
<stop offset="1" stop-color="#d9d9d9"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="256" height="256" fill="none"/>
<circle cx="128" cy="128" r="110" fill="url(#g)" stroke="#9a9a9a" stroke-width="6"/>
<circle cx="128" cy="128" r="70" fill="#ffffff" stroke="#b3b3b3" stroke-width="6"/>
<rect x="88" y="168" width="80" height="26" rx="13" fill="#c7c7c7"/>
<text x="128" y="128" font-family="sans-serif" font-size="28" text-anchor="middle" fill="#444">Jibo</text>
</svg>

After

Width:  |  Height:  |  Size: 696 B

View File

@@ -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())

View File

@@ -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())

View File

@@ -0,0 +1,209 @@
from __future__ import annotations
import os
import shlex
import shutil
import socket
import subprocess
import sys
from pathlib import Path
from typing import Optional
from PySide6.QtCore import QObject, QProcess, QTimer, Signal, Slot, Property
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]]:
"""Return (program, prefix_args) to invoke Python reliably.
On Windows, prefer the repo-local venv; otherwise prefer the `py -3` launcher
when present so we dont depend on `python.exe` being on PATH.
"""
venv_py = REPO_ROOT / ".venv" / ("Scripts" if os.name == "nt" else "bin") / (
"python.exe" if os.name == "nt" else "python"
)
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 ("python", [])
def resolve_python() -> str:
program, prefix = resolve_python_invocation()
if prefix:
# Best-effort string representation (mostly for display)
return " ".join([program] + prefix)
return program
def can_connect(host: str, port: int, timeout_s: float = 0.8) -> bool:
try:
with socket.create_connection((host, port), timeout=timeout_s):
return True
except OSError:
return False
def _pick_terminal_command() -> Optional[list[str]]:
if os.name == "nt":
return None
candidates: list[list[str]] = []
# Debian/Ubuntu alternative system
candidates.append(["x-terminal-emulator", "-e"])
candidates.append(["gnome-terminal", "--"])
candidates.append(["konsole", "-e"])
candidates.append(["xfce4-terminal", "-e"])
candidates.append(["xterm", "-e"])
for cmd in candidates:
if shutil.which(cmd[0]):
return cmd
return None
def spawn_in_terminal(argv: list[str]) -> bool:
"""Best-effort external terminal launcher.
Returns True if a terminal was spawned, False otherwise.
"""
if os.name == "nt":
# Use cmd.exe window, keep it open (/k)
# Build a single string command for cmd.
cmdline = " ".join(shlex.quote(a) for a in argv)
subprocess.Popen(["cmd", "/c", "start", "cmd", "/k", cmdline], shell=False)
return True
term = _pick_terminal_command()
if not term:
return False
subprocess.Popen(term + argv, cwd=str(REPO_ROOT))
return True
class ProcessRunner(QObject):
runningChanged = Signal()
exitCodeChanged = Signal()
outputAppended = Signal(str)
def __init__(self) -> None:
super().__init__()
self._proc = QProcess(self)
self._proc.setProcessChannelMode(QProcess.MergedChannels)
self._proc.readyReadStandardOutput.connect(self._on_ready)
self._proc.finished.connect(self._on_finished)
self._exit_code: int = -1
@Property(bool, notify=runningChanged)
def running(self) -> bool:
return self._proc.state() != QProcess.NotRunning
@Property(int, notify=exitCodeChanged)
def exitCode(self) -> int:
return self._exit_code
@Slot(str, list)
def start(self, program: str, arguments: list) -> None:
if self.running:
return
self._exit_code = -1
self.exitCodeChanged.emit()
self._proc.setProgram(program)
self._proc.setArguments([str(a) for a in arguments])
self._proc.setWorkingDirectory(str(REPO_ROOT))
self._proc.start()
self.runningChanged.emit()
@Slot()
def stop(self) -> None:
if not self.running:
return
self._proc.terminate()
if not self._proc.waitForFinished(1500):
self._proc.kill()
self.runningChanged.emit()
def _on_ready(self) -> None:
data = bytes(self._proc.readAllStandardOutput()).decode("utf-8", errors="replace")
if data:
self.outputAppended.emit(data)
def _on_finished(self, exit_code: int, _status) -> None:
self._exit_code = int(exit_code)
self.exitCodeChanged.emit()
self.runningChanged.emit()
class ConnectionMonitor(QObject):
hostChanged = Signal()
connectedChanged = Signal()
def __init__(self) -> None:
super().__init__()
self._host = ""
self._connected = False
self._timer = QTimer(self)
self._timer.setInterval(1000)
self._timer.timeout.connect(self._poll)
self._timer.start()
@Property(str, notify=hostChanged)
def host(self) -> str:
return self._host
@host.setter
def host(self, value: str) -> None:
value = (value or "").strip()
if value == self._host:
return
self._host = value
self.hostChanged.emit()
self._poll()
@Property(bool, notify=connectedChanged)
def connected(self) -> bool:
return self._connected
def _poll(self) -> None:
host = self._host
connected = False
if host:
connected = can_connect(host, 22)
if connected != self._connected:
self._connected = connected
self.connectedChanged.emit()

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from PySide6.QtCore import QObject, Slot
from .process_runner import spawn_in_terminal
class TerminalHelper(QObject):
@Slot(str, list, result=bool)
def openTerminal(self, program: str, arguments: list) -> bool:
argv = [program] + [str(a) for a in arguments]
return spawn_in_terminal(argv)

View File

@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ToolRunnerWindow</class>
<widget class="QMainWindow" name="ToolRunnerWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>900</width>
<height>560</height>
</rect>
</property>
<property name="windowTitle">
<string>Tool</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="rootLayout">
<property name="leftMargin">
<number>16</number>
</property>
<property name="topMargin">
<number>16</number>
</property>
<property name="rightMargin">
<number>16</number>
</property>
<property name="bottomMargin">
<number>16</number>
</property>
<property name="spacing">
<number>12</number>
</property>
<item>
<layout class="QHBoxLayout" name="headerLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="titleLabel">
<property name="text">
<string>Tool</string>
</property>
<property name="font">
<font>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
</widget>
</item>
<item>
<spacer name="headerSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="startStopButton">
<property name="text">
<string>Start</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="openTerminalButton">
<property name="text">
<string>Open in terminal</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="argsLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QLineEdit" name="hostField">
<property name="placeholderText">
<string>Jibo IP (required for updater)</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="extraArgsField">
<property name="placeholderText">
<string>Extra arguments (optional)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="logEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="footerLayout">
<property name="spacing">
<number>10</number>
</property>
<item>
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>Idle</string>
</property>
</widget>
</item>
<item>
<spacer name="footerSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="clearLogButton">
<property name="text">
<string>Clear log</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -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())

View File

@@ -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)

View File

@@ -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

View File

@@ -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())

View File

@@ -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())

View File

@@ -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",
]

View File

@@ -0,0 +1,2 @@
PySide6
paramiko

View File

@@ -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