chore(ui): simplify editing and importing models via YAML (#6424)

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
Ettore Di Giacinto
2025-10-10 15:10:13 +02:00
committed by GitHub
parent 69a2b91495
commit dc2be93412
5 changed files with 130 additions and 889 deletions

View File

@@ -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 (

View File

@@ -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
}

View File

@@ -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(),

File diff suppressed because it is too large Load Diff

View File

@@ -109,8 +109,8 @@ function operationsStatus() {
init() {
this.fetchOperations();
// Poll every 500ms for smooth updates
this.pollInterval = setInterval(() => this.fetchOperations(), 500);
// Poll every 1s for smooth updates
this.pollInterval = setInterval(() => this.fetchOperations(), 1000);
},
async fetchOperations() {