diff --git a/core/application/startup.go b/core/application/startup.go index 8e2387b9226f..eb387d06debd 100644 --- a/core/application/startup.go +++ b/core/application/startup.go @@ -62,12 +62,12 @@ func New(opts ...config.AppOption) (*Application, error) { } } - if err := coreStartup.InstallModels(application.GalleryService(), options.Galleries, options.BackendGalleries, options.SystemState, application.ModelLoader(), options.EnforcePredownloadScans, options.AutoloadBackendGalleries, nil, options.ModelsURL...); err != nil { + if err := coreStartup.InstallModels(options.Context, application.GalleryService(), options.Galleries, options.BackendGalleries, options.SystemState, application.ModelLoader(), options.EnforcePredownloadScans, options.AutoloadBackendGalleries, nil, options.ModelsURL...); err != nil { log.Error().Err(err).Msg("error installing models") } for _, backend := range options.ExternalBackends { - if err := coreStartup.InstallExternalBackends(options.BackendGalleries, options.SystemState, application.ModelLoader(), nil, backend, "", ""); err != nil { + if err := coreStartup.InstallExternalBackends(options.Context, options.BackendGalleries, options.SystemState, application.ModelLoader(), nil, backend, "", ""); err != nil { log.Error().Err(err).Msg("error installing external backend") } } diff --git a/core/backend/llm.go b/core/backend/llm.go index d6c7bc736e93..3cd74d9a4953 100644 --- a/core/backend/llm.go +++ b/core/backend/llm.go @@ -45,7 +45,7 @@ func ModelInference(ctx context.Context, s string, messages schema.Messages, ima if !slices.Contains(modelNames, c.Name) { utils.ResetDownloadTimers() // if we failed to load the model, we try to download it - err := gallery.InstallModelFromGallery(o.Galleries, o.BackendGalleries, o.SystemState, loader, c.Name, gallery.GalleryModel{}, utils.DisplayDownloadFunction, o.EnforcePredownloadScans, o.AutoloadBackendGalleries) + err := gallery.InstallModelFromGallery(ctx, o.Galleries, o.BackendGalleries, o.SystemState, loader, c.Name, gallery.GalleryModel{}, utils.DisplayDownloadFunction, o.EnforcePredownloadScans, o.AutoloadBackendGalleries) if err != nil { log.Error().Err(err).Msgf("failed to install model %q from gallery", modelFile) //return nil, err diff --git a/core/cli/backends.go b/core/cli/backends.go index 666f1eb0be11..0528d76678d8 100644 --- a/core/cli/backends.go +++ b/core/cli/backends.go @@ -1,6 +1,7 @@ package cli import ( + "context" "encoding/json" "fmt" @@ -102,7 +103,7 @@ func (bi *BackendsInstall) Run(ctx *cliContext.Context) error { } modelLoader := model.NewModelLoader(systemState, true) - err = startup.InstallExternalBackends(galleries, systemState, modelLoader, progressCallback, bi.BackendArgs, bi.Name, bi.Alias) + err = startup.InstallExternalBackends(context.Background(), galleries, systemState, modelLoader, progressCallback, bi.BackendArgs, bi.Name, bi.Alias) if err != nil { return err } diff --git a/core/cli/models.go b/core/cli/models.go index dd5273317bfc..bcbb60d48828 100644 --- a/core/cli/models.go +++ b/core/cli/models.go @@ -135,7 +135,7 @@ func (mi *ModelsInstall) Run(ctx *cliContext.Context) error { } modelLoader := model.NewModelLoader(systemState, true) - err = startup.InstallModels(galleryService, galleries, backendGalleries, systemState, modelLoader, !mi.DisablePredownloadScan, mi.AutoloadBackendGalleries, progressCallback, modelName) + err = startup.InstallModels(context.Background(), galleryService, galleries, backendGalleries, systemState, modelLoader, !mi.DisablePredownloadScan, mi.AutoloadBackendGalleries, progressCallback, modelName) if err != nil { return err } diff --git a/core/cli/worker/worker_llamacpp.go b/core/cli/worker/worker_llamacpp.go index 8a55f2345eee..1b4be6736637 100644 --- a/core/cli/worker/worker_llamacpp.go +++ b/core/cli/worker/worker_llamacpp.go @@ -1,6 +1,7 @@ package worker import ( + "context" "encoding/json" "errors" "fmt" @@ -42,7 +43,7 @@ func findLLamaCPPBackend(galleries string, systemState *system.SystemState) (str log.Error().Err(err).Msg("failed loading galleries") return "", err } - err := gallery.InstallBackendFromGallery(gals, systemState, ml, llamaCPPGalleryName, nil, true) + err := gallery.InstallBackendFromGallery(context.Background(), gals, systemState, ml, llamaCPPGalleryName, nil, true) if err != nil { log.Error().Err(err).Msg("llama-cpp backend not found, failed to install it") return "", err diff --git a/core/gallery/backends.go b/core/gallery/backends.go index 34b175aa78c9..aee4b2d93928 100644 --- a/core/gallery/backends.go +++ b/core/gallery/backends.go @@ -3,6 +3,7 @@ package gallery import ( + "context" "encoding/json" "errors" "fmt" @@ -69,7 +70,7 @@ func writeBackendMetadata(backendPath string, metadata *BackendMetadata) error { } // InstallBackendFromGallery installs a backend from the gallery. -func InstallBackendFromGallery(galleries []config.Gallery, systemState *system.SystemState, modelLoader *model.ModelLoader, name string, downloadStatus func(string, string, string, float64), force bool) error { +func InstallBackendFromGallery(ctx context.Context, galleries []config.Gallery, systemState *system.SystemState, modelLoader *model.ModelLoader, name string, downloadStatus func(string, string, string, float64), force bool) error { if !force { // check if we already have the backend installed backends, err := ListSystemBackends(systemState) @@ -109,7 +110,7 @@ func InstallBackendFromGallery(galleries []config.Gallery, systemState *system.S log.Debug().Str("name", name).Str("bestBackend", bestBackend.Name).Msg("Installing backend from meta backend") // Then, let's install the best backend - if err := InstallBackend(systemState, modelLoader, bestBackend, downloadStatus); err != nil { + if err := InstallBackend(ctx, systemState, modelLoader, bestBackend, downloadStatus); err != nil { return err } @@ -134,10 +135,10 @@ func InstallBackendFromGallery(galleries []config.Gallery, systemState *system.S return nil } - return InstallBackend(systemState, modelLoader, backend, downloadStatus) + return InstallBackend(ctx, systemState, modelLoader, backend, downloadStatus) } -func InstallBackend(systemState *system.SystemState, modelLoader *model.ModelLoader, config *GalleryBackend, downloadStatus func(string, string, string, float64)) error { +func InstallBackend(ctx context.Context, systemState *system.SystemState, modelLoader *model.ModelLoader, config *GalleryBackend, downloadStatus func(string, string, string, float64)) error { // Create base path if it doesn't exist err := os.MkdirAll(systemState.Backend.BackendsPath, 0750) if err != nil { @@ -164,11 +165,17 @@ func InstallBackend(systemState *system.SystemState, modelLoader *model.ModelLoa } } else { uri := downloader.URI(config.URI) - if err := uri.DownloadFile(backendPath, "", 1, 1, downloadStatus); err != nil { + if err := uri.DownloadFileWithContext(ctx, backendPath, "", 1, 1, downloadStatus); err != nil { success := false // Try to download from mirrors for _, mirror := range config.Mirrors { - if err := downloader.URI(mirror).DownloadFile(backendPath, "", 1, 1, downloadStatus); err == nil { + // Check for cancellation before trying next mirror + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + if err := downloader.URI(mirror).DownloadFileWithContext(ctx, backendPath, "", 1, 1, downloadStatus); err == nil { success = true break } diff --git a/core/gallery/backends_test.go b/core/gallery/backends_test.go index 26652caadd05..15900d25018b 100644 --- a/core/gallery/backends_test.go +++ b/core/gallery/backends_test.go @@ -1,6 +1,7 @@ package gallery import ( + "context" "encoding/json" "os" "path/filepath" @@ -55,7 +56,7 @@ var _ = Describe("Runtime capability-based backend selection", func() { ) must(err) sysDefault.GPUVendor = "" // force default selection - backs, err := ListSystemBackends(sysDefault) + backs, err := ListSystemBackends(sysDefault) must(err) aliasBack, ok := backs.Get("llama-cpp") Expect(ok).To(BeTrue()) @@ -77,7 +78,7 @@ var _ = Describe("Runtime capability-based backend selection", func() { must(err) sysNvidia.GPUVendor = "nvidia" sysNvidia.VRAM = 8 * 1024 * 1024 * 1024 - backs, err = ListSystemBackends(sysNvidia) + backs, err = ListSystemBackends(sysNvidia) must(err) aliasBack, ok = backs.Get("llama-cpp") Expect(ok).To(BeTrue()) @@ -116,13 +117,13 @@ var _ = Describe("Gallery Backends", func() { Describe("InstallBackendFromGallery", func() { It("should return error when backend is not found", func() { - err := InstallBackendFromGallery(galleries, systemState, ml, "non-existent", nil, true) + err := InstallBackendFromGallery(context.TODO(), galleries, systemState, ml, "non-existent", nil, true) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("no backend found with name \"non-existent\"")) }) It("should install backend from gallery", func() { - err := InstallBackendFromGallery(galleries, systemState, ml, "test-backend", nil, true) + err := InstallBackendFromGallery(context.TODO(), galleries, systemState, ml, "test-backend", nil, true) Expect(err).ToNot(HaveOccurred()) Expect(filepath.Join(tempDir, "test-backend", "run.sh")).To(BeARegularFile()) }) @@ -298,7 +299,7 @@ var _ = Describe("Gallery Backends", func() { VRAM: 1000000000000, Backend: system.Backend{BackendsPath: tempDir}, } - err = InstallBackendFromGallery([]config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) + err = InstallBackendFromGallery(context.TODO(), []config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) Expect(err).NotTo(HaveOccurred()) metaBackendPath := filepath.Join(tempDir, "meta-backend") @@ -378,7 +379,7 @@ var _ = Describe("Gallery Backends", func() { VRAM: 1000000000000, Backend: system.Backend{BackendsPath: tempDir}, } - err = InstallBackendFromGallery([]config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) + err = InstallBackendFromGallery(context.TODO(), []config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) Expect(err).NotTo(HaveOccurred()) metaBackendPath := filepath.Join(tempDir, "meta-backend") @@ -462,7 +463,7 @@ var _ = Describe("Gallery Backends", func() { VRAM: 1000000000000, Backend: system.Backend{BackendsPath: tempDir}, } - err = InstallBackendFromGallery([]config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) + err = InstallBackendFromGallery(context.TODO(), []config.Gallery{gallery}, nvidiaSystemState, ml, "meta-backend", nil, true) Expect(err).NotTo(HaveOccurred()) metaBackendPath := filepath.Join(tempDir, "meta-backend") @@ -561,7 +562,7 @@ var _ = Describe("Gallery Backends", func() { system.WithBackendPath(newPath), ) Expect(err).NotTo(HaveOccurred()) - err = InstallBackend(systemState, ml, &backend, nil) + err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) Expect(err).To(HaveOccurred()) // Will fail due to invalid URI, but path should be created Expect(newPath).To(BeADirectory()) }) @@ -593,7 +594,7 @@ var _ = Describe("Gallery Backends", func() { system.WithBackendPath(tempDir), ) Expect(err).NotTo(HaveOccurred()) - err = InstallBackend(systemState, ml, &backend, nil) + err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) Expect(err).ToNot(HaveOccurred()) Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).To(BeARegularFile()) dat, err := os.ReadFile(filepath.Join(tempDir, "test-backend", "metadata.json")) @@ -626,7 +627,7 @@ var _ = Describe("Gallery Backends", func() { Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).ToNot(BeARegularFile()) - err = InstallBackend(systemState, ml, &backend, nil) + err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) Expect(err).ToNot(HaveOccurred()) Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).To(BeARegularFile()) }) @@ -647,7 +648,7 @@ var _ = Describe("Gallery Backends", func() { system.WithBackendPath(tempDir), ) Expect(err).NotTo(HaveOccurred()) - err = InstallBackend(systemState, ml, &backend, nil) + err = InstallBackend(context.TODO(), systemState, ml, &backend, nil) Expect(err).ToNot(HaveOccurred()) Expect(filepath.Join(tempDir, "test-backend", "metadata.json")).To(BeARegularFile()) diff --git a/core/gallery/gallery.go b/core/gallery/gallery.go index d8dc3100f1a9..62362148ecef 100644 --- a/core/gallery/gallery.go +++ b/core/gallery/gallery.go @@ -1,6 +1,7 @@ package gallery import ( + "context" "fmt" "os" "path/filepath" @@ -28,6 +29,19 @@ func GetGalleryConfigFromURL[T any](url string, basePath string) (T, error) { return config, nil } +func GetGalleryConfigFromURLWithContext[T any](ctx context.Context, url string, basePath string) (T, error) { + var config T + uri := downloader.URI(url) + err := uri.DownloadWithAuthorizationAndCallback(ctx, basePath, "", func(url string, d []byte) error { + return yaml.Unmarshal(d, &config) + }) + if err != nil { + log.Error().Err(err).Str("url", url).Msg("failed to get gallery config for url") + return config, err + } + return config, nil +} + func ReadConfigFile[T any](filePath string) (*T, error) { // Read the YAML file yamlFile, err := os.ReadFile(filePath) diff --git a/core/gallery/models.go b/core/gallery/models.go index a1abe0ee1182..7205886b633c 100644 --- a/core/gallery/models.go +++ b/core/gallery/models.go @@ -1,6 +1,7 @@ package gallery import ( + "context" "errors" "fmt" "os" @@ -72,6 +73,7 @@ type PromptTemplate struct { // Installs a model from the gallery func InstallModelFromGallery( + ctx context.Context, modelGalleries, backendGalleries []config.Gallery, systemState *system.SystemState, modelLoader *model.ModelLoader, @@ -84,7 +86,7 @@ func InstallModelFromGallery( if len(model.URL) > 0 { var err error - config, err = GetGalleryConfigFromURL[ModelConfig](model.URL, systemState.Model.ModelsPath) + config, err = GetGalleryConfigFromURLWithContext[ModelConfig](ctx, model.URL, systemState.Model.ModelsPath) if err != nil { return err } @@ -125,7 +127,7 @@ func InstallModelFromGallery( return err } - installedModel, err := InstallModel(systemState, installName, &config, model.Overrides, downloadStatus, enforceScan) + installedModel, err := InstallModel(ctx, systemState, installName, &config, model.Overrides, downloadStatus, enforceScan) if err != nil { return err } @@ -133,7 +135,7 @@ func InstallModelFromGallery( if automaticallyInstallBackend && installedModel.Backend != "" { log.Debug().Msgf("Installing backend %q", installedModel.Backend) - if err := InstallBackendFromGallery(backendGalleries, systemState, modelLoader, installedModel.Backend, downloadStatus, false); err != nil { + if err := InstallBackendFromGallery(ctx, backendGalleries, systemState, modelLoader, installedModel.Backend, downloadStatus, false); err != nil { return err } } @@ -154,7 +156,7 @@ func InstallModelFromGallery( return applyModel(model) } -func InstallModel(systemState *system.SystemState, nameOverride string, config *ModelConfig, configOverrides map[string]interface{}, downloadStatus func(string, string, string, float64), enforceScan bool) (*lconfig.ModelConfig, error) { +func InstallModel(ctx context.Context, systemState *system.SystemState, nameOverride string, config *ModelConfig, configOverrides map[string]interface{}, downloadStatus func(string, string, string, float64), enforceScan bool) (*lconfig.ModelConfig, error) { basePath := systemState.Model.ModelsPath // Create base path if it doesn't exist err := os.MkdirAll(basePath, 0750) @@ -168,6 +170,13 @@ func InstallModel(systemState *system.SystemState, nameOverride string, config * // Download files and verify their SHA for i, file := range config.Files { + // Check for cancellation before each file + select { + case <-ctx.Done(): + return nil, ctx.Err() + default: + } + log.Debug().Msgf("Checking %q exists and matches SHA", file.Filename) if err := utils.VerifyPath(file.Filename, basePath); err != nil { @@ -185,7 +194,7 @@ func InstallModel(systemState *system.SystemState, nameOverride string, config * } } uri := downloader.URI(file.URI) - if err := uri.DownloadFile(filePath, file.SHA256, i, len(config.Files), downloadStatus); err != nil { + if err := uri.DownloadFileWithContext(ctx, filePath, file.SHA256, i, len(config.Files), downloadStatus); err != nil { return nil, err } } diff --git a/core/gallery/models_test.go b/core/gallery/models_test.go index 3ae76c203c32..df0bee06ce8e 100644 --- a/core/gallery/models_test.go +++ b/core/gallery/models_test.go @@ -1,6 +1,7 @@ package gallery_test import ( + "context" "errors" "os" "path/filepath" @@ -34,7 +35,7 @@ var _ = Describe("Model test", func() { system.WithModelPath(tempdir), ) Expect(err).ToNot(HaveOccurred()) - _, err = InstallModel(systemState, "", c, map[string]interface{}{}, func(string, string, string, float64) {}, true) + _, err = InstallModel(context.TODO(), systemState, "", c, map[string]interface{}{}, func(string, string, string, float64) {}, true) Expect(err).ToNot(HaveOccurred()) for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "cerebras.yaml"} { @@ -88,7 +89,7 @@ var _ = Describe("Model test", func() { Expect(models[0].URL).To(Equal(bertEmbeddingsURL)) Expect(models[0].Installed).To(BeFalse()) - err = InstallModelFromGallery(galleries, []config.Gallery{}, systemState, nil, "test@bert", GalleryModel{}, func(s1, s2, s3 string, f float64) {}, true, true) + err = InstallModelFromGallery(context.TODO(), galleries, []config.Gallery{}, systemState, nil, "test@bert", GalleryModel{}, func(s1, s2, s3 string, f float64) {}, true, true) Expect(err).ToNot(HaveOccurred()) dat, err := os.ReadFile(filepath.Join(tempdir, "bert.yaml")) @@ -129,7 +130,7 @@ var _ = Describe("Model test", func() { system.WithModelPath(tempdir), ) Expect(err).ToNot(HaveOccurred()) - _, err = InstallModel(systemState, "foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true) + _, err = InstallModel(context.TODO(), systemState, "foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true) Expect(err).ToNot(HaveOccurred()) for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "foo.yaml"} { @@ -149,7 +150,7 @@ var _ = Describe("Model test", func() { system.WithModelPath(tempdir), ) Expect(err).ToNot(HaveOccurred()) - _, err = InstallModel(systemState, "foo", c, map[string]interface{}{"backend": "foo"}, func(string, string, string, float64) {}, true) + _, err = InstallModel(context.TODO(), systemState, "foo", c, map[string]interface{}{"backend": "foo"}, func(string, string, string, float64) {}, true) Expect(err).ToNot(HaveOccurred()) for _, f := range []string{"cerebras", "cerebras-completion.tmpl", "cerebras-chat.tmpl", "foo.yaml"} { @@ -179,7 +180,7 @@ var _ = Describe("Model test", func() { system.WithModelPath(tempdir), ) Expect(err).ToNot(HaveOccurred()) - _, err = InstallModel(systemState, "../../../foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true) + _, err = InstallModel(context.TODO(), systemState, "../../../foo", c, map[string]interface{}{}, func(string, string, string, float64) {}, true) Expect(err).To(HaveOccurred()) }) }) diff --git a/core/http/app_test.go b/core/http/app_test.go index c9c752df578b..2d4ff6d06571 100644 --- a/core/http/app_test.go +++ b/core/http/app_test.go @@ -85,7 +85,7 @@ func getModels(url string) ([]gallery.GalleryModel, error) { response := []gallery.GalleryModel{} uri := downloader.URI(url) // TODO: No tests currently seem to exercise file:// urls. Fix? - err := uri.DownloadWithAuthorizationAndCallback("", bearerKey, func(url string, i []byte) error { + err := uri.DownloadWithAuthorizationAndCallback(context.TODO(), "", bearerKey, func(url string, i []byte) error { // Unmarshal YAML data into a struct return json.Unmarshal(i, &response) }) diff --git a/core/http/routes/ui_api.go b/core/http/routes/ui_api.go index 15a4769d372c..3ea4852e08dd 100644 --- a/core/http/routes/ui_api.go +++ b/core/http/routes/ui_api.go @@ -1,6 +1,7 @@ package routes import ( + "context" "fmt" "math" "net/url" @@ -35,23 +36,35 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig progress := 0 isDeletion := false isQueued := false + isCancelled := false + isCancellable := false message := "" if status != nil { - // Skip completed operations - if status.Processed { + // Skip completed operations (unless cancelled and not yet cleaned up) + if status.Processed && !status.Cancelled { + continue + } + // Skip cancelled operations that are processed (they're done, no need to show) + if status.Processed && status.Cancelled { continue } progress = int(status.Progress) isDeletion = status.Deletion + isCancelled = status.Cancelled + isCancellable = status.Cancellable message = status.Message if isDeletion { taskType = "deletion" } + if isCancelled { + taskType = "cancelled" + } } else { // Job is queued but hasn't started isQueued = true + isCancellable = true message = "Operation queued" } @@ -76,16 +89,18 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig } operations = append(operations, fiber.Map{ - "id": galleryID, - "name": displayName, - "fullName": galleryID, - "jobID": jobID, - "progress": progress, - "taskType": taskType, - "isDeletion": isDeletion, - "isBackend": isBackend, - "isQueued": isQueued, - "message": message, + "id": galleryID, + "name": displayName, + "fullName": galleryID, + "jobID": jobID, + "progress": progress, + "taskType": taskType, + "isDeletion": isDeletion, + "isBackend": isBackend, + "isQueued": isQueued, + "isCancelled": isCancelled, + "cancellable": isCancellable, + "message": message, }) } @@ -108,6 +123,28 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig }) }) + // Cancel operation endpoint + app.Post("/api/operations/:jobID/cancel", func(c *fiber.Ctx) error { + jobID := strings.Clone(c.Params("jobID")) + log.Debug().Msgf("API request to cancel operation: %s", jobID) + + err := galleryService.CancelOperation(jobID) + if err != nil { + log.Error().Err(err).Msgf("Failed to cancel operation: %s", jobID) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + // Clean up opcache for cancelled operation + opcache.DeleteUUID(jobID) + + return c.JSON(fiber.Map{ + "success": true, + "message": "Operation cancelled", + }) + }) + // Model Gallery APIs app.Get("/api/models", func(c *fiber.Ctx) error { term := c.Query("term") @@ -248,12 +285,17 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig uid := id.String() opcache.Set(galleryID, uid) + ctx, cancelFunc := context.WithCancel(context.Background()) op := services.GalleryOp[gallery.GalleryModel, gallery.ModelConfig]{ ID: uid, GalleryElementName: galleryID, Galleries: appConfig.Galleries, BackendGalleries: appConfig.BackendGalleries, + Context: ctx, + CancelFunc: cancelFunc, } + // Store cancellation function immediately so queued operations can be cancelled + galleryService.StoreCancellation(uid, cancelFunc) go func() { galleryService.ModelGalleryChannel <- op }() @@ -291,13 +333,18 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig opcache.Set(galleryID, uid) + ctx, cancelFunc := context.WithCancel(context.Background()) op := services.GalleryOp[gallery.GalleryModel, gallery.ModelConfig]{ ID: uid, Delete: true, GalleryElementName: galleryName, Galleries: appConfig.Galleries, BackendGalleries: appConfig.BackendGalleries, + Context: ctx, + CancelFunc: cancelFunc, } + // Store cancellation function immediately so queued operations can be cancelled + galleryService.StoreCancellation(uid, cancelFunc) go func() { galleryService.ModelGalleryChannel <- op cl.RemoveModelConfig(galleryName) @@ -341,7 +388,7 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig }) } - _, err = gallery.InstallModel(appConfig.SystemState, model.Name, &config, model.Overrides, nil, false) + _, err = gallery.InstallModel(context.Background(), appConfig.SystemState, model.Name, &config, model.Overrides, nil, false) if err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "error": err.Error(), @@ -526,11 +573,16 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig uid := id.String() opcache.Set(backendID, uid) + ctx, cancelFunc := context.WithCancel(context.Background()) op := services.GalleryOp[gallery.GalleryBackend, any]{ ID: uid, GalleryElementName: backendID, Galleries: appConfig.BackendGalleries, + Context: ctx, + CancelFunc: cancelFunc, } + // Store cancellation function immediately so queued operations can be cancelled + galleryService.StoreCancellation(uid, cancelFunc) go func() { galleryService.BackendGalleryChannel <- op }() @@ -568,12 +620,17 @@ func RegisterUIAPIRoutes(app *fiber.App, cl *config.ModelConfigLoader, appConfig opcache.Set(backendID, uid) + ctx, cancelFunc := context.WithCancel(context.Background()) op := services.GalleryOp[gallery.GalleryBackend, any]{ ID: uid, Delete: true, GalleryElementName: backendName, Galleries: appConfig.BackendGalleries, + Context: ctx, + CancelFunc: cancelFunc, } + // Store cancellation function immediately so queued operations can be cancelled + galleryService.StoreCancellation(uid, cancelFunc) go func() { galleryService.BackendGalleryChannel <- op }() diff --git a/core/http/views/partials/inprogress.html b/core/http/views/partials/inprogress.html index 7ebe04927d1b..8c2dce3baebd 100644 --- a/core/http/views/partials/inprogress.html +++ b/core/http/views/partials/inprogress.html @@ -71,15 +71,34 @@

Queued -