ummm new tui thing ?
This commit is contained in:
@@ -67,13 +67,11 @@ def load_config_entries_from_values_md() -> list[ConfigEntry]:
|
||||
if path in seen:
|
||||
continue
|
||||
|
||||
# Filter out non-robot/server dev configs the user doesn't want here.
|
||||
if path.startswith("/hub-shim/"):
|
||||
continue
|
||||
if path.lower().endswith(".md"):
|
||||
continue
|
||||
|
||||
# Keep it focused on JSON files (these are strict JSON configs).
|
||||
if not path.lower().endswith(".json"):
|
||||
continue
|
||||
|
||||
|
||||
@@ -50,12 +50,10 @@ class MainWindowController:
|
||||
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)
|
||||
@@ -65,7 +63,6 @@ class MainWindowController:
|
||||
self.ha_enable = require_child(self.window, "haEnableCheck", QCheckBox)
|
||||
self.ha_server_ip = require_child(self.window, "haServerIpField", QLineEdit)
|
||||
|
||||
# AI Bridge (formerly "AI Provider")
|
||||
self.ai_enable = require_child(self.window, "aiEnableCheck", QCheckBox)
|
||||
self.ai_mode = require_child(self.window, "aiProviderCombo", QComboBox)
|
||||
self.ai_server_base_url = require_child(self.window, "aiEndpointField", QLineEdit)
|
||||
@@ -82,10 +79,8 @@ class MainWindowController:
|
||||
|
||||
self._ai_bridge_obj: Optional[dict[str, Any]] = None
|
||||
|
||||
# Tool settings
|
||||
self.enable_logging_check = require_child(self.window, "enableLoggingCheck", QCheckBox)
|
||||
|
||||
# Config editor (main panel "Config" section)
|
||||
self.config_file_combo = require_child(self.window, "configFileCombo", QComboBox)
|
||||
self.config_read_button = require_child(self.window, "configReadButton", QPushButton)
|
||||
self.config_write_button = require_child(self.window, "configWriteButton", QPushButton)
|
||||
@@ -96,18 +91,15 @@ class MainWindowController:
|
||||
self._config_last_read_text: Optional[str] = None
|
||||
self._config_paths: list[str] = []
|
||||
|
||||
# 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)
|
||||
|
||||
self._robot_settings_window: Optional[object] = None
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -137,7 +129,6 @@ class MainWindowController:
|
||||
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;"
|
||||
@@ -146,22 +137,18 @@ class MainWindowController:
|
||||
"}"
|
||||
)
|
||||
|
||||
# AI Bridge mode choices
|
||||
self.ai_mode.clear()
|
||||
self.ai_mode.addItems(["TEXT", "AUDIO"])
|
||||
|
||||
# Robot controls start disabled until connected.
|
||||
self.robot_settings_button.setEnabled(False)
|
||||
self.robot_action_combo.setEnabled(False)
|
||||
|
||||
# Config editor defaults
|
||||
self.config_editor.setPlaceholderText("Select a config file, then Read")
|
||||
self.config_activity_log.setReadOnly(True)
|
||||
self.config_activity_log.setPlaceholderText("Logging is disabled")
|
||||
self.config_read_button.setEnabled(False)
|
||||
self.config_write_button.setEnabled(False)
|
||||
|
||||
# Defaults
|
||||
self.connect_button.setText("Connect")
|
||||
self.jibo_title.setText("Connect Your Jibo")
|
||||
|
||||
@@ -179,7 +166,6 @@ class MainWindowController:
|
||||
|
||||
self.edit_ai_bridge_button.clicked.connect(self._jump_to_ai_bridge_config)
|
||||
|
||||
# Keep AI Bridge in-memory config in sync with UI edits.
|
||||
self.ai_enable.toggled.connect(self._sync_ai_bridge_obj_from_ui)
|
||||
self.ai_mode.currentIndexChanged.connect(self._sync_ai_bridge_obj_from_ui)
|
||||
self.ai_server_base_url.textChanged.connect(self._sync_ai_bridge_obj_from_ui)
|
||||
@@ -218,7 +204,6 @@ class MainWindowController:
|
||||
ssh_client=self._ssh_client,
|
||||
logging_enabled_check=self.enable_logging_check,
|
||||
)
|
||||
# Refresh the SSH client reference in case we reconnected.
|
||||
try:
|
||||
self._robot_settings_window.set_ssh_client(self._ssh_client) # type: ignore[attr-defined]
|
||||
except Exception:
|
||||
@@ -245,12 +230,10 @@ class MainWindowController:
|
||||
self.ai_followup_enabled.setEnabled(ai_enabled)
|
||||
self.ai_followup_delay_ms.setEnabled(ai_enabled)
|
||||
|
||||
# Connection button enabled unless a connect attempt is in progress.
|
||||
self.connect_button.setEnabled(not self._connecting)
|
||||
|
||||
connected = self.session_connected
|
||||
self.config_read_button.setEnabled(connected and self.config_file_combo.count() > 0)
|
||||
# write button is controlled by editor dirty state
|
||||
|
||||
def _sync_all(self) -> None:
|
||||
host = self.host
|
||||
@@ -285,7 +268,6 @@ class MainWindowController:
|
||||
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))
|
||||
@@ -340,7 +322,6 @@ class MainWindowController:
|
||||
self.config_activity_log.appendPlainText(message)
|
||||
|
||||
def _populate_config_file_combo(self) -> None:
|
||||
# Populate from inventory, excluding /usr/local/etc (those belong under Robot Settings)
|
||||
entries = load_config_entries_from_values_md()
|
||||
paths = [e.remote_path for e in entries if not e.is_usr_local_etc]
|
||||
paths = sorted(paths)
|
||||
@@ -369,8 +350,6 @@ class MainWindowController:
|
||||
self.config_file_combo.setCurrentIndex(idx)
|
||||
self._read_selected_config()
|
||||
|
||||
# Seed editor with the current AI Bridge UI state so the user can
|
||||
# immediately press Write.
|
||||
try:
|
||||
merged = self._merged_ai_bridge_obj_from_ui()
|
||||
desired_text = json.dumps(merged, indent=2, ensure_ascii=False) + "\n"
|
||||
@@ -539,7 +518,6 @@ class MainWindowController:
|
||||
return base
|
||||
|
||||
def _sync_ai_bridge_obj_from_ui(self, *_args: Any) -> None:
|
||||
# Keep unknown keys (if any) from the on-robot JSON.
|
||||
try:
|
||||
self._ai_bridge_obj = self._merged_ai_bridge_obj_from_ui()
|
||||
except Exception:
|
||||
@@ -577,7 +555,6 @@ class MainWindowController:
|
||||
except Exception:
|
||||
old_obj = MISSING
|
||||
|
||||
# Safety: if a /usr/local path ever ends up here, handle remount.
|
||||
if p.startswith("/usr/local/"):
|
||||
cmd = "mount -o remount,rw /usr/local"
|
||||
self._log(f"EXEC {cmd}")
|
||||
@@ -666,12 +643,10 @@ class MainWindowController:
|
||||
|
||||
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}")
|
||||
|
||||
# Auto-populate AI Bridge section when connected.
|
||||
try:
|
||||
self._load_ai_bridge_from_robot()
|
||||
except Exception:
|
||||
@@ -705,7 +680,6 @@ class MainWindowController:
|
||||
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
|
||||
|
||||
@@ -45,7 +45,6 @@ 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, [])
|
||||
@@ -64,7 +63,6 @@ def resolve_python_invocation() -> tuple[str, list[str]]:
|
||||
def resolve_python() -> str:
|
||||
program, prefix = resolve_python_invocation()
|
||||
if prefix:
|
||||
# Best-effort string representation (mostly for display)
|
||||
return " ".join([program] + prefix)
|
||||
return program
|
||||
|
||||
@@ -82,7 +80,6 @@ def _pick_terminal_command() -> Optional[list[str]]:
|
||||
return None
|
||||
|
||||
candidates: list[list[str]] = []
|
||||
# Debian/Ubuntu alternative system
|
||||
candidates.append(["x-terminal-emulator", "-e"])
|
||||
candidates.append(["gnome-terminal", "--"])
|
||||
candidates.append(["konsole", "-e"])
|
||||
@@ -102,8 +99,6 @@ def spawn_in_terminal(argv: list[str]) -> bool:
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
@@ -44,13 +44,11 @@ class RobotSettingsWindow:
|
||||
splitter = QSplitter(Qt.Horizontal)
|
||||
outer.addWidget(splitter, 1)
|
||||
|
||||
# Left: tree
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setHeaderHidden(True)
|
||||
self.tree.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
splitter.addWidget(self.tree)
|
||||
|
||||
# Right: editor + buttons + log
|
||||
right = QWidget()
|
||||
right_layout = QVBoxLayout(right)
|
||||
right_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -168,7 +166,6 @@ class RobotSettingsWindow:
|
||||
|
||||
@Slot()
|
||||
def _on_editor_changed(self) -> None:
|
||||
# Enable write only if we have a loaded file and text changed.
|
||||
if not self._current_remote_path or self._last_read_text is None:
|
||||
self.write_button.setEnabled(False)
|
||||
return
|
||||
@@ -243,14 +240,12 @@ class RobotSettingsWindow:
|
||||
|
||||
new_text_raw = self.editor.toPlainText()
|
||||
|
||||
# Validate JSON if possible; this tool is focused on strict JSON configs.
|
||||
try:
|
||||
new_obj = json.loads(new_text_raw)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self.window, "Invalid JSON", f"JSON parse failed: {e}")
|
||||
return
|
||||
|
||||
# Canonicalize to keep robot-side JSON strict/clean.
|
||||
new_text = json.dumps(new_obj, indent=2, ensure_ascii=False) + "\n"
|
||||
|
||||
try:
|
||||
@@ -264,7 +259,6 @@ class RobotSettingsWindow:
|
||||
except Exception:
|
||||
old_obj = MISSING
|
||||
|
||||
# Mounted dir special case: /usr/local/* is often read-only until remount.
|
||||
if remote_path.startswith("/usr/local/"):
|
||||
cmd = "mount -o remount,rw /usr/local"
|
||||
self._log(f"EXEC {cmd}")
|
||||
@@ -280,7 +274,6 @@ class RobotSettingsWindow:
|
||||
if out.strip():
|
||||
self._log(out.strip())
|
||||
|
||||
# Compute diffs (best-effort).
|
||||
if old_obj is not MISSING:
|
||||
diffs = diff_json(old_obj, new_obj)
|
||||
if diffs:
|
||||
@@ -302,7 +295,6 @@ class RobotSettingsWindow:
|
||||
try:
|
||||
self._sftp_write_text(remote_path, new_text)
|
||||
self._log(f"WROTE {remote_path} ({len(new_text)} bytes)")
|
||||
# Refresh read baseline.
|
||||
self.editor.setPlainText(new_text)
|
||||
self._last_read_text = new_text
|
||||
self.write_button.setEnabled(False)
|
||||
|
||||
@@ -63,7 +63,6 @@ class ToolRunnerWindow(QObject):
|
||||
|
||||
self._host_field.setVisible(self._is_updater)
|
||||
|
||||
# Installer-specific UX
|
||||
self._use_existing_dump.setVisible(self._is_installer)
|
||||
self._dump_path.setVisible(self._is_installer)
|
||||
self._browse_dump.setVisible(self._is_installer)
|
||||
@@ -108,7 +107,6 @@ class ToolRunnerWindow(QObject):
|
||||
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:
|
||||
@@ -125,7 +123,6 @@ class ToolRunnerWindow(QObject):
|
||||
extra = self._extra_args.text().strip()
|
||||
extra_args: list[str] = shlex.split(extra) if extra else []
|
||||
|
||||
# Installer convenience: if the user has an existing dump, pass it via --dump-path
|
||||
if self._is_installer and self._use_existing_dump.isChecked():
|
||||
dump_path = self._dump_path.text().strip()
|
||||
if dump_path and "--dump-path" not in extra_args:
|
||||
@@ -150,7 +147,6 @@ class ToolRunnerWindow(QObject):
|
||||
self._status.setText("Dump file not found")
|
||||
return
|
||||
|
||||
# Reset progress state for a new run.
|
||||
self._output_buffer = ""
|
||||
self._last_step_total = None
|
||||
if self._is_installer:
|
||||
@@ -168,7 +164,6 @@ class ToolRunnerWindow(QObject):
|
||||
|
||||
@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)
|
||||
@@ -181,7 +176,6 @@ class ToolRunnerWindow(QObject):
|
||||
self._start_stop.setText("Stop" if running else "Start")
|
||||
self._open_terminal.setEnabled(not running)
|
||||
if not running and self._is_installer:
|
||||
# Leave progress/status in a meaningful final state.
|
||||
if self.runner.exitCode == 0 and self._last_step_total:
|
||||
self._progress.setRange(0, self._last_step_total)
|
||||
self._progress.setValue(self._last_step_total)
|
||||
@@ -194,7 +188,6 @@ class ToolRunnerWindow(QObject):
|
||||
def _sync_status(self) -> None:
|
||||
if self.runner.running:
|
||||
self._status.setText("Running...")
|
||||
# Indeterminate until we see a structured step marker.
|
||||
if self._is_installer and self._last_step_total is None:
|
||||
self._progress.setRange(0, 0)
|
||||
return
|
||||
@@ -233,7 +226,6 @@ class ToolRunnerWindow(QObject):
|
||||
self._output_buffer += chunk
|
||||
lines = self._output_buffer.splitlines(keepends=True)
|
||||
|
||||
# Keep any partial line for the next chunk.
|
||||
if lines and not (lines[-1].endswith("\n") or lines[-1].endswith("\r")):
|
||||
self._output_buffer = lines[-1]
|
||||
lines = lines[:-1]
|
||||
@@ -245,7 +237,6 @@ class ToolRunnerWindow(QObject):
|
||||
if not clean:
|
||||
continue
|
||||
|
||||
# Also surface meaningful non-step status lines (RCM detection, warnings, etc.)
|
||||
if clean.startswith(("ℹ", "⚠", "✓", "✗")) or "RCM" in clean:
|
||||
msg = _clean_status_line(clean)
|
||||
if msg and not msg.startswith("["):
|
||||
@@ -258,7 +249,6 @@ class ToolRunnerWindow(QObject):
|
||||
total = int(m.group(2))
|
||||
msg = m.group(3).strip()
|
||||
|
||||
# Some flows use [0/6] for dependency checks.
|
||||
if total > 0:
|
||||
self._last_step_total = total
|
||||
self._progress.setRange(0, total)
|
||||
@@ -284,9 +274,7 @@ def _strip_ansi(s: str) -> str:
|
||||
|
||||
|
||||
def _clean_status_line(s: str) -> str:
|
||||
# Drop leading glyphs used by the CLI (info/warn/success/error)
|
||||
s = re.sub(r"^[✓⚠✗ℹ]\s+", "", s).strip()
|
||||
# Collapse extra whitespace
|
||||
s = re.sub(r"\s+", " ", s).strip()
|
||||
return s
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ def load_ui(ui_path: Path) -> object:
|
||||
|
||||
|
||||
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__})")
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# -*- 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,
|
||||
@@ -149,7 +142,6 @@ class Ui_ToolRunnerWindow(object):
|
||||
self.retranslateUi(ToolRunnerWindow)
|
||||
|
||||
QMetaObject.connectSlotsByName(ToolRunnerWindow)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, ToolRunnerWindow):
|
||||
ToolRunnerWindow.setWindowTitle(QCoreApplication.translate("ToolRunnerWindow", u"Tool", None))
|
||||
@@ -165,5 +157,4 @@ class Ui_ToolRunnerWindow(object):
|
||||
self.currentStepLabel.setText(QCoreApplication.translate("ToolRunnerWindow", u"Idle", None))
|
||||
self.statusLabel.setText(QCoreApplication.translate("ToolRunnerWindow", u"Idle", None))
|
||||
self.clearLogButton.setText(QCoreApplication.translate("ToolRunnerWindow", u"Clear log", None))
|
||||
# retranslateUi
|
||||
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
# -*- 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,
|
||||
@@ -557,7 +550,6 @@ class Ui_MainWindow(object):
|
||||
|
||||
|
||||
QMetaObject.connectSlotsByName(MainWindow)
|
||||
# setupUi
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"Jibo Tools", None))
|
||||
@@ -627,5 +619,4 @@ class Ui_MainWindow(object):
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user