ui: move model selector to models panel (#957)

* models panel

* models panel

* fix ui report

* stupid scroll

* move model selector to models panel

* cleanup whitespaces

* cleanup bad merge

---------

Co-authored-by: DevTekVE <devtekve@gmail.com>
This commit is contained in:
Nayan
2025-05-30 07:06:09 -04:00
committed by GitHub
parent d9d32ebea2
commit be406d8f83
4 changed files with 249 additions and 244 deletions
@@ -5,8 +5,209 @@
* See the LICENSE.md file in the root directory for more details.
*/
#include <algorithm>
#include <QJsonDocument>
#include "common/model.h"
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/models_panel.h"
#include "selfdrive/ui/sunnypilot/qt/widgets/scrollview.h"
ModelsPanel::ModelsPanel(QWidget *parent) : QWidget(parent) {
QVBoxLayout *main_layout = new QVBoxLayout(this);
main_layout->setContentsMargins(50, 20, 50, 20);
ListWidgetSP *list = new ListWidgetSP(this);
ScrollViewSP *scroller = new ScrollViewSP(list, this);
main_layout->addWidget(scroller);
const auto current_model = GetActiveModelName();
currentModelLblBtn = new ButtonControlSP(tr("Current Model"), tr("SELECT"), current_model);
currentModelLblBtn->setValue(current_model);
connect(currentModelLblBtn, &ButtonControlSP::clicked, this, &ModelsPanel::handleCurrentModelLblBtnClicked);
connect(uiState(), &UIState::offroadTransition, [=](bool offroad) {
is_onroad = !offroad;
updateLabels();
});
connect(uiStateSP(), &UIStateSP::uiUpdate, this, &ModelsPanel::updateLabels);
list->addItem(currentModelLblBtn);
}
/**
* @brief Updates the UI with bundle download progress information
* Reads status from modelManagerSP cereal message and displays status for all models
*/
void ModelsPanel::handleBundleDownloadProgress() {
using DS = cereal::ModelManagerSP::DownloadStatus;
if (!model_manager.hasSelectedBundle() && !model_manager.hasActiveBundle()) {
currentModelLblBtn->setDescription(tr("No custom model selected!"));
return;
}
const bool showSelectedBundle = model_manager.hasSelectedBundle() && (isDownloading() || model_manager.getSelectedBundle().getStatus() == DS::FAILED);
const auto &bundle = showSelectedBundle ? model_manager.getSelectedBundle() : model_manager.getActiveBundle();
const auto &models = bundle.getModels();
download_status = bundle.getStatus();
const auto download_status_changed = prev_download_status != download_status;
QStringList status;
// Get status for each model type in order
for (const auto &model: models) {
QString typeName;
QString modelName = QString::fromStdString(bundle.getDisplayName());
switch (model.getType()) {
case cereal::ModelManagerSP::Model::Type::SUPERCOMBO:
typeName = tr("Driving");
break;
case cereal::ModelManagerSP::Model::Type::NAVIGATION:
typeName = tr("Navigation");
break;
case cereal::ModelManagerSP::Model::Type::VISION:
typeName = tr("Vision");
break;
case cereal::ModelManagerSP::Model::Type::POLICY:
typeName = tr("Policy");
break;
}
const auto &progress = model.getArtifact().getDownloadProgress();
QString line;
if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING) {
line = tr("Downloading %1 model [%2]... (%3%)").arg(typeName, modelName).arg(progress.getProgress(), 0, 'f', 2);
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADED) {
line = tr("%1 model [%2] %3").arg(typeName, modelName, download_status_changed ? tr("downloaded") : tr("ready"));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::CACHED) {
line = tr("%1 model [%2] %3").arg(typeName, modelName, download_status_changed ? tr("from cache") : tr("ready"));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::FAILED) {
line = tr("%1 model [%2] download failed").arg(typeName, modelName);
} else {
line = tr("%1 model [%2] pending...").arg(typeName, modelName);
}
status.append(line);
}
currentModelLblBtn->setDescription(status.join("\n"));
if (prev_download_status != download_status) {
switch (bundle.getStatus()) {
case cereal::ModelManagerSP::DownloadStatus::DOWNLOADING:
case cereal::ModelManagerSP::DownloadStatus::CACHED:
case cereal::ModelManagerSP::DownloadStatus::DOWNLOADED:
currentModelLblBtn->showDescription();
break;
case cereal::ModelManagerSP::DownloadStatus::FAILED:
default:
break;
}
}
prev_download_status = download_status;
}
/**
* @brief Gets the name of the currently selected model bundle
* @return Display name of the selected bundle or default model name
*/
QString ModelsPanel::GetActiveModelName() {
if (model_manager.hasActiveBundle()) {
return QString::fromStdString(model_manager.getActiveBundle().getDisplayName());
}
return DEFAULT_MODEL;
}
void ModelsPanel::updateModelManagerState() {
const SubMaster &sm = *(uiStateSP()->sm);
model_manager = sm["modelManagerSP"].getModelManagerSP();
}
/**
* @brief Handles the model bundle selection button click
* Displays available bundles, allows selection, and initiates download
*/
void ModelsPanel::handleCurrentModelLblBtnClicked() {
currentModelLblBtn->setEnabled(false);
currentModelLblBtn->setValue(tr("Fetching models..."));
// Create mapping of bundle indices to display names
QMap<uint32_t, QString> index_to_bundle;
const auto bundles = model_manager.getAvailableBundles();
for (const auto &bundle: bundles) {
index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundle.getDisplayName()));
}
// Sort bundles by index in descending order
QStringList bundleNames;
// Add "Default" as the first option
bundleNames.append(tr("Use Default"));
auto indices = index_to_bundle.keys();
std::sort(indices.begin(), indices.end(), std::greater<uint32_t>());
for (const auto &index: indices) {
bundleNames.append(index_to_bundle[index]);
}
currentModelLblBtn->setValue(GetActiveModelName());
const QString selectedBundleName = MultiOptionDialog::getSelection(
tr("Select a Model"), bundleNames, GetActiveModelName(), this);
if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) {
return;
}
// Handle "Stock" selection differently
if (selectedBundleName == tr("Use Default")) {
params.remove("ModelManager_ActiveBundle");
currentModelLblBtn->setValue(tr("Default"));
showResetParamsDialog();
} else {
// Find selected bundle and initiate download
for (const auto &bundle: bundles) {
if (QString::fromStdString(bundle.getDisplayName()) == selectedBundleName) {
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {
showResetParamsDialog();
}
break;
}
}
}
updateLabels();
}
/**
* @brief Updates the UI elements based on current state
*/
void ModelsPanel::updateLabels() {
if (!isVisible()) {
return;
}
updateModelManagerState();
handleBundleDownloadProgress();
currentModelLblBtn->setEnabled(!is_onroad && !isDownloading());
currentModelLblBtn->setValue(GetActiveModelName());
}
/**
* @brief Shows dialog prompting user to reset calibration after model download
*/
void ModelsPanel::showResetParamsDialog() {
const auto confirmMsg = QString("%1<br><br><b>%2</b><br><br><b>%3</b>")
.arg(tr("Model download has started in the background."))
.arg(tr("We STRONGLY suggest you to reset calibration."))
.arg(tr("Would you like to do that now?"));
const auto button_text = tr("Reset Calibration");
QString content("<body><h2 style=\"text-align: center;\">" + tr("Driving Model Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + confirmMsg + "</p></body>");
if (showConfirmationDialog(content, button_text, false)) {
params.remove("CalibrationParams");
params.remove("LiveTorqueParameters");
}
}
@@ -14,4 +14,51 @@ class ModelsPanel : public QWidget {
public:
explicit ModelsPanel(QWidget *parent = nullptr);
private:
QString GetActiveModelName();
void updateModelManagerState();
bool isDownloading() const {
if (!model_manager.hasSelectedBundle()) {
return false;
}
const auto &selected_bundle = model_manager.getSelectedBundle();
return selected_bundle.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING;
}
// UI update related methods
void updateLabels();
void handleCurrentModelLblBtnClicked();
void handleBundleDownloadProgress();
void showResetParamsDialog();
cereal::ModelManagerSP::Reader model_manager;
cereal::ModelManagerSP::DownloadStatus download_status{};
cereal::ModelManagerSP::DownloadStatus prev_download_status{};
bool canContinueOnMeteredDialog() {
if (!is_metered) return true;
return showConfirmationDialog(QString(), QString(), is_metered);
}
inline bool showConfirmationDialog(const QString &message = QString(), const QString &confirmButtonText = QString(), const bool show_metered_warning = false) {
return showConfirmationDialog(this, message, confirmButtonText, show_metered_warning);
}
static inline bool showConfirmationDialog(QWidget *parent, const QString &message = QString(), const QString &confirmButtonText = QString(), const bool show_metered_warning = false) {
const QString warning_message = show_metered_warning ? tr("Warning: You are on a metered connection!") : QString();
const QString final_message = QString("%1%2").arg(!message.isEmpty() ? message + "\n" : QString(), warning_message);
const QString final_buttonText = !confirmButtonText.isEmpty() ? confirmButtonText : QString(tr("Continue") + " %1").arg(show_metered_warning ? tr("on Metered") : "");
return ConfirmationDialog(final_message, final_buttonText, tr("Cancel"), true, parent).exec();
}
bool is_metered{};
bool is_wifi{};
bool is_onroad = false;
ButtonControlSP *currentModelLblBtn;
Params params;
};
@@ -7,18 +7,8 @@
#include "selfdrive/ui/sunnypilot/qt/offroad/settings/software_panel.h"
#include <algorithm>
#include <QJsonDocument>
#include "common/model.h"
/**
* @brief Constructs the software panel with model bundle selection functionality
* @param parent Parent widget
*/
SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
// branch selector
// branch selector
QObject::disconnect(targetBranchBtn, nullptr, nullptr, nullptr);
connect(targetBranchBtn, &ButtonControlSP::clicked, [=]() {
InputDialog d(tr("Search Branch"), this, tr("Enter search keywords, or leave blank to list all branches."), false);
@@ -27,196 +17,7 @@ SoftwarePanelSP::SoftwarePanelSP(QWidget *parent) : SoftwarePanel(parent) {
if (ret) {
searchBranches(d.text());
}
});
const auto current_model = GetActiveModelName();
currentModelLblBtn = new ButtonControlSP(tr("Current Model"), tr("SELECT"), current_model);
currentModelLblBtn->setValue(current_model);
connect(currentModelLblBtn, &ButtonControlSP::clicked, this, &SoftwarePanelSP::handleCurrentModelLblBtnClicked);
QObject::connect(uiStateSP(), &UIStateSP::uiUpdate, this, &SoftwarePanelSP::updateLabels);
AddWidgetAt(0, currentModelLblBtn);
}
/**
* @brief Updates the UI with bundle download progress information
* Reads status from modelManagerSP cereal message and displays status for all models
*/
void SoftwarePanelSP::handleBundleDownloadProgress() {
using DS = cereal::ModelManagerSP::DownloadStatus;
if (!model_manager.hasSelectedBundle() && !model_manager.hasActiveBundle()) {
currentModelLblBtn->setDescription(tr("No custom model selected!"));
return;
}
const bool showSelectedBundle = model_manager.hasSelectedBundle() && (isDownloading() || model_manager.getSelectedBundle().getStatus() == DS::FAILED);
const auto &bundle = showSelectedBundle ? model_manager.getSelectedBundle() : model_manager.getActiveBundle();
const auto &models = bundle.getModels();
download_status = bundle.getStatus();
const auto download_status_changed = prev_download_status != download_status;
QStringList status;
// Get status for each model type in order
for (const auto &model: models) {
QString typeName;
QString modelName = QString::fromStdString(bundle.getDisplayName());
switch (model.getType()) {
case cereal::ModelManagerSP::Model::Type::SUPERCOMBO:
typeName = tr("Driving");
break;
case cereal::ModelManagerSP::Model::Type::NAVIGATION:
typeName = tr("Navigation");
break;
case cereal::ModelManagerSP::Model::Type::VISION:
typeName = tr("Vision");
break;
case cereal::ModelManagerSP::Model::Type::POLICY:
typeName = tr("Policy");
break;
}
const auto &progress = model.getArtifact().getDownloadProgress();
QString line;
if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING) {
line = tr("Downloading %1 model [%2]... (%3%)").arg(typeName, modelName).arg(progress.getProgress(), 0, 'f', 2);
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADED) {
line = tr("%1 model [%2] %3").arg(typeName, modelName, download_status_changed ? tr("downloaded") : tr("ready"));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::CACHED) {
line = tr("%1 model [%2] %3").arg(typeName, modelName, download_status_changed ? tr("from cache") : tr("ready"));
} else if (progress.getStatus() == cereal::ModelManagerSP::DownloadStatus::FAILED) {
line = tr("%1 model [%2] download failed").arg(typeName, modelName);
} else {
line = tr("%1 model [%2] pending...").arg(typeName, modelName);
}
status.append(line);
}
currentModelLblBtn->setDescription(status.join("\n"));
if (prev_download_status != download_status) {
switch (bundle.getStatus()) {
case cereal::ModelManagerSP::DownloadStatus::DOWNLOADING:
case cereal::ModelManagerSP::DownloadStatus::CACHED:
case cereal::ModelManagerSP::DownloadStatus::DOWNLOADED:
currentModelLblBtn->showDescription();
break;
case cereal::ModelManagerSP::DownloadStatus::FAILED:
default:
break;
}
}
prev_download_status = download_status;
}
/**
* @brief Gets the name of the currently selected model bundle
* @return Display name of the selected bundle or default model name
*/
QString SoftwarePanelSP::GetActiveModelName() {
if (model_manager.hasActiveBundle()) {
return QString::fromStdString(model_manager.getActiveBundle().getDisplayName());
}
return DEFAULT_MODEL;
}
void SoftwarePanelSP::updateModelManagerState() {
const SubMaster &sm = *(uiStateSP()->sm);
model_manager = sm["modelManagerSP"].getModelManagerSP();
}
/**
* @brief Handles the model bundle selection button click
* Displays available bundles, allows selection, and initiates download
*/
void SoftwarePanelSP::handleCurrentModelLblBtnClicked() {
currentModelLblBtn->setEnabled(false);
currentModelLblBtn->setValue(tr("Fetching models..."));
// Create mapping of bundle indices to display names
QMap<uint32_t, QString> index_to_bundle;
const auto bundles = model_manager.getAvailableBundles();
for (const auto &bundle: bundles) {
index_to_bundle.insert(bundle.getIndex(), QString::fromStdString(bundle.getDisplayName()));
}
// Sort bundles by index in descending order
QStringList bundleNames;
// Add "Default" as the first option
bundleNames.append(tr("Use Default"));
auto indices = index_to_bundle.keys();
std::sort(indices.begin(), indices.end(), std::greater<uint32_t>());
for (const auto &index: indices) {
bundleNames.append(index_to_bundle[index]);
}
currentModelLblBtn->setValue(GetActiveModelName());
const QString selectedBundleName = MultiOptionDialog::getSelection(
tr("Select a Model"), bundleNames, GetActiveModelName(), this);
if (selectedBundleName.isEmpty() || !canContinueOnMeteredDialog()) {
return;
}
// Handle "Stock" selection differently
if (selectedBundleName == tr("Use Default")) {
params.remove("ModelManager_ActiveBundle");
currentModelLblBtn->setValue(tr("Default"));
showResetParamsDialog();
} else {
// Find selected bundle and initiate download
for (const auto &bundle: bundles) {
if (QString::fromStdString(bundle.getDisplayName()) == selectedBundleName) {
params.put("ModelManager_DownloadIndex", std::to_string(bundle.getIndex()));
if (bundle.getGeneration() != model_manager.getActiveBundle().getGeneration()) {
showResetParamsDialog();
}
break;
}
}
}
updateLabels();
}
/**
* @brief Updates the UI elements based on current state
*/
void SoftwarePanelSP::updateLabels() {
if (!isVisible()) {
return;
}
updateModelManagerState();
handleBundleDownloadProgress();
currentModelLblBtn->setEnabled(!is_onroad && !isDownloading());
currentModelLblBtn->setValue(GetActiveModelName());
SoftwarePanel::updateLabels();
}
/**
* @brief Shows dialog prompting user to reset calibration after model download
*/
void SoftwarePanelSP::showResetParamsDialog() {
const auto confirmMsg = QString("%1<br><br><b>%2</b><br><br><b>%3</b>")
.arg(tr("Model download has started in the background."))
.arg(tr("We STRONGLY suggest you to reset calibration."))
.arg(tr("Would you like to do that now?"));
const auto button_text = tr("Reset Calibration");
QString content("<body><h2 style=\"text-align: center;\">" + tr("Driving Model Selector") + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + confirmMsg + "</p></body>");
if (showConfirmationDialog(content, button_text, false)) {
params.remove("CalibrationParams");
params.remove("LiveTorqueParameters");
}
}
/**
@@ -7,7 +7,6 @@
#pragma once
#include <QJsonObject>
#include "selfdrive/ui/sunnypilot/ui.h"
#include "selfdrive/ui/sunnypilot/qt/util.h"
#include "selfdrive/ui/qt/offroad/settings.h"
@@ -20,47 +19,4 @@ public:
private:
void searchBranches(const QString &query);
private:
QString GetActiveModelName();
void updateModelManagerState();
bool isDownloading() const {
if (!model_manager.hasSelectedBundle()) {
return false;
}
const auto &selected_bundle = model_manager.getSelectedBundle();
return selected_bundle.getStatus() == cereal::ModelManagerSP::DownloadStatus::DOWNLOADING;
}
// UI update related methods
void updateLabels() override;
void handleCurrentModelLblBtnClicked();
void handleBundleDownloadProgress();
void showResetParamsDialog();
cereal::ModelManagerSP::Reader model_manager;
cereal::ModelManagerSP::DownloadStatus download_status{};
cereal::ModelManagerSP::DownloadStatus prev_download_status{};
bool canContinueOnMeteredDialog() {
if (!is_metered) return true;
return showConfirmationDialog(QString(), QString(), is_metered);
}
inline bool showConfirmationDialog(const QString &message = QString(), const QString &confirmButtonText = QString(), const bool show_metered_warning = false) {
return showConfirmationDialog(this, message, confirmButtonText, show_metered_warning);
}
static inline bool showConfirmationDialog(QWidget *parent, const QString &message = QString(), const QString &confirmButtonText = QString(), const bool show_metered_warning = false) {
const QString warning_message = show_metered_warning ? tr("Warning: You are on a metered connection!") : QString();
const QString final_message = QString("%1%2").arg(!message.isEmpty() ? message + "\n" : QString(), warning_message);
const QString final_buttonText = !confirmButtonText.isEmpty() ? confirmButtonText : QString(tr("Continue") + " %1").arg(show_metered_warning ? tr("on Metered") : "");
return ConfirmationDialog(final_message, final_buttonText, tr("Cancel"), true, parent).exec();
}
bool is_metered{};
bool is_wifi{};
ButtonControlSP *currentModelLblBtn;
};