wxWidgets based Stable Diffusion C++ GUi
Ferenc Szontágh
2024-02-03 2088e1b7aa6419dec58800bc1d0cb24f5808affe
added queue handler against simple start
9 files modified
1 files added
1 files deleted
612 ■■■■■ changed files
.gitignore 2 ●●● patch | view | raw | blame | history
.vscode/extensions.json 4 ●●●● patch | view | raw | blame | history
.vscode/launch.json 28 ●●●●● patch | view | raw | blame | history
.vscode/settings.json 11 ●●●●● patch | view | raw | blame | history
.vscode/tasks.json 28 ●●●●● patch | view | raw | blame | history
CMakeLists.txt 14 ●●●●● patch | view | raw | blame | history
README.md 23 ●●●●● patch | view | raw | blame | history
ui/MainWindowUI.cpp 331 ●●●●● patch | view | raw | blame | history
ui/MainWindowUI.h 55 ●●●● patch | view | raw | blame | history
ui/QueueManager.cpp 93 ●●●● patch | view | raw | blame | history
ui/QueueManager.h 23 ●●●● patch | view | raw | blame | history
.gitignore
@@ -20,4 +20,4 @@
*.out
*.app
vcpkg_installed
build/**
build/
.vscode/extensions.json
@@ -1,7 +1,7 @@
{
    "recommendations": [
        "wayou.vscode-todo-highlight",
        "ms-vscode.cpptools",
        "jeff-hykin.better-cpp-syntax"
        "jeff-hykin.better-cpp-syntax",
        "ms-vscode.cpptools"
    ]
}
.vscode/launch.json
File was deleted
.vscode/settings.json
@@ -100,5 +100,16 @@
        "regex": "cpp",
        "typeindex": "cpp",
        "valarray": "cpp"
    },
    "debug.terminal.clearBeforeReusing": true,
    "C_Cpp.loggingLevel": "Information",
    "remote.WSL.debug": true,
    "cmake.configureOnOpen": false,
    "cmake.debugConfig": {
        "stopAtEntry": false,
        "console": "newExternalWindow",
        "logging": {
            "trace": true
        }
    }
}
.vscode/tasks.json
New file
@@ -0,0 +1,28 @@
{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: cl.exe build active file",
            "command": "cl.exe",
            "args": [
                "/Zi",
                "/EHsc",
                "/nologo",
                "/Fe${fileDirname}\\${fileBasenameNoExtension}.exe",
                "${file}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$msCompile"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}
CMakeLists.txt
@@ -39,7 +39,7 @@
    
    if(NOT CMAKE_BUILD_TYPE)
        set(CMAKE_BUILD_TYPE Debug)
        set(CMAKE_BUILD_TYPE Release)
    endif()
    set(STABLE_DIFFUSION_LIB ${CMAKE_SOURCE_DIR}/external/stable-diffusion.cpp/${CMAKE_BUILD_TYPE}/lib/stable-diffusion.lib)
@@ -61,6 +61,18 @@
    message("CUDA_PATH=" ${CUDA_PATH})
    
    add_custom_command(TARGET sd.ui POST_BUILD        # Adds a post-build event to MyTest
    COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake - E copy_if_different..."
        "${STABLE_DIFFUSION_DLL}"      # <--this is in-file
        $<TARGET_FILE_DIR:sd.ui>)                 # <--this is out-file path0
    add_custom_command(TARGET sd.ui POST_BUILD        # Adds a post-build event to MyTest
    COMMAND ${CMAKE_COMMAND} -E copy_if_different  # which executes "cmake - E copy_if_different..."
        "${GGML_DLL}"      # <--this is in-file
        $<TARGET_FILE_DIR:sd.ui>)                 # <--this is out-file path0
ENDIF(MSVC)
set(OpenCV_DIR "${VCPKG_INSTALLED_DIR}/x64-windows/share/opencv4")
README.md
@@ -17,6 +17,29 @@
in the external forlder, you can find the precompuled stable-diffusion.cpp and ggml as submodulde to it. 
The default libs are configured with only CUDA, the release is Debug and Release. To use with another version (for example cpu only), you need to compile and overwrite 
#### Build stable-diffusion.cpp
```Bash
git clone --recursive https://github.com/leejet/stable-diffusion.cpp
cd stable-diffusion.cpp
git pull origin master
git submodule init
git submodule update
mkdir build
cd build
#with CUDA
cmake .. -DSD_CUBLAS=ON -DBUILD_SHARED_LIBS=ON
#build
cmake --build . --config Release
#or Debug
cmake --build . --config Debug
#test
./bin/sd -m ../models/sd-v1-4.ckpt -p "a lovely cat"
```
## - TO-DOs
See @git.spamming.hu ticketing system
ui/MainWindowUI.cpp
@@ -6,6 +6,7 @@
    this->ini_path = wxStandardPaths::Get().GetUserConfigDir() + wxFileName::GetPathSeparator() + "sd.ui.config.ini";
    this->sd_params = new sd_gui_utils::SDParams;
    this->notification = new wxNotificationMessage();
    this->JobTableItems = new std::map<int, QM::QueueItem>();
    this->notification->SetParent(this);
@@ -15,7 +16,9 @@
    this->m_joblist->AppendTextColumn("Id");
    this->m_joblist->AppendTextColumn("Created");
    this->m_joblist->AppendTextColumn("Update");
    this->m_joblist->AppendTextColumn("Model");
    this->m_joblist->AppendTextColumn("Sampler");
    this->m_joblist->AppendTextColumn("Seed");
    this->m_joblist->AppendTextColumn("Status");
    this->SetTitle(this->GetTitle() + SD_GUI_VERSION);
@@ -97,12 +100,6 @@
        // add the selected vae
        this->sd_params->vae_path = this->VaeFiles.at(selection.ToStdString());
    }
    // just select the vae file and add the live paramaeters, do not load the model, just add job to the queu on press the queue button
    /*  if (this->m_generate->IsEnabled() == true)
    {
    wxPostEvent(this->m_model, event);
    }
    */
}
void MainWindowUI::onSamplerSelect(wxCommandEvent &event)
@@ -146,10 +143,28 @@
void MainWindowUI::onGenerate(wxCommandEvent &event)
{
    // this->StartGeneration();
    //  dont start the generation, just add the job to the queue, to handle it the queue manager
    //  so it will possible to add more job afterall
    this->qmanager->AddItem(this->sd_params);
    // prepare params
    this->sd_params->model_path = this->ModelFiles.at(this->m_model->GetStringSelection().ToStdString());
    this->sd_params->lora_model_dir = this->cfg->lora;
    this->sd_params->embeddings_path = this->cfg->embedding;
    this->sd_params->prompt = this->m_prompt->GetValue().ToStdString();
    this->sd_params->negative_prompt = this->m_neg_prompt->GetValue().ToStdString();
    this->sd_params->cfg_scale = static_cast<float>(this->m_cfg->GetValue());
    this->sd_params->seed = this->m_seed->GetValue();
    this->sd_params->clip_skip = this->m_clip_skip->GetValue();
    this->sd_params->sample_steps = this->m_steps->GetValue();
    this->sd_params->sample_method = (sample_method_t)this->m_sampler->GetCurrentSelection();
    this->sd_params->batch_count = this->m_batch_count->GetValue();
    this->sd_params->width = this->m_width->GetValue();
    this->sd_params->height = this->m_height->GetValue();
    // add the queue item
    auto id = this->qmanager->AddItem(this->sd_params);
}
void MainWindowUI::onSavePreset(wxCommandEvent &event)
@@ -243,29 +258,30 @@
        this->LoadPresets();
    }
}
/*
    this->m_joblist->AppendTextColumn("Id");
    this->m_joblist->AppendTextColumn("Created");
    this->m_joblist->AppendTextColumn("Model");
    this->m_joblist->AppendTextColumn("Sampler");
    this->m_joblist->AppendTextColumn("Seed");
    this->m_joblist->AppendTextColumn("Status");
*/
void MainWindowUI::OnQueueItemManagerItemAdded(QM::QueueItem item)
{
    wxVector<wxVariant> data;
    data.push_back(wxVariant(item.id));
    data.push_back(wxVariant(item.created_at));
    data.push_back(wxVariant(item.updated_at));
    data.push_back(wxVariant(std::to_string(item.id)));
    data.push_back(wxVariant(std::to_string(item.created_at)));
    data.push_back(wxVariant(item.params.model_path));
    data.push_back(wxVariant(sd_gui_utils::sample_method_str[(int)item.params.sample_method]));
    data.push_back(wxVariant(std::to_string(item.params.seed)));
    data.push_back(wxVariant(QM::QueueStatus_str[item.status]));
    this->m_joblist->AppendItem(data);
    /*
    for (auto model : this->ModelFiles)
    {
        // auto size = sd_gui_utils::HumanReadable{std::filesystem::file_size(model.second)};
        auto size = std::filesystem::file_size(model.second);
        wxVector<wxVariant> data;
        data.push_back(wxVariant(model.first));
        data.push_back(wxVariant(std::to_string(size)));
        this->m_data_model_list->AppendItem(data);
    }
    this->m_data_model_list->Refresh();*/
    auto store = this->m_joblist->GetStore();
    (*this->JobTableItems)[store->GetCount() - 1] = item;
}
void MainWindowUI::LoadFileList(sd_gui_utils::DirTypes type)
@@ -396,22 +412,22 @@
void MainWindowUI::OnThreadMessage(wxThreadEvent &e)
{
    e.Skip();
    auto msg = e.GetString().ToStdString();
    std::string token = msg.substr(0, msg.find(":"));
    std::string content = msg.substr(msg.find(":") + 1);
    //    this->logs->AppendText(fmt::format("Got thread message: {}\n", e.GetString().ToStdString()));
    // this->logs->AppendText(fmt::format("Got thread message: {}\n", e.GetString().ToStdString()));
    if (token == "QUEUE")
    {
        m_statusBar166->SetStatusText("got QUEUE cmd: " + msg);
        // only numbers here...
        QM::QueueEvents event = (QM::QueueEvents)std::stoi(content);
        // only handle the QUEUE messages, what this class generate
        // alway QM::EueueItem the payload, with the new data
        auto payload = e.GetPayload<QM::QueueItem>();
        QM::QueueItem payload;
        payload = e.GetPayload<QM::QueueItem>();
        switch (event)
        {
            // new item added
@@ -420,11 +436,14 @@
            break;
            // item status changed
        case QM::QueueEvents::ITEM_STATUS_CHANGED:
            this->OnQueueItemManagerItemAdded(payload);
            this->OnQueueItemManagerItemStatusChanged(payload);
            break;
            // item updated... ? ? ?
        case QM::QueueEvents::ITEM_UPDATED:
            this->OnQueueItemManagerItemAdded(payload);
            this->OnQueueItemManagerItemUpdated(payload);
            break;
        case QM::QueueEvents::ITEM_START:
            this->StartGeneration(payload);
            break;
        default:
@@ -433,12 +452,13 @@
    }
    if (token == "MODEL_LOAD_DONE")
    {
        this->m_generate->Enable();
        this->m_model->Enable();
        this->m_vae->Enable();
        this->m_refresh->Enable();
        // this->m_generate->Enable();
        // this->m_model->Enable();
        // this->m_vae->Enable();
        // this->m_refresh->Enable();
        this->logs->AppendText(fmt::format("Model loaded: {}\n", content));
        // this->logs->AppendText(fmt::format("Model loaded: {}\n", content));
        this->modelLoaded = true;
        this->sd_ctx = e.GetPayload<sd_ctx_t *>();
        if (!this->IsShownOnScreen())
        {
@@ -450,19 +470,20 @@
    }
    if (token == "MODEL_LOAD_START")
    {
        this->m_generate->Disable();
        this->m_model->Disable();
        this->m_vae->Disable();
        this->m_refresh->Disable();
        // this->m_generate->Disable();
        // this->m_model->Disable();
        // this->m_vae->Disable();
        // this->m_refresh->Disable();
        this->logs->AppendText(fmt::format("Model load start: {}\n", content));
    }
    if (token == "MODEL_LOAD_ERROR")
    {
        this->m_generate->Disable();
        this->m_model->Enable();
        this->m_vae->Disable();
        this->m_refresh->Enable();
        // this->m_generate->Disable();
        // this->m_model->Enable();
        // this->m_vae->Disable();
        // this->m_refresh->Enable();
        this->logs->AppendText(fmt::format("Model load error: {}\n", content));
        this->modelLoaded = false;
        if (!this->IsShownOnScreen())
        {
            this->notification->SetFlags(wxICON_ERROR);
@@ -474,62 +495,62 @@
    if (token == "GENERATION_START")
    {
        sd_gui_utils::SDParams *params = e.GetPayload<sd_gui_utils::SDParams *>();
        auto myjob = e.GetPayload<QM::QueueItem>();
        this->m_generate->Disable();
        this->m_model->Disable();
        this->m_vae->Disable();
        this->m_refresh->Disable();
        this->logs->AppendText(fmt::format("Difusion started. Seed: {} Batch: {} {}x{}px Cfg: {} Steps: {}\n",
                                           params->seed,
                                           params->batch_count,
                                           params->width,
                                           params->height,
                                           params->cfg_scale,
                                           params->sample_steps));
        // this->m_generate->Disable();
        // this->m_model->Disable();
        // this->m_vae->Disable();
        // this->m_refresh->Disable();
        this->logs->AppendText(fmt::format("Diffusion started. Seed: {} Batch: {} {}x{}px Cfg: {} Steps: {}\n",
                                           myjob.params.seed,
                                           myjob.params.batch_count,
                                           myjob.params.width,
                                           myjob.params.height,
                                           myjob.params.cfg_scale,
                                           myjob.params.sample_steps));
    }
    // never, not implemented in sd.cpp
    if (token == "GENERATION_PROGRESS")
    {
        this->m_generate->Disable();
        this->logs->AppendText(fmt::format("Generation progress: {}\n", content));
        // this->m_generate->Disable();
        // this->logs->AppendText(fmt::format("Generation progress: {}\n", content));
    }
    if (token == "GENERATION_DONE")
    {
        this->m_generate->Enable();
        this->m_model->Enable();
        this->m_vae->Enable();
        this->m_refresh->Enable();
        sd_image_t *results = e.GetPayload<sd_image_t *>();
        // this->m_generate->Enable();
        // this->m_model->Enable();
        // this->m_vae->Enable();
        // this->m_refresh->Enable();
        // sd_image_t *results = e.GetPayload<sd_image_t *>();
        // show images in new window...
        for (int i = 0; i < this->sd_params->batch_count; i++)
        {
            MainWindowImageViewer *imgWindow = new MainWindowImageViewer(this);
            // wxBitmap *img = new wxBitmap(results[i].data, (int)results[i].width, (int)results[i].height, (int)results[i].channel);
            wxImage img(results[i].width, results[i].height, results[i].data);
        /* for (int i = 0; i < this->sd_params->batch_count; i++)
         {
             MainWindowImageViewer *imgWindow = new MainWindowImageViewer(this);
             // wxBitmap *img = new wxBitmap(results[i].data, (int)results[i].width, (int)results[i].height, (int)results[i].channel);
             wxImage img(results[i].width, results[i].height, results[i].data);
            wxBitmapBundle wxBmapB(img);
            imgWindow->m_bitmap->SetBitmap(wxBmapB);
            imgWindow->m_bitmap->SetSize(results[i].width, results[i].height);
            imgWindow->SetSize(results[i].width + 200, results[i].height);
             wxBitmapBundle wxBmapB(img);
             imgWindow->m_bitmap->SetBitmap(wxBmapB);
             imgWindow->m_bitmap->SetSize(results[i].width, results[i].height);
             imgWindow->SetSize(results[i].width + 200, results[i].height);
            std::string details = fmt::format("Prompt:\n\n{}\n\nNegative prompt: \n\n{}\n\nSeed: {} \nCfg scale: {}\nClip skip: {}\nSampler: {}\nSteps: {}\nWidth: {} Height: {}",
                                              this->sd_params->prompt, this->sd_params->negative_prompt,
                                              this->sd_params->seed + i, this->sd_params->cfg_scale,
                                              this->sd_params->clip_skip, sd_gui_utils::sample_method_str[this->sd_params->sample_method], this->sd_params->sample_steps,
                                              results[i].width, results[i].height);
            imgWindow->m_textCtrl4->AppendText(wxString(details));
            imgWindow->Show();
             std::string details = fmt::format("Prompt:\n\n{}\n\nNegative prompt: \n\n{}\n\nSeed: {} \nCfg scale: {}\nClip skip: {}\nSampler: {}\nSteps: {}\nWidth: {} Height: {}",
                                               this->sd_params->prompt, this->sd_params->negative_prompt,
                                               this->sd_params->seed + i, this->sd_params->cfg_scale,
                                               this->sd_params->clip_skip, sd_gui_utils::sample_method_str[this->sd_params->sample_method], this->sd_params->sample_steps,
                                               results[i].width, results[i].height);
             imgWindow->m_textCtrl4->AppendText(wxString(details));
             imgWindow->Show();
            // imgWindow->m_bitmap->SetBitmap(img);
            /// imgWindow->m_bitmap->Set
        }
             // imgWindow->m_bitmap->SetBitmap(img);
             /// imgWindow->m_bitmap->Set
         }*/
    }
    if (token == "GENERATION_ERROR")
    {
        this->m_generate->Enable();
        this->m_model->Enable();
        this->m_vae->Enable();
        // this->m_generate->Enable();
        // this->m_model->Enable();
        // this->m_vae->Enable();
        this->logs->AppendText(fmt::format("Generation error: {}\n", content));
        if (!this->IsShownOnScreen())
        {
@@ -560,10 +581,11 @@
    }
}
void MainWindowUI::LoadModel(wxEvtHandler *eventHandler)
void MainWindowUI::LoadModel(wxEvtHandler *eventHandler, QM::QueueItem myItem)
{
    wxThreadEvent *e = new wxThreadEvent();
    e->SetString(wxString::Format("MODEL_LOAD_START:%s", this->sd_params->model_path));
    e->SetPayload(myItem);
    wxQueueEvent(eventHandler, e);
    /*this->sd_ctx = new_sd_ctx(this->sd_params->model_path.c_str(),
@@ -580,22 +602,25 @@
                              this->sd_params->rng_type,
                              this->sd_params->schedule,
                              this->sd_params->control_net_cpu);*/
    this->sd_ctx = new_sd_ctx(this->sd_params->model_path.c_str(), this->sd_params->vae_path.c_str(), this->sd_params->taesd_path.c_str(), this->sd_params->lora_model_dir.c_str(), true, false, false, this->sd_params->n_threads, this->sd_params->wtype, this->sd_params->rng_type, this->sd_params->schedule);
    // this->sd_ctx = new_sd_ctx(this->sd_params->model_path.c_str(), this->sd_params->vae_path.c_str(), this->sd_params->taesd_path.c_str(), this->sd_params->lora_model_dir.c_str(), true, false, false, this->sd_params->n_threads, this->sd_params->wtype, this->sd_params->rng_type, this->sd_params->schedule);
    this->sd_ctx = new_sd_ctx(myItem.params.model_path.c_str(), myItem.params.vae_path.c_str(), myItem.params.taesd_path.c_str(), myItem.params.lora_model_dir.c_str(), true, false, false, myItem.params.n_threads, myItem.params.wtype, myItem.params.rng_type, myItem.params.schedule);
    if (this->sd_ctx == NULL)
    {
        wxThreadEvent *c = new wxThreadEvent();
        c->SetString(wxString::Format("MODEL_LOAD_ERROR:%s", this->sd_params->model_path));
        c->SetPayload(myItem);
        wxQueueEvent(eventHandler, c);
        this->modelLoaded = false;
        return;
    }
    else
    {
        wxThreadEvent *c = new wxThreadEvent();
        c->SetString(wxString::Format("MODEL_LOAD_DONE:%s", this->sd_params->model_path));
        // c->SetEventObject(this->sd_ctx);
        c->SetPayload(this->sd_ctx);
        wxQueueEvent(eventHandler, c);
        this->modelLoaded = true;
        return;
    }
@@ -639,7 +664,7 @@
    // populate data from sd_params as default...
    if (!this->m_generate->IsEnabled())
    if (!this->modelLoaded)
    {
        this->m_cfg->SetValue(static_cast<double>(this->sd_params->cfg_scale));
        this->m_seed->SetValue(static_cast<int>(this->sd_params->seed));
@@ -649,38 +674,11 @@
        this->m_height->SetValue(this->sd_params->height);
        this->m_batch_count->SetValue(this->sd_params->batch_count);
    }
    // hide unusable configs...
    /*
    if (SD_CPP_VERSION == "c6071fa") {
            // .. nope, configs in another window...
    }*/
}
void MainWindowUI::StartGeneration()
void MainWindowUI::StartGeneration(QM::QueueItem myJob)
{
    // prepare params
    this->sd_params->model_path = this->ModelFiles.at(this->m_model->GetStringSelection().ToStdString());
    this->sd_params->lora_model_dir = this->cfg->lora;
    this->sd_params->embeddings_path = this->cfg->embedding;
    this->sd_params->prompt = this->m_prompt->GetValue().ToStdString();
    this->sd_params->negative_prompt = this->m_neg_prompt->GetValue().ToStdString();
    this->sd_params->cfg_scale = static_cast<float>(this->m_cfg->GetValue());
    this->sd_params->seed = this->m_seed->GetValue();
    this->sd_params->clip_skip = this->m_clip_skip->GetValue();
    this->sd_params->sample_steps = this->m_steps->GetValue();
    /* sample method */
    this->sd_params->sample_method = (sample_method_t)this->m_sampler->GetCurrentSelection();
    /* sample method */
    this->sd_params->batch_count = this->m_batch_count->GetValue();
    this->sd_params->width = this->m_width->GetValue();
    this->sd_params->height = this->m_height->GetValue();
    this->threads.push_back(std::thread(std::bind(&MainWindowUI::Generate, this, this->GetEventHandler())));
    this->threads.push_back(std::thread(std::bind(&MainWindowUI::Generate, this, this->GetEventHandler(), myJob)));
}
void MainWindowUI::OnCloseSettings(wxCloseEvent &event)
@@ -717,31 +715,46 @@
    this->m_data_model_list->Refresh();
}
void MainWindowUI::Generate(wxEvtHandler *eventHandler)
void MainWindowUI::Generate(wxEvtHandler *eventHandler, QM::QueueItem myItem)
{
    // calculate time
    // @brief model loading is done in the same thread which generating stuffs... no need new thread
    // @brief to load the model if no model loaded, or the loaded model is different from the job's model...
    // @brief if all second job have different model, it will be slower than same model on all jobs
    // TODO: abort job if model can not be loaded...
    if (!this->modelLoaded)
    {
        this->LoadModel(eventHandler, myItem);
        this->currentModel = myItem.params.model_path;
    }
    else
    {
        if (myItem.params.model_path != this->currentModel)
        {
            free_sd_ctx(this->sd_ctx);
            this->LoadModel(eventHandler, myItem);
        }
    }
    if (!this->modelLoaded)
    {
        wxThreadEvent *f = new wxThreadEvent();
        f->SetString("GENERATION_ERROR:Model load failed...");
        f->SetPayload(myItem);
        wxQueueEvent(eventHandler, f);
        return;
    }
    auto start = std::chrono::system_clock::now();
    wxThreadEvent *e = new wxThreadEvent();
    e->SetString(wxString::Format("GENERATION_START:%s", this->sd_params->model_path));
    e->SetPayload(this->sd_params);
    e->SetPayload(myItem);
    wxQueueEvent(eventHandler, e);
    sd_image_t *control_image = NULL;
    sd_image_t *results;
    /*results = txt2img(sd_ctx,
                      this->sd_params->prompt.c_str(),
                      this->sd_params->negative_prompt.c_str(),
                      this->sd_params->clip_skip,
                      this->sd_params->cfg_scale,
                      this->sd_params->width,
                      this->sd_params->height,
                      this->sd_params->sample_method,
                      this->sd_params->sample_steps,
                      this->sd_params->seed,
                      this->sd_params->batch_count,
                      control_image,
                      this->sd_params->control_strength);*/
    results = txt2img(this->sd_ctx,
                      this->sd_params->prompt.c_str(), this->sd_params->negative_prompt.c_str(), this->sd_params->clip_skip, this->sd_params->cfg_scale, this->sd_params->width, this->sd_params->height, this->sd_params->sample_method, this->sd_params->sample_steps, this->sd_params->seed, this->sd_params->batch_count);
@@ -749,6 +762,7 @@
    {
        wxThreadEvent *f = new wxThreadEvent();
        f->SetString("GENERATION_ERROR:Something wrong happened at image generation...");
        f->SetPayload(myItem);
        wxQueueEvent(eventHandler, f);
        return;
    }
@@ -785,7 +799,12 @@
        {
            wxThreadEvent *g = new wxThreadEvent();
            g->SetString(wxString::Format("GENERATION_ERROR:Failed to save image into %s", filename));
            g->SetPayload(myItem);
            wxQueueEvent(eventHandler, g);
        }
        else
        {
            myItem.images.emplace_back(filename);
        }
        // handle data??
@@ -798,18 +817,29 @@
    wxThreadEvent *h = new wxThreadEvent();
    auto msg = fmt::format("MESSAGE:Image generation done in {}s. Saved into {}", elapsed_seconds.count(), this->cfg->output);
    h->SetString(wxString(msg.c_str()));
    h->SetPayload(myItem);
    wxQueueEvent(eventHandler, h);
    // send to reset the buttons
    wxThreadEvent *i = new wxThreadEvent();
    i->SetString(wxString::Format("GENERATION_DONE:ok"));
    i->SetPayload(results);
    wxQueueEvent(eventHandler, i);
    // send to the queue manager
    wxThreadEvent *j = new wxThreadEvent();
    j->SetString(wxString::Format("QUEUE:%d", QM::QueueEvents::ITEM_FINISHED));
    j->SetPayload(myItem);
    wxQueueEvent(eventHandler, j);
    return;
}
void MainWindowUI::HandleSDLog(sd_log_level_t level, const char *text, void *data)
{
    if (level != sd_log_level_t::SD_LOG_INFO)
    {
        return;
    }
    // wxEvtHandler *eventHandler = (wxEvtHandler *)data;
    MainWindowUI *ui = (MainWindowUI *)data;
    wxEvtHandler *eventHandler = ui->GetEventHandler();
@@ -821,12 +851,37 @@
void MainWindowUI::OnQueueItemManagerItemStatusChanged(QM::QueueItem item)
{
    /*
    this->m_joblist->AppendTextColumn("Id");
    this->m_joblist->AppendTextColumn("Created");
    this->m_joblist->AppendTextColumn("Model");
    this->m_joblist->AppendTextColumn("Sampler");
    this->m_joblist->AppendTextColumn("Seed");
    this->m_joblist->AppendTextColumn("Status");
    */
    // TODO: how to update an item in the table...
    // auto item = this->m_joblist->RowToItem();
    auto store = this->m_joblist->GetStore();
    for (auto it = this->JobTableItems->begin(); it != this->JobTableItems->end(); ++it)
    {
        if (it->second.id == item.id)
        {
            // auto store = *this->m_joblist()->GetStore();
            int lastCol = this->m_joblist->GetColumnCount();
            // always update the last col, so the last col is always need to be the status
            store->SetValueByRow(wxVariant(QM::QueueStatus_str[item.status]), it->first, lastCol - 1);
            this->m_joblist->Refresh();
            break;
        }
    }
}
MainWindowUI::~MainWindowUI()
{
    // clean up things...
    if (this->m_generate->IsEnabled())
    if (this->modelLoaded)
    {
        free_sd_ctx(this->sd_ctx);
    }
@@ -861,7 +916,7 @@
        free_sd_ctx(this->sd_ctx);
        return;
    }
    if (this->m_generate->IsEnabled())
    if (this->modelLoaded)
    {
        free_sd_ctx(this->sd_ctx);
    }
@@ -871,5 +926,5 @@
    this->m_refresh->Disable();
    this->sd_params->model_path = this->ModelFiles.at(selection.ToStdString());
    this->sd_params->lora_model_dir = this->fileConfig->Read("/paths/lora", "").ToStdString();
    this->threads.push_back(std::thread(std::bind(&MainWindowUI::LoadModel, this, this->GetEventHandler())));
    // this->threads.push_back(std::thread(std::bind(&MainWindowUI::LoadModel, this, this->GetEventHandler())));
}
ui/MainWindowUI.h
@@ -33,27 +33,28 @@
/** Implementing UI */
class MainWindowUI : public UI
{
    protected:
        // Handlers for UI events.
        void onSettings( wxCommandEvent& event );
        void onModelsRefresh( wxCommandEvent& event );
        void onModelSelect( wxCommandEvent& event );
        void onVaeSelect( wxCommandEvent& event );
        void onSamplerSelect( wxCommandEvent& event );
        void onResolutionSwap( wxCommandEvent& event );
        void onJobsStart( wxCommandEvent& event );
        void onJobsPause( wxCommandEvent& event );
        void onJobsDelete( wxCommandEvent& event );
        void onJoblistItemActivated( wxDataViewEvent& event );
        void onJoblistSelectionChanged( wxDataViewEvent& event );
        void onGenerate( wxCommandEvent& event );
        void onSavePreset( wxCommandEvent& event );
        void onLoadPreset( wxCommandEvent& event );
        void onSelectPreset( wxCommandEvent& event );
        void onDeletePreset( wxCommandEvent& event );
    public:
        /** Constructor */
        MainWindowUI( wxWindow* parent );
protected:
    // Handlers for UI events.
    void onSettings(wxCommandEvent &event);
    void onModelsRefresh(wxCommandEvent &event);
    void onModelSelect(wxCommandEvent &event);
    void onVaeSelect(wxCommandEvent &event);
    void onSamplerSelect(wxCommandEvent &event);
    void onResolutionSwap(wxCommandEvent &event);
    void onJobsStart(wxCommandEvent &event);
    void onJobsPause(wxCommandEvent &event);
    void onJobsDelete(wxCommandEvent &event);
    void onJoblistItemActivated(wxDataViewEvent &event);
    void onJoblistSelectionChanged(wxDataViewEvent &event);
    void onGenerate(wxCommandEvent &event);
    void onSavePreset(wxCommandEvent &event);
    void onLoadPreset(wxCommandEvent &event);
    void onSelectPreset(wxCommandEvent &event);
    void onDeletePreset(wxCommandEvent &event);
public:
    /** Constructor */
    MainWindowUI(wxWindow *parent);
    //// end generated class members
    ~MainWindowUI();
    void OnThreadMessage(wxThreadEvent &e);
@@ -71,9 +72,14 @@
    // the queue manager
    QM::QueueManager *qmanager;
    bool modelLoaded = false;
    std::string currentModel;
    sd_ctx_t *sd_ctx;
    std::streambuf *buffer;
    std::vector<std::thread> threads;
    // row,QueueItem
    std::map<int, QM::QueueItem> *JobTableItems;
    void initConfig();
    void loadModelList();
@@ -85,14 +91,14 @@
    static void HandleSDLog(sd_log_level_t level, const char *text, void *data);
    // load the model in a new thread
    void LoadModel(wxEvtHandler *eventHandler);
    void LoadModel(wxEvtHandler *eventHandler, QM::QueueItem myItem);
    // generate in another thread
    void Generate(wxEvtHandler *eventHandler);
    void Generate(wxEvtHandler *eventHandler, QM::QueueItem myItem);
    // start a thread for model loading...
    void StartLoadModel();
    // start a thread to generate image
    void StartGeneration();
    void StartGeneration(QM::QueueItem myJob);
    // handle queue managers events, manipulate data table by events
    void OnQueueItemManagerItemAdded(QM::QueueItem item);
@@ -100,7 +106,6 @@
    void OnQueueItemManagerItemStatusChanged(QM::QueueItem item);
    wxNotificationMessage *notification;
};
#endif // __MainWindowUI__
ui/QueueManager.cpp
@@ -6,6 +6,7 @@
    this->eventHandler = eventHandler;
    this->eventHandler->Bind(wxEVT_THREAD, &QueueManager::OnThreadMessage, this);
    this->jobsDir = jobsdir;
    this->QueueList = std::map<int, QM::QueueItem>();
}
int QM::QueueManager::AddItem(QM::QueueItem item)
@@ -16,6 +17,11 @@
    }
    this->QueueList[item.id] = item;
    this->SendEventToMainWindow(QM::QueueEvents::ITEM_ADDED, item);
    if (this->isRunning == false)
    {
        this->SendEventToMainWindow(QM::QueueEvents::ITEM_START, item);
        this->isRunning = true;
    }
    return item.id;
}
@@ -24,8 +30,7 @@
    QM::QueueItem item;
    item.params = *params;
    item.created_at = item.id = this->GetCurrentUnixTimestamp();
    this->AddItem(item);
    return item.id;
    return this->AddItem(item);
}
int QM::QueueManager::AddItem(sd_gui_utils::SDParams params)
@@ -33,8 +38,7 @@
    QM::QueueItem item;
    item.params = params;
    item.created_at = item.id = this->GetCurrentUnixTimestamp();
    this->AddItem(item);
    return item.id;
    return this->AddItem(item);
}
QM::QueueItem QM::QueueManager::GetItem(int id)
@@ -45,7 +49,7 @@
    }
    else
    {
        return this->QueueList.at(id);
        return this->QueueList[id];
    }
}
@@ -78,15 +82,16 @@
void QM::QueueManager::SetStatus(QM::QueueStatus status, int id)
{
    // todo, start - stop - pause
    auto item = this->GetItem(id);
    item.status = status;
    this->SendEventToMainWindow(QM::QueueEvents::ITEM_STATUS_CHANGED, item);
    if (this->QueueList.find(id) != this->QueueList.end())
    {
        this->QueueList[id].status = status;
        this->SendEventToMainWindow(QM::QueueEvents::ITEM_STATUS_CHANGED, this->QueueList[id]);
    }
}
void QM::QueueManager::PauseAll()
{
    for (auto &[key, value] : this->QueueList)
    for (auto [key, value] : this->QueueList)
    {
        if (value.status == QM::QueueStatus::PENDING)
        {
@@ -99,21 +104,83 @@
void QM::QueueManager::SendEventToMainWindow(QM::QueueEvents eventType, QM::QueueItem item)
{
    wxThreadEvent *e = new wxThreadEvent();
    // e->SetString(wxString::Format("MODEL_LOAD_START:%s", this->sd_params->model_path));
    e->SetString(wxString("QUEUE:" + (int)eventType));
    e->SetString(wxString::Format("QUEUE:%d", (int)eventType));
    e->SetPayload(item);
    wxQueueEvent(this->eventHandler, e);
}
void QM::QueueManager::OnThreadMessage(wxThreadEvent &e)
{
    e.Skip();
    auto msg = e.GetString().ToStdString();
    std::string token = msg.substr(0, msg.find(":"));
    std::string content = msg.substr(msg.find(":") + 1);
    // only numbers here...
    QM::QueueEvents event = (QM::QueueEvents)std::stoi(content);
    // only handle the QUEUE messages, what this class generate
    if (token == "QUEUE")
    {
        QM::QueueEvents event = (QM::QueueEvents)std::stoi(content);
        auto payload = e.GetPayload<QM::QueueItem>();
        if (event == QM::QueueEvents::ITEM_START)
        {
            this->SetStatus(QM::QueueStatus::RUNNING, payload.id);
            this->isRunning = true;
        }
        if (event == QM::QueueEvents::ITEM_FINISHED)
        {
            this->SetStatus(QM::QueueStatus::DONE, payload.id);
            this->isRunning = false;
            // jump to the next item in queue
            // find waiting jobs
            for (auto job : this->QueueList)
            {
                if (job.second.status == QM::QueueStatus::PENDING)
                {
                    if (this->isRunning == false)
                    {
                        this->SendEventToMainWindow(QM::QueueEvents::ITEM_START, job.second);
                        this->isRunning = true;
                    }
                    break;
                }
            }
            // move finished job into another list - ??
        }
    }
    if (token == "MODEL_LOAD_START")
    {
        auto payload = e.GetPayload<QM::QueueItem>();
        this->SetStatus(QM::QueueStatus::MODEL_LOADING, payload.id);
    }
    // this state can not usable at here, because the payload is the sd_ctx* pointer here..
    // we can't identify the current running job here... (we can maybe guess it, but not needed)
    // see  GENERATION_START
    if (token == "MODEL_LOAD_DONE")
    {
        // auto payload = e.GetPayload<QM::QueueItem>();
        // this->SetStatus(QM::QueueStatus::RUNNING, payload.id);
    }
    if (token == "MODEL_LOAD_ERROR" || token == "GENERATION_ERROR")
    {
        auto payload = e.GetPayload<QM::QueueItem>();
        this->SetStatus(QM::QueueStatus::FAILED, payload.id);
        this->isRunning = false;
    }
    if (token == "GENERATION_START")
    {
        auto payload = e.GetPayload<QM::QueueItem>();
        this->SetStatus(QM::QueueStatus::RUNNING, payload.id);
        this->isRunning = true;
    }
    // nothing to todo here, the payload is the generated image list
    if (token == "GENERATION_DONE")
    {
    }
}
int QM::QueueManager::GetCurrentUnixTimestamp()
ui/QueueManager.h
@@ -17,27 +17,34 @@
        PENDING,
        RUNNING,
        PAUSED,
        FAILED
        FAILED,
        MODEL_LOADING,
        DONE
    };
    inline const char *QueueStatus_str[] = {
        "pending",
        "running",
        "paused",
        "failed"};
        "failed",
        "model loading...",
        "finished"
        };
    enum QueueEvents
    {
        ITEM_DELETED,
        ITEM_ADDED,
        ITEM_STATUS_CHANGED,
        ITEM_UPDATED
        ITEM_UPDATED,
        ITEM_START,
        ITEM_FINISHED
    };
    struct QueueItem
    {
        QueueItem() = default;
        QueueItem(const QueueItem &other)
            : id(other.id), created_at(other.created_at), updated_at(other.updated_at),
              finished_at(other.finished_at), params(other.params), status(other.status) {}
              finished_at(other.finished_at), params(other.params), status(other.status), images(other.images) {}
        QueueItem &operator=(const QueueItem &other)
        {
@@ -47,6 +54,7 @@
                created_at = other.created_at;
                updated_at = other.updated_at;
                finished_at = other.finished_at;
                images = other.images;
                // Másolatot készítünk a params referenciáról
                // Ha a params referenciához tartozó SDParams objektum módosulna,
                // akkor ebben az esetben másolatban marad az eredeti QueueItem-ben
@@ -55,9 +63,10 @@
            }
            return *this;
        }
        int id, created_at, updated_at, finished_at;
        int id, created_at = 0, updated_at = 0, finished_at = 0;
        sd_gui_utils::SDParams params;
        QM::QueueStatus status = QM::QueueStatus::PENDING;
        std::vector<std::string> images;
    };
    inline void to_json(nlohmann::json &j, const QueueItem &p)
@@ -67,6 +76,7 @@
            {"created_at", p.created_at},
            {"updated_at", p.updated_at},
            {"finished_at", p.finished_at},
            {"images", p.images},
            //   {"params", p.params},
            {"status", (int)p.status}};
    }
@@ -76,6 +86,7 @@
        j.at("id").get_to(p.id);
        j.at("created_at").get_to(p.created_at);
        j.at("updated_at").get_to(p.updated_at);
        j.at("images").get_to(p.images);
        j.at("finished_at").get_to(p.finished_at);
        //  j.at("params").get_to(p.params);
        p.status = j.at("status").get<QM::QueueStatus>();
@@ -105,6 +116,8 @@
        void onItemAdded(QM::QueueItem item);
        void LodJobsFromFile();
        void SaveJobsToFile();
        // @brief check if something is running or not
        bool isRunning = false;
        wxEvtHandler *eventHandler;
        wxWindow *parent;