diff --git a/core/config/model_config.go b/core/config/model_config.go index e372febb7239..1cfe0f411371 100644 --- a/core/config/model_config.go +++ b/core/config/model_config.go @@ -26,6 +26,7 @@ type TTSConfig struct { // ModelConfig represents a model configuration type ModelConfig struct { + modelConfigFile string `yaml:"-" json:"-"` schema.PredictionOptions `yaml:"parameters" json:"parameters"` Name string `yaml:"name" json:"name"` @@ -492,6 +493,10 @@ func (c *ModelConfig) HasTemplate() bool { return c.TemplateConfig.Completion != "" || c.TemplateConfig.Edit != "" || c.TemplateConfig.Chat != "" || c.TemplateConfig.ChatMessage != "" } +func (c *ModelConfig) GetModelConfigFile() string { + return c.modelConfigFile +} + type ModelConfigUsecases int const ( diff --git a/core/config/model_config_loader.go b/core/config/model_config_loader.go index 5b16cadc8758..9895a4a0e5f8 100644 --- a/core/config/model_config_loader.go +++ b/core/config/model_config_loader.go @@ -88,6 +88,7 @@ func readMultipleModelConfigsFromFile(file string, opts ...ConfigLoaderOption) ( } for _, cc := range *c { + cc.modelConfigFile = file cc.SetDefaults(opts...) } @@ -108,6 +109,8 @@ func readModelConfigFromFile(file string, opts ...ConfigLoaderOption) (*ModelCon } c.SetDefaults(opts...) + + c.modelConfigFile = file return c, nil } diff --git a/core/http/endpoints/localai/edit_model.go b/core/http/endpoints/localai/edit_model.go index de93632cebdd..d68775e1f5f5 100644 --- a/core/http/endpoints/localai/edit_model.go +++ b/core/http/endpoints/localai/edit_model.go @@ -1,11 +1,8 @@ package localai import ( - "encoding/json" "fmt" "os" - "path/filepath" - "strings" "github.com/gofiber/fiber/v2" "github.com/mudler/LocalAI/core/config" @@ -37,21 +34,19 @@ func GetEditModelPage(cl *config.ModelConfigLoader, appConfig *config.Applicatio return c.Status(404).JSON(response) } - configData, err := yaml.Marshal(modelConfig) - if err != nil { + modelConfigFile := modelConfig.GetModelConfigFile() + if modelConfigFile == "" { response := ModelResponse{ Success: false, - Error: "Failed to marshal configuration: " + err.Error(), + Error: "Model configuration file not found", } - return c.Status(500).JSON(response) + return c.Status(404).JSON(response) } - - // Marshal the config to JSON for the template - configJSON, err := json.Marshal(modelConfig) + configData, err := os.ReadFile(modelConfigFile) if err != nil { response := ModelResponse{ Success: false, - Error: "Failed to marshal configuration: " + err.Error(), + Error: "Failed to read configuration file: " + err.Error(), } return c.Status(500).JSON(response) } @@ -69,7 +64,6 @@ func GetEditModelPage(cl *config.ModelConfigLoader, appConfig *config.Applicatio Title: "LocalAI - Edit Model " + modelName, ModelName: modelName, Config: &modelConfig, - ConfigJSON: string(configJSON), ConfigYAML: string(configData), BaseURL: httpUtils.BaseURL(c), Version: internal.PrintableVersion(), @@ -91,6 +85,15 @@ func EditModelEndpoint(cl *config.ModelConfigLoader, appConfig *config.Applicati return c.Status(400).JSON(response) } + modelConfig, exists := cl.GetModelConfig(modelName) + if !exists { + response := ModelResponse{ + Success: false, + Error: "Existing model configuration not found", + } + return c.Status(404).JSON(response) + } + // Get the raw body body := c.Body() if len(body) == 0 { @@ -101,50 +104,16 @@ func EditModelEndpoint(cl *config.ModelConfigLoader, appConfig *config.Applicati return c.Status(400).JSON(response) } - // Check content type to determine how to parse - contentType := string(c.Context().Request.Header.ContentType()) + // Check content to see if it's a valid model config var req config.ModelConfig - var err error - if strings.Contains(contentType, "application/json") { - // Parse JSON - if err := json.Unmarshal(body, &req); err != nil { - response := ModelResponse{ - Success: false, - Error: "Failed to parse JSON: " + err.Error(), - } - return c.Status(400).JSON(response) - } - } else if strings.Contains(contentType, "application/x-yaml") || strings.Contains(contentType, "text/yaml") { - // Parse YAML - if err := yaml.Unmarshal(body, &req); err != nil { - response := ModelResponse{ - Success: false, - Error: "Failed to parse YAML: " + err.Error(), - } - return c.Status(400).JSON(response) - } - } else { - // Try to auto-detect format - if strings.TrimSpace(string(body))[0] == '{' { - // Looks like JSON - if err := json.Unmarshal(body, &req); err != nil { - response := ModelResponse{ - Success: false, - Error: "Failed to parse JSON: " + err.Error(), - } - return c.Status(400).JSON(response) - } - } else { - // Assume YAML - if err := yaml.Unmarshal(body, &req); err != nil { - response := ModelResponse{ - Success: false, - Error: "Failed to parse YAML: " + err.Error(), - } - return c.Status(400).JSON(response) - } + // Parse YAML + if err := yaml.Unmarshal(body, &req); err != nil { + response := ModelResponse{ + Success: false, + Error: "Failed to parse YAML: " + err.Error(), } + return c.Status(400).JSON(response) } // Validate required fields @@ -156,19 +125,6 @@ func EditModelEndpoint(cl *config.ModelConfigLoader, appConfig *config.Applicati return c.Status(400).JSON(response) } - // Load the existing configuration - configPath := filepath.Join(appConfig.SystemState.Model.ModelsPath, modelName+".yaml") - if err := utils.VerifyPath(modelName+".yaml", appConfig.SystemState.Model.ModelsPath); err != nil { - response := ModelResponse{ - Success: false, - Error: "Model configuration not trusted: " + err.Error(), - } - return c.Status(404).JSON(response) - } - - // Set defaults - req.SetDefaults() - // Validate the configuration if !req.Validate() { response := ModelResponse{ @@ -179,18 +135,18 @@ func EditModelEndpoint(cl *config.ModelConfigLoader, appConfig *config.Applicati return c.Status(400).JSON(response) } - // Create the YAML file - yamlData, err := yaml.Marshal(req) - if err != nil { + // Load the existing configuration + configPath := modelConfig.GetModelConfigFile() + if err := utils.VerifyPath(configPath, appConfig.SystemState.Model.ModelsPath); err != nil { response := ModelResponse{ Success: false, - Error: "Failed to marshal configuration: " + err.Error(), + Error: "Model configuration not trusted: " + err.Error(), } - return c.Status(500).JSON(response) + return c.Status(404).JSON(response) } - // Write to file - if err := os.WriteFile(configPath, yamlData, 0644); err != nil { + // Write new content to file + if err := os.WriteFile(configPath, body, 0644); err != nil { response := ModelResponse{ Success: false, Error: "Failed to write configuration file: " + err.Error(), diff --git a/core/http/views/model-editor.html b/core/http/views/model-editor.html index 440ca8c7a56a..d5cbc550b7c4 100644 --- a/core/http/views/model-editor.html +++ b/core/http/views/model-editor.html @@ -24,7 +24,7 @@

{{if .ModelName}}Edit Model: {{.ModelName}}{{else}}Import New Model{{end}}

-

Configure your model settings using the form or YAML editor

+

Configure your model settings using YAML

- +
+

+
+
-

-
-
- -
+ YAML Configuration Editor + +
+ +
- - -
-
- -
-

-
- -
- YAML Editor -

-
- - -
-
-
-
-
+
+
@@ -227,84 +196,6 @@

border-radius: 0.5rem !important; } -/* Enhanced form styling */ -.form-section { - background: linear-gradient(135deg, rgba(55, 65, 81, 0.3) 0%, rgba(75, 85, 99, 0.3) 100%); - border: 1px solid rgba(107, 114, 128, 0.3); - border-radius: 1rem; - backdrop-filter: blur(8px); - transition: all 0.3s ease; -} - -.form-section:hover { - border-color: rgba(139, 92, 246, 0.3); - box-shadow: 0 4px 15px rgba(139, 92, 246, 0.1); -} - -.form-section.expanded { - border-color: rgba(139, 92, 246, 0.4); - box-shadow: 0 8px 25px rgba(139, 92, 246, 0.15); -} - -.section-header { - background: linear-gradient(135deg, rgba(75, 85, 99, 0.4) 0%, rgba(107, 114, 128, 0.4) 100%); - border-bottom: 1px solid rgba(107, 114, 128, 0.3); - padding: 1rem 1.5rem; - border-radius: 1rem 1rem 0 0; - cursor: pointer; - transition: all 0.3s ease; -} - -.section-header:hover { - background: linear-gradient(135deg, rgba(107, 114, 128, 0.4) 0%, rgba(139, 92, 246, 0.2) 100%); -} - -.form-input { - background: linear-gradient(135deg, rgba(17, 24, 39, 0.8) 0%, rgba(31, 41, 55, 0.8) 100%) !important; - border: 1px solid rgba(107, 114, 128, 0.4) !important; - border-radius: 0.75rem !important; - padding: 0.75rem 1rem !important; - color: #e5e7eb !important; - transition: all 0.3s ease !important; - backdrop-filter: blur(4px) !important; -} - -.form-input:focus { - border-color: rgba(139, 92, 246, 0.6) !important; - box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1) !important; - background: linear-gradient(135deg, rgba(31, 41, 55, 0.9) 0%, rgba(55, 65, 81, 0.9) 100%) !important; -} - -.form-input:hover { - border-color: rgba(139, 92, 246, 0.4) !important; -} - -/* Enhanced button styling */ -.action-button { - background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); - border-radius: 0.75rem; - padding: 0.5rem 1rem; - font-weight: 600; - transition: all 0.3s ease; - border: 1px solid rgba(139, 92, 246, 0.3); -} - -.action-button:hover { - transform: scale(1.05); - box-shadow: 0 8px 25px rgba(139, 92, 246, 0.3); - background: linear-gradient(135deg, #8b5cf6 0%, #a855f7 100%); -} - -.danger-button { - background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%); - border: 1px solid rgba(239, 68, 68, 0.3); -} - -.danger-button:hover { - background: linear-gradient(135deg, #ef4444 0%, #f87171 100%); - box-shadow: 0 8px 25px rgba(239, 68, 68, 0.3); -} - /* Alert styling */ .alert { border-radius: 1rem; @@ -353,9 +244,6 @@