|
|
@@ -1,30 +1,30 @@
|
|
|
#include "model_manager.h"
|
|
|
-#include "model_detector.h"
|
|
|
-#include "stable_diffusion_wrapper.h"
|
|
|
-#include "logger.h"
|
|
|
-#include <iostream>
|
|
|
-#include <fstream>
|
|
|
+#include <openssl/evp.h>
|
|
|
#include <algorithm>
|
|
|
-#include <filesystem>
|
|
|
-#include <shared_mutex>
|
|
|
+#include <atomic>
|
|
|
#include <chrono>
|
|
|
+#include <filesystem>
|
|
|
+#include <fstream>
|
|
|
#include <future>
|
|
|
-#include <atomic>
|
|
|
-#include <set>
|
|
|
-#include <openssl/evp.h>
|
|
|
-#include <sstream>
|
|
|
#include <iomanip>
|
|
|
+#include <iostream>
|
|
|
#include <nlohmann/json.hpp>
|
|
|
+#include <set>
|
|
|
+#include <shared_mutex>
|
|
|
+#include <sstream>
|
|
|
+#include "logger.h"
|
|
|
+#include "model_detector.h"
|
|
|
+#include "stable_diffusion_wrapper.h"
|
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
|
// File extension mappings for each model type
|
|
|
const std::vector<std::string> CHECKPOINT_FILE_EXTENSIONS = {"safetensors", "ckpt", "gguf"};
|
|
|
-const std::vector<std::string> EMBEDDING_FILE_EXTENSIONS = {"safetensors", "pt"};
|
|
|
-const std::vector<std::string> LORA_FILE_EXTENSIONS = {"safetensors", "ckpt"};
|
|
|
-const std::vector<std::string> VAE_FILE_EXTENSIONS = {"safetensors", "pt", "ckpt", "gguf"};
|
|
|
-const std::vector<std::string> TAESD_FILE_EXTENSIONS = {"safetensors", "pth", "gguf"};
|
|
|
-const std::vector<std::string> ESRGAN_FILE_EXTENSIONS = {"pth", "pt"};
|
|
|
+const std::vector<std::string> EMBEDDING_FILE_EXTENSIONS = {"safetensors", "pt"};
|
|
|
+const std::vector<std::string> LORA_FILE_EXTENSIONS = {"safetensors", "ckpt"};
|
|
|
+const std::vector<std::string> VAE_FILE_EXTENSIONS = {"safetensors", "pt", "ckpt", "gguf"};
|
|
|
+const std::vector<std::string> TAESD_FILE_EXTENSIONS = {"safetensors", "pth", "gguf"};
|
|
|
+const std::vector<std::string> ESRGAN_FILE_EXTENSIONS = {"pth", "pt"};
|
|
|
const std::vector<std::string> CONTROLNET_FILE_EXTENSIONS = {"safetensors", "pth"};
|
|
|
|
|
|
class ModelManager::Impl {
|
|
|
@@ -182,7 +182,7 @@ public:
|
|
|
// First check configured directories (if any)
|
|
|
for (const auto& [type, directory] : modelTypeDirectories) {
|
|
|
if (!directory.empty()) {
|
|
|
- fs::path absoluteDirPath = fs::absolute(directory).lexically_normal();
|
|
|
+ fs::path absoluteDirPath = fs::absolute(directory).lexically_normal();
|
|
|
fs::path normalizedFilePath = absoluteFilePath.lexically_normal();
|
|
|
|
|
|
// Check if the file is under this directory (directly or in subdirectories)
|
|
|
@@ -191,9 +191,9 @@ public:
|
|
|
|
|
|
// If relative path doesn't start with "..", then file is under the directory
|
|
|
std::string relPathStr = relativePath.string();
|
|
|
- bool isUnderDirectory = !relPathStr.empty() &&
|
|
|
- relPathStr.substr(0, 2) != ".." &&
|
|
|
- relPathStr[0] != '/';
|
|
|
+ bool isUnderDirectory = !relPathStr.empty() &&
|
|
|
+ relPathStr.substr(0, 2) != ".." &&
|
|
|
+ relPathStr[0] != '/';
|
|
|
|
|
|
if (isUnderDirectory && isExtensionMatch(extension, type)) {
|
|
|
return type;
|
|
|
@@ -263,11 +263,11 @@ public:
|
|
|
* @return std::pair<bool, std::pair<uintmax_t, fs::file_time_type>> Success flag and file info
|
|
|
*/
|
|
|
std::pair<bool, std::pair<uintmax_t, fs::file_time_type>> getFileInfoWithTimeout(
|
|
|
- const fs::path& filePath, int timeoutMs = 5000) {
|
|
|
-
|
|
|
+ const fs::path& filePath,
|
|
|
+ int timeoutMs = 5000) {
|
|
|
auto future = std::async(std::launch::async, [&filePath]() -> std::pair<uintmax_t, fs::file_time_type> {
|
|
|
try {
|
|
|
- uintmax_t fileSize = fs::file_size(filePath);
|
|
|
+ uintmax_t fileSize = fs::file_size(filePath);
|
|
|
fs::file_time_type modifiedAt = fs::last_write_time(filePath);
|
|
|
return {fileSize, modifiedAt};
|
|
|
} catch (const fs::filesystem_error&) {
|
|
|
@@ -312,7 +312,7 @@ public:
|
|
|
}
|
|
|
|
|
|
if (entry.is_regular_file()) {
|
|
|
- fs::path filePath = entry.path();
|
|
|
+ fs::path filePath = entry.path();
|
|
|
ModelType detectedType = determineModelType(filePath);
|
|
|
|
|
|
// Only add files that have a valid model type (not NONE)
|
|
|
@@ -350,24 +350,24 @@ public:
|
|
|
|
|
|
// Check if model already exists to avoid duplicates
|
|
|
if (modelsMap.find(modelName) == modelsMap.end()) {
|
|
|
- info.name = modelName;
|
|
|
- info.path = filePath.string();
|
|
|
- info.fullPath = fs::absolute(filePath).string();
|
|
|
- info.type = detectedType;
|
|
|
- info.isLoaded = false;
|
|
|
- info.description = ""; // Initialize description
|
|
|
- info.metadata = {}; // Initialize metadata
|
|
|
+ info.name = modelName;
|
|
|
+ info.path = filePath.string();
|
|
|
+ info.fullPath = fs::absolute(filePath).string();
|
|
|
+ info.type = detectedType;
|
|
|
+ info.isLoaded = false;
|
|
|
+ info.description = ""; // Initialize description
|
|
|
+ info.metadata = {}; // Initialize metadata
|
|
|
|
|
|
// Get file info with timeout
|
|
|
auto [success, fileInfo] = getFileInfoWithTimeout(filePath);
|
|
|
if (success) {
|
|
|
- info.fileSize = fileInfo.first;
|
|
|
+ info.fileSize = fileInfo.first;
|
|
|
info.modifiedAt = fileInfo.second;
|
|
|
- info.createdAt = fileInfo.second; // Use modified time as creation time for now
|
|
|
+ info.createdAt = fileInfo.second; // Use modified time as creation time for now
|
|
|
} else {
|
|
|
- info.fileSize = 0;
|
|
|
+ info.fileSize = 0;
|
|
|
info.modifiedAt = fs::file_time_type{};
|
|
|
- info.createdAt = fs::file_time_type{};
|
|
|
+ info.createdAt = fs::file_time_type{};
|
|
|
}
|
|
|
|
|
|
// Try to load cached hash from .json file
|
|
|
@@ -382,10 +382,10 @@ public:
|
|
|
info.sha256 = "";
|
|
|
}
|
|
|
} catch (...) {
|
|
|
- info.sha256 = ""; // If parsing fails, leave empty
|
|
|
+ info.sha256 = ""; // If parsing fails, leave empty
|
|
|
}
|
|
|
} else {
|
|
|
- info.sha256 = ""; // No cached hash file
|
|
|
+ info.sha256 = ""; // No cached hash file
|
|
|
}
|
|
|
|
|
|
// Detect architecture for checkpoint models (including diffusion_models)
|
|
|
@@ -393,23 +393,23 @@ public:
|
|
|
// Try to get cached result first
|
|
|
ModelDetectionCache::CacheEntry cachedEntry =
|
|
|
ModelDetectionCache::getCachedResult(info.fullPath, info.modifiedAt);
|
|
|
-
|
|
|
+
|
|
|
if (cachedEntry.isValid) {
|
|
|
// Use cached results
|
|
|
- info.architecture = cachedEntry.architecture;
|
|
|
- info.recommendedVAE = cachedEntry.recommendedVAE;
|
|
|
- info.recommendedWidth = cachedEntry.recommendedWidth;
|
|
|
- info.recommendedHeight = cachedEntry.recommendedHeight;
|
|
|
- info.recommendedSteps = cachedEntry.recommendedSteps;
|
|
|
- info.recommendedSampler = cachedEntry.recommendedSampler;
|
|
|
- info.requiredModels = cachedEntry.requiredModels;
|
|
|
- info.missingModels = cachedEntry.missingModels;
|
|
|
- info.cacheValid = true;
|
|
|
- info.cacheModifiedAt = cachedEntry.cachedAt;
|
|
|
- info.cachePathType = cachedEntry.pathType;
|
|
|
+ info.architecture = cachedEntry.architecture;
|
|
|
+ info.recommendedVAE = cachedEntry.recommendedVAE;
|
|
|
+ info.recommendedWidth = cachedEntry.recommendedWidth;
|
|
|
+ info.recommendedHeight = cachedEntry.recommendedHeight;
|
|
|
+ info.recommendedSteps = cachedEntry.recommendedSteps;
|
|
|
+ info.recommendedSampler = cachedEntry.recommendedSampler;
|
|
|
+ info.requiredModels = cachedEntry.requiredModels;
|
|
|
+ info.missingModels = cachedEntry.missingModels;
|
|
|
+ info.cacheValid = true;
|
|
|
+ info.cacheModifiedAt = cachedEntry.cachedAt;
|
|
|
+ info.cachePathType = cachedEntry.pathType;
|
|
|
info.useFolderBasedDetection = (cachedEntry.detectionSource == "folder");
|
|
|
- info.detectionSource = cachedEntry.detectionSource;
|
|
|
-
|
|
|
+ info.detectionSource = cachedEntry.detectionSource;
|
|
|
+
|
|
|
if (verbose) {
|
|
|
std::cout << "Using cached detection for " << info.name
|
|
|
<< " (source: " << cachedEntry.detectionSource << ")" << std::endl;
|
|
|
@@ -418,57 +418,55 @@ public:
|
|
|
// Perform new detection
|
|
|
try {
|
|
|
// First try folder-based detection
|
|
|
- std::string checkpointsDir = getModelTypeDirectory(ModelType::CHECKPOINT);
|
|
|
+ std::string checkpointsDir = getModelTypeDirectory(ModelType::CHECKPOINT);
|
|
|
std::string diffusionModelsDir = getModelTypeDirectory(ModelType::DIFFUSION_MODELS);
|
|
|
- std::string pathType = ModelPathSelector::selectPathType(
|
|
|
+ std::string pathType = ModelPathSelector::selectPathType(
|
|
|
info.fullPath, checkpointsDir, diffusionModelsDir, verbose);
|
|
|
-
|
|
|
+
|
|
|
bool useFolderBasedDetection = (pathType == "diffusion_model_path");
|
|
|
-
|
|
|
+
|
|
|
ModelDetectionResult detection;
|
|
|
std::string detectionSource;
|
|
|
-
|
|
|
+
|
|
|
if (useFolderBasedDetection) {
|
|
|
// For models in diffusion_models directory, we can skip full detection
|
|
|
// and use folder-based logic
|
|
|
- detectionSource = "folder";
|
|
|
- info.architecture = "Modern Architecture (Flux/SD3)";
|
|
|
- info.recommendedVAE = "ae.safetensors";
|
|
|
- info.recommendedWidth = 1024;
|
|
|
- info.recommendedHeight = 1024;
|
|
|
- info.recommendedSteps = 20;
|
|
|
+ detectionSource = "folder";
|
|
|
+ info.architecture = "Modern Architecture (Flux/SD3)";
|
|
|
+ info.recommendedVAE = "ae.safetensors";
|
|
|
+ info.recommendedWidth = 1024;
|
|
|
+ info.recommendedHeight = 1024;
|
|
|
+ info.recommendedSteps = 20;
|
|
|
info.recommendedSampler = "euler";
|
|
|
-
|
|
|
+
|
|
|
// Create a minimal detection result for caching
|
|
|
- detection.architecture = ModelArchitecture::FLUX_DEV; // Default modern
|
|
|
- detection.architectureName = info.architecture;
|
|
|
- detection.recommendedVAE = info.recommendedVAE;
|
|
|
- detection.suggestedParams["width"] = std::to_string(info.recommendedWidth);
|
|
|
- detection.suggestedParams["height"] = std::to_string(info.recommendedHeight);
|
|
|
- detection.suggestedParams["steps"] = std::to_string(info.recommendedSteps);
|
|
|
+ detection.architecture = ModelArchitecture::FLUX_DEV; // Default modern
|
|
|
+ detection.architectureName = info.architecture;
|
|
|
+ detection.recommendedVAE = info.recommendedVAE;
|
|
|
+ detection.suggestedParams["width"] = std::to_string(info.recommendedWidth);
|
|
|
+ detection.suggestedParams["height"] = std::to_string(info.recommendedHeight);
|
|
|
+ detection.suggestedParams["steps"] = std::to_string(info.recommendedSteps);
|
|
|
detection.suggestedParams["sampler"] = info.recommendedSampler;
|
|
|
-
|
|
|
- if (verbose) {
|
|
|
- std::cout << "Using folder-based detection for " << info.name
|
|
|
- << " in " << pathType << std::endl;
|
|
|
- }
|
|
|
+
|
|
|
+ LOG_DEBUG("Using folder-based detection for: " + info.name + " in " + pathType);
|
|
|
+
|
|
|
} else {
|
|
|
// Perform full architecture detection
|
|
|
detectionSource = "architecture";
|
|
|
- detection = ModelDetector::detectModel(info.fullPath);
|
|
|
-
|
|
|
+ detection = ModelDetector::detectModel(info.fullPath);
|
|
|
+
|
|
|
// For .ckpt files that can't be detected, default to SD1.5
|
|
|
if (detection.architecture == ModelArchitecture::UNKNOWN &&
|
|
|
(filePath.extension() == ".ckpt" || filePath.extension() == ".pt")) {
|
|
|
- info.architecture = "Stable Diffusion 1.5 (assumed)";
|
|
|
- info.recommendedVAE = "vae-ft-mse-840000-ema-pruned.safetensors";
|
|
|
- info.recommendedWidth = 512;
|
|
|
- info.recommendedHeight = 512;
|
|
|
- info.recommendedSteps = 20;
|
|
|
+ info.architecture = "Stable Diffusion 1.5 (assumed)";
|
|
|
+ info.recommendedVAE = "vae-ft-mse-840000-ema-pruned.safetensors";
|
|
|
+ info.recommendedWidth = 512;
|
|
|
+ info.recommendedHeight = 512;
|
|
|
+ info.recommendedSteps = 20;
|
|
|
info.recommendedSampler = "euler_a";
|
|
|
- detectionSource = "fallback";
|
|
|
+ detectionSource = "fallback";
|
|
|
} else {
|
|
|
- info.architecture = detection.architectureName;
|
|
|
+ info.architecture = detection.architectureName;
|
|
|
info.recommendedVAE = detection.recommendedVAE;
|
|
|
|
|
|
// Parse recommended parameters
|
|
|
@@ -485,10 +483,8 @@ public:
|
|
|
info.recommendedSampler = detection.suggestedParams["sampler"];
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if (verbose) {
|
|
|
- LOG_INFO("Using architecture-based detection for " + info.name);
|
|
|
- }
|
|
|
+
|
|
|
+ LOG_DEBUG("Using architecture-based detection for " + info.name);
|
|
|
}
|
|
|
|
|
|
// Build list of required models based on detection
|
|
|
@@ -523,44 +519,40 @@ public:
|
|
|
// Create a temporary ModelManager instance to check existence
|
|
|
ModelManager tempManager;
|
|
|
std::vector<ModelDetails> modelDetails = tempManager.checkRequiredModelsExistence(info.requiredModels);
|
|
|
-
|
|
|
+
|
|
|
// Clear missing models and repopulate based on existence check
|
|
|
info.missingModels.clear();
|
|
|
-
|
|
|
+
|
|
|
for (const auto& detail : modelDetails) {
|
|
|
if (!detail.exists) {
|
|
|
info.missingModels.push_back(detail.type + ": " + detail.name);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- if (verbose) {
|
|
|
- std::cout << "Model " << info.name << " requires " << info.requiredModels.size()
|
|
|
- << " models, " << info.missingModels.size() << " are missing" << std::endl;
|
|
|
- }
|
|
|
+
|
|
|
+ LOG_DEBUG("Model " + info.name + " required " + std::to_string(info.requiredModels.size()) + " models, " + std::to_string(info.missingModels.size()) + " are missing");
|
|
|
}
|
|
|
|
|
|
// Cache the detection result
|
|
|
ModelDetectionCache::cacheDetectionResult(
|
|
|
info.fullPath, detection, pathType, detectionSource, info.modifiedAt);
|
|
|
-
|
|
|
- info.cacheValid = true;
|
|
|
- info.cacheModifiedAt = std::filesystem::file_time_type::clock::now();
|
|
|
- info.cachePathType = pathType;
|
|
|
+
|
|
|
+ info.cacheValid = true;
|
|
|
+ info.cacheModifiedAt = std::filesystem::file_time_type::clock::now();
|
|
|
+ info.cachePathType = pathType;
|
|
|
info.useFolderBasedDetection = useFolderBasedDetection;
|
|
|
- info.detectionSource = detectionSource;
|
|
|
-
|
|
|
+ info.detectionSource = detectionSource;
|
|
|
+
|
|
|
} catch (const std::exception& e) {
|
|
|
// If detection fails completely, default to SD1.5
|
|
|
- info.architecture = "Stable Diffusion 1.5 (assumed)";
|
|
|
- info.recommendedVAE = "vae-ft-mse-840000-ema-pruned.safetensors";
|
|
|
- info.recommendedWidth = 512;
|
|
|
- info.recommendedHeight = 512;
|
|
|
- info.recommendedSteps = 20;
|
|
|
+ info.architecture = "Stable Diffusion 1.5 (assumed)";
|
|
|
+ info.recommendedVAE = "vae-ft-mse-840000-ema-pruned.safetensors";
|
|
|
+ info.recommendedWidth = 512;
|
|
|
+ info.recommendedHeight = 512;
|
|
|
+ info.recommendedSteps = 20;
|
|
|
info.recommendedSampler = "euler_a";
|
|
|
- info.detectionSource = "fallback";
|
|
|
-
|
|
|
- std::cerr << "Detection failed for " << info.name << ": " << e.what()
|
|
|
- << ", using SD1.5 defaults" << std::endl;
|
|
|
+ info.detectionSource = "fallback";
|
|
|
+
|
|
|
+ LOG_ERROR("Detection failed for " + info.name + ": " + e.what() + ", using sd1.5 defaults");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -599,15 +591,14 @@ bool ModelManager::scanModelsDirectory() {
|
|
|
|
|
|
// Scan all configured directories for all model types recursively
|
|
|
// We scan each directory once and detect all model types within it
|
|
|
- std::set<std::string> scannedDirectories; // Avoid scanning the same directory multiple times
|
|
|
+ std::set<std::string> scannedDirectories; // Avoid scanning the same directory multiple times
|
|
|
std::vector<std::string> directoriesToScan;
|
|
|
|
|
|
// Collect unique directories to scan
|
|
|
std::vector<ModelType> allTypes = {
|
|
|
ModelType::CHECKPOINT, ModelType::CONTROLNET, ModelType::LORA,
|
|
|
ModelType::VAE, ModelType::TAESD, ModelType::ESRGAN, ModelType::EMBEDDING,
|
|
|
- ModelType::DIFFUSION_MODELS
|
|
|
- };
|
|
|
+ ModelType::DIFFUSION_MODELS};
|
|
|
|
|
|
for (const auto& type : allTypes) {
|
|
|
std::string dirPath = pImpl->getModelTypeDirectory(type);
|
|
|
@@ -660,8 +651,8 @@ bool ModelManager::loadModel(const std::string& name, const std::string& path, M
|
|
|
// Set up generation parameters for model loading
|
|
|
StableDiffusionWrapper::GenerationParams loadParams;
|
|
|
loadParams.modelPath = path;
|
|
|
- loadParams.modelType = "f16"; // Default to f16 for better performance
|
|
|
- loadParams.verbose = pImpl->verbose;
|
|
|
+ loadParams.modelType = "f16"; // Default to f16 for better performance
|
|
|
+ loadParams.verbose = pImpl->verbose;
|
|
|
|
|
|
// Try to detect model type automatically for checkpoint and diffusion models
|
|
|
if (type == ModelType::CHECKPOINT || type == ModelType::DIFFUSION_MODELS) {
|
|
|
@@ -686,7 +677,7 @@ bool ModelManager::loadModel(const std::string& name, const std::string& path, M
|
|
|
std::string vaeDirectory = getModelTypeDirectory(ModelType::VAE);
|
|
|
if (!vaeDirectory.empty()) {
|
|
|
std::string resolvedVAEPath = vaeDirectory + "/" + detection.recommendedVAE;
|
|
|
-
|
|
|
+
|
|
|
// Check if the resolved VAE file exists before setting the path
|
|
|
if (fs::exists(resolvedVAEPath) && fs::is_regular_file(resolvedVAEPath)) {
|
|
|
loadParams.vaePath = resolvedVAEPath;
|
|
|
@@ -725,7 +716,25 @@ bool ModelManager::loadModel(const std::string& name, const std::string& path, M
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Try to load the model
|
|
|
+ // Handle upscaler models differently - they don't need to be pre-loaded
|
|
|
+ if (type == ModelType::ESRGAN || type == ModelType::UPSCALER) {
|
|
|
+ if (pImpl->verbose) {
|
|
|
+ std::cout << "Upscaler model '" << name << "' does not need pre-loading, marking as available for use" << std::endl;
|
|
|
+ }
|
|
|
+
|
|
|
+ // For upscaler models, we don't create a wrapper or call loadModel
|
|
|
+ // They are loaded dynamically during upscaling
|
|
|
+ pImpl->loadedModels[name] = nullptr; // Mark as "loaded" but with null wrapper
|
|
|
+
|
|
|
+ // Update model info
|
|
|
+ if (pImpl->availableModels.find(name) != pImpl->availableModels.end()) {
|
|
|
+ pImpl->availableModels[name].isLoaded = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Try to load the model (for checkpoint and diffusion models)
|
|
|
if (!wrapper->loadModel(path, loadParams)) {
|
|
|
std::cerr << "Failed to load model '" << name << "': " << wrapper->getLastError() << std::endl;
|
|
|
return false;
|
|
|
@@ -739,24 +748,24 @@ bool ModelManager::loadModel(const std::string& name, const std::string& path, M
|
|
|
} else {
|
|
|
// Create a new model info entry
|
|
|
ModelInfo info;
|
|
|
- info.name = name;
|
|
|
- info.path = path;
|
|
|
- info.fullPath = fs::absolute(path).string();
|
|
|
- info.type = type;
|
|
|
- info.isLoaded = true;
|
|
|
- info.sha256 = "";
|
|
|
- info.description = ""; // Initialize description
|
|
|
- info.metadata = {}; // Initialize metadata
|
|
|
+ info.name = name;
|
|
|
+ info.path = path;
|
|
|
+ info.fullPath = fs::absolute(path).string();
|
|
|
+ info.type = type;
|
|
|
+ info.isLoaded = true;
|
|
|
+ info.sha256 = "";
|
|
|
+ info.description = ""; // Initialize description
|
|
|
+ info.metadata = {}; // Initialize metadata
|
|
|
|
|
|
try {
|
|
|
- info.fileSize = fs::file_size(path);
|
|
|
+ info.fileSize = fs::file_size(path);
|
|
|
info.modifiedAt = fs::last_write_time(path);
|
|
|
- info.createdAt = info.modifiedAt; // Use modified time as creation time for now
|
|
|
+ info.createdAt = info.modifiedAt; // Use modified time as creation time for now
|
|
|
} catch (const fs::filesystem_error& e) {
|
|
|
std::cerr << "Error getting file info for " << path << ": " << e.what() << std::endl;
|
|
|
- info.fileSize = 0;
|
|
|
+ info.fileSize = 0;
|
|
|
info.modifiedAt = fs::file_time_type{};
|
|
|
- info.createdAt = fs::file_time_type{};
|
|
|
+ info.createdAt = fs::file_time_type{};
|
|
|
}
|
|
|
|
|
|
pImpl->availableModels[name] = info;
|
|
|
@@ -787,12 +796,55 @@ bool ModelManager::loadModel(const std::string& name) {
|
|
|
// Extract path and type while we have the lock
|
|
|
path = it->second.path;
|
|
|
type = it->second.type;
|
|
|
- } // Release lock here
|
|
|
+ } // Release lock here
|
|
|
|
|
|
// Load the model without holding the lock
|
|
|
return loadModel(name, path, type);
|
|
|
}
|
|
|
|
|
|
+bool ModelManager::loadModel(const std::string& name, std::function<void(float)> progressCallback) {
|
|
|
+ std::string path;
|
|
|
+ ModelType type;
|
|
|
+
|
|
|
+ {
|
|
|
+ std::unique_lock<std::shared_mutex> lock(pImpl->modelsMutex);
|
|
|
+
|
|
|
+ // Check if model exists in available models
|
|
|
+ auto it = pImpl->availableModels.find(name);
|
|
|
+ if (it == pImpl->availableModels.end()) {
|
|
|
+ std::cerr << "Model '" << name << "' not found in available models" << std::endl;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Check if already loaded
|
|
|
+ if (pImpl->loadedModels.find(name) != pImpl->loadedModels.end()) {
|
|
|
+ if (progressCallback) {
|
|
|
+ progressCallback(1.0f); // 100% if already loaded
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Extract path and type while we have the lock
|
|
|
+ path = it->second.path;
|
|
|
+ type = it->second.type;
|
|
|
+ } // Release lock here
|
|
|
+
|
|
|
+ // Start loading progress
|
|
|
+ if (progressCallback) {
|
|
|
+ progressCallback(0.0f); // 0% - starting to load
|
|
|
+ }
|
|
|
+
|
|
|
+ // Load the model with progress tracking
|
|
|
+ bool result = loadModel(name, path, type);
|
|
|
+
|
|
|
+ // Finish loading progress
|
|
|
+ if (progressCallback) {
|
|
|
+ progressCallback(result ? 1.0f : 0.0f); // 100% if successful, 0% if failed
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
bool ModelManager::unloadModel(const std::string& name) {
|
|
|
std::unique_lock<std::shared_mutex> lock(pImpl->modelsMutex);
|
|
|
|
|
|
@@ -851,7 +903,7 @@ ModelManager::ModelInfo ModelManager::getModelInfo(const std::string& name) cons
|
|
|
|
|
|
auto it = pImpl->availableModels.find(name);
|
|
|
if (it == pImpl->availableModels.end()) {
|
|
|
- return ModelInfo{}; // Return empty ModelInfo if not found
|
|
|
+ return ModelInfo{}; // Return empty ModelInfo if not found
|
|
|
}
|
|
|
|
|
|
return it->second;
|
|
|
@@ -982,8 +1034,8 @@ std::map<ModelType, std::string> ModelManager::getAllModelTypeDirectories() cons
|
|
|
return pImpl->modelTypeDirectories;
|
|
|
}
|
|
|
|
|
|
- // Legacy resetToLegacyDirectories method removed
|
|
|
- // Using explicit directory configuration only
|
|
|
+// Legacy resetToLegacyDirectories method removed
|
|
|
+// Using explicit directory configuration only
|
|
|
|
|
|
bool ModelManager::configureFromServerConfig(const ServerConfig& config) {
|
|
|
std::unique_lock<std::shared_mutex> lock(pImpl->modelsMutex);
|
|
|
@@ -1041,19 +1093,19 @@ void ModelManager::cancelScan() {
|
|
|
|
|
|
void ModelManager::unloadAllModels() {
|
|
|
std::unique_lock<std::shared_mutex> lock(pImpl->modelsMutex);
|
|
|
-
|
|
|
+
|
|
|
// Create a list of loaded model names to avoid modifying map while iterating
|
|
|
std::vector<std::string> loadedModelNames;
|
|
|
for (const auto& [name, wrapper] : pImpl->loadedModels) {
|
|
|
loadedModelNames.push_back(name);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Unload each model
|
|
|
for (const auto& modelName : loadedModelNames) {
|
|
|
if (pImpl->verbose) {
|
|
|
std::cout << "Unloading model: " << modelName << std::endl;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
auto it = pImpl->loadedModels.find(modelName);
|
|
|
if (it != pImpl->loadedModels.end()) {
|
|
|
// Unload the model properly
|
|
|
@@ -1063,12 +1115,12 @@ void ModelManager::unloadAllModels() {
|
|
|
pImpl->loadedModels.erase(it);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Update model info for all unloaded models
|
|
|
for (auto& [name, info] : pImpl->availableModels) {
|
|
|
info.isLoaded = false;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (pImpl->verbose) {
|
|
|
std::cout << "Unloaded " << loadedModelNames.size() << " models" << std::endl;
|
|
|
}
|
|
|
@@ -1110,7 +1162,7 @@ std::string ModelManager::computeModelHash(const std::string& modelName) {
|
|
|
char buffer[bufferSize];
|
|
|
|
|
|
// Hash computation output removed from stdout
|
|
|
- size_t totalRead = 0;
|
|
|
+ size_t totalRead = 0;
|
|
|
size_t lastReportedMB = 0;
|
|
|
|
|
|
while (file.read(buffer, bufferSize) || file.gcount() > 0) {
|
|
|
@@ -1198,13 +1250,13 @@ bool ModelManager::saveModelHashToFile(const std::string& modelName, const std::
|
|
|
}
|
|
|
|
|
|
std::string jsonPath = it->second.fullPath + ".json";
|
|
|
- size_t fileSize = it->second.fileSize;
|
|
|
+ size_t fileSize = it->second.fileSize;
|
|
|
lock.unlock();
|
|
|
|
|
|
try {
|
|
|
nlohmann::json j;
|
|
|
- j["sha256"] = hash;
|
|
|
- j["file_size"] = fileSize;
|
|
|
+ j["sha256"] = hash;
|
|
|
+ j["file_size"] = fileSize;
|
|
|
j["computed_at"] = std::chrono::system_clock::now().time_since_epoch().count();
|
|
|
|
|
|
std::ofstream jsonFile(jsonPath);
|
|
|
@@ -1286,49 +1338,48 @@ std::string ModelManager::ModelPathSelector::selectPathType(
|
|
|
const std::string& checkpointsDir,
|
|
|
const std::string& diffusionModelsDir,
|
|
|
bool verbose) {
|
|
|
-
|
|
|
-if (verbose) {
|
|
|
+ if (verbose) {
|
|
|
LOG_INFO("Selecting path type for model: " + modelPath);
|
|
|
LOG_INFO("Checkpoints directory: " + checkpointsDir);
|
|
|
LOG_INFO("Diffusion models directory: " + diffusionModelsDir);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Check if model is in diffusion_models directory first (priority)
|
|
|
if (!diffusionModelsDir.empty() && isModelInDirectory(modelPath, diffusionModelsDir)) {
|
|
|
-if (verbose) {
|
|
|
- LOG_INFO("Model is in diffusion_models directory, using diffusion_model_path");
|
|
|
- }
|
|
|
+ if (verbose) {
|
|
|
+ LOG_INFO("Model is in diffusion_models directory, using diffusion_model_path");
|
|
|
+ }
|
|
|
return "diffusion_model_path";
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Check if model is in checkpoints directory
|
|
|
if (!checkpointsDir.empty() && isModelInDirectory(modelPath, checkpointsDir)) {
|
|
|
-if (verbose) {
|
|
|
- LOG_INFO("Model is in checkpoints directory, using model_path");
|
|
|
- }
|
|
|
+ if (verbose) {
|
|
|
+ LOG_INFO("Model is in checkpoints directory, using model_path");
|
|
|
+ }
|
|
|
return "model_path";
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Fallback: use directory name detection
|
|
|
std::filesystem::path modelFilePath(modelPath);
|
|
|
std::filesystem::path parentDir = modelFilePath.parent_path();
|
|
|
-
|
|
|
+
|
|
|
if (parentDir.filename().string() == "diffusion_models") {
|
|
|
-if (verbose) {
|
|
|
- LOG_INFO("Model is in diffusion_models directory (detected from path), using diffusion_model_path");
|
|
|
- }
|
|
|
+ if (verbose) {
|
|
|
+ LOG_INFO("Model is in diffusion_models directory (detected from path), using diffusion_model_path");
|
|
|
+ }
|
|
|
return "diffusion_model_path";
|
|
|
} else if (parentDir.filename().string() == "checkpoints") {
|
|
|
-if (verbose) {
|
|
|
- LOG_INFO("Model is in checkpoints directory (detected from path), using model_path");
|
|
|
- }
|
|
|
+ if (verbose) {
|
|
|
+ LOG_INFO("Model is in checkpoints directory (detected from path), using model_path");
|
|
|
+ }
|
|
|
return "model_path";
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Default fallback for unknown locations
|
|
|
-if (verbose) {
|
|
|
- LOG_INFO("Model location unknown, defaulting to model_path for backward compatibility");
|
|
|
- }
|
|
|
+ if (verbose) {
|
|
|
+ LOG_INFO("Model location unknown, defaulting to model_path for backward compatibility");
|
|
|
+ }
|
|
|
return "model_path";
|
|
|
}
|
|
|
|
|
|
@@ -1336,20 +1387,20 @@ bool ModelManager::ModelPathSelector::isModelInDirectory(const std::string& mode
|
|
|
if (modelPath.empty() || directory.empty()) {
|
|
|
return false;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
std::filesystem::path absoluteModelPath = std::filesystem::absolute(modelPath).lexically_normal();
|
|
|
- std::filesystem::path absoluteDirPath = std::filesystem::absolute(directory).lexically_normal();
|
|
|
-
|
|
|
+ std::filesystem::path absoluteDirPath = std::filesystem::absolute(directory).lexically_normal();
|
|
|
+
|
|
|
// Get relative path from directory to model
|
|
|
- auto relativePath = absoluteModelPath.lexically_relative(absoluteDirPath);
|
|
|
+ auto relativePath = absoluteModelPath.lexically_relative(absoluteDirPath);
|
|
|
std::string relPathStr = relativePath.string();
|
|
|
-
|
|
|
+
|
|
|
// Check if the relative path doesn't start with ".." and is not empty
|
|
|
bool isUnderDirectory = !relPathStr.empty() &&
|
|
|
- relPathStr.substr(0, 2) != ".." &&
|
|
|
- relPathStr[0] != '/';
|
|
|
-
|
|
|
+ relPathStr.substr(0, 2) != ".." &&
|
|
|
+ relPathStr[0] != '/';
|
|
|
+
|
|
|
return isUnderDirectory;
|
|
|
} catch (const std::filesystem::filesystem_error& e) {
|
|
|
std::cerr << "Error checking if model is in directory: " << e.what() << std::endl;
|
|
|
@@ -1364,26 +1415,25 @@ std::mutex ModelManager::ModelDetectionCache::cacheMutex_;
|
|
|
ModelManager::ModelDetectionCache::CacheEntry ModelManager::ModelDetectionCache::getCachedResult(
|
|
|
const std::string& modelPath,
|
|
|
const std::filesystem::file_time_type& currentModifiedTime) {
|
|
|
-
|
|
|
std::lock_guard<std::mutex> lock(cacheMutex_);
|
|
|
-
|
|
|
+
|
|
|
auto it = cache_.find(modelPath);
|
|
|
if (it == cache_.end()) {
|
|
|
- return CacheEntry{}; // Return invalid entry if not found
|
|
|
+ return CacheEntry{}; // Return invalid entry if not found
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
const CacheEntry& entry = it->second;
|
|
|
-
|
|
|
+
|
|
|
// Check if cache is still valid (file hasn't been modified)
|
|
|
if (entry.fileModifiedAt == currentModifiedTime && entry.isValid) {
|
|
|
std::cout << "Using cached detection result for: " << modelPath << std::endl;
|
|
|
return entry;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Cache is stale, remove it
|
|
|
cache_.erase(it);
|
|
|
std::cout << "Cache entry expired for: " << modelPath << std::endl;
|
|
|
- return CacheEntry{}; // Return invalid entry
|
|
|
+ return CacheEntry{}; // Return invalid entry
|
|
|
}
|
|
|
|
|
|
void ModelManager::ModelDetectionCache::cacheDetectionResult(
|
|
|
@@ -1392,22 +1442,21 @@ void ModelManager::ModelDetectionCache::cacheDetectionResult(
|
|
|
const std::string& pathType,
|
|
|
const std::string& detectionSource,
|
|
|
const std::filesystem::file_time_type& fileModifiedTime) {
|
|
|
-
|
|
|
std::lock_guard<std::mutex> lock(cacheMutex_);
|
|
|
-
|
|
|
+
|
|
|
CacheEntry entry;
|
|
|
- entry.architecture = detection.architectureName;
|
|
|
- entry.recommendedVAE = detection.recommendedVAE;
|
|
|
- entry.recommendedWidth = 0;
|
|
|
- entry.recommendedHeight = 0;
|
|
|
- entry.recommendedSteps = 0;
|
|
|
+ entry.architecture = detection.architectureName;
|
|
|
+ entry.recommendedVAE = detection.recommendedVAE;
|
|
|
+ entry.recommendedWidth = 0;
|
|
|
+ entry.recommendedHeight = 0;
|
|
|
+ entry.recommendedSteps = 0;
|
|
|
entry.recommendedSampler = "";
|
|
|
- entry.pathType = pathType;
|
|
|
- entry.detectionSource = detectionSource;
|
|
|
- entry.cachedAt = std::filesystem::file_time_type::clock::now();
|
|
|
- entry.fileModifiedAt = fileModifiedTime;
|
|
|
- entry.isValid = true;
|
|
|
-
|
|
|
+ entry.pathType = pathType;
|
|
|
+ entry.detectionSource = detectionSource;
|
|
|
+ entry.cachedAt = std::filesystem::file_time_type::clock::now();
|
|
|
+ entry.fileModifiedAt = fileModifiedTime;
|
|
|
+ entry.isValid = true;
|
|
|
+
|
|
|
// Parse recommended parameters
|
|
|
for (const auto& [param, value] : detection.suggestedParams) {
|
|
|
if (param == "width") {
|
|
|
@@ -1420,51 +1469,52 @@ void ModelManager::ModelDetectionCache::cacheDetectionResult(
|
|
|
entry.recommendedSampler = value;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Build list of required models
|
|
|
// Note: VAE is now optional for SD1x and SDXL models, so we don't add it to requiredModels
|
|
|
// The VAE will still be recommended but not required
|
|
|
-
|
|
|
+
|
|
|
if (detection.suggestedParams.count("clip_l_required")) {
|
|
|
entry.requiredModels.push_back("CLIP-L: " + detection.suggestedParams.at("clip_l_required"));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (detection.suggestedParams.count("clip_g_required")) {
|
|
|
entry.requiredModels.push_back("CLIP-G: " + detection.suggestedParams.at("clip_g_required"));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (detection.suggestedParams.count("t5xxl_required")) {
|
|
|
entry.requiredModels.push_back("T5XXL: " + detection.suggestedParams.at("t5xxl_required"));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (detection.suggestedParams.count("qwen2vl_required")) {
|
|
|
entry.requiredModels.push_back("Qwen2-VL: " + detection.suggestedParams.at("qwen2vl_required"));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (detection.suggestedParams.count("qwen2vl_vision_required")) {
|
|
|
entry.requiredModels.push_back("Qwen2-VL-Vision: " + detection.suggestedParams.at("qwen2vl_vision_required"));
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Check for missing models and store in cache
|
|
|
if (!entry.requiredModels.empty()) {
|
|
|
// Create a temporary ModelManager instance to check existence
|
|
|
// Note: This is a simplified approach - in a production environment,
|
|
|
// we might want to pass the models directory or use a different approach
|
|
|
std::string baseModelsDir = "/data/SD_MODELS";
|
|
|
-
|
|
|
+
|
|
|
for (const auto& requiredModel : entry.requiredModels) {
|
|
|
size_t colonPos = requiredModel.find(':');
|
|
|
- if (colonPos == std::string::npos) continue;
|
|
|
-
|
|
|
+ if (colonPos == std::string::npos)
|
|
|
+ continue;
|
|
|
+
|
|
|
std::string modelType = requiredModel.substr(0, colonPos);
|
|
|
std::string modelName = requiredModel.substr(colonPos + 1);
|
|
|
-
|
|
|
+
|
|
|
// Trim whitespace
|
|
|
modelType.erase(0, modelType.find_first_not_of(" \t"));
|
|
|
modelType.erase(modelType.find_last_not_of(" \t") + 1);
|
|
|
modelName.erase(0, modelName.find_first_not_of(" \t"));
|
|
|
modelName.erase(modelName.find_last_not_of(" \t") + 1);
|
|
|
-
|
|
|
+
|
|
|
// Determine the appropriate subdirectory
|
|
|
std::string subdirectory;
|
|
|
if (modelType == "VAE") {
|
|
|
@@ -1478,7 +1528,7 @@ void ModelManager::ModelDetectionCache::cacheDetectionResult(
|
|
|
} else if (modelType == "Qwen2-VL" || modelType == "Qwen2-VL-Vision") {
|
|
|
subdirectory = "qwen2vl";
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Check if model exists
|
|
|
std::string fullPath;
|
|
|
if (!subdirectory.empty()) {
|
|
|
@@ -1486,7 +1536,7 @@ void ModelManager::ModelDetectionCache::cacheDetectionResult(
|
|
|
} else {
|
|
|
fullPath = baseModelsDir + "/" + modelName;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
try {
|
|
|
if (!fs::exists(fullPath) || !fs::is_regular_file(fullPath)) {
|
|
|
entry.missingModels.push_back(requiredModel);
|
|
|
@@ -1497,7 +1547,7 @@ void ModelManager::ModelDetectionCache::cacheDetectionResult(
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
cache_[modelPath] = entry;
|
|
|
std::cout << "Cached detection result for: " << modelPath
|
|
|
<< " (source: " << detectionSource << ", path type: " << pathType << ")" << std::endl;
|
|
|
@@ -1505,7 +1555,7 @@ void ModelManager::ModelDetectionCache::cacheDetectionResult(
|
|
|
|
|
|
void ModelManager::ModelDetectionCache::invalidateCache(const std::string& modelPath) {
|
|
|
std::lock_guard<std::mutex> lock(cacheMutex_);
|
|
|
-
|
|
|
+
|
|
|
auto it = cache_.find(modelPath);
|
|
|
if (it != cache_.end()) {
|
|
|
cache_.erase(it);
|
|
|
@@ -1515,7 +1565,7 @@ void ModelManager::ModelDetectionCache::invalidateCache(const std::string& model
|
|
|
|
|
|
void ModelManager::ModelDetectionCache::clearAllCache() {
|
|
|
std::lock_guard<std::mutex> lock(cacheMutex_);
|
|
|
-
|
|
|
+
|
|
|
size_t count = cache_.size();
|
|
|
cache_.clear();
|
|
|
std::cout << "Cleared " << count << " cache entries" << std::endl;
|
|
|
@@ -1523,38 +1573,38 @@ void ModelManager::ModelDetectionCache::clearAllCache() {
|
|
|
|
|
|
std::vector<ModelManager::ModelDetails> ModelManager::checkRequiredModelsExistence(const std::vector<std::string>& requiredModels) {
|
|
|
std::vector<ModelDetails> modelDetails;
|
|
|
-
|
|
|
+
|
|
|
// Base models directory according to project guidelines
|
|
|
std::string baseModelsDir = "/data/SD_MODELS";
|
|
|
-
|
|
|
+
|
|
|
for (const auto& requiredModel : requiredModels) {
|
|
|
ModelDetails details;
|
|
|
-
|
|
|
+
|
|
|
// Parse the required model string (format: "TYPE: filename")
|
|
|
size_t colonPos = requiredModel.find(':');
|
|
|
if (colonPos == std::string::npos) {
|
|
|
// Invalid format, skip
|
|
|
continue;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
std::string modelType = requiredModel.substr(0, colonPos);
|
|
|
std::string modelName = requiredModel.substr(colonPos + 1);
|
|
|
-
|
|
|
+
|
|
|
// Trim whitespace
|
|
|
modelType.erase(0, modelType.find_first_not_of(" \t"));
|
|
|
modelType.erase(modelType.find_last_not_of(" \t") + 1);
|
|
|
modelName.erase(0, modelName.find_first_not_of(" \t"));
|
|
|
modelName.erase(modelName.find_last_not_of(" \t") + 1);
|
|
|
-
|
|
|
- details.name = modelName;
|
|
|
- details.type = modelType;
|
|
|
- details.is_required = true;
|
|
|
+
|
|
|
+ details.name = modelName;
|
|
|
+ details.type = modelType;
|
|
|
+ details.is_required = true;
|
|
|
details.is_recommended = false;
|
|
|
- details.exists = false;
|
|
|
- details.file_size = 0;
|
|
|
- details.path = "";
|
|
|
- details.sha256 = "";
|
|
|
-
|
|
|
+ details.exists = false;
|
|
|
+ details.file_size = 0;
|
|
|
+ details.path = "";
|
|
|
+ details.sha256 = "";
|
|
|
+
|
|
|
// Determine the appropriate subdirectory based on model type
|
|
|
std::string subdirectory;
|
|
|
if (modelType == "VAE") {
|
|
|
@@ -1571,7 +1621,7 @@ std::vector<ModelManager::ModelDetails> ModelManager::checkRequiredModelsExisten
|
|
|
// For unknown types, check in root directory
|
|
|
subdirectory = "";
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Construct the full path to check
|
|
|
std::string fullPath;
|
|
|
if (!subdirectory.empty()) {
|
|
|
@@ -1579,14 +1629,14 @@ std::vector<ModelManager::ModelDetails> ModelManager::checkRequiredModelsExisten
|
|
|
} else {
|
|
|
fullPath = baseModelsDir + "/" + modelName;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
// Check if the file exists
|
|
|
try {
|
|
|
if (fs::exists(fullPath) && fs::is_regular_file(fullPath)) {
|
|
|
- details.exists = true;
|
|
|
- details.path = fs::absolute(fullPath).string();
|
|
|
+ details.exists = true;
|
|
|
+ details.path = fs::absolute(fullPath).string();
|
|
|
details.file_size = fs::file_size(fullPath);
|
|
|
-
|
|
|
+
|
|
|
// Try to get cached hash
|
|
|
std::string jsonPath = fullPath + ".json";
|
|
|
if (fs::exists(jsonPath)) {
|
|
|
@@ -1596,7 +1646,7 @@ std::vector<ModelManager::ModelDetails> ModelManager::checkRequiredModelsExisten
|
|
|
nlohmann::json j;
|
|
|
jsonFile >> j;
|
|
|
jsonFile.close();
|
|
|
-
|
|
|
+
|
|
|
if (j.contains("sha256") && j["sha256"].is_string()) {
|
|
|
details.sha256 = j["sha256"].get<std::string>();
|
|
|
}
|
|
|
@@ -1605,7 +1655,7 @@ std::vector<ModelManager::ModelDetails> ModelManager::checkRequiredModelsExisten
|
|
|
std::cerr << "Error loading hash for " << fullPath << ": " << e.what() << std::endl;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
if (pImpl->verbose) {
|
|
|
std::ostringstream info_oss;
|
|
|
info_oss << "Found required model: " << modelType << " at " << details.path;
|
|
|
@@ -1619,9 +1669,9 @@ std::vector<ModelManager::ModelDetails> ModelManager::checkRequiredModelsExisten
|
|
|
} catch (const fs::filesystem_error& e) {
|
|
|
std::cerr << "Error checking model existence for " << fullPath << ": " << e.what() << std::endl;
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
modelDetails.push_back(details);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
return modelDetails;
|
|
|
}
|