150 lines
5.3 KiB
Go
150 lines
5.3 KiB
Go
package localai
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/mudler/LocalAI/core/config"
|
|
httpUtils "github.com/mudler/LocalAI/core/http/middleware"
|
|
"github.com/mudler/LocalAI/core/services/galleryop"
|
|
"github.com/mudler/LocalAI/core/services/modeladmin"
|
|
"github.com/mudler/LocalAI/internal"
|
|
"github.com/mudler/LocalAI/pkg/model"
|
|
)
|
|
|
|
// GetEditModelPage renders the edit model page with current configuration
|
|
func GetEditModelPage(cl *config.ModelConfigLoader, appConfig *config.ApplicationConfig) echo.HandlerFunc {
|
|
svc := modeladmin.NewConfigService(cl, appConfig)
|
|
return func(c echo.Context) error {
|
|
modelName := c.Param("name")
|
|
if decoded, err := url.PathUnescape(modelName); err == nil {
|
|
modelName = decoded
|
|
}
|
|
view, err := svc.GetConfig(c.Request().Context(), modelName)
|
|
if err != nil {
|
|
return c.JSON(httpStatusForModelAdminError(err), ModelResponse{Success: false, Error: err.Error()})
|
|
}
|
|
// Render the edit page with the current configuration. Re-fetch the
|
|
// in-memory config from the loader for the template — the on-disk YAML
|
|
// view from svc doesn't carry the loader's parsed struct fields.
|
|
modelConfig, _ := cl.GetModelConfig(modelName)
|
|
templateData := struct {
|
|
Title string
|
|
ModelName string
|
|
Config *config.ModelConfig
|
|
ConfigJSON string
|
|
ConfigYAML string
|
|
BaseURL string
|
|
Version string
|
|
DisableRuntimeSettings bool
|
|
}{
|
|
Title: "LocalAI - Edit Model " + modelName,
|
|
ModelName: modelName,
|
|
Config: &modelConfig,
|
|
ConfigYAML: view.YAML,
|
|
BaseURL: httpUtils.BaseURL(c),
|
|
Version: internal.PrintableVersion(),
|
|
DisableRuntimeSettings: appConfig.DisableRuntimeSettings,
|
|
}
|
|
|
|
return c.Render(http.StatusOK, "views/model-editor", templateData)
|
|
}
|
|
}
|
|
|
|
// EditModelEndpoint handles updating existing model configurations
|
|
func EditModelEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, gs *galleryop.GalleryService, appConfig *config.ApplicationConfig) echo.HandlerFunc {
|
|
svc := modeladmin.NewConfigService(cl, appConfig)
|
|
return func(c echo.Context) error {
|
|
modelName := c.Param("name")
|
|
if decoded, err := url.PathUnescape(modelName); err == nil {
|
|
modelName = decoded
|
|
}
|
|
body, err := io.ReadAll(c.Request().Body)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, ModelResponse{Success: false, Error: "Failed to read request body: " + err.Error()})
|
|
}
|
|
result, err := svc.EditYAML(c.Request().Context(), modelName, body, ml)
|
|
if err != nil {
|
|
return c.JSON(httpStatusForModelAdminError(err), ModelResponse{Success: false, Error: err.Error()})
|
|
}
|
|
|
|
// Tell peer replicas to refresh their in-memory config: this endpoint
|
|
// only reloaded the local loader. A rename is a delete of the old name
|
|
// plus an install of the new one. No-op in standalone mode.
|
|
if gs != nil {
|
|
if result.Renamed {
|
|
gs.BroadcastModelsChanged(result.OldName, "delete")
|
|
}
|
|
gs.BroadcastModelsChanged(result.NewName, "install")
|
|
}
|
|
|
|
msg := fmt.Sprintf("Model '%s' updated successfully. Model has been reloaded with new configuration.", result.NewName)
|
|
if result.Renamed {
|
|
msg = fmt.Sprintf("Model '%s' renamed to '%s' and updated successfully.", result.OldName, result.NewName)
|
|
}
|
|
return c.JSON(http.StatusOK, ModelResponse{
|
|
Success: true,
|
|
Message: msg,
|
|
Filename: result.Filename,
|
|
Config: result.Config,
|
|
})
|
|
}
|
|
}
|
|
|
|
// httpStatusForModelAdminError maps the typed errors from modeladmin to
|
|
// the HTTP status codes the existing endpoints used to return — keeps the
|
|
// REST contract identical after the refactor.
|
|
func httpStatusForModelAdminError(err error) int {
|
|
switch {
|
|
case errors.Is(err, modeladmin.ErrNameRequired),
|
|
errors.Is(err, modeladmin.ErrEmptyBody),
|
|
errors.Is(err, modeladmin.ErrPathSeparator),
|
|
errors.Is(err, modeladmin.ErrBadAction),
|
|
errors.Is(err, modeladmin.ErrInvalidConfig):
|
|
return http.StatusBadRequest
|
|
case errors.Is(err, modeladmin.ErrNotFound),
|
|
errors.Is(err, modeladmin.ErrConfigFileMissing):
|
|
return http.StatusNotFound
|
|
case errors.Is(err, modeladmin.ErrPathNotTrusted):
|
|
return http.StatusForbidden
|
|
case errors.Is(err, modeladmin.ErrConflict):
|
|
return http.StatusConflict
|
|
default:
|
|
return http.StatusInternalServerError
|
|
}
|
|
}
|
|
|
|
// ReloadModelsEndpoint handles reloading model configurations from disk
|
|
func ReloadModelsEndpoint(cl *config.ModelConfigLoader, appConfig *config.ApplicationConfig) echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
// Reload configurations
|
|
if err := cl.LoadModelConfigsFromPath(appConfig.SystemState.Model.ModelsPath, appConfig.ToConfigLoaderOptions()...); err != nil {
|
|
response := ModelResponse{
|
|
Success: false,
|
|
Error: "Failed to reload configurations: " + err.Error(),
|
|
}
|
|
return c.JSON(http.StatusInternalServerError, response)
|
|
}
|
|
|
|
// Preload the models
|
|
if err := cl.Preload(appConfig.SystemState.Model.ModelsPath); err != nil {
|
|
response := ModelResponse{
|
|
Success: false,
|
|
Error: "Failed to preload models: " + err.Error(),
|
|
}
|
|
return c.JSON(http.StatusInternalServerError, response)
|
|
}
|
|
|
|
// Return success response
|
|
response := ModelResponse{
|
|
Success: true,
|
|
Message: "Model configurations reloaded successfully",
|
|
}
|
|
return c.JSON(http.StatusOK, response)
|
|
}
|
|
}
|