Files
wehub-resource-sync 1b8708893a
Security Scan / tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:12:26 +08:00

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