From: Shane Jaroch Date: Mon, 26 Jan 2026 16:51:11 +0000 (-0500) Subject: wip X-Git-Url: https://git.nutra.tk/v1?a=commitdiff_plain;h=refs%2Fpull%2F2%2Fhead;p=nutratech%2Fgui.git wip --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 631e350..6c4b33f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) # Find Qt6 or Qt5 -find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Sql) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Sql) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Sql Network) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Sql Network) # Sources @@ -43,7 +43,7 @@ add_compile_definitions(NUTRA_VERSION_STRING="${NUTRA_VERSION}") # Main Executable add_executable(nutra src/main.cpp ${CORE_SOURCES} ${UI_SOURCES} "resources.qrc") target_include_directories(nutra PUBLIC ${CMAKE_SOURCE_DIR}/include) -target_link_libraries(nutra PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql) +target_link_libraries(nutra PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Network) # Testing enable_testing() @@ -79,6 +79,15 @@ if(NUTRA_DB_FILE AND EXISTS "${NUTRA_DB_FILE}") install(FILES "${NUTRA_DB_FILE}" DESTINATION share/nutra RENAME usda.sqlite3) endif() +# Install Python NLP service (optional feature) +if(EXISTS "${CMAKE_SOURCE_DIR}/lib/pylang_serv/pylang_serv") + install(DIRECTORY lib/pylang_serv/pylang_serv + DESTINATION share/nutra/pylang_serv + FILES_MATCHING PATTERN "*.py") + install(FILES lib/pylang_serv/pyproject.toml + DESTINATION share/nutra/pylang_serv) +endif() + # AppImage generation (requires linuxdeploy and linuxdeploy-plugin-qt in PATH) find_program(LINUXDEPLOY linuxdeploy) diff --git a/include/utils/pythonservicemanager.h b/include/utils/pythonservicemanager.h new file mode 100644 index 0000000..f08879a --- /dev/null +++ b/include/utils/pythonservicemanager.h @@ -0,0 +1,62 @@ +#ifndef PYTHONSERVICEMANAGER_H +#define PYTHONSERVICEMANAGER_H + +#include +#include +#include +#include +#include + +/** + * @brief Manages the optional Python NLP microservice. + * + * This is an optional feature that can be enabled/disabled in settings. + * When enabled, spawns a Python Flask server for natural language + * ingredient parsing. + */ +class PythonServiceManager : public QObject { + Q_OBJECT + +public: + static PythonServiceManager& instance(); + + bool isEnabled() const; + void setEnabled(bool enabled); + + bool isRunning() const; + + /** + * @brief Parse an ingredient string using NLP. + * @param text Natural language ingredient (e.g., "2 cups flour") + * + * Emits parseComplete or parseError when done. + */ + void parseIngredient(const QString& text); + +signals: + void serviceStarted(); + void serviceStopped(); + void parseComplete(const QJsonObject& result); + void parseError(const QString& error); + +public slots: + void startService(); + void stopService(); + +private: + explicit PythonServiceManager(QObject* parent = nullptr); + ~PythonServiceManager() override; + + void findPythonPath(); + + QProcess* m_process = nullptr; + QNetworkAccessManager* m_network = nullptr; + QString m_pythonPath; + bool m_enabled = false; + int m_port = 5001; + + static constexpr const char* SETTING_ENABLED = "nlp/enabled"; + static constexpr const char* SETTING_PORT = "nlp/port"; +}; + +#endif // PYTHONSERVICEMANAGER_H diff --git a/lib/pylang_serv b/lib/pylang_serv index dadc883..68db5e4 160000 --- a/lib/pylang_serv +++ b/lib/pylang_serv @@ -1 +1 @@ -Subproject commit dadc883bd65f4a316ae67cef6934c80331385af9 +Subproject commit 68db5e41cd4d8e84738b6fca4ae1a92b8cc7d09b diff --git a/src/utils/pythonservicemanager.cpp b/src/utils/pythonservicemanager.cpp new file mode 100644 index 0000000..3c718b1 --- /dev/null +++ b/src/utils/pythonservicemanager.cpp @@ -0,0 +1,191 @@ +#include "utils/pythonservicemanager.h" + +#include +#include +#include +#include +#include +#include + +PythonServiceManager& PythonServiceManager::instance() { + static PythonServiceManager instance; + return instance; +} + +PythonServiceManager::PythonServiceManager(QObject* parent) : QObject(parent) { + m_network = new QNetworkAccessManager(this); + + // Load settings + QSettings settings; + m_enabled = settings.value(SETTING_ENABLED, false).toBool(); + m_port = settings.value(SETTING_PORT, 5001).toInt(); + + findPythonPath(); + + // Auto-start if enabled + if (m_enabled) { + startService(); + } +} + +PythonServiceManager::~PythonServiceManager() { + stopService(); +} + +bool PythonServiceManager::isEnabled() const { + return m_enabled; +} + +void PythonServiceManager::setEnabled(bool enabled) { + if (m_enabled == enabled) return; + + m_enabled = enabled; + QSettings settings; + settings.setValue(SETTING_ENABLED, enabled); + + if (enabled) { + startService(); + } else { + stopService(); + } +} + +bool PythonServiceManager::isRunning() const { + return m_process && m_process->state() == QProcess::Running; +} + +void PythonServiceManager::findPythonPath() { + // Try common Python paths + QStringList candidates = { + "python3", + "python", +#ifdef Q_OS_WIN + "py", +#endif + }; + + for (const QString& candidate : candidates) { + QString path = QStandardPaths::findExecutable(candidate); + if (!path.isEmpty()) { + m_pythonPath = path; + qDebug() << "Found Python at:" << m_pythonPath; + return; + } + } + + qWarning() << "Python not found in PATH"; +} + +void PythonServiceManager::startService() { + if (isRunning()) { + qDebug() << "Python service already running"; + return; + } + + if (m_pythonPath.isEmpty()) { + emit parseError("Python not found. Install Python 3 to use NLP features."); + return; + } + + if (m_process) { + m_process->deleteLater(); + } + + m_process = new QProcess(this); + + // Find the pylang_serv module + QString modulePath; + + // Check relative to app (development) + QString devPath = QCoreApplication::applicationDirPath() + "/../lib/pylang_serv"; + if (QDir(devPath).exists()) { + modulePath = devPath; + } + + // Check installed location + QString installPath = "/usr/share/nutra/pylang_serv"; + if (modulePath.isEmpty() && QDir(installPath).exists()) { + modulePath = installPath; + } + + // Check user local + QString userPath = QDir::homePath() + "/.local/share/nutra/pylang_serv"; + if (modulePath.isEmpty() && QDir(userPath).exists()) { + modulePath = userPath; + } + + if (modulePath.isEmpty()) { + emit parseError("Python NLP module not found. Ensure pylang_serv is installed."); + return; + } + + connect(m_process, &QProcess::started, this, [this]() { + qDebug() << "Python NLP service started on port" << m_port; + emit serviceStarted(); + }); + + connect(m_process, QOverload::of(&QProcess::finished), this, + [this](int exitCode, QProcess::ExitStatus status) { + qDebug() << "Python service stopped. Exit code:" << exitCode; + emit serviceStopped(); + }); + + connect(m_process, &QProcess::errorOccurred, this, [this](QProcess::ProcessError error) { + qWarning() << "Python service error:" << error; + emit parseError("Failed to start Python service"); + }); + + // Start the server + m_process->setWorkingDirectory(modulePath); + m_process->start(m_pythonPath, {"-m", "pylang_serv.server"}); +} + +void PythonServiceManager::stopService() { + if (!m_process) return; + + if (m_process->state() == QProcess::Running) { + m_process->terminate(); + if (!m_process->waitForFinished(3000)) { + m_process->kill(); + } + } + + m_process->deleteLater(); + m_process = nullptr; +} + +void PythonServiceManager::parseIngredient(const QString& text) { + if (!isRunning()) { + emit parseError("NLP service not running. Enable it in Settings."); + return; + } + + QUrl url(QString("http://127.0.0.1:%1/parse").arg(m_port)); + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + + QJsonObject json; + json["text"] = text; + QByteArray data = QJsonDocument(json).toJson(); + + QNetworkReply* reply = m_network->post(request, data); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + reply->deleteLater(); + + if (reply->error() != QNetworkReply::NoError) { + emit parseError(reply->errorString()); + return; + } + + QByteArray responseData = reply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(responseData); + + if (doc.isNull() || !doc.isObject()) { + emit parseError("Invalid response from NLP service"); + return; + } + + emit parseComplete(doc.object()); + }); +}