package http import ( "embed" "errors" "fmt" "net/http" "github.com/dave-gray101/v2keyauth" "github.com/mudler/LocalAI/pkg/utils" "github.com/mudler/LocalAI/core/http/endpoints/localai" "github.com/mudler/LocalAI/core/http/endpoints/openai" "github.com/mudler/LocalAI/core/http/middleware" "github.com/mudler/LocalAI/core/http/routes" "github.com/mudler/LocalAI/core/config" "github.com/mudler/LocalAI/core/schema" "github.com/mudler/LocalAI/core/services" "github.com/mudler/LocalAI/pkg/model" "github.com/gofiber/contrib/fiberzerolog" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/cors" "github.com/gofiber/fiber/v2/middleware/csrf" "github.com/gofiber/fiber/v2/middleware/favicon" "github.com/gofiber/fiber/v2/middleware/filesystem" "github.com/gofiber/fiber/v2/middleware/recover" // swagger handler "github.com/rs/zerolog/log" ) // Embed a directory // //go:embed static/* var embedDirStatic embed.FS // @title LocalAI API // @version 2.0.0 // @description The LocalAI Rest API. // @termsOfService // @contact.name LocalAI // @contact.url https://localai.io // @license.name MIT // @license.url https://raw.githubusercontent.com/mudler/LocalAI/master/LICENSE // @BasePath / // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization func App(cl *config.BackendConfigLoader, ml *model.ModelLoader, appConfig *config.ApplicationConfig) (*fiber.App, error) { fiberCfg := fiber.Config{ Views: renderEngine(), BodyLimit: appConfig.UploadLimitMB * 1024 * 1024, // this is the default limit of 4MB // We disable the Fiber startup message as it does not conform to structured logging. // We register a startup log line with connection information in the OnListen hook to keep things user friendly though DisableStartupMessage: true, // Override default error handler } if !appConfig.OpaqueErrors { // Normally, return errors as JSON responses fiberCfg.ErrorHandler = func(ctx *fiber.Ctx, err error) error { // Status code defaults to 500 code := fiber.StatusInternalServerError // Retrieve the custom status code if it's a *fiber.Error var e *fiber.Error if errors.As(err, &e) { code = e.Code } // Send custom error page return ctx.Status(code).JSON( schema.ErrorResponse{ Error: &schema.APIError{Message: err.Error(), Code: code}, }, ) } } else { // If OpaqueErrors are required, replace everything with a blank 500. fiberCfg.ErrorHandler = func(ctx *fiber.Ctx, _ error) error { return ctx.Status(500).SendString("") } } app := fiber.New(fiberCfg) app.Hooks().OnListen(func(listenData fiber.ListenData) error { scheme := "http" if listenData.TLS { scheme = "https" } log.Info().Str("endpoint", scheme+"://"+listenData.Host+":"+listenData.Port).Msg("LocalAI API is listening! Please connect to the endpoint for API documentation.") return nil }) // Have Fiber use zerolog like the rest of the application rather than it's built-in logger logger := log.Logger app.Use(fiberzerolog.New(fiberzerolog.Config{ Logger: &logger, })) // Default middleware config if !appConfig.Debug { app.Use(recover.New()) } if !appConfig.DisableMetrics { metricsService, err := services.NewLocalAIMetricsService() if err != nil { return nil, err } if metricsService != nil { app.Use(localai.LocalAIMetricsAPIMiddleware(metricsService)) app.Hooks().OnShutdown(func() error { return metricsService.Shutdown() }) } } // Health Checks should always be exempt from auth, so register these first routes.HealthRoutes(app) kaConfig, err := middleware.GetKeyAuthConfig(appConfig) if err != nil || kaConfig == nil { return nil, fmt.Errorf("failed to create key auth config: %w", err) } // Auth is applied to _all_ endpoints. No exceptions. Filtering out endpoints to bypass is the role of the Filter property of the KeyAuth Configuration app.Use(v2keyauth.New(*kaConfig)) if appConfig.CORS { var c func(ctx *fiber.Ctx) error if appConfig.CORSAllowOrigins == "" { c = cors.New() } else { c = cors.New(cors.Config{AllowOrigins: appConfig.CORSAllowOrigins}) } app.Use(c) } if appConfig.CSRF { log.Debug().Msg("Enabling CSRF middleware. Tokens are now required for state-modifying requests") app.Use(csrf.New()) } // Load config jsons utils.LoadConfig(appConfig.UploadDir, openai.UploadedFilesFile, &openai.UploadedFiles) utils.LoadConfig(appConfig.ConfigsDir, openai.AssistantsConfigFile, &openai.Assistants) utils.LoadConfig(appConfig.ConfigsDir, openai.AssistantsFileConfigFile, &openai.AssistantFiles) galleryService := services.NewGalleryService(appConfig) galleryService.Start(appConfig.Context, cl) routes.RegisterElevenLabsRoutes(app, cl, ml, appConfig) routes.RegisterLocalAIRoutes(app, cl, ml, appConfig, galleryService) routes.RegisterOpenAIRoutes(app, cl, ml, appConfig) if !appConfig.DisableWebUI { routes.RegisterUIRoutes(app, cl, ml, appConfig, galleryService) } routes.RegisterJINARoutes(app, cl, ml, appConfig) httpFS := http.FS(embedDirStatic) app.Use(favicon.New(favicon.Config{ URL: "/favicon.ico", FileSystem: httpFS, File: "static/favicon.ico", })) app.Use("/static", filesystem.New(filesystem.Config{ Root: httpFS, PathPrefix: "static", Browse: true, })) // Define a custom 404 handler // Note: keep this at the bottom! app.Use(notFoundHandler) return app, nil }