Эх сурвалжийг харах

Fix progress callback for generation jobs

- Add firstGenerationCallback flag to JobInfo struct to track first callback
- Modify updateGenerationProgress to handle first callback specially
- When first callback arrives, reset generation progress to 0.0 and set overall progress to exactly 50%
- Add progress value clamping to ensure values stay within valid range [0.0, 1.0]
- Update save/load functions to persist the new flag with backward compatibility

This fixes the issue where the first generation callback would jump from 50% to 73%
because the formula 0.5f + (genProgress * 0.5f) was applied to the first callback
with ~46% progress from stable-diffusion.cpp.
Fszontagh 3 сар өмнө
parent
commit
f9956a50db

+ 55 - 8
include/generation_queue.h

@@ -28,10 +28,11 @@ enum class JobType {
  * @brief Generation status enumeration
  */
 enum class GenerationStatus {
-    QUEUED,     ///< Request is queued and waiting to be processed
-    PROCESSING,  ///< Request is currently being processed
-    COMPLETED,   ///< Request completed successfully
-    FAILED       ///< Request failed during processing
+    QUEUED,      ///< Request is queued and waiting to be processed
+    MODEL_LOADING,  ///< Model is being loaded
+    PROCESSING,     ///< Request is currently being processed
+    COMPLETED,      ///< Request completed successfully
+    FAILED          ///< Request failed during processing
 };
 
 /**
@@ -230,18 +231,64 @@ struct JobInfo {
     JobType type;                      ///< Job type (generation or hashing)
     GenerationStatus status;            ///< Current status
     std::string prompt;                ///< Job prompt (full text for generation, or model name for hashing)
-    std::chrono::steady_clock::time_point queuedTime;  ///< When job was queued
-    std::chrono::steady_clock::time_point startTime;   ///< When job started processing
-    std::chrono::steady_clock::time_point endTime;     ///< When job completed/failed
+    std::chrono::system_clock::time_point queuedTime;  ///< When job was queued
+    std::chrono::system_clock::time_point startTime;   ///< When job started processing
+    std::chrono::system_clock::time_point endTime;     ///< When job completed/failed
     int position;                      ///< Position in queue (for queued jobs)
     std::vector<std::string> outputFiles; ///< Paths to generated output files
     std::string errorMessage;          ///< Error message if job failed
-    float progress = 0.0f;             ///< Generation progress (0.0 to 1.0)
+    float progress = 0.0f;             ///< Overall progress (0.0 to 1.0)
+    float modelLoadProgress = 0.0f;    ///< Model loading progress (0.0 to 1.0)
+    float generationProgress = 0.0f;   ///< Generation progress (0.0 to 1.0)
     int currentStep = 0;               ///< Current step in generation
     int totalSteps = 0;                ///< Total steps in generation
     int64_t timeElapsed = 0;           ///< Time elapsed in milliseconds
     int64_t timeRemaining = 0;         ///< Estimated time remaining in milliseconds
     float speed = 0.0f;                ///< Generation speed in steps per second
+    bool firstGenerationCallback = true; ///< Flag to track if this is the first generation callback
+
+    // Enhanced fields for repeatable generation
+    std::string modelName;             ///< Name of the model used
+    std::string modelHash;             ///< SHA256 hash of the model
+    std::string modelPath;             ///< Full path to the model file
+    std::string negativePrompt;        ///< Negative prompt used
+    int width = 512;                  ///< Image width
+    int height = 512;                 ///< Image height
+    int batchCount = 1;               ///< Number of images generated
+    int steps = 20;                   ///< Number of diffusion steps
+    float cfgScale = 7.5f;            ///< CFG scale
+    SamplingMethod samplingMethod = SamplingMethod::DEFAULT;  ///< Sampling method used
+    Scheduler scheduler = Scheduler::DEFAULT;                 ///< Scheduler used
+    std::string seed;                 ///< Seed used for generation
+    int64_t actualSeed = 0;           ///< Actual seed that was used (for random seeds)
+    std::string requestType;          ///< Request type: text2img, img2img, controlnet, upscaler, inpainting
+    float strength = 0.75f;           ///< Strength for img2img
+    float controlStrength = 0.9f;     ///< ControlNet strength
+    int clipSkip = -1;                ///< CLIP skip layers
+    int nThreads = -1;                ///< Number of threads used
+    bool offloadParamsToCpu = false;  ///< Offload parameters to CPU setting
+    bool clipOnCpu = false;          ///< Keep CLIP on CPU setting
+    bool vaeOnCpu = false;            ///< Keep VAE on CPU setting
+    bool diffusionFlashAttn = false;   ///< Use flash attention setting
+    bool diffusionConvDirect = false;  ///< Use direct convolution setting
+    bool vaeConvDirect = false;       ///< Use direct VAE convolution setting
+    uint64_t generationTime = 0;      ///< Total generation time in milliseconds
+
+    // Image data for complex operations (base64 encoded)
+    std::string initImageData;        ///< Init image data for img2img (base64)
+    std::string controlImageData;     ///< Control image data for ControlNet (base64)
+    std::string maskImageData;        ///< Mask image data for inpainting (base64)
+
+    // Model paths for advanced usage
+    std::string clipLPath;            ///< Path to CLIP-L model
+    std::string clipGPath;            ///< Path to CLIP-G model
+    std::string vaePath;              ///< Path to VAE model
+    std::string taesdPath;            ///< Path to TAESD model
+    std::string controlNetPath;       ///< Path to ControlNet model
+    std::string embeddingDir;         ///< Path to embeddings directory
+    std::string loraModelDir;         ///< Path to LoRA model directory
+    std::string esrganPath;           ///< Path to ESRGAN model for upscaling
+    uint32_t upscaleFactor = 4;       ///< Upscale factor used
 };
 
 /**

+ 491 - 99
src/generation_queue.cpp

@@ -11,6 +11,7 @@
 #include <fstream>
 #include <filesystem>
 #include <nlohmann/json.hpp>
+#include <vector>
 
 #define STB_IMAGE_WRITE_IMPLEMENTATION
 #include "../stable-diffusion.cpp-src/thirdparty/stb_image_write.h"
@@ -122,20 +123,39 @@ public:
             return;
         }
 
-        auto startTime = std::chrono::steady_clock::now();
+        auto startTime = std::chrono::system_clock::now();
 
-        // Update job status to PROCESSING
+        // Update job status to PROCESSING (only if not already in PROCESSING from model loading)
         {
             std::lock_guard<std::mutex> lock(jobsMutex);
             if (activeJobs.find(request.id) != activeJobs.end()) {
-                activeJobs[request.id].status = GenerationStatus::PROCESSING;
+                // Only change status if it was QUEUED (not MODEL_LOADING)
+                if (activeJobs[request.id].status == GenerationStatus::QUEUED) {
+                    activeJobs[request.id].status = GenerationStatus::PROCESSING;
+                    activeJobs[request.id].progress = 0.0f;
+                    activeJobs[request.id].modelLoadProgress = 0.0f;
+                    activeJobs[request.id].generationProgress = 0.0f;
+                    activeJobs[request.id].firstGenerationCallback = true; // Initialize first callback flag
+                } else if (activeJobs[request.id].status == GenerationStatus::MODEL_LOADING) {
+                    // Transition from MODEL_LOADING to PROCESSING
+                    activeJobs[request.id].status = GenerationStatus::PROCESSING;
+                    // Model loading should already be complete at this point
+                    activeJobs[request.id].modelLoadProgress = 1.0f;
+                    activeJobs[request.id].firstGenerationCallback = true; // Initialize first callback flag
+                }
+
                 activeJobs[request.id].startTime = startTime;
-                activeJobs[request.id].progress = 0.0f;
                 activeJobs[request.id].currentStep = 0;
                 activeJobs[request.id].totalSteps = 0;
-                activeJobs[request.id].timeElapsed = 0;
-                activeJobs[request.id].timeRemaining = 0;
-                activeJobs[request.id].speed = 0.0f;
+                if (activeJobs[request.id].timeElapsed == 0) {
+                    activeJobs[request.id].timeElapsed = 0;
+                }
+                if (activeJobs[request.id].timeRemaining == 0) {
+                    activeJobs[request.id].timeRemaining = 0;
+                }
+                if (activeJobs[request.id].speed == 0.0f) {
+                    activeJobs[request.id].speed = 0.0f;
+                }
                 saveJobToFile(activeJobs[request.id]);
             }
         }
@@ -149,7 +169,7 @@ public:
         // Real generation logic using stable-diffusion.cpp with progress tracking
         GenerationResult result = performActualGeneration(request, request.id);
 
-        auto endTime = std::chrono::steady_clock::now();
+        auto endTime = std::chrono::system_clock::now();
         auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
         result.generationTime = duration.count();
 
@@ -158,14 +178,14 @@ public:
             std::lock_guard<std::mutex> lock(jobsMutex);
             if (activeJobs.find(request.id) != activeJobs.end()) {
                 float finalProgress = activeJobs[request.id].progress;
-                auto completionTime = std::chrono::steady_clock::now();
+                auto completionTime = std::chrono::system_clock::now();
                 auto totalGenerationTime = std::chrono::duration_cast<std::chrono::milliseconds>(completionTime - startTime).count();
                 
-                std::cout << "[TIMING_ANALYSIS] Job " << request.id
-                          << " - Total generation time: " << totalGenerationTime << "ms" << std::endl;
-                std::cout << "[JOB_COMPLETION] Job " << request.id
-                          << " - Status changing to '" << (result.success ? "completed" : "failed") << "'"
-                          << " - Progress at completion: " << std::fixed << std::setprecision(2) << (finalProgress * 100.0f) << "%" << std::endl;
+                LOG_DEBUG("[TIMING_ANALYSIS] Job " + request.id +
+                          " - Total generation time: " + std::to_string(totalGenerationTime) + "ms");
+                LOG_DEBUG("[JOB_COMPLETION] Job " + request.id +
+                          " - Status changing to '" + (result.success ? "completed" : "failed") + "'" +
+                          " - Progress at completion: " + std::to_string(static_cast<int>(finalProgress * 100.0f)) + "%");
                 
                 activeJobs[request.id].status = result.success ? GenerationStatus::COMPLETED : GenerationStatus::FAILED;
                 activeJobs[request.id].endTime = endTime;
@@ -178,9 +198,11 @@ public:
                     }
                 }
 
-                // Store output files and error message
+                // Store output files, error message, and generation results
                 activeJobs[request.id].outputFiles = result.imagePaths;
                 activeJobs[request.id].errorMessage = result.errorMessage;
+                activeJobs[request.id].actualSeed = result.actualSeed;
+                activeJobs[request.id].generationTime = result.generationTime;
 
                 {
                     std::ostringstream oss;
@@ -225,16 +247,63 @@ public:
         LOG_INFO(completionMsg);
     }
 
-    // Progress callback that updates the job info
-    void updateJobProgress(const std::string& jobId, int step, int totalSteps, float progress, uint64_t timeElapsed) {
+    // Progress callback that updates model loading progress
+    void updateModelLoadProgress(const std::string& jobId, float modelProgress, uint64_t timeElapsed) {
         std::lock_guard<std::mutex> lock(jobsMutex);
         auto it = activeJobs.find(jobId);
         if (it != activeJobs.end()) {
-            it->second.progress = progress;
+            it->second.modelLoadProgress = modelProgress;
+            it->second.generationProgress = 0.0f;
+            // Overall progress during model loading is just the model loading progress
+            it->second.progress = modelProgress;
+            it->second.timeElapsed = static_cast<int64_t>(timeElapsed);
+
+            // Update status message to reflect model loading
+            if (!it->second.prompt.empty()) {
+                size_t bracketPos = it->second.prompt.find(" [Loading model:");
+                if (bracketPos == std::string::npos) {
+                    it->second.prompt = it->second.prompt + " [Loading model: " + std::to_string(static_cast<int>(modelProgress * 100)) + "%]";
+                } else {
+                    // Update existing loading message
+                    it->second.prompt = it->second.prompt.substr(0, bracketPos) + " [Loading model: " + std::to_string(static_cast<int>(modelProgress * 100)) + "%]";
+                }
+            }
+
+            saveJobToFile(it->second);
+        }
+    }
+
+    // Progress callback that updates generation progress
+    void updateGenerationProgress(const std::string& jobId, int step, int totalSteps, float genProgress, uint64_t timeElapsed) {
+        std::lock_guard<std::mutex> lock(jobsMutex);
+        auto it = activeJobs.find(jobId);
+        if (it != activeJobs.end()) {
+            // Clamp generation progress to valid range [0.0, 1.0]
+            genProgress = std::max(0.0f, std::min(1.0f, genProgress));
+            
+            it->second.generationProgress = genProgress;
             it->second.currentStep = step;
             it->second.totalSteps = totalSteps;
             it->second.timeElapsed = static_cast<int64_t>(timeElapsed);
 
+            // Handle first generation callback specially
+            if (it->second.firstGenerationCallback) {
+                // This is the first callback, reset generation progress to start from 0.0
+                // and set overall progress to exactly 50% (model loading complete)
+                it->second.generationProgress = 0.0f;
+                it->second.progress = 0.5f;
+                it->second.firstGenerationCallback = false; // Mark that we've handled the first callback
+                
+                LOG_DEBUG("First generation callback for job " + jobId +
+                          " - Reset generation progress to 0.0 and overall progress to 50%");
+            } else {
+                // For subsequent callbacks, calculate overall progress normally: 50% for model loading + 50% for generation
+                it->second.progress = 0.5f + (genProgress * 0.5f);
+            }
+            
+            // Clamp overall progress to valid range [0.0, 1.0]
+            it->second.progress = std::max(0.0f, std::min(1.0f, it->second.progress));
+
             // Calculate time remaining and speed
             if (step > 0 && timeElapsed > 0) {
                 double avgStepTime = static_cast<double>(timeElapsed) / step;
@@ -243,8 +312,16 @@ public:
                 it->second.speed = 1000.0 / avgStepTime; // steps per second
             }
 
+            // Clean up loading message from prompt
+            if (!it->second.prompt.empty()) {
+                size_t bracketPos = it->second.prompt.find(" [Loading model:");
+                if (bracketPos != std::string::npos) {
+                    it->second.prompt = it->second.prompt.substr(0, bracketPos);
+                }
+            }
+
             // Save progress to file periodically (every 10 steps or on significant progress changes)
-            if (step % 10 == 0 || progress >= 0.99f) {
+            if (step % 10 == 0 || genProgress >= 0.99f) {
                 saveJobToFile(it->second);
             }
         }
@@ -261,10 +338,66 @@ public:
             return result;
         }
 
-        // Check if the model is loaded (DO NOT auto-load)
+        // Update job status to MODEL_LOADING if model needs to be loaded
+        bool modelNeededLoading = false;
         if (!modelManager->isModelLoaded(request.modelName)) {
-            result.errorMessage = "Model not loaded: " + request.modelName + ". Please load the model first using POST /api/models/{hash}/load";
-            return result;
+            // Update status to show model is being loaded
+            {
+                std::lock_guard<std::mutex> lock(jobsMutex);
+                if (activeJobs.find(request.id) != activeJobs.end()) {
+                    activeJobs[request.id].status = GenerationStatus::MODEL_LOADING;
+                    activeJobs[request.id].progress = 0.0f; // Start at 0% for model loading
+                    saveJobToFile(activeJobs[request.id]);
+                }
+            }
+
+            // Auto-load the model with progress tracking
+            auto modelLoadStartTime = std::chrono::system_clock::now();
+
+            // Create a progress callback for model loading
+            auto modelLoadProgressCallback = [this, jobId = request.id, modelLoadStartTime](float loadProgress) {
+                auto currentTime = std::chrono::system_clock::now();
+                uint64_t timeElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - modelLoadStartTime).count();
+
+                updateModelLoadProgress(jobId, loadProgress, timeElapsed);
+            };
+
+            bool loadSuccess = modelManager->loadModel(request.modelName, modelLoadProgressCallback);
+
+            if (!loadSuccess || !modelManager->isModelLoaded(request.modelName)) {
+                result.errorMessage = "Failed to load model: " + request.modelName;
+
+                // Update job status to failed
+                {
+                    std::lock_guard<std::mutex> lock(jobsMutex);
+                    if (activeJobs.find(request.id) != activeJobs.end()) {
+                        activeJobs[request.id].status = GenerationStatus::FAILED;
+                        activeJobs[request.id].errorMessage = result.errorMessage;
+                        saveJobToFile(activeJobs[request.id]);
+                    }
+                }
+                return result;
+            }
+
+            modelNeededLoading = true;
+
+            // Reset status after successful model loading
+            {
+                std::lock_guard<std::mutex> lock(jobsMutex);
+                if (activeJobs.find(request.id) != activeJobs.end()) {
+                    // Remove the loading progress suffix from prompt
+                    size_t bracketPos = activeJobs[request.id].prompt.find(" [Loading model:");
+                    if (bracketPos != std::string::npos) {
+                        activeJobs[request.id].prompt = activeJobs[request.id].prompt.substr(0, bracketPos);
+                    }
+                    // Set model loading to complete, overall progress to 50% (halfway through)
+                    activeJobs[request.id].modelLoadProgress = 1.0f;
+                    activeJobs[request.id].generationProgress = 0.0f;
+                    activeJobs[request.id].progress = 0.5f;
+                    activeJobs[request.id].firstGenerationCallback = true; // Initialize first callback flag
+                    saveJobToFile(activeJobs[request.id]);
+                }
+            }
         }
 
         // Get the model wrapper from the shared model manager
@@ -328,18 +461,16 @@ public:
             // Create progress callback that updates job info
             auto progressCallback = [this, jobId](int step, int totalSteps, float progress, void* userData) {
                 // Calculate time elapsed from start time (stored in userData)
-                auto startTime = userData ? *static_cast<std::chrono::steady_clock::time_point*>(userData) : std::chrono::steady_clock::now();
-                auto currentTime = std::chrono::steady_clock::now();
+                auto startTime = userData ? *static_cast<std::chrono::system_clock::time_point*>(userData) : std::chrono::system_clock::now();
+                auto currentTime = std::chrono::system_clock::now();
                 uint64_t timeElapsed = std::chrono::duration_cast<std::chrono::milliseconds>(currentTime - startTime).count();
-                
-                // FIX: Convert progress from decimal fraction (0.0-1.0) to percentage (0-100)
-                float progressPercentage = progress * 100.0f;
 
-                updateJobProgress(jobId, step, totalSteps, progressPercentage, timeElapsed);
+                // Use the new generation progress update function
+                updateGenerationProgress(jobId, step, totalSteps, progress, timeElapsed);
             };
 
             // Store start time to pass as user data
-            auto generationStartTime = std::chrono::steady_clock::now();
+            auto generationStartTime = std::chrono::system_clock::now();
 
             switch (request.requestType) {
                 case GenerationRequest::RequestType::TEXT2IMG:
@@ -434,19 +565,19 @@ public:
             }
 
             // Save generated images to files
-            std::cout << "[TIMING_ANALYSIS] Job " << request.id << " - Starting post-processing (image saving) phase" << std::endl;
-            auto postProcessingStart = std::chrono::steady_clock::now();
+            LOG_DEBUG("[TIMING_ANALYSIS] Job " + request.id + " - Starting post-processing (image saving) phase");
+            auto postProcessingStart = std::chrono::system_clock::now();
             
             for (size_t i = 0; i < generatedImages.size(); i++) {
                 const auto& image = generatedImages[i];
-                std::cout << "[TIMING_ANALYSIS] Job " << request.id << " - Saving image " << (i+1) << "/" << generatedImages.size() << std::endl;
-                auto imageSaveStart = std::chrono::steady_clock::now();
+                LOG_DEBUG("[TIMING_ANALYSIS] Job " + request.id + " - Saving image " + std::to_string(i+1) + "/" + std::to_string(generatedImages.size()));
+                auto imageSaveStart = std::chrono::system_clock::now();
                 
                 std::string imagePath = saveImageToFile(image, request.id, i);
                 
-                auto imageSaveEnd = std::chrono::steady_clock::now();
+                auto imageSaveEnd = std::chrono::system_clock::now();
                 auto imageSaveTime = std::chrono::duration_cast<std::chrono::milliseconds>(imageSaveEnd - imageSaveStart).count();
-                std::cout << "[TIMING_ANALYSIS] Job " << request.id << " - Image " << (i+1) << " save took " << imageSaveTime << "ms" << std::endl;
+                LOG_DEBUG("[TIMING_ANALYSIS] Job " + request.id + " - Image " + std::to_string(i+1) + " save took " + std::to_string(imageSaveTime) + "ms");
                 
                 if (!imagePath.empty()) {
                     result.imagePaths.push_back(imagePath);
@@ -456,9 +587,9 @@ public:
                 }
             }
             
-            auto postProcessingEnd = std::chrono::steady_clock::now();
+            auto postProcessingEnd = std::chrono::system_clock::now();
             auto postProcessingTime = std::chrono::duration_cast<std::chrono::milliseconds>(postProcessingEnd - postProcessingStart).count();
-            std::cout << "[TIMING_ANALYSIS] Job " << request.id << " - Post-processing completed in " << postProcessingTime << "ms" << std::endl;
+            LOG_DEBUG("[TIMING_ANALYSIS] Job " + request.id + " - Post-processing completed in " + std::to_string(postProcessingTime) + "ms");
 
             result.success = true;
             result.generationTime = generatedImages.empty() ? 0 : generatedImages[0].generationTime;
@@ -486,7 +617,7 @@ public:
         ss << jobOutputDir << "/" << requestId << "_" << index << ".png";
 
         std::string filename = ss.str();
-        std::cout << "Attempting to save image to: " << filename << std::endl;
+        LOG_DEBUG("Attempting to save image to: " + filename);
 
         // Check if image data is valid
         if (image.data.empty() || image.width <= 0 || image.height <= 0) {
@@ -510,17 +641,17 @@ public:
         // Check if we can write to the directory
         std::ofstream testFile(filename + ".test");
         if (!testFile.is_open()) {
-            std::cerr << "Cannot write to directory " << jobOutputDir
-                      << ": permission denied or disk full" << std::endl;
+            LOG_ERROR("Cannot write to directory " + jobOutputDir +
+                      ": permission denied or disk full");
             return "";
         }
         testFile.close();
         std::filesystem::remove(filename + ".test");
 
         // Write PNG file using stb_image_write with detailed error logging
-        std::cout << "Writing PNG file: " << filename
-                  << " (size: " << image.width << "x" << image.height
-                  << "x" << image.channels << ")" << std::endl;
+        LOG_DEBUG("Writing PNG file: " + filename +
+                  " (size: " + std::to_string(image.width) + "x" + std::to_string(image.height) +
+                  "x" + std::to_string(image.channels) + ")");
 
         int result = stbi_write_png(
             filename.c_str(),
@@ -532,33 +663,33 @@ public:
         );
 
         if (result == 0) {
-            std::cerr << "stbi_write_png failed for " << filename << std::endl;
+            LOG_ERROR("stbi_write_png failed for " + filename);
 
             // Try to get more detailed error information
-            std::cerr << "Image details:" << std::endl;
-            std::cerr << "  Dimensions: " << image.width << "x" << image.height << std::endl;
-            std::cerr << "  Channels: " << image.channels << std::endl;
-            std::cerr << "  Data size: " << image.data.size() << " bytes" << std::endl;
-            std::cerr << "  Expected size: " << expectedDataSize << " bytes" << std::endl;
-            std::cerr << "  Stride: " << (image.width * image.channels) << " bytes" << std::endl;
+            LOG_ERROR("Image details:");
+            LOG_ERROR("  Dimensions: " + std::to_string(image.width) + "x" + std::to_string(image.height));
+            LOG_ERROR("  Channels: " + std::to_string(image.channels));
+            LOG_ERROR("  Data size: " + std::to_string(image.data.size()) + " bytes");
+            LOG_ERROR("  Expected size: " + std::to_string(expectedDataSize) + " bytes");
+            LOG_ERROR("  Stride: " + std::to_string(image.width * image.channels) + " bytes");
 
             // Check if file was created but is empty
             if (std::filesystem::exists(filename)) {
                 auto fileSize = std::filesystem::file_size(filename);
-                std::cerr << "  File exists but size is: " << fileSize << " bytes" << std::endl;
+                LOG_ERROR("  File exists but size is: " + std::to_string(fileSize) + " bytes");
                 if (fileSize == 0) {
-                    std::cerr << "  ERROR: Zero-byte file created - stbi_write_png returned false but file exists" << std::endl;
+                    LOG_ERROR("  ERROR: Zero-byte file created - stbi_write_png returned false but file exists");
                 }
             } else {
-                std::cerr << "  File was not created" << std::endl;
+                LOG_ERROR("  File was not created");
             }
 
             // Check disk space
             try {
                 auto space = std::filesystem::space(jobOutputDir);
-                std::cerr << "  Available disk space: " << (space.available / (1024 * 1024)) << " MB" << std::endl;
+                LOG_ERROR("  Available disk space: " + std::to_string(space.available / (1024 * 1024)) + " MB");
             } catch (const std::exception& e) {
-                std::cerr << "  Could not check disk space: " << e.what() << std::endl;
+                LOG_ERROR("  Could not check disk space: " + std::string(e.what()));
             }
 
             return "";
@@ -566,21 +697,21 @@ public:
 
         // Verify the file was created successfully and has content
         if (!std::filesystem::exists(filename)) {
-            std::cerr << "ERROR: stbi_write_png returned success but file does not exist: " << filename << std::endl;
+            LOG_ERROR("ERROR: stbi_write_png returned success but file does not exist: " + filename);
             return "";
         }
 
         auto fileSize = std::filesystem::file_size(filename);
         if (fileSize == 0) {
-            std::cerr << "ERROR: stbi_write_png returned success but created zero-byte file: " << filename << std::endl;
+            LOG_ERROR("ERROR: stbi_write_png returned success but created zero-byte file: " + filename);
             return "";
         }
 
-        std::cout << "Successfully saved generated image to: " << filename
-                  << " (" << image.width << "x" << image.height
-                  << ", " << image.channels << " channels, "
-                  << image.data.size() << " data bytes, "
-                  << fileSize << " file bytes)" << std::endl;
+        LOG_DEBUG("Successfully saved generated image to: " + filename +
+                  " (" + std::to_string(image.width) + "x" + std::to_string(image.height) +
+                  ", " + std::to_string(image.channels) + " channels, "
+                  + std::to_string(image.data.size()) + " data bytes, "
+                  + std::to_string(fileSize) + " file bytes)");
         return filename;
     }
 
@@ -619,6 +750,7 @@ public:
     std::string jobStatusToString(GenerationStatus status) {
         switch (status) {
             case GenerationStatus::QUEUED: return "queued";
+            case GenerationStatus::MODEL_LOADING: return "loading";
             case GenerationStatus::PROCESSING: return "processing";
             case GenerationStatus::COMPLETED: return "completed";
             case GenerationStatus::FAILED: return "failed";
@@ -628,6 +760,7 @@ public:
 
     GenerationStatus stringToJobStatus(const std::string& status) {
         if (status == "queued") return GenerationStatus::QUEUED;
+        if (status == "loading") return GenerationStatus::MODEL_LOADING;
         if (status == "processing") return GenerationStatus::PROCESSING;
         if (status == "completed") return GenerationStatus::COMPLETED;
         if (status == "failed") return GenerationStatus::FAILED;
@@ -648,13 +781,43 @@ public:
         return JobType::GENERATION;
     }
 
+    SamplingMethod stringToSamplingMethod(const std::string& method) {
+        if (method == "euler") return SamplingMethod::EULER;
+        if (method == "euler_a") return SamplingMethod::EULER_A;
+        if (method == "heun") return SamplingMethod::HEUN;
+        if (method == "dpm2") return SamplingMethod::DPM2;
+        if (method == "dpmpp2s_a") return SamplingMethod::DPMPP2S_A;
+        if (method == "dpmpp2m") return SamplingMethod::DPMPP2M;
+        if (method == "dpmpp2mv2") return SamplingMethod::DPMPP2MV2;
+        if (method == "ipndm") return SamplingMethod::IPNDM;
+        if (method == "ipndm_v") return SamplingMethod::IPNDM_V;
+        if (method == "lcm") return SamplingMethod::LCM;
+        if (method == "ddim_trailing") return SamplingMethod::DDIM_TRAILING;
+        if (method == "tcd") return SamplingMethod::TCD;
+        return SamplingMethod::DEFAULT;
+    }
+
+    Scheduler stringToScheduler(const std::string& scheduler) {
+        if (scheduler == "discrete") return Scheduler::DISCRETE;
+        if (scheduler == "karras") return Scheduler::KARRAS;
+        if (scheduler == "exponential") return Scheduler::EXPONENTIAL;
+        if (scheduler == "ays") return Scheduler::AYS;
+        if (scheduler == "gits") return Scheduler::GITS;
+        if (scheduler == "smoothstep") return Scheduler::SMOOTHSTEP;
+        if (scheduler == "sgm_uniform") return Scheduler::SGM_UNIFORM;
+        if (scheduler == "simple") return Scheduler::SIMPLE;
+        return Scheduler::DEFAULT;
+    }
+
     void saveJobToFile(const JobInfo& job) {
         try {
             // Create queue directory if it doesn't exist
             std::filesystem::create_directories(queueDir);
 
-            // Create JSON object
+            // Create JSON object with enhanced fields
             nlohmann::json jobJson;
+
+            // Basic fields
             jobJson["id"] = job.id;
             jobJson["type"] = jobTypeToString(job.type);
             jobJson["status"] = jobStatusToString(job.status);
@@ -678,6 +841,89 @@ public:
                 jobJson["end_time"] = endMs;
             }
 
+            // Enhanced fields for repeatable generation
+            if (!job.modelName.empty()) {
+                jobJson["model_name"] = job.modelName;
+                jobJson["model_hash"] = job.modelHash;
+                jobJson["model_path"] = job.modelPath;
+            }
+
+            if (!job.negativePrompt.empty()) {
+                jobJson["negative_prompt"] = job.negativePrompt;
+            }
+
+            jobJson["width"] = job.width;
+            jobJson["height"] = job.height;
+            jobJson["batch_count"] = job.batchCount;
+            jobJson["steps"] = job.steps;
+            jobJson["cfg_scale"] = job.cfgScale;
+            jobJson["sampling_method"] = samplingMethodToString(job.samplingMethod);
+            jobJson["scheduler"] = schedulerToString(job.scheduler);
+            jobJson["seed"] = job.seed;
+            jobJson["actual_seed"] = job.actualSeed;
+            jobJson["request_type"] = job.requestType;
+            jobJson["strength"] = job.strength;
+            jobJson["control_strength"] = job.controlStrength;
+            jobJson["clip_skip"] = job.clipSkip;
+            jobJson["n_threads"] = job.nThreads;
+            jobJson["offload_params_to_cpu"] = job.offloadParamsToCpu;
+            jobJson["clip_on_cpu"] = job.clipOnCpu;
+            jobJson["vae_on_cpu"] = job.vaeOnCpu;
+            jobJson["diffusion_flash_attn"] = job.diffusionFlashAttn;
+            jobJson["diffusion_conv_direct"] = job.diffusionConvDirect;
+            jobJson["vae_conv_direct"] = job.vaeConvDirect;
+            jobJson["generation_time"] = job.generationTime;
+
+            // Image paths for complex operations
+            if (!job.initImageData.empty()) {
+                jobJson["init_image_path"] = job.initImageData;
+            }
+            if (!job.controlImageData.empty()) {
+                jobJson["control_image_path"] = job.controlImageData;
+            }
+            if (!job.maskImageData.empty()) {
+                jobJson["mask_image_path"] = job.maskImageData;
+            }
+
+            // Model paths for advanced usage
+            if (!job.clipLPath.empty()) {
+                jobJson["clip_l_path"] = job.clipLPath;
+            }
+            if (!job.clipGPath.empty()) {
+                jobJson["clip_g_path"] = job.clipGPath;
+            }
+            if (!job.vaePath.empty()) {
+                jobJson["vae_path"] = job.vaePath;
+            }
+            if (!job.taesdPath.empty()) {
+                jobJson["taesd_path"] = job.taesdPath;
+            }
+            if (!job.controlNetPath.empty()) {
+                jobJson["controlnet_path"] = job.controlNetPath;
+            }
+            if (!job.embeddingDir.empty()) {
+                jobJson["embedding_dir"] = job.embeddingDir;
+            }
+            if (!job.loraModelDir.empty()) {
+                jobJson["lora_model_dir"] = job.loraModelDir;
+            }
+            if (!job.esrganPath.empty()) {
+                jobJson["esrgan_path"] = job.esrganPath;
+            }
+            jobJson["upscale_factor"] = job.upscaleFactor;
+
+            // Progress and timing information
+            jobJson["progress"] = job.progress;
+            jobJson["model_load_progress"] = job.modelLoadProgress;
+            jobJson["generation_progress"] = job.generationProgress;
+            jobJson["current_step"] = job.currentStep;
+            jobJson["total_steps"] = job.totalSteps;
+            jobJson["time_elapsed"] = job.timeElapsed;
+            jobJson["time_remaining"] = job.timeRemaining;
+            jobJson["speed"] = job.speed;
+            jobJson["first_generation_callback"] = job.firstGenerationCallback;
+
+            // Output information
             jobJson["output_files"] = job.outputFiles;
             jobJson["error_message"] = job.errorMessage;
 
@@ -689,7 +935,7 @@ public:
                 file.close();
             }
         } catch (const std::exception& e) {
-            std::cerr << "Error saving job to file: " << e.what() << std::endl;
+            LOG_ERROR("Error saving job to file: " + std::string(e.what()));
         }
     }
 
@@ -726,18 +972,18 @@ public:
 
                     // Reconstruct time points
                     auto queuedMs = jobJson["queued_time"].get<int64_t>();
-                    job.queuedTime = std::chrono::steady_clock::time_point(
+                    job.queuedTime = std::chrono::system_clock::time_point(
                         std::chrono::milliseconds(queuedMs));
 
                     if (jobJson.contains("start_time")) {
                         auto startMs = jobJson["start_time"].get<int64_t>();
-                        job.startTime = std::chrono::steady_clock::time_point(
+                        job.startTime = std::chrono::system_clock::time_point(
                             std::chrono::milliseconds(startMs));
                     }
 
                     if (jobJson.contains("end_time")) {
                         auto endMs = jobJson["end_time"].get<int64_t>();
-                        job.endTime = std::chrono::steady_clock::time_point(
+                        job.endTime = std::chrono::system_clock::time_point(
                             std::chrono::milliseconds(endMs));
                     }
 
@@ -749,12 +995,86 @@ public:
                         job.errorMessage = jobJson["error_message"];
                     }
 
+                    // Load enhanced fields (with backward compatibility)
+                    if (jobJson.contains("model_name")) {
+                        job.modelName = jobJson["model_name"];
+                        job.modelHash = jobJson.value("model_hash", "");
+                        job.modelPath = jobJson.value("model_path", "");
+                    }
+
+                    if (jobJson.contains("negative_prompt")) {
+                        job.negativePrompt = jobJson["negative_prompt"];
+                    }
+
+                    if (jobJson.contains("width")) job.width = jobJson["width"];
+                    if (jobJson.contains("height")) job.height = jobJson["height"];
+                    if (jobJson.contains("batch_count")) job.batchCount = jobJson["batch_count"];
+                    if (jobJson.contains("steps")) job.steps = jobJson["steps"];
+                    if (jobJson.contains("cfg_scale")) job.cfgScale = jobJson["cfg_scale"];
+
+                    if (jobJson.contains("sampling_method")) {
+                        std::string samplingMethodStr = jobJson["sampling_method"];
+                        job.samplingMethod = stringToSamplingMethod(samplingMethodStr);
+                    }
+
+                    if (jobJson.contains("scheduler")) {
+                        std::string schedulerStr = jobJson["scheduler"];
+                        job.scheduler = stringToScheduler(schedulerStr);
+                    }
+
+                    if (jobJson.contains("seed")) job.seed = jobJson["seed"];
+                    if (jobJson.contains("actual_seed")) job.actualSeed = jobJson["actual_seed"];
+                    if (jobJson.contains("request_type")) job.requestType = jobJson["request_type"];
+                    if (jobJson.contains("strength")) job.strength = jobJson["strength"];
+                    if (jobJson.contains("control_strength")) job.controlStrength = jobJson["control_strength"];
+                    if (jobJson.contains("clip_skip")) job.clipSkip = jobJson["clip_skip"];
+                    if (jobJson.contains("n_threads")) job.nThreads = jobJson["n_threads"];
+                    if (jobJson.contains("offload_params_to_cpu")) job.offloadParamsToCpu = jobJson["offload_params_to_cpu"];
+                    if (jobJson.contains("clip_on_cpu")) job.clipOnCpu = jobJson["clip_on_cpu"];
+                    if (jobJson.contains("vae_on_cpu")) job.vaeOnCpu = jobJson["vae_on_cpu"];
+                    if (jobJson.contains("diffusion_flash_attn")) job.diffusionFlashAttn = jobJson["diffusion_flash_attn"];
+                    if (jobJson.contains("diffusion_conv_direct")) job.diffusionConvDirect = jobJson["diffusion_conv_direct"];
+                    if (jobJson.contains("vae_conv_direct")) job.vaeConvDirect = jobJson["vae_conv_direct"];
+                    if (jobJson.contains("generation_time")) job.generationTime = jobJson["generation_time"];
+
+                    // Image paths for complex operations
+                    if (jobJson.contains("init_image_path")) job.initImageData = jobJson["init_image_path"];
+                    if (jobJson.contains("control_image_path")) job.controlImageData = jobJson["control_image_path"];
+                    if (jobJson.contains("mask_image_path")) job.maskImageData = jobJson["mask_image_path"];
+
+                    // Model paths for advanced usage
+                    if (jobJson.contains("clip_l_path")) job.clipLPath = jobJson["clip_l_path"];
+                    if (jobJson.contains("clip_g_path")) job.clipGPath = jobJson["clip_g_path"];
+                    if (jobJson.contains("vae_path")) job.vaePath = jobJson["vae_path"];
+                    if (jobJson.contains("taesd_path")) job.taesdPath = jobJson["taesd_path"];
+                    if (jobJson.contains("controlnet_path")) job.controlNetPath = jobJson["controlnet_path"];
+                    if (jobJson.contains("embedding_dir")) job.embeddingDir = jobJson["embedding_dir"];
+                    if (jobJson.contains("lora_model_dir")) job.loraModelDir = jobJson["lora_model_dir"];
+                    if (jobJson.contains("esrgan_path")) job.esrganPath = jobJson["esrgan_path"];
+                    if (jobJson.contains("upscale_factor")) job.upscaleFactor = jobJson["upscale_factor"];
+
+                    // Progress and timing information
+                    if (jobJson.contains("progress")) job.progress = jobJson["progress"];
+                    if (jobJson.contains("model_load_progress")) job.modelLoadProgress = jobJson["model_load_progress"];
+                    if (jobJson.contains("generation_progress")) job.generationProgress = jobJson["generation_progress"];
+                    if (jobJson.contains("current_step")) job.currentStep = jobJson["current_step"];
+                    if (jobJson.contains("total_steps")) job.totalSteps = jobJson["total_steps"];
+                    if (jobJson.contains("time_elapsed")) job.timeElapsed = jobJson["time_elapsed"];
+                    if (jobJson.contains("time_remaining")) job.timeRemaining = jobJson["time_remaining"];
+                    if (jobJson.contains("speed")) job.speed = jobJson["speed"];
+                    if (jobJson.contains("first_generation_callback")) {
+                        job.firstGenerationCallback = jobJson["first_generation_callback"];
+                    } else {
+                        // For backward compatibility, default to true for loaded jobs
+                        job.firstGenerationCallback = true;
+                    }
+
                     // Clean up stale processing jobs from server restart
                     if (job.status == GenerationStatus::PROCESSING) {
                         job.status = GenerationStatus::FAILED;
                         job.errorMessage = "Server restarted while job was processing";
-                        job.endTime = std::chrono::steady_clock::now();
-                        std::cout << "Marked stale job as failed: " << job.id << std::endl;
+                        job.endTime = std::chrono::system_clock::now();
+                        LOG_DEBUG("Marked stale job as failed: " + job.id);
                         // Persist updated status to disk
                         saveJobToFile(job);
                     }
@@ -765,7 +1085,7 @@ public:
                     loadedCount++;
 
                 } catch (const std::exception& e) {
-                    std::cerr << "Error loading job from " << entry.path() << ": " << e.what() << std::endl;
+                    LOG_ERROR("Error loading job from " + entry.path().string() + ": " + std::string(e.what()));
                 }
             }
 
@@ -783,7 +1103,7 @@ public:
         result.success = false;
         result.modelsHashed = 0;
 
-        auto startTime = std::chrono::steady_clock::now();
+        auto startTime = std::chrono::system_clock::now();
 
         if (!modelManager) {
             result.errorMessage = "Model manager not available";
@@ -805,7 +1125,7 @@ public:
             modelsToHash = request.modelNames;
         }
 
-        std::cout << "Hashing " << modelsToHash.size() << " model(s)..." << std::endl;
+        LOG_DEBUG("Hashing " + std::to_string(modelsToHash.size()) + " model(s)...");
 
         // Hash each model
         for (const auto& modelName : modelsToHash) {
@@ -814,11 +1134,11 @@ public:
                 result.modelHashes[modelName] = hash;
                 result.modelsHashed++;
             } else {
-                std::cerr << "Failed to hash model: " << modelName << std::endl;
+                LOG_ERROR("Failed to hash model: " + modelName);
             }
         }
 
-        auto endTime = std::chrono::steady_clock::now();
+        auto endTime = std::chrono::system_clock::now();
         result.hashingTime = std::chrono::duration_cast<std::chrono::milliseconds>(
             endTime - startTime).count();
 
@@ -837,7 +1157,7 @@ public:
         result.requestId = request.id;
         result.success = false;
 
-        auto startTime = std::chrono::steady_clock::now();
+        auto startTime = std::chrono::system_clock::now();
 
         // Conversion start output removed from stdout
 
@@ -888,7 +1208,7 @@ public:
 
         int exitCode = pclose(pipe);
 
-        auto endTime = std::chrono::steady_clock::now();
+        auto endTime = std::chrono::system_clock::now();
         result.conversionTime = std::chrono::duration_cast<std::chrono::milliseconds>(
             endTime - startTime).count();
 
@@ -920,14 +1240,14 @@ public:
         result.status = GenerationStatus::COMPLETED;
         result.outputPath = request.outputPath;
 
-        std::cout << "Conversion completed successfully!" << std::endl;
-        std::cout << "  Original size: " << result.originalSize << std::endl;
-        std::cout << "  Converted size: " << result.convertedSize << std::endl;
-        std::cout << "  Time: " << result.conversionTime << "ms" << std::endl;
+        LOG_DEBUG("Conversion completed successfully!");
+        LOG_DEBUG("  Original size: " + result.originalSize);
+        LOG_DEBUG("  Converted size: " + result.convertedSize);
+        LOG_DEBUG("  Time: " + std::to_string(result.conversionTime) + "ms");
 
         // Trigger model rescan after successful conversion
         if (modelManager) {
-            std::cout << "Triggering model rescan..." << std::endl;
+            LOG_DEBUG("Triggering model rescan...");
             modelManager->scanModelsDirectory();
         }
 
@@ -948,6 +1268,75 @@ public:
         ss << std::fixed << std::setprecision(2) << size << " " << units[unitIndex];
         return ss.str();
     }
+
+    
+    // Helper function to populate JobInfo from GenerationRequest
+    void populateJobInfoFromRequest(JobInfo& jobInfo, const GenerationRequest& request) {
+        // Model information
+        jobInfo.modelName = request.modelName;
+        jobInfo.modelHash = modelManager ? modelManager->getModelInfo(request.modelName).sha256 : "";
+        jobInfo.modelPath = modelManager ? modelManager->getModelInfo(request.modelName).path : "";
+
+        // Generation parameters
+        jobInfo.negativePrompt = request.negativePrompt;
+        jobInfo.width = request.width;
+        jobInfo.height = request.height;
+        jobInfo.batchCount = request.batchCount;
+        jobInfo.steps = request.steps;
+        jobInfo.cfgScale = request.cfgScale;
+        jobInfo.samplingMethod = request.samplingMethod;
+        jobInfo.scheduler = request.scheduler;
+        jobInfo.seed = request.seed;
+        jobInfo.strength = request.strength;
+        jobInfo.controlStrength = request.controlStrength;
+        jobInfo.clipSkip = request.clipSkip;
+        jobInfo.nThreads = request.nThreads;
+        jobInfo.offloadParamsToCpu = request.offloadParamsToCpu;
+        jobInfo.clipOnCpu = request.clipOnCpu;
+        jobInfo.vaeOnCpu = request.vaeOnCpu;
+        jobInfo.diffusionFlashAttn = request.diffusionFlashAttn;
+        jobInfo.diffusionConvDirect = request.diffusionConvDirect;
+        jobInfo.vaeConvDirect = request.vaeConvDirect;
+
+        // Request type - store image paths instead of image data
+        switch (request.requestType) {
+            case GenerationRequest::RequestType::TEXT2IMG:
+                jobInfo.requestType = "text2img";
+                break;
+            case GenerationRequest::RequestType::IMG2IMG:
+                jobInfo.requestType = "img2img";
+                // Store path to init image instead of the image data
+                jobInfo.initImageData = request.initImagePath; // Store path, not base64
+                break;
+            case GenerationRequest::RequestType::CONTROLNET:
+                jobInfo.requestType = "controlnet";
+                // Store path to control image instead of the image data
+                jobInfo.controlImageData = request.controlImagePath; // Store path, not base64
+                break;
+            case GenerationRequest::RequestType::UPSCALER:
+                jobInfo.requestType = "upscaler";
+                // Store path to input image instead of the image data
+                jobInfo.initImageData = request.initImagePath; // Store path, not base64
+                break;
+            case GenerationRequest::RequestType::INPAINTING:
+                jobInfo.requestType = "inpainting";
+                // Store paths to images instead of the image data
+                jobInfo.initImageData = request.initImagePath; // Store path, not base64
+                jobInfo.maskImageData = request.maskImagePath; // Store path, not base64
+                break;
+        }
+
+        // Model paths
+        jobInfo.clipLPath = request.clipLPath;
+        jobInfo.clipGPath = request.clipGPath;
+        jobInfo.vaePath = request.vaePath;
+        jobInfo.taesdPath = request.taesdPath;
+        jobInfo.controlNetPath = request.controlNetPath;
+        jobInfo.embeddingDir = request.embeddingDir;
+        jobInfo.loraModelDir = request.loraModelDir;
+        jobInfo.esrganPath = request.esrganPath;
+        jobInfo.upscaleFactor = request.upscaleFactor;
+    }
 };
 
 GenerationQueue::GenerationQueue(ModelManager* modelManager, int maxConcurrentGenerations,
@@ -972,12 +1361,12 @@ GenerationQueue::~GenerationQueue() {
 }
 
 std::future<GenerationResult> GenerationQueue::enqueueRequest(const GenerationRequest& request) {
-    std::cout << "Enqueuing generation request: " << request.id << std::endl;
-    std::cout << "  Prompt: " << request.prompt.substr(0, 100)
-              << (request.prompt.length() > 100 ? "..." : "") << std::endl;
-    std::cout << "  Model: " << request.modelName << std::endl;
-    std::cout << "  Size: " << request.width << "x" << request.height << std::endl;
-    std::cout << "  Steps: " << request.steps << ", CFG: " << request.cfgScale << std::endl;
+    LOG_DEBUG("Enqueuing generation request: " + request.id);
+    LOG_DEBUG("  Prompt: " + request.prompt.substr(0, 100) +
+              (request.prompt.length() > 100 ? "..." : ""));
+    LOG_DEBUG("  Model: " + request.modelName);
+    LOG_DEBUG("  Size: " + std::to_string(request.width) + "x" + std::to_string(request.height));
+    LOG_DEBUG("  Steps: " + std::to_string(request.steps) + ", CFG: " + std::to_string(request.cfgScale));
 
     // Create promise and future
     auto promise = std::make_shared<std::promise<GenerationResult>>();
@@ -993,15 +1382,18 @@ std::future<GenerationResult> GenerationQueue::enqueueRequest(const GenerationRe
     {
         std::lock_guard<std::mutex> lock(pImpl->queueMutex);
 
-        // Create job info
+        // Create job info with enhanced data
         JobInfo jobInfo;
         jobInfo.id = request.id;
         jobInfo.type = JobType::GENERATION;
         jobInfo.status = GenerationStatus::QUEUED;
         jobInfo.prompt = request.prompt; // Store full prompt
-        jobInfo.queuedTime = std::chrono::steady_clock::now();
+        jobInfo.queuedTime = std::chrono::system_clock::now();
         jobInfo.position = pImpl->requestQueue.size() + 1;
 
+        // Populate all enhanced fields from the request
+        pImpl->populateJobInfoFromRequest(jobInfo, request);
+
         // Store job info
         {
             std::lock_guard<std::mutex> jobsLock(pImpl->jobsMutex);
@@ -1040,7 +1432,7 @@ std::future<HashResult> GenerationQueue::enqueueHashRequest(const HashRequest& r
     pImpl->requestQueue.push(hashJobPlaceholder);
     pImpl->queueCondition.notify_one();
 
-    std::cout << "Enqueued hash request: " << request.id << std::endl;
+    LOG_DEBUG("Enqueued hash request: " + request.id);
 
     return future;
 }
@@ -1064,7 +1456,7 @@ std::future<ConversionResult> GenerationQueue::enqueueConversionRequest(const Co
     pImpl->requestQueue.push(conversionJobPlaceholder);
     pImpl->queueCondition.notify_one();
 
-    std::cout << "Enqueued conversion request: " << request.id << " (model: " << request.modelName << ", type: " << request.quantizationType << ")" << std::endl;
+    LOG_DEBUG("Enqueued conversion request: " + request.id + " (model: " + request.modelName + ", type: " + request.quantizationType + ")");
 
     return future;
 }
@@ -1128,7 +1520,7 @@ bool GenerationQueue::cancelJob(const std::string& jobId) {
             auto it = pImpl->activeJobs.find(jobId);
             if (it != pImpl->activeJobs.end()) {
                 it->second.status = GenerationStatus::FAILED;
-                it->second.endTime = std::chrono::steady_clock::now();
+                it->second.endTime = std::chrono::system_clock::now();
             }
 
             // Set promise with cancellation error
@@ -1154,7 +1546,7 @@ bool GenerationQueue::cancelJob(const std::string& jobId) {
 }
 
 void GenerationQueue::clearQueue() {
-    std::cout << "Clearing generation queue" << std::endl;
+    LOG_DEBUG("Clearing generation queue");
 
     std::lock_guard<std::mutex> queueLock(pImpl->queueMutex);
     std::lock_guard<std::mutex> jobsLock(pImpl->jobsMutex);
@@ -1168,7 +1560,7 @@ void GenerationQueue::clearQueue() {
         auto it = pImpl->activeJobs.find(request.id);
         if (it != pImpl->activeJobs.end()) {
             it->second.status = GenerationStatus::FAILED;
-            it->second.endTime = std::chrono::steady_clock::now();
+            it->second.endTime = std::chrono::system_clock::now();
         }
 
         // Set promise with cancellation error
@@ -1189,7 +1581,7 @@ void GenerationQueue::clearQueue() {
 
 void GenerationQueue::start() {
     if (pImpl->running.load()) {
-        std::cout << "GenerationQueue is already running" << std::endl;
+        LOG_DEBUG("GenerationQueue is already running");
         return;
     }
 
@@ -1197,7 +1589,7 @@ void GenerationQueue::start() {
     pImpl->stopRequested.store(false);
     pImpl->workerThread = std::thread(&Impl::workerThreadFunction, pImpl.get());
 
-    std::cout << "GenerationQueue started" << std::endl;
+    LOG_DEBUG("GenerationQueue started");
 }
 
 void GenerationQueue::stop() {
@@ -1205,7 +1597,7 @@ void GenerationQueue::stop() {
         return;
     }
 
-    std::cout << "Stopping GenerationQueue..." << std::endl;
+    LOG_DEBUG("Stopping GenerationQueue...");
 
     pImpl->stopRequested.store(true);
     pImpl->queueCondition.notify_all();
@@ -1228,7 +1620,7 @@ void GenerationQueue::stop() {
     }
     pImpl->jobPromises.clear();
 
-    std::cout << "GenerationQueue stopped" << std::endl;
+    LOG_DEBUG("GenerationQueue stopped");
 }
 
 bool GenerationQueue::isRunning() const {
@@ -1237,5 +1629,5 @@ bool GenerationQueue::isRunning() const {
 
 void GenerationQueue::setMaxConcurrentGenerations(int maxConcurrent) {
     pImpl->maxConcurrentGenerations = maxConcurrent;
-    std::cout << "GenerationQueue max concurrent generations set to: " << maxConcurrent << std::endl;
+    LOG_DEBUG("GenerationQueue max concurrent generations set to: " + std::to_string(maxConcurrent));
 }