[education/kstars] kstars/ekos/guide: AI Guide: Fix wizard close/reopen navigation, export latest session logs only, Add button to read offline training instructions
Jasem Mutlaq <[email protected]>
| Newsgroups | gmane.comp.kde.cvs |
|---|---|
| Message-ID | <[email protected]> |
Git commit 1b36a20c2a139f95a58dd7f92dee1d4173832863 by Jasem Mutlaq, on behalf of Pavan Kumar S G.
Committed on 20/07/2026 at 06:00.
Pushed by mutlaqja into branch 'master'.
AI Guide: Fix wizard close/reopen navigation, export latest session logs only, Add button to read offline training instructions
M +75 -24 kstars/ekos/guide/aiguidewizard.cpp
M +1 -0 kstars/ekos/guide/aiguidewizard.h
M +19 -0 kstars/ekos/guide/aiguidewizard.ui
https://invent.kde.org/education/kstars/-/commit/1b36a20c2a139f95a58dd7f92dee1d4173832863
diff --git a/kstars/ekos/guide/aiguidewizard.cpp b/kstars/ekos/guide/aiguidewizard.cpp
index cb1601492c..dbf406567c 100644
--- a/kstars/ekos/guide/aiguidewizard.cpp
+++ b/kstars/ekos/guide/aiguidewizard.cpp
@@ -10,10 +10,13 @@
#include "kspaths.h"
#include <QDateTime>
#include <QDebug>
+#include <QDesktopServices>
#include <QDir>
#include <QFileDialog>
#include <QDirIterator>
#include <QFileInfo>
+#include <QFutureWatcher>
+#include <QtConcurrent>
#include <zlib.h>
namespace
@@ -28,7 +31,7 @@ class ZipWriter
return m_file.open(QIODevice::WriteOnly);
}
- int addTree(const QString &dir, const QString &prefix)
+ int addTree(const QString &dir, const QString &prefix, const QDateTime &since = QDateTime())
{
if (!QDir(dir).exists())
return 0;
@@ -38,6 +41,8 @@ class ZipWriter
while (it.hasNext())
{
const QString fp = it.next();
+ if (since.isValid() && QFileInfo(fp).lastModified() < since)
+ continue;
if (addOne(fp, prefix + "/" + QDir(baseDir).relativeFilePath(fp)))
++n;
}
@@ -54,7 +59,7 @@ class ZipWriter
const QByteArray name = archiveName.toUtf8();
const quint32 crc = static_cast<quint32>(crc32(0L, reinterpret_cast<const Bytef *>(data.constData()),
- static_cast<uInt>(data.size())));
+ static_cast<uInt>(data.size())));
const QByteArray comp = rawDeflate(data);
const QDateTime mtime = QFileInfo(path).lastModified();
@@ -234,6 +239,11 @@ AIGuideWizard::AIGuideWizard(AIGuideProtocol *protocol, QWidget *parent) : QWiza
m_Protocol->requestTraining();
});
+ connect(offlineDocsButton, &QPushButton::clicked, this, []()
+ {
+ QDesktopServices::openUrl(QUrl("https://kstars-docs.kde.org/en/user_manual/ekos-guide.html#training-the-model-offline"));
+ });
+
setButtonText(QWizard::CustomButton1, "Export Logs");
setOption(QWizard::HaveCustomButton1, true);
connect(this, &QWizard::customButtonClicked, this, [this](int which)
@@ -249,22 +259,33 @@ void AIGuideWizard::showEvent(QShowEvent *event)
{
QWizard::showEvent(event);
+ m_AutoNavigating = true;
+
// If protocol is already running (e.g. started via EkosLive), jump to page 2
if (m_Protocol->state() != AIGuideProtocol::STATE_IDLE &&
m_Protocol->state() != AIGuideProtocol::STATE_DONE &&
m_Protocol->state() != AIGuideProtocol::STATE_ERROR)
{
- while (currentId() != 2)
+ for (int i = 0; i < 4 && currentId() != 2; i++)
next();
+
+ stopButton->setText(i18n("Stop"));
+ stopButton->setEnabled(true);
+ disconnect(stopButton, &QPushButton::clicked, this, &AIGuideWizard::slotStartProtocol);
+ connect(stopButton, &QPushButton::clicked, this, &AIGuideWizard::slotStopProtocol, Qt::UniqueConnection);
}
// If protocol already completed, jump to training page (page 3)
else if (m_Protocol->state() == AIGuideProtocol::STATE_DONE)
{
- while (currentId() != 3)
+ for (int i = 0; i < 4 && currentId() != 3; i++)
next();
exportOfflineButton->setEnabled(true);
progressBar->setValue(100);
}
+ else if (m_Protocol->state() == AIGuideProtocol::STATE_IDLE && currentId() != 0)
+ restart();
+
+ m_AutoNavigating = false;
}
int AIGuideWizard::state() const
@@ -305,30 +326,58 @@ void AIGuideWizard::onTrainingResult(bool success, const QJsonObject &result)
void AIGuideWizard::slotExportLogs()
{
const QString base = KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
+
+ QDateTime since = QDateTime::currentDateTime().addDays(-1);
+ const auto sessions = QDir(base + "/ai_training_logs").entryList(QStringList("sysid_data_*.json"),
+ QDir::Files, QDir::Name);
+ if (!sessions.isEmpty())
+ {
+ const QString stamp = QFileInfo(sessions.last()).completeBaseName().mid(QString("sysid_data_").size());
+ const QDateTime newest = QDateTime::fromString(stamp, "yyyyMMdd_HHmmss");
+ if (newest.isValid())
+ since = newest;
+ }
const QString suggested = QDir::homePath() + "/ekos_ai_logs_" +
QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss") + ".zip";
const QString out = QFileDialog::getSaveFileName(this, "Export Logs", suggested, "Zip Archives (*.zip)");
if (out.isEmpty()) return;
- ZipWriter zip;
- if (!zip.open(out))
- {
- appendLog("Failed to create archive: " + out);
- return;
- }
+ if (auto *exportButton = button(QWizard::CustomButton1))
+ exportButton->setEnabled(false);
+ appendLog(QString("Exporting logs to %1...").arg(out));
- int n = 0;
- n += zip.addTree(base + "/ai_debug_logs", "ai_debug_logs");
- n += zip.addTree(base + "/ai_training_logs", "ai_training_logs");
- n += zip.addTree(base + "/guidelogs", "guidelogs");
- n += zip.addTree(base + "/logs", "logs");
- n += zip.addOne(base + "/guide_log.txt", "guide_log.txt");
- zip.finish();
+ auto *watcher = new QFutureWatcher<int>(this);
+ connect(watcher, &QFutureWatcher<int>::finished, this, [this, watcher, out]()
+ {
+ watcher->deleteLater();
+ const int n = watcher->result();
+ if (auto *exportButton = button(QWizard::CustomButton1))
+ exportButton->setEnabled(true);
+ if (n < 0)
+ appendLog("Failed to create archive: " + out);
+ else if (n > 0)
+ appendLog(QString("Exported %1 log file(s) to: %2").arg(n).arg(out));
+ else
+ appendLog("No logs found for the latest session. Enable 'Log to File' in KStars settings to also capture terminal logs.");
+ });
- if (n > 0)
- appendLog(QString("Exported %1 log file(s) to: %2").arg(n).arg(out));
- else
- appendLog("No logs found. Enable 'Log to File' in KStars settings to also capture terminal logs.");
+ watcher->setFuture(QtConcurrent::run([base, out, since]()
+ {
+ int n = -1;
+ ZipWriter zip;
+ if (zip.open(out))
+ {
+ n = 0;
+ n += zip.addTree(base + "/ai_debug_logs", "ai_debug_logs", since);
+ n += zip.addTree(base + "/ai_training_logs", "ai_training_logs", since);
+ n += zip.addTree(base + "/guidelogs", "guidelogs", since);
+ n += zip.addTree(base + "/logs", "logs", since);
+ if (!since.isValid() || QFileInfo(base + "/guide_log.txt").lastModified() >= since)
+ n += zip.addOne(base + "/guide_log.txt", "guide_log.txt");
+ zip.finish();
+ }
+ return n;
+ }));
}
void AIGuideWizard::initializePage(int id)
@@ -336,7 +385,7 @@ void AIGuideWizard::initializePage(int id)
QWizard::initializePage(id);
// Page 2 (0-indexed) is the "System Identification Progress" page
- if (id == 2)
+ if (id == 2 && !m_AutoNavigating)
{
progressBar->setValue(0);
logTextEdit->clear();
@@ -377,7 +426,7 @@ void AIGuideWizard::slotExportOffline()
}
QString savePath = QFileDialog::getSaveFileName(this, "Export Offline Training Data",
- QDir::homePath() + "/sysid_data.json", "JSON Files (*.json)");
+ QDir::homePath() + "/sysid_data.json", "JSON Files (*.json)");
if (!savePath.isEmpty())
{
if (QFile::copy(filename, savePath))
@@ -395,7 +444,9 @@ void AIGuideWizard::slotStopProtocol()
void AIGuideWizard::done(int result)
{
- m_Protocol->stop();
+ const auto state = m_Protocol->state();
+ if (state > AIGuideProtocol::STATE_IDLE && state < AIGuideProtocol::STATE_DONE)
+ m_Protocol->stop();
QWizard::done(result);
}
diff --git a/kstars/ekos/guide/aiguidewizard.h b/kstars/ekos/guide/aiguidewizard.h
index 4a716dc5b6..7556b44986 100644
--- a/kstars/ekos/guide/aiguidewizard.h
+++ b/kstars/ekos/guide/aiguidewizard.h
@@ -64,6 +64,7 @@ class AIGuideWizard : public QWizard, public Ui::AIGuideWizard
void appendLog(const QString &message);
AIGuideProtocol *m_Protocol { nullptr };
+ bool m_AutoNavigating { false };
};
}
\ No newline at end of file
diff --git a/kstars/ekos/guide/aiguidewizard.ui b/kstars/ekos/guide/aiguidewizard.ui
index 183647c5cc..51cee1bea3 100644
--- a/kstars/ekos/guide/aiguidewizard.ui
+++ b/kstars/ekos/guide/aiguidewizard.ui
@@ -298,6 +298,25 @@ p, li { white-space: pre-wrap; }
</property>
</widget>
</item>
+ <item>
+ <widget class="QPushButton" name="offlineDocsButton">
+ <property name="text">
+ <string>Offline Training Guide</string>
+ </property>
+ <property name="toolTip">
+ <string>Open the KStars documentation on training the model offline.</string>
+ </property>
+ <property name="icon">
+ <iconset theme="help-contents"/>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>40</height>
+ </size>
+ </property>
+ </widget>
+ </item>
<item>
<spacer name="verticalSpacer_bottom">
<property name="orientation">