Selaa lähdekoodia

Add --hash-all-models command-line argument to hash all models during startup

- Added hashAllModels flag to ServerConfig structure
- Implemented hashAllModels method in ModelManager with progress reporting
- Added command-line argument parsing for --hash-all-models flag
- Integrated hashing into server startup sequence before starting HTTP server
- Added help text documentation for the new argument
- Server startup is blocked until all models are hashed when flag is used
- Progress is reported as 'Hashing model X of Y: model_name.safetensors'
Fszontagh 3 kuukautta sitten
vanhempi
sitoutus
d8e8f40b42

+ 1 - 0
.roo/mcp.json

@@ -0,0 +1 @@
+{"mcpServers":{}}

+ 53 - 167
AGENTS.md

@@ -1,176 +1,62 @@
-# AGENTS.md - Agent Guide for stable-diffusion.cpp-rest (fszontagh/stable-diffusion.cpp-rest)
-
-This document contains essential information for agents working with the stable-diffusion.cpp-rest codebase.
-
-## Project Overview
-
-This is a C++ REST API server that wraps the stable-diffusion.cpp library, providing HTTP endpoints for image generation with Stable Diffusion models. The project includes both a C++ backend and a Next.js frontend (webui).
-
-
-Always use todo tools to track the status
-
-## Build Commands
-
-### Main Build Process
-```bash
-# Create build directory and configure
-mkdir build && cd build
-cmake ..
-cmake --build . -j
-
-# Alternative: Use the existing rule file command
-cd build
-cmake --build . -j
-```
-Always use the build directory for compiling the project. Use ninja instead make
-
-### Web UI Only
-```bash
-cd webui
-npm install
-npm run build
-```
-
-### Web UI Development Workflow
-```bash
-# For faster development when only changing the webui
-cd webui; npm run build-static
-
-# This builds the webui and copies the static files to build/webui/
-# No need to rebuild the entire server or stop it during webui updates
-# The server will automatically serve the updated static files
-# always cd into the webui directory before run npm
-```
-
-## Project Structure
-
-### C++ Backend
-- `src/` - Source files (.cpp)
-- `include/` - Header files (.h)
-- `CMakeLists.txt` - Main build configuration
-- `.clang-format` - C++ formatting rules (Chromium-based, 4-space indent)
-
-### Web Frontend
-- `webui/` - Next.js React application
-- `webui/package.json` - Node.js dependencies
-- `webui/tsconfig.json` - TypeScript configuration
-- `webui/next.config.ts` - Next.js config (static export with /ui basePath)
-
-### Model Organization
-Models are stored in `/data/SD_MODELS/` (not in project root to keep directory clean). Supported model types include:
-- CHECKPOINT (.safetensors, .pt, .ckpt)
-- LORA models
-- VAE models
-- Various other types (embeddings, upscalers, etc.)
-
-## Code Standards and Conventions
-
-### C++ Standards
-- **C++17** standard
-- **NO using aliases** - Never use aliases like: `using json = nlohmann::json;`
-- Follow **Rule of 5** for class design
-- Use **absolute paths** when printing directory/file names to console
-- **4-space indentation** (configured in .clang-format)
-
-### TypeScript/React Standards
-- **TypeScript** with strict mode enabled
-- **Next.js 16** with React 19
-- **Tailwind CSS** for styling
-- **ESLint** with Next.js configuration
-- Path aliases: `@/*` maps to webui root
-
-## Key Architecture Patterns
-
-### Model Detection System
-The project uses intelligent model detection in `src/model_detector.cpp`:
-- Detects architecture: SD 1.5, SD 2.1, SDXL, Flux, SD3, Qwen2VL
-- Traditional models → `ctxParams.model_path`
-- Modern architectures → `ctxParams.diffusion_model_path`
-- Unknown architectures fallback to traditional loading
-
-### Three-Tier Architecture
-1. **HTTP Server** - Request parsing, response formatting
-2. **Generation Queue** - Sequential job processing, thread-safe
-3. **Model Manager** - Model loading, architecture detection, memory management
-
-## Authentication System
-Multiple authentication methods supported:
-- `none` - No authentication (default)
-- `jwt` - JWT token authentication
-- `api-key` - Static API keys
-- `unix` - Unix system authentication
-- `pam` - PAM authentication (requires libpam0g-dev)
-- `optional` - Authentication optional
-
-
-### Server Testing
-- Start server in background during testing: `cd build; ./src/stable-diffusion-rest-server --models-dir /data/SD_MODELS --port 8082 --host 0.0.0.0 --ui-dir ./webui --verbose`
-- **IMPORTANT**: Never kill running server processes - the user manages server lifecycle
+# AGENTS.md - Non-obvious Information for stable-diffusion.cpp-rest
+
+This document contains only non-obvious, project-specific information that agents need to know.
+
+## Critical Build Gotchas
+
+- **NEVER delete build/_deps folder** - Rebuilding dependencies takes hours
+- Use `cd webui; npm run build-static` for webui-only changes (no server restart needed)
+- Web UI is served from build/webui/ with base path `/ui`
+- Models directory is ALWAYS `/data/SD_MODELS/` (not project root)
+
+## Architecture-Specific Patterns
+
+### Dual Model Loading Paths
+The project uses intelligent model detection with two different loading paths:
+- Traditional models (SD 1.5/2.1/SDXL) → `ctxParams.model_path`
+- Modern architectures (Flux/SD3/Qwen2VL) → `ctxParams.diffusion_model_path`
+
+### String Lifetime Management
+When passing strings to stable-diffusion.cpp:
+- Ensure string lifetime exceeds the entire generation process
+- Use std::string with proper scope management
+- Avoid temporary string objects in API calls
+
+## Server Lifecycle Constraints
+
+- **NEVER kill running server processes** - User manages server lifecycle
 - Agents should not start/stop servers unless explicitly requested
+- Start server in background during testing with full path to models directory
+- Server automatically serves updated webui files after `npm run build-static`
 
-## Important Gotchas
+## Authentication Patterns
 
-### Build System
-- **Keep build/_deps folder** - Rebuilding dependencies takes very long time
-- Web UI automatically builds with main project via CMake
-- CMake automatically downloads and builds stable-diffusion.cpp as external project
-- For webui-only changes, use `npm run build-static` to avoid rebuilding the entire server
-- The `build-static` command copies the built webui to build/webui/ for the server to serve
+- Unix auth generates pseudo-tokens that must be validated properly
+- JWT tokens require proper expiration handling
+- API keys are static and stored in configuration
 
-### Path Management
-- Always use absolute paths when printing to console
-- Models directory: `/data/SD_MODELS/` (not project root)
-- The build wenui directory is in the build folder: build/webui
-- Keep project directory clean - no output/queue directories in root
+## Progress Callback Cleanup
+
+- Progress callbacks must be cleaned up after generation completes
+- Use RAII patterns for callback management
+- Ensure callback deregistration to prevent memory leaks
 
-### Web UI Configuration
-- Next.js configured for static export (`output: 'export'`)
-- Base path: `/ui`
-- Asset prefix: `/ui`
-- No image optimization (`unoptimized: true`)
+## Path Management Requirements
 
-## Development Workflow
+- Always use absolute paths when printing to console (std::cout)
+- Model paths must be fully qualified paths
+- Web UI assets use `/ui` base path in all requests
 
-1. **Code Changes**: Make changes to C++ or TypeScript files
-2. **Build**: Run `cmake --build . -j4` from build directory
-3. **Test**: Use appropriate test methods (shell scripts, browser tools)
-4. **Web UI Updates**: If changing webui, the build process includes it. If the API server already running and no changes made in the c++ code, just build the webui: `cd webui; npm run build-static` but do not stop the running API server
-5. **Commit**: Always commit and push changes, reference related issues
+## Counterintuitive Practices
+
+- Keep project directory clean - no output/queue directories in root
+- Use ninja instead of make for faster builds
+- Web UI development can happen without server restart
+- Model architecture detection happens at runtime, not compile time
 
 ## MCP Tools Integration
-- **Always use available MCP tools** for speeding up tasks
-- **Gogs MCP tools** for repository issue management
-  - When user asks about issues, always use `mcp_gogs_*` tools for current repo
-  - Current repository: `fszontagh/stable-diffusion.cpp-rest`
-  - always create gogs issues based on the user request. Check if an issue exists with the same request
-  - always manage the gogs issues. If a task is done, mark in the issue. If the issue is closeable, close it
-
-## Configuration Files
-- `.clang-format` - C++ code formatting
-- `.clang-tidy` - C++ static analysis
-- `webui/tsconfig.json` - TypeScript configuration
-- `webui/eslint.config.mjs` - ESLint configuration
-- `webui/next.config.ts` - Next.js build configuration
-
-## Dependencies
-
-### C++ Dependencies
-- stable-diffusion.cpp (auto-downloaded by CMake)
-- CUDA Toolkit (optional, for GPU acceleration)
-- PAM libraries (optional, for PAM auth)
-
-### Web UI Dependencies
-- Node.js and npm
-- Next.js 16
-- React 19
-- Tailwind CSS 4
-- Radix UI components
-
-## Model File Formats
-The project supports various model quantization levels:
-- `f32`, `f16` - Floating point formats
-- `q8_0`, `q4_0`, `q3_k` - Quantized formats
-- GGUF format for converted models
-- Original .safetensors and .ckpt formats
-
-Remember: This project serves as a bridge between the C++ stable-diffusion.cpp library and web applications, with emphasis on performance, security, and ease of use.
+
+- Always use available MCP tools for task efficiency
+- Gogs MCP tools for repository issue management
+- Create issues for all non-trivial tasks
+- Mark issues as done when tasks complete

+ 8 - 9
CMakeLists.txt

@@ -34,7 +34,7 @@ if(GIT_FOUND)
         OUTPUT_STRIP_TRAILING_WHITESPACE
         ERROR_QUIET
     )
-    
+
     # Get full git commit hash
     execute_process(
         COMMAND ${GIT_EXECUTABLE} rev-parse HEAD
@@ -43,7 +43,7 @@ if(GIT_FOUND)
         OUTPUT_STRIP_TRAILING_WHITESPACE
         ERROR_QUIET
     )
-    
+
     # Get current git tag (prefer annotated tags)
     execute_process(
         COMMAND ${GIT_EXECUTABLE} describe --tags --exact-match --abbrev=0
@@ -52,7 +52,7 @@ if(GIT_FOUND)
         OUTPUT_STRIP_TRAILING_WHITESPACE
         ERROR_QUIET
     )
-    
+
     # Get git describe for fallback (e.g., v1.0.0-5-g12345678)
     execute_process(
         COMMAND ${GIT_EXECUTABLE} describe --tags --always --abbrev=8
@@ -61,7 +61,7 @@ if(GIT_FOUND)
         OUTPUT_STRIP_TRAILING_WHITESPACE
         ERROR_QUIET
     )
-    
+
     # Get git branch
     execute_process(
         COMMAND ${GIT_EXECUTABLE} rev-parse --abbrev-ref HEAD
@@ -70,7 +70,7 @@ if(GIT_FOUND)
         OUTPUT_STRIP_TRAILING_WHITESPACE
         ERROR_QUIET
     )
-    
+
     # Check if working directory is clean
     execute_process(
         COMMAND ${GIT_EXECUTABLE} status --porcelain
@@ -79,7 +79,7 @@ if(GIT_FOUND)
         OUTPUT_STRIP_TRAILING_WHITESPACE
         ERROR_QUIET
     )
-    
+
     if(NOT GIT_COMMIT_HASH)
         set(GIT_COMMIT_HASH "unknown")
     endif()
@@ -92,7 +92,7 @@ if(GIT_FOUND)
     if(NOT GIT_BRANCH)
         set(GIT_BRANCH "unknown")
     endif()
-    
+
     # Determine if working directory is clean
     if(GIT_STATUS STREQUAL "")
         set(GIT_CLEAN "true")
@@ -230,8 +230,7 @@ if(BUILD_WEBUI)
             # Generate version file
             COMMAND ${CMAKE_COMMAND} -E echo "{\"version\":\"${PROJECT_VERSION_FULL}\",\"commit\":\"${PROJECT_VERSION_COMMIT}\",\"branch\":\"${PROJECT_VERSION_BRANCH}\",\"buildTime\":\"${BUILD_TIMESTAMP}\",\"type\":\"${PROJECT_VERSION_TYPE}\",\"clean\":${PROJECT_VERSION_CLEAN}}" > ${WEBUI_SOURCE_DIR}/public/version.json
             COMMAND ${NPM_EXECUTABLE} install
-            COMMAND ${NPM_EXECUTABLE} run build
-            COMMAND ${CMAKE_COMMAND} -E copy_directory ${WEBUI_SOURCE_DIR}/out ${WEBUI_OUTPUT_DIR}
+            COMMAND ${NPM_EXECUTABLE} run build-static
             WORKING_DIRECTORY ${WEBUI_SOURCE_DIR}
             COMMENT "Building Web UI with npm (version: ${PROJECT_VERSION_FULL})"
             VERBATIM

+ 1 - 0
auth/api_keys.json

@@ -0,0 +1 @@
+{}

+ 1 - 0
auth/users.json

@@ -0,0 +1 @@
+{}

+ 8 - 0
include/model_manager.h

@@ -356,6 +356,14 @@ public:
      */
     std::vector<ModelDetails> checkRequiredModelsExistence(const std::vector<std::string>& requiredModels);
 
+    /**
+     * @brief Hash all models in the models directory with progress reporting
+     *
+     * @param progressCallback Callback function for progress updates (0.0-1.0)
+     * @return true if all models were hashed successfully, false otherwise
+     */
+    bool hashAllModels(std::function<void(int, int, const std::string&)> progressCallback = nullptr);
+
 private:
     class Impl;
     std::unique_ptr<Impl> pImpl; // Pimpl idiom

+ 3 - 0
include/server_config.h

@@ -84,6 +84,9 @@ struct ServerConfig {
     std::string defaultAdminPassword = "admin123";
     std::string defaultAdminEmail    = "admin@localhost";
     
+    // Hash all models option
+    bool hashAllModels = false;  // Hash all models during startup
+    
     // Authentication configuration
     AuthConfig auth;
 };

+ 26 - 2
src/main.cpp

@@ -184,6 +184,8 @@ ServerConfig parseArguments(int argc, char* argv[]) {
             config.defaultAdminPassword = argv[++i];
         } else if (arg == "--default-admin-email" && i + 1 < argc) {
             config.defaultAdminEmail = argv[++i];
+        } else if (arg == "--hash-all-models") {
+            config.hashAllModels = true;
         } else if (arg == "--jwt-audience" && i + 1 < argc) {
             config.auth.jwtAudience = argv[++i];
         } else if (arg == "--help" || arg == "-h") {
@@ -247,6 +249,10 @@ ServerConfig parseArguments(int argc, char* argv[]) {
             LOG_INFO("  --default-admin-password <pass> Default admin password (default: admin123)");
             LOG_INFO("  --default-admin-email <email>   Default admin email (default: admin@localhost)");
             LOG_INFO("");
+            LOG_INFO("Model Management Options:");
+            LOG_INFO("  --hash-all-models              Hash all models in the models directory during startup");
+            LOG_INFO("                                  (blocks server startup until all models are hashed)");
+            LOG_INFO("");
             LOG_INFO("Other Options:");
             LOG_INFO("  --help, -h                     Show this help message");
             LOG_INFO("");
@@ -364,7 +370,7 @@ int main(int argc, char* argv[]) {
         LOG_INFO("  Max concurrent generations: " + std::to_string(config.maxConcurrentGenerations));
         LOG_INFO("  Queue directory: " + config.queueDir);
         LOG_INFO("  Output directory: " + config.outputDir);
-        LOG_INFO("\nModel Directories:");
+        LOG_INFO("Model Directories:");
         LOG_INFO("  Base models directory: " + config.modelsDir);
         LOG_INFO("  Checkpoints:  " + config.checkpoints);
         LOG_INFO("  ControlNet:   " + config.controlnetDir);
@@ -510,7 +516,7 @@ if (config.verbose) {
 
         // Set global server pointer for signal handler access
         g_server = server.get();
-        
+
         // Set global shutdown delay from config
         g_shutdownDelayMs = config.shutdownDelayMs;
 
@@ -537,6 +543,24 @@ if (config.verbose) {
             LOG_INFO("Found " + std::to_string(modelManager->getAvailableModelsCount()) + " models");
         }
 
+        // Hash all models if requested
+        if (config.hashAllModels) {
+            LOG_INFO("Hashing all models before starting server...");
+            
+            // Progress callback for hashing
+            auto hashProgressCallback = [](int current, int total, const std::string& modelName) {
+                // Use absolute path when printing to console
+                std::cout << "Hashing model " << current << " of " << total << ": " << modelName << std::endl;
+            };
+            
+            if (!modelManager->hashAllModels(hashProgressCallback)) {
+                LOG_ERROR("Failed to hash all models. Server will not start.");
+                return 1;
+            }
+            
+            LOG_INFO("All models hashed successfully. Starting server...");
+        }
+
         // Start the generation queue
         generationQueue->start();
         if (config.verbose) {

+ 101 - 0
src/model_manager.cpp

@@ -1675,3 +1675,104 @@ std::vector<ModelManager::ModelDetails> ModelManager::checkRequiredModelsExisten
 
     return modelDetails;
 }
+
+bool ModelManager::hashAllModels(std::function<void(int, int, const std::string&)> progressCallback) {
+    std::shared_lock<std::shared_mutex> lock(pImpl->modelsMutex);
+    
+    if (pImpl->availableModels.empty()) {
+        if (pImpl->verbose) {
+            std::cout << "No models found to hash" << std::endl;
+        }
+        return true;
+    }
+    
+    // Create a copy of model names to avoid holding the lock during hashing
+    std::vector<std::string> modelNames;
+    for (const auto& [name, info] : pImpl->availableModels) {
+        modelNames.push_back(name);
+    }
+    lock.unlock();
+    
+    const int totalModels = static_cast<int>(modelNames.size());
+    int processedModels = 0;
+    int successfulHashes = 0;
+    
+    if (pImpl->verbose) {
+        std::cout << "Hashing " << totalModels << " models..." << std::endl;
+    }
+    
+    for (const auto& modelName : modelNames) {
+        if (pImpl->scanCancelled.load()) {
+            if (pImpl->verbose) {
+                std::cout << "Model hashing cancelled" << std::endl;
+            }
+            return false;
+        }
+        
+        // Report progress
+        if (progressCallback) {
+            progressCallback(processedModels + 1, totalModels, modelName);
+        }
+        
+        // Get model info
+        ModelInfo modelInfo;
+        {
+            std::shared_lock<std::shared_mutex> infoLock(pImpl->modelsMutex);
+            auto it = pImpl->availableModels.find(modelName);
+            if (it != pImpl->availableModels.end()) {
+                modelInfo = it->second;
+            } else {
+                // Model was removed, skip it
+                processedModels++;
+                continue;
+            }
+        }
+        
+        // Check if model already has a hash
+        if (!modelInfo.sha256.empty()) {
+            if (pImpl->verbose) {
+                std::cout << "Model " << modelName << " already has hash: " << modelInfo.sha256.substr(0, 10) << "..." << std::endl;
+            }
+            processedModels++;
+            successfulHashes++;
+            continue;
+        }
+        
+        // Hash the model
+        if (pImpl->verbose) {
+            std::cout << "Hashing model " << modelName << " (" << std::filesystem::absolute(modelInfo.fullPath).string() << ")" << std::endl;
+        }
+        
+        std::string hash = computeModelHash(modelName);
+        if (!hash.empty()) {
+            // Save hash to file
+            if (saveModelHashToFile(modelName, hash)) {
+                // Update in-memory model info
+                {
+                    std::unique_lock<std::shared_mutex> updateLock(pImpl->modelsMutex);
+                    auto it = pImpl->availableModels.find(modelName);
+                    if (it != pImpl->availableModels.end()) {
+                        it->second.sha256 = hash;
+                    }
+                }
+                successfulHashes++;
+                
+                if (pImpl->verbose) {
+                    std::cout << "Successfully hashed model " << modelName << ": " << hash.substr(0, 10) << "..." << std::endl;
+                }
+            } else {
+                std::cerr << "Failed to save hash for model: " << modelName << std::endl;
+            }
+        } else {
+            std::cerr << "Failed to compute hash for model: " << modelName << std::endl;
+        }
+        
+        processedModels++;
+    }
+    
+    if (pImpl->verbose) {
+        std::cout << "Hashing complete. Successfully hashed " << successfulHashes << " out of " << totalModels << " models" << std::endl;
+    }
+    
+    return successfulHashes == totalModels;  // Return true only if all models were hashed successfully
+}

+ 22 - 10
webui/app/img2img/page.tsx

@@ -637,11 +637,17 @@ function Img2ImgForm() {
                      </SelectTrigger>
                      <SelectContent>
                        <SelectItem value="none">None</SelectItem>
-                       {vaeModels.map((model) => (
-                         <SelectItem key={model.name} value={model.name}>
-                           {model.name}
-                         </SelectItem>
-                       ))}
+                       {vaeModels.map((model) => {
+                         const modelId = model.sha256_short || model.sha256 || model.id || model.name;
+                         const displayName = model.sha256_short
+                           ? `${model.name} (${model.sha256_short})`
+                           : model.name;
+                         return (
+                           <SelectItem key={modelId} value={modelId}>
+                             {displayName}
+                           </SelectItem>
+                         );
+                       })}
                      </SelectContent>
                    </Select>
                  </div>
@@ -657,11 +663,17 @@ function Img2ImgForm() {
                      </SelectTrigger>
                      <SelectContent>
                        <SelectItem value="none">None</SelectItem>
-                       {taesdModels.map((model) => (
-                         <SelectItem key={model.name} value={model.name}>
-                           {model.name}
-                         </SelectItem>
-                       ))}
+                       {taesdModels.map((model) => {
+                         const modelId = model.sha256_short || model.sha256 || model.id || model.name;
+                         const displayName = model.sha256_short
+                           ? `${model.name} (${model.sha256_short})`
+                           : model.name;
+                         return (
+                           <SelectItem key={modelId} value={modelId}>
+                             {displayName}
+                           </SelectItem>
+                         );
+                       })}
                      </SelectContent>
                    </Select>
                  </div>

+ 22 - 10
webui/app/inpainting/page.tsx

@@ -476,11 +476,17 @@ function InpaintingForm() {
                           </SelectTrigger>
                           <SelectContent>
                             <SelectItem value="none">None</SelectItem>
-                            {vaeModels.map((model) => (
-                              <SelectItem key={model.name} value={model.name}>
-                                {model.name}
-                              </SelectItem>
-                            ))}
+                            {vaeModels.map((model) => {
+                              const modelId = model.sha256_short || model.sha256 || model.id || model.name;
+                              const displayName = model.sha256_short
+                                ? `${model.name} (${model.sha256_short})`
+                                : model.name;
+                              return (
+                                <SelectItem key={modelId} value={modelId}>
+                                  {displayName}
+                                </SelectItem>
+                              );
+                            })}
                           </SelectContent>
                         </Select>
                       </div>
@@ -496,11 +502,17 @@ function InpaintingForm() {
                           </SelectTrigger>
                           <SelectContent>
                             <SelectItem value="none">None</SelectItem>
-                            {taesdModels.map((model) => (
-                              <SelectItem key={model.name} value={model.name}>
-                                {model.name}
-                              </SelectItem>
-                            ))}
+                            {taesdModels.map((model) => {
+                              const modelId = model.sha256_short || model.sha256 || model.id || model.name;
+                              const displayName = model.sha256_short
+                                ? `${model.name} (${model.sha256_short})`
+                                : model.name;
+                              return (
+                                <SelectItem key={modelId} value={modelId}>
+                                  {displayName}
+                                </SelectItem>
+                              );
+                            })}
                           </SelectContent>
                         </Select>
                       </div>

+ 22 - 10
webui/app/text2img/page.tsx

@@ -506,11 +506,17 @@ function Text2ImgForm() {
                      </SelectTrigger>
                      <SelectContent>
                        <SelectItem value="none">None</SelectItem>
-                       {vaeModels.map((model) => (
-                         <SelectItem key={model.name} value={model.name}>
-                           {model.name}
-                         </SelectItem>
-                       ))}
+                       {vaeModels.map((model) => {
+                         const modelId = model.sha256_short || model.sha256 || model.id || model.name;
+                         const displayName = model.sha256_short
+                           ? `${model.name} (${model.sha256_short})`
+                           : model.name;
+                         return (
+                           <SelectItem key={modelId} value={modelId}>
+                             {displayName}
+                           </SelectItem>
+                         );
+                       })}
                      </SelectContent>
                    </Select>
                  </div>
@@ -526,11 +532,17 @@ function Text2ImgForm() {
                      </SelectTrigger>
                      <SelectContent>
                        <SelectItem value="none">None</SelectItem>
-                       {taesdModels.map((model) => (
-                         <SelectItem key={model.name} value={model.name}>
-                           {model.name}
-                         </SelectItem>
-                       ))}
+                       {taesdModels.map((model) => {
+                         const modelId = model.sha256_short || model.sha256 || model.id || model.name;
+                         const displayName = model.sha256_short
+                           ? `${model.name} (${model.sha256_short})`
+                           : model.name;
+                         return (
+                           <SelectItem key={modelId} value={modelId}>
+                             {displayName}
+                           </SelectItem>
+                         );
+                       })}
                      </SelectContent>
                    </Select>
                  </div>

+ 23 - 10
webui/app/upscaler/page.tsx

@@ -109,9 +109,11 @@ function UpscalerForm() {
 
         // Set first model as default if none selected
         if (modelsData.models?.length > 0 && !formData.model) {
+          const firstModel = modelsData.models[0];
+          const modelId = firstModel.sha256_short || firstModel.sha256 || firstModel.id || firstModel.name;
           setFormData((prev) => ({
             ...prev,
-            model: modelsData.models[0].name,
+            model: modelId,
           }));
         }
       } catch (err) {
@@ -274,8 +276,13 @@ function UpscalerForm() {
       }
 
       // Unload all currently loaded models and load the selected upscaler model
-      const selectedModel = upscalerModels.find(m => m.name === formData.model);
-      const modelId = selectedModel?.id || selectedModel?.sha256;
+      const selectedModel = upscalerModels.find(m =>
+        m.sha256_short === formData.model ||
+        m.sha256 === formData.model ||
+        m.id === formData.model ||
+        m.name === formData.model
+      );
+      const modelId = selectedModel?.sha256_short || selectedModel?.sha256 || selectedModel?.id;
       if (!selectedModel) {
         setError("Selected upscaler model not found.");
         setLoading(false);
@@ -293,7 +300,7 @@ function UpscalerForm() {
 
         // Unload all loaded models
         for (const model of loadedModels) {
-          const unloadId = model.id || model.sha256;
+          const unloadId = model.sha256_short || model.sha256 || model.id;
           if (unloadId) {
             try {
               await apiClient.unloadModel(unloadId);
@@ -315,7 +322,7 @@ function UpscalerForm() {
 
       const job = await apiClient.upscale({
         image: uploadedImage,
-        model: formData.model,
+        model: modelId, // Use the hash ID instead of name
         upscale_factor: formData.upscale_factor,
       });
       setJobInfo(job);
@@ -419,11 +426,17 @@ function UpscalerForm() {
                         <SelectValue placeholder="Select upscaler model" />
                       </SelectTrigger>
                       <SelectContent>
-                        {upscalerModels.map((model) => (
-                          <SelectItem key={model.name} value={model.name}>
-                            {model.name}
-                          </SelectItem>
-                        ))}
+                        {upscalerModels.map((model) => {
+                          const modelId = model.sha256_short || model.sha256 || model.id || model.name;
+                          const displayName = model.sha256_short
+                            ? `${model.name} (${model.sha256_short})`
+                            : model.name;
+                          return (
+                            <SelectItem key={modelId} value={modelId}>
+                              {displayName}
+                            </SelectItem>
+                          );
+                        })}
                       </SelectContent>
                     </Select>
                     {modelsLoading && (

+ 28 - 22
webui/contexts/model-selection-context.tsx

@@ -7,9 +7,9 @@ import { AutoModelSelector } from '@/lib/services/auto-model-selector';
 // Types for the context
 interface ModelSelectionState {
   selectedCheckpoint: string | null;
-  selectedModels: Record<string, string | undefined>; // modelType -> modelName
-  autoSelectedModels: Record<string, string>; // modelType -> modelName
-  userOverrides: Record<string, string>; // modelType -> modelName (user manual selections)
+  selectedModels: Record<string, string | undefined>; // modelType -> modelHash
+  autoSelectedModels: Record<string, string>; // modelType -> modelHash
+  userOverrides: Record<string, string>; // modelType -> modelHash (user manual selections)
   autoSelectionState: AutoSelectionState | null;
   availableModels: ModelInfo[];
   isLoading: boolean;
@@ -21,8 +21,8 @@ interface ModelSelectionState {
 type ModelSelectionAction =
   | { type: 'SET_MODELS'; payload: ModelInfo[] }
   | { type: 'SET_SELECTED_CHECKPOINT'; payload: string | null }
-  | { type: 'SET_SELECTED_MODEL'; payload: { type: string; name: string | undefined } }
-  | { type: 'SET_USER_OVERRIDE'; payload: { type: string; name: string } }
+  | { type: 'SET_SELECTED_MODEL'; payload: { type: string; hash: string | undefined } }
+  | { type: 'SET_USER_OVERRIDE'; payload: { type: string; hash: string } }
   | { type: 'CLEAR_USER_OVERRIDE'; payload: string }
   | { type: 'SET_AUTO_SELECTION_STATE'; payload: AutoSelectionState }
   | { type: 'SET_LOADING'; payload: boolean }
@@ -74,7 +74,7 @@ function modelSelectionReducer(
         ...state,
         selectedModels: {
           ...state.selectedModels,
-          [action.payload.type]: action.payload.name,
+          [action.payload.type]: action.payload.hash,
         },
       };
 
@@ -83,11 +83,11 @@ function modelSelectionReducer(
         ...state,
         userOverrides: {
           ...state.userOverrides,
-          [action.payload.type]: action.payload.name,
+          [action.payload.type]: action.payload.hash,
         },
         selectedModels: {
           ...state.selectedModels,
-          [action.payload.type]: action.payload.name,
+          [action.payload.type]: action.payload.hash,
         },
       };
 
@@ -170,9 +170,9 @@ interface ModelSelectionContextType {
   state: ModelSelectionState;
   actions: {
     setModels: (models: ModelInfo[]) => void;
-    setSelectedCheckpoint: (checkpointName: string | null) => void;
-    setSelectedModel: (type: string, name: string | undefined) => void;
-    setUserOverride: (type: string, name: string) => void;
+    setSelectedCheckpoint: (checkpointHash: string | null) => void;
+    setSelectedModel: (type: string, hash: string | undefined) => void;
+    setUserOverride: (type: string, hash: string) => void;
     clearUserOverride: (type: string) => void;
     performAutoSelection: (checkpointModel: ModelInfo) => Promise<void>;
     clearWarnings: () => void;
@@ -208,16 +208,16 @@ export function ModelSelectionProvider({ children }: ModelSelectionProviderProps
       dispatch({ type: 'SET_MODELS', payload: models });
     },
 
-    setSelectedCheckpoint: (checkpointName: string | null) => {
-      dispatch({ type: 'SET_SELECTED_CHECKPOINT', payload: checkpointName });
+    setSelectedCheckpoint: (checkpointHash: string | null) => {
+      dispatch({ type: 'SET_SELECTED_CHECKPOINT', payload: checkpointHash });
     },
 
-    setSelectedModel: (type: string, name: string | undefined) => {
-      dispatch({ type: 'SET_SELECTED_MODEL', payload: { type, name } });
+    setSelectedModel: (type: string, hash: string | undefined) => {
+      dispatch({ type: 'SET_SELECTED_MODEL', payload: { type, hash } });
     },
 
-    setUserOverride: (type: string, name: string) => {
-      dispatch({ type: 'SET_USER_OVERRIDE', payload: { type, name } });
+    setUserOverride: (type: string, hash: string) => {
+      dispatch({ type: 'SET_USER_OVERRIDE', payload: { type, hash } });
     },
 
     clearUserOverride: (type: string) => {
@@ -259,7 +259,8 @@ export function ModelSelectionProvider({ children }: ModelSelectionProviderProps
   useEffect(() => {
     if (state.selectedCheckpoint && state.availableModels.length > 0) {
       const checkpointModel = state.availableModels.find(
-        model => model.name === state.selectedCheckpoint && model.type === 'checkpoint'
+        model => (model.sha256_short === state.selectedCheckpoint || model.sha256 === state.selectedCheckpoint || model.id === state.selectedCheckpoint) &&
+        (model.type === 'checkpoint' || model.type === 'stable-diffusion')
       );
 
       if (checkpointModel) {
@@ -297,8 +298,13 @@ export function useCheckpointSelection() {
     model.type === 'checkpoint' || model.type === 'stable-diffusion'
   );
 
-  const selectedCheckpointModel = state.selectedCheckpoint 
-    ? state.availableModels.find(model => model.name === state.selectedCheckpoint)
+  const selectedCheckpointModel = state.selectedCheckpoint
+    ? state.availableModels.find(model =>
+        model.sha256_short === state.selectedCheckpoint ||
+        model.sha256 === state.selectedCheckpoint ||
+        model.id === state.selectedCheckpoint ||
+        model.name === state.selectedCheckpoint
+      )
     : null;
 
   return {
@@ -331,8 +337,8 @@ export function useModelTypeSelection(modelType: string) {
     selectedModel,
     isUserOverride,
     isAutoSelected,
-    setSelectedModel: (name: string | undefined) => actions.setSelectedModel(modelType, name),
-    setUserOverride: (name: string) => actions.setUserOverride(modelType, name),
+    setSelectedModel: (hash: string | undefined) => actions.setSelectedModel(modelType, hash),
+    setUserOverride: (hash: string) => actions.setUserOverride(modelType, hash),
     clearUserOverride: () => actions.clearUserOverride(modelType),
   };
 }

+ 27 - 8
webui/lib/api.ts

@@ -738,7 +738,7 @@ class ApiClient {
 
     const models = response.models.map((model) => ({
       ...model,
-      id: model.sha256_short || model.name,
+      id: model.sha256_short || model.sha256 || model.id || model.name,
       size: model.file_size || model.size,
       path: model.path || model.name,
     }));
@@ -780,7 +780,7 @@ class ApiClient {
 
     const models = response.models.map((model) => ({
       ...model,
-      id: model.sha256_short || model.name,
+      id: model.sha256_short || model.sha256 || model.id || model.name,
       size: model.file_size || model.size,
       path: model.path || model.name,
     }));
@@ -850,17 +850,36 @@ class ApiClient {
     // Clear model cache when loading
     cache.delete(`model_info_${modelId}`);
 
-    return this.request<void>(`/models/${modelId}/load`, {
-      method: "POST",
-    });
+    try {
+      return await this.request<void>(`/models/${modelId}/load`, {
+        method: "POST",
+      });
+    } catch (error) {
+      // Enhance error message for hash validation failures
+      const errorMessage = error instanceof Error ? error.message : "Failed to load model";
+      if (errorMessage.includes("INVALID_MODEL_IDENTIFIER") || errorMessage.includes("MODEL_NOT_FOUND")) {
+        throw new Error(`Hash validation failed: ${errorMessage}. Please ensure you're using a valid model hash instead of a name.`);
+      }
+      throw error;
+    }
   }
 
   async unloadModel(modelId: string): Promise<void> {
     // Clear model cache when unloading
     cache.delete(`models_*`);
-    return this.request<void>(`/models/${modelId}/unload`, {
-      method: "POST",
-    });
+    
+    try {
+      return await this.request<void>(`/models/${modelId}/unload`, {
+        method: "POST",
+      });
+    } catch (error) {
+      // Enhance error message for hash validation failures
+      const errorMessage = error instanceof Error ? error.message : "Failed to unload model";
+      if (errorMessage.includes("INVALID_MODEL_IDENTIFIER") || errorMessage.includes("MODEL_NOT_FOUND")) {
+        throw new Error(`Hash validation failed: ${errorMessage}. Please ensure you're using a valid model hash instead of a name.`);
+      }
+      throw error;
+    }
   }
 
   async computeModelHash(modelId: string): Promise<{request_id: string, status: string, message: string}> {

+ 15 - 11
webui/lib/services/auto-model-selector.ts

@@ -126,7 +126,7 @@ export class AutoModelSelector {
 
   // Perform automatic model selection for a checkpoint
   async selectModels(checkpointModel: ModelInfo): Promise<AutoSelectionState> {
-    const cacheKey = checkpointModel.id || checkpointModel.name;
+    const cacheKey = checkpointModel.sha256_short || checkpointModel.sha256 || checkpointModel.id || checkpointModel.name;
     
     // Check cache first
     const cached = this.cache.get(cacheKey);
@@ -156,12 +156,13 @@ export class AutoModelSelector {
         const bestModel = this.getBestModelForType(required.type);
         
         if (bestModel) {
-          state.autoSelectedModels[required.type] = bestModel.name;
-          state.selectedModels[required.type] = bestModel.name;
+          const modelId = bestModel.sha256_short || bestModel.sha256 || bestModel.id || bestModel.name;
+          state.autoSelectedModels[required.type] = modelId;
+          state.selectedModels[required.type] = modelId;
           
           if (!bestModel.loaded && !required.optional) {
             state.warnings.push(
-              `Selected ${required.type} model "${bestModel.name}" is not loaded. Consider loading it for better performance.`
+              `Selected ${required.type} model "${bestModel.name}" (${modelId}) is not loaded. Consider loading it for better performance.`
             );
           }
         } else if (!required.optional) {
@@ -179,12 +180,15 @@ export class AutoModelSelector {
       // Check for recommended models
       if (checkpointModel.recommended_vae) {
         const vae = this.getBestModelForType('vae', checkpointModel.recommended_vae.name);
-        if (vae && vae.name !== state.selectedModels['vae']) {
-          state.autoSelectedModels['vae'] = vae.name;
-          state.selectedModels['vae'] = vae.name;
-          state.warnings.push(
-            `Using recommended VAE: ${vae.name} (${checkpointModel.recommended_vae.reason})`
-          );
+        if (vae) {
+          const vaeId = vae.sha256_short || vae.sha256 || vae.id || vae.name;
+          if (vaeId !== state.selectedModels['vae']) {
+            state.autoSelectedModels['vae'] = vaeId;
+            state.selectedModels['vae'] = vaeId;
+            state.warnings.push(
+              `Using recommended VAE: ${vae.name} (${vaeId}) - ${checkpointModel.recommended_vae.reason}`
+            );
+          }
         }
       }
 
@@ -205,7 +209,7 @@ export class AutoModelSelector {
     const results: Record<string, AutoSelectionState> = {};
     
     for (const checkpoint of checkpoints) {
-      const key = checkpoint.id || checkpoint.name;
+      const key = checkpoint.sha256_short || checkpoint.sha256 || checkpoint.id || checkpoint.name;
       results[key] = await this.selectModels(checkpoint);
     }