From 0ac6ac8dce6a456f04733ad1bf4ada1e5f068e77 Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Fri, 10 May 2024 02:13:02 +0100 Subject: [PATCH] runner-go (#102) Reviewed-on: https://git.andr3h3nriqu3s.com/andr3/fyp/pulls/102 Co-authored-by: Andre Henriques Co-committed-by: Andre Henriques --- go.mod | 4 +- logic/db_types/classes.go | 16 + logic/db_types/definitions.go | 95 + logic/db_types/layer.go | 69 + logic/db_types/types.go | 117 +- logic/db_types/user.go | 17 +- logic/models/classes/main.go | 6 +- logic/models/data.go | 4 +- logic/models/delete.go | 2 +- logic/models/edit.go | 6 +- logic/models/train/remote_train.go | 118 + logic/models/train/reset.go | 2 +- logic/models/train/train.go | 481 +- logic/tasks/index.go | 2 + logic/tasks/runner.go | 960 ++++ logic/tasks/runner/runner.go | 120 +- logic/tasks/runner/utils.go | 51 + logic/tasks/runner_data.go | 25 + logic/tasks/utils/runner.go | 29 + logic/tasks/utils/utils.go | 6 +- logic/users/users.go | 11 + logic/utils/config.go | 4 + logic/utils/handler.go | 4 +- main.go | 4 +- nginx.dev.conf | 2 +- sql/tasks.sql | 11 + views/py/python_model_template.py | 10 +- webpage/bun.lockb | Bin 129378 -> 156844 bytes webpage/package-lock.json | 4125 +++++++++++++++++ webpage/package.json | 9 +- webpage/src/NavBar.svelte | 10 + webpage/src/app.html | 2 + webpage/src/routes/UserStore.svelte.ts | 2 +- webpage/src/routes/admin/runners/+page.svelte | 348 ++ .../src/routes/admin/runners/CardInfo.svelte | 90 + webpage/src/routes/admin/runners/types.ts | 10 + webpage/src/routes/models/+page.svelte | 89 +- webpage/src/routes/models/add/+page.svelte | 14 +- webpage/src/routes/models/edit/+page.svelte | 2 +- .../src/routes/models/edit/RunModel.svelte | 156 +- .../src/routes/models/edit/tasks/Stats.svelte | 54 +- .../models/edit/tasks/TasksTable.svelte | 14 +- webpage/src/routes/models/edit/tasks/types.ts | 12 + webpage/vite.config.ts | 7 +- 44 files changed, 6609 insertions(+), 511 deletions(-) create mode 100644 logic/db_types/definitions.go create mode 100644 logic/db_types/layer.go create mode 100644 logic/models/train/remote_train.go create mode 100644 logic/tasks/runner.go create mode 100644 logic/tasks/runner/utils.go create mode 100644 logic/tasks/runner_data.go create mode 100644 logic/tasks/utils/runner.go create mode 100644 webpage/package-lock.json create mode 100644 webpage/src/routes/admin/runners/+page.svelte create mode 100644 webpage/src/routes/admin/runners/CardInfo.svelte create mode 100644 webpage/src/routes/admin/runners/types.ts create mode 100644 webpage/src/routes/models/edit/tasks/types.ts diff --git a/go.mod b/go.mod index 9ac9940..b697e3c 100644 --- a/go.mod +++ b/go.mod @@ -9,10 +9,11 @@ require ( github.com/google/uuid v1.6.0 github.com/lib/pq v1.10.9 golang.org/x/crypto v0.19.0 + github.com/BurntSushi/toml v1.3.2 + github.com/goccy/go-json v0.10.2 ) require ( - github.com/BurntSushi/toml v1.3.2 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/lipgloss v0.9.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect @@ -20,7 +21,6 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.19.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgx v3.6.2+incompatible // indirect diff --git a/logic/db_types/classes.go b/logic/db_types/classes.go index 49eee43..b74e5a5 100644 --- a/logic/db_types/classes.go +++ b/logic/db_types/classes.go @@ -6,3 +6,19 @@ const ( DATA_POINT_MODE_TRAINING DATA_POINT_MODE = 1 DATA_POINT_MODE_TESTING = 2 ) + +type ModelClassStatus int + +const ( + CLASS_STATUS_TO_TRAIN ModelClassStatus = iota + 1 + CLASS_STATUS_TRAINING + CLASS_STATUS_TRAINED +) + +type ModelClass struct { + Id string `db:"mc.id" json:"id"` + ModelId string `db:"mc.model_id" json:"model_id"` + Name string `db:"mc.name" json:"name"` + ClassOrder int `db:"mc.class_order" json:"class_order"` + Status int `db:"mc.status" json:"status"` +} diff --git a/logic/db_types/definitions.go b/logic/db_types/definitions.go new file mode 100644 index 0000000..28392e0 --- /dev/null +++ b/logic/db_types/definitions.go @@ -0,0 +1,95 @@ +package dbtypes + +import ( + "time" + + "git.andr3h3nriqu3s.com/andr3/fyp/logic/db" +) + +type DefinitionStatus int + +const ( + DEFINITION_STATUS_CANCELD_TRAINING DefinitionStatus = -4 + DEFINITION_STATUS_FAILED_TRAINING = -3 + DEFINITION_STATUS_PRE_INIT = 1 + DEFINITION_STATUS_INIT = 2 + DEFINITION_STATUS_TRAINING = 3 + DEFINITION_STATUS_PAUSED_TRAINING = 6 + DEFINITION_STATUS_TRANIED = 4 + DEFINITION_STATUS_READY = 5 +) + +type Definition struct { + Id string `db:"md.id" json:"id"` + ModelId string `db:"md.model_id" json:"model_id"` + Accuracy float64 `db:"md.accuracy" json:"accuracy"` + TargetAccuracy int `db:"md.target_accuracy" json:"target_accuracy"` + Epoch int `db:"md.epoch" json:"epoch"` + Status int `db:"md.status" json:"status"` + CreatedOn time.Time `db:"md.created_on" json:"created"` + EpochProgress int `db:"md.epoch_progress" json:"epoch_progress"` +} + +type SortByAccuracyDefinitions []*Definition + +func (nf SortByAccuracyDefinitions) Len() int { return len(nf) } +func (nf SortByAccuracyDefinitions) Swap(i, j int) { nf[i], nf[j] = nf[j], nf[i] } +func (nf SortByAccuracyDefinitions) Less(i, j int) bool { + return nf[i].Accuracy < nf[j].Accuracy +} + +func GetDefinition(db db.Db, definition_id string) (definition Definition, err error) { + err = GetDBOnce(db, &definition, "model_definition as md where id=$1;", definition_id) + return +} + +func MakeDefenition(db db.Db, model_id string, target_accuracy int) (definition Definition, err error) { + var NewDefinition = struct { + ModelId string `db:"model_id"` + TargetAccuracy int `db:"target_accuracy"` + }{ModelId: model_id, TargetAccuracy: target_accuracy} + + id, err := InsertReturnId(db, &NewDefinition, "model_definition", "id") + if err != nil { + return + } + return GetDefinition(db, id) +} + +func (d Definition) UpdateStatus(db db.Db, status DefinitionStatus) (err error) { + _, err = db.Exec("update model_definition set status=$1 where id=$2", status, d.Id) + return +} + +func (d Definition) MakeLayer(db db.Db, layer_order int, layer_type LayerType, shape string) (layer Layer, err error) { + var NewLayer = struct { + DefinitionId string `db:"def_id"` + LayerOrder int `db:"layer_order"` + LayerType LayerType `db:"layer_type"` + Shape string `db:"shape"` + }{ + DefinitionId: d.Id, + LayerOrder: layer_order, + LayerType: layer_type, + Shape: shape, + } + + id, err := InsertReturnId(db, &NewLayer, "model_definition_layer", "id") + if err != nil { + return + } + + return GetLayer(db, id) +} + +func (d Definition) GetLayers(db db.Db, filter string, args ...any) (layer []*Layer, err error) { + args = append(args, d.Id) + return GetDbMultitple[Layer](db, "model_definition_layer as mdl where mdl.def_id=$1 "+filter, args...) +} + +func (d *Definition) UpdateAfterEpoch(db db.Db, accuracy float64, epoch int) (err error) { + d.Accuracy = accuracy + d.Epoch += epoch + _, err = db.Exec("update model_definition set epoch=$1, accuracy=$2 where id=$3", d.Epoch, d.Accuracy, d.Id) + return +} diff --git a/logic/db_types/layer.go b/logic/db_types/layer.go new file mode 100644 index 0000000..c8e12cd --- /dev/null +++ b/logic/db_types/layer.go @@ -0,0 +1,69 @@ +package dbtypes + +import ( + "encoding/json" + "fmt" + + "git.andr3h3nriqu3s.com/andr3/fyp/logic/db" + "github.com/charmbracelet/log" +) + +type LayerType int + +const ( + LAYER_INPUT LayerType = 1 + LAYER_DENSE = 2 + LAYER_FLATTEN = 3 + LAYER_SIMPLE_BLOCK = 4 +) + +type Layer struct { + Id string `db:"mdl.id" json:"id"` + DefinitionId string `db:"mdl.def_id" json:"definition_id"` + LayerOrder int `db:"mdl.layer_order" json:"layer_order"` + LayerType LayerType `db:"mdl.layer_type" json:"layer_type"` + Shape string `db:"mdl.shape" json:"shape"` + ExpType int `db:"mdl.exp_type" json:"exp_type"` +} + +func (x *Layer) ShapeToSize() { + v := x.GetShape() + switch x.LayerType { + case LAYER_INPUT: + x.Shape = fmt.Sprintf("%d,%d", v[1], v[2]) + case LAYER_DENSE: + x.Shape = fmt.Sprintf("(%d)", v[0]) + default: + x.Shape = "ERROR" + } +} + +func ShapeToString(args ...int) string { + text, err := json.Marshal(args) + if err != nil { + log.Error("json err!", "err", err) + panic("Could not generate Shape") + } + return string(text) +} + +func StringToShape(str string) (shape []int64) { + err := json.Unmarshal([]byte(str), &shape) + if err != nil { + log.Error("json err!", "err", err) + panic("Could not parse Shape") + } + return +} + +func (l Layer) GetShape() []int64 { + if l.Shape == "" { + return []int64{} + } + return StringToShape(l.Shape) +} + +func GetLayer(db db.Db, layer_id string) (layer Layer, err error) { + err = GetDBOnce(db, &layer, "model_definition_layer as mdl where mdl.id=$1", layer_id) + return +} diff --git a/logic/db_types/types.go b/logic/db_types/types.go index 97fb993..bad0035 100644 --- a/logic/db_types/types.go +++ b/logic/db_types/types.go @@ -2,23 +2,25 @@ package dbtypes import ( "errors" + "fmt" "git.andr3h3nriqu3s.com/andr3/fyp/logic/db" ) -const ( - FAILED_TRAINING = -4 - FAILED_PREPARING_TRAINING = -3 - FAILED_PREPARING_ZIP_FILE = -2 - FAILED_PREPARING = -1 +type ModelStatus int - PREPARING = 1 - CONFIRM_PRE_TRAINING = 2 - PREPARING_ZIP_FILE = 3 - TRAINING = 4 - READY = 5 - READY_ALTERATION = 6 - READY_ALTERATION_FAILED = -6 +const ( + FAILED_TRAINING ModelStatus = -4 + FAILED_PREPARING_TRAINING = -3 + FAILED_PREPARING_ZIP_FILE = -2 + FAILED_PREPARING = -1 + PREPARING = 1 + CONFIRM_PRE_TRAINING = 2 + PREPARING_ZIP_FILE = 3 + TRAINING = 4 + READY = 5 + READY_ALTERATION = 6 + READY_ALTERATION_FAILED = -6 READY_RETRAIN = 7 READY_RETRAIN_FAILED = -7 @@ -26,15 +28,6 @@ const ( type ModelDefinitionStatus int -type LayerType int - -const ( - LAYER_INPUT LayerType = 1 - LAYER_DENSE = 2 - LAYER_FLATTEN = 3 - LAYER_SIMPLE_BLOCK = 4 -) - const ( MODEL_DEFINITION_STATUS_CANCELD_TRAINING ModelDefinitionStatus = -4 MODEL_DEFINITION_STATUS_FAILED_TRAINING = -3 @@ -46,14 +39,6 @@ const ( MODEL_DEFINITION_STATUS_READY = 5 ) -type ModelClassStatus int - -const ( - MODEL_CLASS_STATUS_TO_TRAIN ModelClassStatus = 1 - MODEL_CLASS_STATUS_TRAINING = 2 - MODEL_CLASS_STATUS_TRAINED = 3 -) - type ModelHeadStatus int const ( @@ -65,17 +50,16 @@ const ( ) type BaseModel struct { - Name string - Status int - Id string - - ModelType int `db:"model_type"` - ImageModeRaw string `db:"color_mode"` - ImageMode int `db:"0"` - Width int - Height int - Format string - CanTrain int `db:"can_train"` + Name string `json:"name"` + Status int `json:"status"` + Id string `json:"id"` + ModelType int `db:"model_type" json:"model_type"` + ImageModeRaw string `db:"color_mode" json:"image_more_raw"` + ImageMode int `db:"0" json:"image_mode"` + Width int `json:"width"` + Height int `json:"height"` + Format string `json:"format"` + CanTrain int `db:"can_train" json:"can_train"` } var ModelNotFoundError = errors.New("Model not found error") @@ -97,6 +81,59 @@ func (m BaseModel) CanEval() bool { return true } +// DO NOT Pass un filtered data on filters +func (m BaseModel) GetDefinitions(db db.Db, filters string, args ...any) ([]*Definition, error) { + n_args := []any{m.Id} + n_args = append(n_args, args...) + return GetDbMultitple[Definition](db, fmt.Sprintf("model_definition as md where md.model_id=$1 %s", filters), n_args...) +} + +func (m BaseModel) GetClasses(db db.Db, filters string, args ...any) ([]*ModelClass, error) { + n_args := []any{m.Id} + n_args = append(n_args, args...) + return GetDbMultitple[ModelClass](db, fmt.Sprintf("model_classes as mc where mc.model_id=$1 %s", filters), n_args...) +} + +func (m *BaseModel) UpdateStatus(db db.Db, status ModelStatus) (err error) { + _, err = db.Exec("update models set status=$1 where id=$2", status, m.Id) + return +} + +type DataPoint struct { + Id string `json:"id"` + Class int `json:"class"` + Path string `json:"path"` +} + +func (m BaseModel) DataPoints(db db.Db, mode DATA_POINT_MODE) (data []DataPoint, err error) { + rows, err := db.Query( + "select mdp.id, mc.class_order, mdp.file_path from model_data_point as mdp inner "+ + "join model_classes as mc on mc.id = mdp.class_id "+ + "where mc.model_id = $1 and mdp.model_mode=$2;", + m.Id, mode) + if err != nil { + return + } + defer rows.Close() + + data = []DataPoint{} + + for rows.Next() { + var id string + var class_order int + var file_path string + if err = rows.Scan(&id, &class_order, &file_path); err != nil { + return + } + data = append(data, DataPoint{ + Id: id, + Path: file_path, + Class: class_order, + }) + } + return +} + func StringToImageMode(colorMode string) int { switch colorMode { case "greyscale": diff --git a/logic/db_types/user.go b/logic/db_types/user.go index 1996241..f381063 100644 --- a/logic/db_types/user.go +++ b/logic/db_types/user.go @@ -14,10 +14,19 @@ const ( ) type User struct { - Id string `db:"u.id"` - Username string `db:"u.username"` - Email string `db:"u.email"` - UserType int `db:"u.user_type"` + Id string `db:"u.id" json:"id"` + Username string `db:"u.username" json:"username"` + Email string `db:"u.email" json:"email"` + UserType int `db:"u.user_type" json:"user_type"` +} + +func UserFromId(db db.Db, id string) (*User, error) { + var user User + err := GetDBOnce(db, &user, "users as u where u.id=$1", id) + if err != nil { + return nil, err + } + return &user, nil } func UserFromToken(db db.Db, token string) (*User, error) { diff --git a/logic/models/classes/main.go b/logic/models/classes/main.go index 81d7563..8f91403 100644 --- a/logic/models/classes/main.go +++ b/logic/models/classes/main.go @@ -7,15 +7,15 @@ import ( . "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" ) -type ModelClass struct { +type ModelClassJSON struct { Id string `json:"id"` ModelId string `json:"model_id" db:"model_id"` Name string `json:"name"` Status int `json:"status"` } -func ListClasses(c BasePack, model_id string) (cls []*ModelClass, err error) { - return GetDbMultitple[ModelClass](c.GetDb(), "model_classes where model_id=$1", model_id) +func ListClasses(c BasePack, model_id string) (cls []*ModelClassJSON, err error) { + return GetDbMultitple[ModelClassJSON](c.GetDb(), "model_classes where model_id=$1", model_id) } func ModelHasDataPoints(db db.Db, model_id string) (result bool, err error) { diff --git a/logic/models/data.go b/logic/models/data.go index 47112a0..1e6a89c 100644 --- a/logic/models/data.go +++ b/logic/models/data.go @@ -495,7 +495,7 @@ func handleDataUpload(handle *Handle) { return c.E500M("Could not create class", err) } - var modelClass model_classes.ModelClass + var modelClass model_classes.ModelClassJSON err = GetDBOnce(c, &modelClass, "model_classes where id=$1;", id) if err != nil { return c.E500M("Failed to get class information but class was creted", err) @@ -704,7 +704,7 @@ func handleDataUpload(handle *Handle) { return c.Error500(err) } } else { - _, err = handle.Db.Exec("delete from model_classes where model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TO_TRAIN) + _, err = handle.Db.Exec("delete from model_classes where model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TO_TRAIN) if err != nil { return c.Error500(err) } diff --git a/logic/models/delete.go b/logic/models/delete.go index ed0bf27..7abc28f 100644 --- a/logic/models/delete.go +++ b/logic/models/delete.go @@ -51,7 +51,7 @@ func handleDelete(handle *Handle) { return c.E500M("Faield to get model", err) } - switch model.Status { + switch ModelStatus(model.Status) { case FAILED_TRAINING: fallthrough case FAILED_PREPARING_ZIP_FILE: diff --git a/logic/models/edit.go b/logic/models/edit.go index 0a6efa8..6530d93 100644 --- a/logic/models/edit.go +++ b/logic/models/edit.go @@ -35,9 +35,9 @@ func handleEdit(handle *Handle) { } type ReturnType struct { - Classes []*model_classes.ModelClass `json:"classes"` - HasData bool `json:"has_data"` - NumberOfInvalidImages int `json:"number_of_invalid_images"` + Classes []*model_classes.ModelClassJSON `json:"classes"` + HasData bool `json:"has_data"` + NumberOfInvalidImages int `json:"number_of_invalid_images"` } c.ShowMessage = false diff --git a/logic/models/train/remote_train.go b/logic/models/train/remote_train.go new file mode 100644 index 0000000..7cbf356 --- /dev/null +++ b/logic/models/train/remote_train.go @@ -0,0 +1,118 @@ +package models_train + +import ( + "errors" + + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/utils" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" + "github.com/charmbracelet/log" + "github.com/goccy/go-json" +) + +func PrepareTraining(handler *Handle, b BasePack, task Task, runner_id string) (err error) { + l := b.GetLogger() + + model, err := GetBaseModel(b.GetDb(), *task.ModelId) + if err != nil { + task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Failed to get model information") + l.Error("Failed to get model information", "err", err) + return err + } + + if model.Status != TRAINING { + task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Model not in the correct status for training") + return errors.New("Model not in the right status") + } + + // TODO do this when the runner says it's OK + //task.UpdateStatusLog(b, TASK_RUNNING, "Training model") + + // TODO move this to the runner part as well + var dat struct { + NumberOfModels int + Accuracy int + } + + err = json.Unmarshal([]byte(task.ExtraTaskInfo), &dat) + if err != nil { + task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Failed to get model extra information") + } + + if model.ModelType == 2 { + full_error := generateExpandableDefinitions(b, model, dat.Accuracy, dat.NumberOfModels) + if full_error != nil { + l.Error("Failed to generate defintions", "err", full_error) + task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Failed generate model") + return errors.New("Failed to generate definitions") + } + } else { + error := generateDefinitions(b, model, dat.Accuracy, dat.NumberOfModels) + if error != nil { + task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Failed generate model") + return errors.New("Failed to generate definitions") + } + } + + runners := handler.DataMap["runners"].(map[string]interface{}) + runner := runners[runner_id].(map[string]interface{}) + runner["task"] = &task + runners[runner_id] = runner + handler.DataMap["runners"] = runners + + return +} + +func CleanUpFailed(b BasePack, task *Task) { + db := b.GetDb() + l := b.GetLogger() + model, err := GetBaseModel(db, *task.ModelId) + if err != nil { + l.Error("Failed to get model", "err", err) + } else { + err = model.UpdateStatus(db, FAILED_TRAINING) + if err != nil { + l.Error("Failed to get status", err) + } + } + // Set the class status to trained + err = SetModelClassStatus(b, CLASS_STATUS_TO_TRAIN, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) + if err != nil { + l.Error("Failed to set class status") + return + } +} + +func CleanUpFailedRetrain(b BasePack, task *Task) { + db := b.GetDb() + l := b.GetLogger() + + model, err := GetBaseModel(db, *task.ModelId) + if err != nil { + l.Error("Failed to get model", "err", err) + } else { + err = model.UpdateStatus(db, FAILED_TRAINING) + if err != nil { + l.Error("Failed to get status", err) + } + } + + ResetClasses(b, model) + ModelUpdateStatus(b, model.Id, READY_RETRAIN_FAILED) + + var defData struct { + Id string `db:"md.id"` + TargetAcuuracy float64 `db:"md.target_accuracy"` + } + + err = GetDBOnce(db, &defData, "models as m inner join model_definition as md on m.id = md.model_id where m.id=$1;", task.ModelId) + if err != nil { + log.Error("failed to get def data", err) + return + } + + _, err_ := db.Exec("delete from exp_model_head where def_id=$1 and status in (2,3)", defData.Id) + if err_ != nil { + panic(err_) + } +} diff --git a/logic/models/train/reset.go b/logic/models/train/reset.go index bc92eeb..655bcc1 100644 --- a/logic/models/train/reset.go +++ b/logic/models/train/reset.go @@ -17,7 +17,7 @@ func handleRest(handle *Handle) { return c.E500M("Failed to get model", err) } - if model.Status != FAILED_PREPARING_TRAINING && model.Status != FAILED_TRAINING { + if model.Status != FAILED_PREPARING_TRAINING && model.Status != int(FAILED_TRAINING) { return c.JsonBadRequest("Model is not in status that be reset") } diff --git a/logic/models/train/train.go b/logic/models/train/train.go index 8c4fb32..e45553a 100644 --- a/logic/models/train/train.go +++ b/logic/models/train/train.go @@ -39,16 +39,6 @@ func getDir() string { return dir } -// This function creates a new model_definition -func MakeDefenition(db db.Db, model_id string, target_accuracy int) (id string, err error) { - var NewDefinition = struct { - ModelId string `db:"model_id"` - TargetAccuracy int `db:"target_accuracy"` - }{ModelId: model_id, TargetAccuracy: target_accuracy} - - return InsertReturnId(db, &NewDefinition, "model_definition", "id") -} - func ModelDefinitionUpdateStatus(c BasePack, id string, status ModelDefinitionStatus) (err error) { _, err = c.GetDb().Exec("update model_definition set status = $1 where id = $2", status, id) return @@ -111,6 +101,10 @@ func setModelClassStatus(c BasePack, status ModelClassStatus, filter string, arg return } +func SetModelClassStatus(c BasePack, status ModelClassStatus, filter string, args ...any) (err error) { + return setModelClassStatus(c, status, filter, args...) +} + func generateCvsExp(c BasePack, run_path string, model_id string, doPanic bool) (count int, err error) { db := c.GetDb() @@ -118,14 +112,14 @@ func generateCvsExp(c BasePack, run_path string, model_id string, doPanic bool) var co struct { Count int `db:"count(*)"` } - err = GetDBOnce(db, &co, "model_classes where model_id=$1 and status=$2;", model_id, MODEL_CLASS_STATUS_TRAINING) + err = GetDBOnce(db, &co, "model_classes where model_id=$1 and status=$2;", model_id, CLASS_STATUS_TRAINING) if err != nil { return } count = co.Count if count == 0 { - err = setModelClassStatus(c, MODEL_CLASS_STATUS_TRAINING, "model_id=$1 and status=$2;", model_id, MODEL_CLASS_STATUS_TO_TRAIN) + err = setModelClassStatus(c, CLASS_STATUS_TRAINING, "model_id=$1 and status=$2;", model_id, CLASS_STATUS_TO_TRAIN) if err != nil { return } @@ -137,7 +131,7 @@ func generateCvsExp(c BasePack, run_path string, model_id string, doPanic bool) return generateCvsExp(c, run_path, model_id, true) } - data, err := db.Query("select mdp.id, mc.class_order, mdp.file_path from model_data_point as mdp inner join model_classes as mc on mc.id = mdp.class_id where mc.model_id = $1 and mdp.model_mode=$2 and mc.status=$3;", model_id, DATA_POINT_MODE_TRAINING, MODEL_CLASS_STATUS_TRAINING) + data, err := db.Query("select mdp.id, mc.class_order, mdp.file_path from model_data_point as mdp inner join model_classes as mc on mc.id = mdp.class_id where mc.model_id = $1 and mdp.model_mode=$2 and mc.status=$3;", model_id, DATA_POINT_MODE_TRAINING, CLASS_STATUS_TRAINING) if err != nil { return } @@ -167,40 +161,23 @@ func generateCvsExp(c BasePack, run_path string, model_id string, doPanic bool) return } -func trainDefinition(c BasePack, model *BaseModel, definition_id string, load_prev bool) (accuracy float64, err error) { +func trainDefinition(c BasePack, model *BaseModel, def Definition, load_prev bool) (accuracy float64, err error) { l := c.GetLogger() - db := c.GetDb() l.Warn("About to start training definition") accuracy = 0 - layers, err := db.Query("select layer_type, shape from model_definition_layer where def_id=$1 order by layer_order asc;", definition_id) + + layers, err := def.GetLayers(c.GetDb(), " order by layer_order asc;") if err != nil { return } - defer layers.Close() - type layerrow struct { - LayerType int - Shape string - LayerNum int - } - - got := []layerrow{} - i := 1 - - for layers.Next() { - var row = layerrow{} - if err = layers.Scan(&row.LayerType, &row.Shape); err != nil { - return - } - row.Shape = shapeToSize(row.Shape) - row.LayerNum = 1 - got = append(got, row) - i = i + 1 + for _, layer := range layers { + layer.ShapeToSize() } // Generate run folder - run_path := path.Join("/tmp", model.Id, "defs", definition_id) + run_path := path.Join("/tmp", model.Id, "defs", def.Id) err = os.MkdirAll(run_path, os.ModePerm) if err != nil { @@ -225,17 +202,17 @@ func trainDefinition(c BasePack, model *BaseModel, definition_id string, load_pr } // Copy result around - result_path := path.Join("savedData", model.Id, "defs", definition_id) + result_path := path.Join("savedData", model.Id, "defs", def.Id) if err = tmpl.Execute(f, AnyMap{ - "Layers": got, - "Size": got[0].Shape, + "Layers": layers, + "Size": layers[0].Shape, "DataDir": path.Join(getDir(), "savedData", model.Id, "data"), "RunPath": run_path, "ColorMode": model.ImageMode, "Model": model, "EPOCH_PER_RUN": EPOCH_PER_RUN, - "DefId": definition_id, + "DefId": def.Id, "LoadPrev": load_prev, "LastModelRunPath": path.Join(getDir(), result_path, "model.keras"), "SaveModelPath": path.Join(getDir(), result_path), @@ -287,7 +264,7 @@ func generateCvsExpandExp(c BasePack, run_path string, model_id string, offset i var co struct { Count int `db:"count(*)"` } - err = GetDBOnce(db, &co, "model_classes where model_id=$1 and status=$2;", model_id, MODEL_CLASS_STATUS_TRAINING) + err = GetDBOnce(db, &co, "model_classes where model_id=$1 and status=$2;", model_id, CLASS_STATUS_TRAINING) if err != nil { return } @@ -296,7 +273,7 @@ func generateCvsExpandExp(c BasePack, run_path string, model_id string, offset i count := co.Count if count == 0 { - err = setModelClassStatus(c, MODEL_CLASS_STATUS_TRAINING, "model_id=$1 and status=$2;", model_id, MODEL_CLASS_STATUS_TO_TRAIN) + err = setModelClassStatus(c, CLASS_STATUS_TRAINING, "model_id=$1 and status=$2;", model_id, CLASS_STATUS_TO_TRAIN) if err != nil { return } else if doPanic { @@ -305,7 +282,7 @@ func generateCvsExpandExp(c BasePack, run_path string, model_id string, offset i return generateCvsExpandExp(c, run_path, model_id, offset, true) } - data, err := db.Query("select mdp.id, mc.class_order, mdp.file_path from model_data_point as mdp inner join model_classes as mc on mc.id = mdp.class_id where mc.model_id = $1 and mdp.model_mode=$2 and mc.status=$3;", model_id, DATA_POINT_MODE_TRAINING, MODEL_CLASS_STATUS_TRAINING) + data, err := db.Query("select mdp.id, mc.class_order, mdp.file_path from model_data_point as mdp inner join model_classes as mc on mc.id = mdp.class_id where mc.model_id = $1 and mdp.model_mode=$2 and mc.status=$3;", model_id, DATA_POINT_MODE_TRAINING, CLASS_STATUS_TRAINING) if err != nil { return } @@ -339,7 +316,7 @@ func generateCvsExpandExp(c BasePack, run_path string, model_id string, offset i // This is to load some extra data so that the model has more things to train on // - data_other, err := db.Query("select mdp.id, mc.class_order, mdp.file_path from model_data_point as mdp inner join model_classes as mc on mc.id = mdp.class_id where mc.model_id = $1 and mdp.model_mode=$2 and mc.status=$3 limit $4;", model_id, DATA_POINT_MODE_TRAINING, MODEL_CLASS_STATUS_TRAINED, count*10) + data_other, err := db.Query("select mdp.id, mc.class_order, mdp.file_path from model_data_point as mdp inner join model_classes as mc on mc.id = mdp.class_id where mc.model_id = $1 and mdp.model_mode=$2 and mc.status=$3 limit $4;", model_id, DATA_POINT_MODE_TRAINING, CLASS_STATUS_TRAINED, count*10) if err != nil { return } @@ -362,7 +339,7 @@ func generateCvsExpandExp(c BasePack, run_path string, model_id string, offset i return } -func trainDefinitionExpandExp(c BasePack, model *BaseModel, definition_id string, load_prev bool) (accuracy float64, err error) { +func trainDefinitionExpandExp(c BasePack, model *BaseModel, def Definition, load_prev bool) (accuracy float64, err error) { accuracy = 0 l := c.GetLogger() @@ -377,7 +354,7 @@ func trainDefinitionExpandExp(c BasePack, model *BaseModel, definition_id string } // status = 2 (INIT) 3 (TRAINING) - heads, err := GetDbMultitple[ExpHead](c.GetDb(), "exp_model_head where def_id=$1 and (status = 2 or status = 3)", definition_id) + heads, err := GetDbMultitple[ExpHead](c.GetDb(), "exp_model_head where def_id=$1 and (status = 2 or status = 3)", def.Id) if err != nil { return } else if len(heads) == 0 { @@ -396,62 +373,49 @@ func trainDefinitionExpandExp(c BasePack, model *BaseModel, definition_id string return } - layers, err := c.GetDb().Query("select layer_type, shape, exp_type from model_definition_layer where def_id=$1 order by layer_order asc;", definition_id) + layers, err := def.GetLayers(c.GetDb(), " order by layer_order asc;") if err != nil { return } - defer layers.Close() - type layerrow struct { - LayerType int - Shape string - ExpType int - LayerNum int - } - - got := []layerrow{} + var got []*Layer i := 1 - var last *layerrow = nil + var last *Layer = nil got_2 := false - var first *layerrow = nil + var first *Layer = nil - for layers.Next() { - var row = layerrow{} - if err = layers.Scan(&row.LayerType, &row.Shape, &row.ExpType); err != nil { - return - } + for _, layer := range layers { + layer.ShapeToSize() // Keep track of the first layer so we can keep the size of the image if first == nil { - first = &row + first = layer } - row.LayerNum = i - row.Shape = shapeToSize(row.Shape) - if row.ExpType == 2 { + if layer.ExpType == 2 { if !got_2 { - got = append(got, *last) + got = append(got, last) got_2 = true } - got = append(got, row) + got = append(got, layer) } - last = &row + last = layer i += 1 } - got = append(got, layerrow{ - LayerType: LAYER_DENSE, - Shape: fmt.Sprintf("%d", exp.End-exp.Start+1), - ExpType: 2, - LayerNum: i, + got = append(got, &Layer{ + LayerType: LAYER_DENSE, + Shape: fmt.Sprintf("%d", exp.End-exp.Start+1), + ExpType: 2, + LayerOrder: len(got), }) l.Info("Got layers", "layers", got) // Generate run folder - run_path := path.Join("/tmp", model.Id+"-defs-"+definition_id+"-retrain") + run_path := path.Join("/tmp", model.Id+"-defs-"+def.Id+"-retrain") err = os.MkdirAll(run_path, os.ModePerm) if err != nil { @@ -482,7 +446,7 @@ func trainDefinitionExpandExp(c BasePack, model *BaseModel, definition_id string } // Copy result around - result_path := path.Join("savedData", model.Id, "defs", definition_id) + result_path := path.Join("savedData", model.Id, "defs", def.Id) if err = tmpl.Execute(f, AnyMap{ "Layers": got, @@ -538,7 +502,7 @@ func trainDefinitionExpandExp(c BasePack, model *BaseModel, definition_id string return } -func trainDefinitionExp(c BasePack, model *BaseModel, definition_id string, load_prev bool) (accuracy float64, err error) { +func trainDefinitionExp(c BasePack, model *BaseModel, def Definition, load_prev bool) (accuracy float64, err error) { accuracy = 0 l := c.GetLogger() db := c.GetDb() @@ -554,7 +518,7 @@ func trainDefinitionExp(c BasePack, model *BaseModel, definition_id string, load } // status = 2 (INIT) 3 (TRAINING) - heads, err := GetDbMultitple[ExpHead](db, "exp_model_head where def_id=$1 and (status = 2 or status = 3)", definition_id) + heads, err := GetDbMultitple[ExpHead](db, "exp_model_head where def_id=$1 and (status = 2 or status = 3)", def.Id) if err != nil { return } else if len(heads) == 0 { @@ -572,42 +536,24 @@ func trainDefinitionExp(c BasePack, model *BaseModel, definition_id string, load return } - layers, err := db.Query("select layer_type, shape, exp_type from model_definition_layer where def_id=$1 order by layer_order asc;", definition_id) + layers, err := def.GetLayers(db, " order by layer_order asc;") if err != nil { return } - defer layers.Close() - type layerrow struct { - LayerType int - Shape string - ExpType int - LayerNum int + for _, layer := range layers { + layer.ShapeToSize() } - got := []layerrow{} - i := 1 - - for layers.Next() { - var row = layerrow{} - if err = layers.Scan(&row.LayerType, &row.Shape, &row.ExpType); err != nil { - return - } - row.LayerNum = i - row.Shape = shapeToSize(row.Shape) - got = append(got, row) - i += 1 - } - - got = append(got, layerrow{ - LayerType: LAYER_DENSE, - Shape: fmt.Sprintf("%d", exp.End-exp.Start+1), - ExpType: 2, - LayerNum: i, + layers = append(layers, &Layer{ + LayerType: LAYER_DENSE, + Shape: fmt.Sprintf("%d", exp.End-exp.Start+1), + ExpType: 2, + LayerOrder: len(layers), }) // Generate run folder - run_path := path.Join("/tmp", model.Id+"-defs-"+definition_id) + run_path := path.Join("/tmp", model.Id+"-defs-"+def.Id) err = os.MkdirAll(run_path, os.ModePerm) if err != nil { @@ -634,11 +580,11 @@ func trainDefinitionExp(c BasePack, model *BaseModel, definition_id string, load } // Copy result around - result_path := path.Join("savedData", model.Id, "defs", definition_id) + result_path := path.Join("savedData", model.Id, "defs", def.Id) if err = tmpl.Execute(f, AnyMap{ - "Layers": got, - "Size": got[0].Shape, + "Layers": layers, + "Size": layers[0].Shape, "DataDir": path.Join(getDir(), "savedData", model.Id, "data"), "HeadId": exp.Id, "RunPath": run_path, @@ -706,21 +652,6 @@ func remove[T interface{}](lst []T, i int) []T { return append(lst[:i], lst[i+1:]...) } -type TrainModelRow struct { - id string - target_accuracy int - epoch int - acuracy float64 -} - -type TraingModelRowDefinitions []TrainModelRow - -func (nf TraingModelRowDefinitions) Len() int { return len(nf) } -func (nf TraingModelRowDefinitions) Swap(i, j int) { nf[i], nf[j] = nf[j], nf[i] } -func (nf TraingModelRowDefinitions) Less(i, j int) bool { - return nf[i].acuracy < nf[j].acuracy -} - type ToRemoveList []int func (nf ToRemoveList) Len() int { return len(nf) } @@ -733,32 +664,18 @@ func trainModel(c BasePack, model *BaseModel) (err error) { db := c.GetDb() l := c.GetLogger() - definitionsRows, err := db.Query("select id, target_accuracy, epoch from model_definition where status=$1 and model_id=$2", MODEL_DEFINITION_STATUS_INIT, model.Id) + defs_, err := model.GetDefinitions(db, "and md.status=$2", MODEL_DEFINITION_STATUS_INIT) if err != nil { - l.Error("Failed to train Model! Err:") - l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + l.Error("Failed to train Model!", "err", err) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } - defer definitionsRows.Close() - var definitions TraingModelRowDefinitions = []TrainModelRow{} + var defs SortByAccuracyDefinitions = defs_ - for definitionsRows.Next() { - var rowv TrainModelRow - rowv.acuracy = 0 - if err = definitionsRows.Scan(&rowv.id, &rowv.target_accuracy, &rowv.epoch); err != nil { - l.Error("Failed to train Model Could not read definition from db!Err:") - l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) - return - } - definitions = append(definitions, rowv) - } - - if len(definitions) == 0 { + if len(defs) == 0 { l.Error("No Definitions defined!") - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } @@ -767,35 +684,32 @@ func trainModel(c BasePack, model *BaseModel) (err error) { for { var toRemove ToRemoveList = []int{} - for i, def := range definitions { - ModelDefinitionUpdateStatus(c, def.id, MODEL_DEFINITION_STATUS_TRAINING) - accuracy, err := trainDefinition(c, model, def.id, !firstRound) + for i, def := range defs { + ModelDefinitionUpdateStatus(c, def.Id, MODEL_DEFINITION_STATUS_TRAINING) + accuracy, err := trainDefinition(c, model, *def, !firstRound) if err != nil { l.Error("Failed to train definition!Err:", "err", err) - ModelDefinitionUpdateStatus(c, def.id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) + ModelDefinitionUpdateStatus(c, def.Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) toRemove = append(toRemove, i) continue } - def.epoch += EPOCH_PER_RUN + def.Epoch += EPOCH_PER_RUN accuracy = accuracy * 100 - def.acuracy = float64(accuracy) + def.Accuracy = float64(accuracy) - definitions[i].epoch += EPOCH_PER_RUN - definitions[i].acuracy = accuracy - - if accuracy >= float64(def.target_accuracy) { + if accuracy >= float64(def.TargetAccuracy) { l.Info("Found a definition that reaches target_accuracy!") - _, err = db.Exec("update model_definition set accuracy=$1, status=$2, epoch=$3 where id=$4", accuracy, MODEL_DEFINITION_STATUS_TRANIED, def.epoch, def.id) + _, err = db.Exec("update model_definition set accuracy=$1, status=$2, epoch=$3 where id=$4", accuracy, MODEL_DEFINITION_STATUS_TRANIED, def.Epoch, def.Id) if err != nil { l.Error("Failed to train definition!Err:\n", "err", err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return err } - _, err = db.Exec("update model_definition set status=$1 where id!=$2 and model_id=$3 and status!=$4", MODEL_DEFINITION_STATUS_CANCELD_TRAINING, def.id, model.Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) + _, err = db.Exec("update model_definition set status=$1 where id!=$2 and model_id=$3 and status!=$4", MODEL_DEFINITION_STATUS_CANCELD_TRAINING, def.Id, model.Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) if err != nil { l.Error("Failed to train definition!Err:\n", "err", err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return err } @@ -803,17 +717,17 @@ func trainModel(c BasePack, model *BaseModel) (err error) { break } - if def.epoch > MAX_EPOCH { - fmt.Printf("Failed to train definition! Accuracy less %f < %d\n", accuracy, def.target_accuracy) - ModelDefinitionUpdateStatus(c, def.id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) + if def.Epoch > MAX_EPOCH { + fmt.Printf("Failed to train definition! Accuracy less %f < %d\n", accuracy, def.TargetAccuracy) + ModelDefinitionUpdateStatus(c, def.Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) toRemove = append(toRemove, i) continue } - _, err = db.Exec("update model_definition set accuracy=$1, epoch=$2, status=$3 where id=$4", accuracy, def.epoch, MODEL_DEFINITION_STATUS_PAUSED_TRAINING, def.id) + _, err = db.Exec("update model_definition set accuracy=$1, epoch=$2, status=$3 where id=$4", accuracy, def.Epoch, MODEL_DEFINITION_STATUS_PAUSED_TRAINING, def.Id) if err != nil { l.Error("Failed to train definition!Err:\n", "err", err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return err } } @@ -828,28 +742,26 @@ func trainModel(c BasePack, model *BaseModel) (err error) { l.Info("Round done", "toRemove", toRemove) for _, n := range toRemove { - definitions = remove(definitions, n) + defs = remove(defs, n) } - len_def := len(definitions) + len_def := len(defs) if len_def == 0 { break - } - - if len_def == 1 { + } else if len_def == 1 { continue } - sort.Sort(sort.Reverse(definitions)) + sort.Sort(sort.Reverse(defs)) - acc := definitions[0].acuracy - 20.0 + acc := defs[0].Accuracy - 20.0 - l.Info("Training models, Highest acc", "acc", definitions[0].acuracy, "mod_acc", acc) + l.Info("Training models, Highest acc", "acc", defs[0].Accuracy, "mod_acc", acc) toRemove = []int{} - for i, def := range definitions { - if def.acuracy < acc { + for i, def := range defs { + if def.Accuracy < acc { toRemove = append(toRemove, i) } } @@ -859,8 +771,8 @@ func trainModel(c BasePack, model *BaseModel) (err error) { sort.Sort(sort.Reverse(toRemove)) for _, n := range toRemove { l.Warn("Removing definition not fast enough learning", "n", n) - ModelDefinitionUpdateStatus(c, definitions[n].id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) - definitions = remove(definitions, n) + ModelDefinitionUpdateStatus(c, defs[n].Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) + defs = remove(defs, n) } } @@ -868,7 +780,7 @@ func trainModel(c BasePack, model *BaseModel) (err error) { if err != nil { l.Error("DB: failed to read definition") l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } defer rows.Close() @@ -876,7 +788,7 @@ func trainModel(c BasePack, model *BaseModel) (err error) { if !rows.Next() { // TODO Make the Model status have a message l.Error("All definitions failed to train!") - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } @@ -884,14 +796,14 @@ func trainModel(c BasePack, model *BaseModel) (err error) { if err = rows.Scan(&id); err != nil { l.Error("Failed to read id:") l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } if _, err = db.Exec("update model_definition set status=$1 where id=$2;", MODEL_DEFINITION_STATUS_READY, id); err != nil { l.Error("Failed to update model definition") l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } @@ -899,7 +811,7 @@ func trainModel(c BasePack, model *BaseModel) (err error) { if err != nil { l.Error("Failed to select model_definition to delete") l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } defer to_delete.Close() @@ -909,7 +821,7 @@ func trainModel(c BasePack, model *BaseModel) (err error) { if err = to_delete.Scan(&id); err != nil { l.Error("Failed to scan the id of a model_definition to delete") l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } os.RemoveAll(path.Join("savedData", model.Id, "defs", id)) @@ -919,7 +831,7 @@ func trainModel(c BasePack, model *BaseModel) (err error) { if _, err = db.Exec("delete from model_definition where status!=$1 and model_id=$2;", MODEL_DEFINITION_STATUS_READY, model.Id); err != nil { l.Error("Failed to delete model_definition") l.Error(err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) return } @@ -928,33 +840,18 @@ func trainModel(c BasePack, model *BaseModel) (err error) { return } -type TrainModelRowUsable struct { - Id string - TargetAccuracy int `db:"target_accuracy"` - Epoch int - Acuracy float64 `db:"0"` -} - -type TrainModelRowUsables []*TrainModelRowUsable - -func (nf TrainModelRowUsables) Len() int { return len(nf) } -func (nf TrainModelRowUsables) Swap(i, j int) { nf[i], nf[j] = nf[j], nf[i] } -func (nf TrainModelRowUsables) Less(i, j int) bool { - return nf[i].Acuracy < nf[j].Acuracy -} - func trainModelExp(c BasePack, model *BaseModel) (err error) { l := c.GetLogger() db := c.GetDb() - var definitions TrainModelRowUsables - - definitions, err = GetDbMultitple[TrainModelRowUsable](db, "model_definition where status=$1 and model_id=$2", MODEL_DEFINITION_STATUS_INIT, model.Id) + defs_, err := model.GetDefinitions(db, " and status=$2;", MODEL_DEFINITION_STATUS_INIT) if err != nil { l.Error("Failed to get definitions") return } - if len(definitions) == 0 { + var defs SortByAccuracyDefinitions = defs_ + + if len(defs) == 0 { l.Error("No Definitions defined!") return errors.New("No Definitions found") } @@ -964,9 +861,9 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { for { var toRemove ToRemoveList = []int{} - for i, def := range definitions { + for i, def := range defs { ModelDefinitionUpdateStatus(c, def.Id, MODEL_DEFINITION_STATUS_TRAINING) - accuracy, err := trainDefinitionExp(c, model, def.Id, !firstRound) + accuracy, err := trainDefinitionExp(c, model, *def, !firstRound) if err != nil { l.Error("Failed to train definition!Err:", "err", err) ModelDefinitionUpdateStatus(c, def.Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) @@ -975,10 +872,10 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { } def.Epoch += EPOCH_PER_RUN accuracy = accuracy * 100 - def.Acuracy = float64(accuracy) + def.Accuracy = float64(accuracy) - definitions[i].Epoch += EPOCH_PER_RUN - definitions[i].Acuracy = accuracy + defs[i].Epoch += EPOCH_PER_RUN + defs[i].Accuracy = accuracy if accuracy >= float64(def.TargetAccuracy) { l.Info("Found a definition that reaches target_accuracy!") @@ -1028,10 +925,10 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { l.Info("Round done", "toRemove", toRemove) for _, n := range toRemove { - definitions = remove(definitions, n) + defs = remove(defs, n) } - len_def := len(definitions) + len_def := len(defs) if len_def == 0 { break @@ -1039,14 +936,14 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { continue } - sort.Sort(sort.Reverse(definitions)) - acc := definitions[0].Acuracy - 20.0 + sort.Sort(sort.Reverse(defs)) + acc := defs[0].Accuracy - 20.0 - l.Info("Training models, Highest acc", "acc", definitions[0].Acuracy, "mod_acc", acc) + l.Info("Training models, Highest acc", "acc", defs[0].Accuracy, "mod_acc", acc) toRemove = []int{} - for i, def := range definitions { - if def.Acuracy < acc { + for i, def := range defs { + if def.Accuracy < acc { toRemove = append(toRemove, i) } } @@ -1056,8 +953,8 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { sort.Sort(sort.Reverse(toRemove)) for _, n := range toRemove { l.Warn("Removing definition not fast enough learning", "n", n) - ModelDefinitionUpdateStatus(c, definitions[n].Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) - definitions = remove(definitions, n) + ModelDefinitionUpdateStatus(c, defs[n].Id, MODEL_DEFINITION_STATUS_FAILED_TRAINING) + defs = remove(defs, n) } } @@ -1066,12 +963,18 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { err = GetDBOnce(db, &dat, "model_definition where model_id=$1 and status=$2 order by accuracy desc limit 1;", model.Id, MODEL_DEFINITION_STATUS_TRANIED) if err == NotFoundError { // Set the class status to trained - err = setModelClassStatus(c, MODEL_CLASS_STATUS_TO_TRAIN, "model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TRAINING) + err = setModelClassStatus(c, CLASS_STATUS_TO_TRAIN, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) if err != nil { l.Error("All definitions failed to train! And Failed to set class status") return err } + err = model.UpdateStatus(db, FAILED_TRAINING) + if err != nil { + l.Error("All definitions failed to train! And Failed to set model status") + return err + } + l.Error("All definitions failed to train!") return err } else if err != nil { @@ -1100,8 +1003,8 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { return err } - if err = splitModel(c, model); err != nil { - err = setModelClassStatus(c, MODEL_CLASS_STATUS_TO_TRAIN, "model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TRAINING) + if err = SplitModel(c, model); err != nil { + err = setModelClassStatus(c, CLASS_STATUS_TO_TRAIN, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) if err != nil { l.Error("Failed to split the model! And Failed to set class status") return err @@ -1112,7 +1015,7 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { } // Set the class status to trained - err = setModelClassStatus(c, MODEL_CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TRAINING) + err = setModelClassStatus(c, CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) if err != nil { l.Error("Failed to set class status") return err @@ -1133,7 +1036,7 @@ func trainModelExp(c BasePack, model *BaseModel) (err error) { return } -func splitModel(c BasePack, model *BaseModel) (err error) { +func SplitModel(c BasePack, model *BaseModel) (err error) { db := c.GetDb() l := c.GetLogger() @@ -1270,9 +1173,8 @@ func generateDefinition(c BasePack, model *BaseModel, target_accuracy int, numbe } db := c.GetDb() - l := c.GetLogger() - def_id, err := MakeDefenition(db, model.Id, target_accuracy) + def, err := MakeDefenition(db, model.Id, target_accuracy) if err != nil { failed() return @@ -1281,46 +1183,17 @@ func generateDefinition(c BasePack, model *BaseModel, target_accuracy int, numbe order := 1 // Note the shape of the first layer defines the import size - if complexity == 2 { - // Note the shape for now is no used - width := int(math.Pow(2, math.Floor(math.Log(float64(model.Width))/math.Log(2.0)))) - height := int(math.Pow(2, math.Floor(math.Log(float64(model.Height))/math.Log(2.0)))) - l.Warn("Complexity 2 creating model with smaller size", "width", width, "height", height) - err = MakeLayer(db, def_id, order, LAYER_INPUT, fmt.Sprintf("%d,%d,1", width, height)) - if err != nil { - failed() - return - } - order++ - } else { - err = MakeLayer(db, def_id, order, LAYER_INPUT, fmt.Sprintf("%d,%d,1", model.Width, model.Height)) - if err != nil { - failed() - return - } - order++ - } - - loop := max(int((math.Log(float64(model.Width)) / math.Log(float64(10)))), 1) - for i := 0; i < loop; i++ { - err = MakeLayer(db, def_id, order, LAYER_SIMPLE_BLOCK, "") - order++ - if err != nil { - failed() - return - } - } - - err = MakeLayer(db, def_id, order, LAYER_FLATTEN, "") + //_, err = def.MakeLayer(db, order, LAYER_INPUT, ShapeToString(model.Width, model.Height, model.ImageMode)) + _, err = def.MakeLayer(db, order, LAYER_INPUT, ShapeToString(3, model.Width, model.Height)) if err != nil { failed() return } order++ - loop = max(int((math.Log(float64(number_of_classes))/math.Log(float64(10)))/2), 1) + loop := max(1, int((math.Log(float64(model.Width)) / math.Log(float64(10))))) for i := 0; i < loop; i++ { - err = MakeLayer(db, def_id, order, LAYER_DENSE, fmt.Sprintf("%d,1", number_of_classes*(loop-i))) + _, err = def.MakeLayer(db, order, LAYER_SIMPLE_BLOCK, "") order++ if err != nil { failed() @@ -1328,13 +1201,27 @@ func generateDefinition(c BasePack, model *BaseModel, target_accuracy int, numbe } } - err = ModelDefinitionUpdateStatus(c, def_id, MODEL_DEFINITION_STATUS_INIT) + _, err = def.MakeLayer(db, order, LAYER_FLATTEN, "") if err != nil { failed() return } + order++ - return nil + loop = int((math.Log(float64(number_of_classes)) / math.Log(float64(10))) / 2) + if loop == 0 { + loop = 1 + } + for i := 0; i < loop; i++ { + _, err = def.MakeLayer(db, order, LAYER_DENSE, ShapeToString(number_of_classes*(loop-i))) + order++ + if err != nil { + failed() + return + } + } + + return def.UpdateStatus(db, DEFINITION_STATUS_INIT) } func generateDefinitions(c BasePack, model *BaseModel, target_accuracy int, number_of_models int) (err error) { @@ -1393,27 +1280,22 @@ func generateExpandableDefinition(c BasePack, model *BaseModel, target_accuracy return } - def_id, err := MakeDefenition(c.GetDb(), model.Id, target_accuracy) + def, err := MakeDefenition(c.GetDb(), model.Id, target_accuracy) if err != nil { failed() return } + def_id := def.Id + order := 1 - width := model.Width - height := model.Height - - // Note the shape of the first layer defines the import size - if complexity == 2 { - // Note the shape for now is no used - width := int(math.Pow(2, math.Floor(math.Log(float64(model.Width))/math.Log(2.0)))) - height := int(math.Pow(2, math.Floor(math.Log(float64(model.Height))/math.Log(2.0)))) - l.Warn("Complexity 2 creating model with smaller size", "width", width, "height", height) + err = MakeLayerExpandable(c.GetDb(), def_id, order, LAYER_INPUT, ShapeToString(3, model.Width, model.Height), 1) + if err != nil { + failed() + return } - err = MakeLayerExpandable(c.GetDb(), def_id, order, LAYER_INPUT, fmt.Sprintf("%d,%d,1", width, height), 1) - order++ // handle the errors inside the pervious if block @@ -1431,7 +1313,7 @@ func generateExpandableDefinition(c BasePack, model *BaseModel, target_accuracy log.Info("Size of the simple block", "loop", loop) - //loop = max(loop, 3) + loop = max(loop, min(2, model.ImageMode)) for i := 0; i < loop; i++ { err = MakeLayerExpandable(db, def_id, order, LAYER_SIMPLE_BLOCK, "", 1) @@ -1451,7 +1333,7 @@ func generateExpandableDefinition(c BasePack, model *BaseModel, target_accuracy order++ // Flatten the blocks into dense - err = MakeLayerExpandable(db, def_id, order, LAYER_DENSE, fmt.Sprintf("%d,1", number_of_classes*2), 1) + err = MakeLayerExpandable(db, def_id, order, LAYER_DENSE, ShapeToString(number_of_classes*2), 1) if err != nil { failed() return @@ -1465,7 +1347,7 @@ func generateExpandableDefinition(c BasePack, model *BaseModel, target_accuracy loop = max(loop, 3) for i := 0; i < loop; i++ { - err = MakeLayerExpandable(db, def_id, order, LAYER_DENSE, fmt.Sprintf("%d,1", number_of_classes*(loop-i)*2), 2) + err = MakeLayerExpandable(db, def_id, order, LAYER_DENSE, ShapeToString(number_of_classes*(loop-i)*2), 2) order++ if err != nil { failed() @@ -1533,7 +1415,7 @@ func generateExpandableDefinitions(c BasePack, model *BaseModel, target_accuracy } func ResetClasses(c BasePack, model *BaseModel) { - _, err := c.GetDb().Exec("update model_classes set status=$1 where status=$2 and model_id=$3", MODEL_CLASS_STATUS_TO_TRAIN, MODEL_CLASS_STATUS_TRAINING, model.Id) + _, err := c.GetDb().Exec("update model_classes set status=$1 where status=$2 and model_id=$3", CLASS_STATUS_TO_TRAIN, CLASS_STATUS_TRAINING, model.Id) if err != nil { c.GetLogger().Error("Error while reseting the classes", "error", err) } @@ -1544,35 +1426,35 @@ func trainExpandable(c *Context, model *BaseModel) { failed := func(msg string) { c.Logger.Error(msg, "err", err) - ModelUpdateStatus(c, model.Id, FAILED_TRAINING) + ModelUpdateStatus(c, model.Id, int(FAILED_TRAINING)) ResetClasses(c, model) } - var definitions TrainModelRowUsables - - definitions, err = GetDbMultitple[TrainModelRowUsable](c, "model_definition where status=$1 and model_id=$2", MODEL_DEFINITION_STATUS_READY, model.Id) + defs_, err := model.GetDefinitions(c, " and status=$2", MODEL_DEFINITION_STATUS_READY) if err != nil { failed("Failed to get definitions") return } - if len(definitions) != 1 { + var defs SortByAccuracyDefinitions = defs_ + + if len(defs) != 1 { failed("There should only be one definition available!") return } firstRound := true - def := definitions[0] + def := defs[0] epoch := 0 for { - acc, err := trainDefinitionExp(c, model, def.Id, !firstRound) + acc, err := trainDefinitionExp(c, model, *def, !firstRound) if err != nil { failed("Failed to train definition!") return } epoch += EPOCH_PER_RUN - if float64(acc*100) >= float64(def.Acuracy) { + if float64(acc*100) >= float64(def.Accuracy) { c.Logger.Info("Found a definition that reaches target_accuracy!") _, err = c.Db.Exec("update exp_model_head set status=$1 where def_id=$2 and status=$3;", MODEL_HEAD_STATUS_READY, def.Id, MODEL_HEAD_STATUS_TRAINING) @@ -1588,7 +1470,7 @@ func trainExpandable(c *Context, model *BaseModel) { } // Set the class status to trained - err = setModelClassStatus(c, MODEL_CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TRAINING) + err = setModelClassStatus(c, CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) if err != nil { failed("Failed to set class status") return @@ -1648,7 +1530,7 @@ func RunTaskTrain(b BasePack, task Task) (err error) { if err != nil { l.Error("Failed to train model", "err", err) task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Failed generate model") - ModelUpdateStatus(b, model.Id, FAILED_TRAINING) + ModelUpdateStatus(b, model.Id, int(FAILED_TRAINING)) return } @@ -1677,22 +1559,18 @@ func RunTaskRetrain(b BasePack, task Task) (err error) { task.UpdateStatusLog(b, TASK_RUNNING, "Model retraining") - var defData struct { - Id string `db:"md.id"` - TargetAcuuracy float64 `db:"md.target_accuracy"` - } - - err = GetDBOnce(db, &defData, "models as m inner join model_definition as md on m.id = md.model_id where m.id=$1;", task.ModelId) + defs, err := model.GetDefinitions(db, "") if err != nil { failed() return } + def := *defs[0] failed = func() { ResetClasses(b, model) ModelUpdateStatus(b, model.Id, READY_RETRAIN_FAILED) task.UpdateStatusLog(b, TASK_FAILED_RUNNING, "Model failed retraining") - _, err_ := db.Exec("delete from exp_model_head where def_id=$1 and status in (2,3)", defData.Id) + _, err_ := db.Exec("delete from exp_model_head where def_id=$1 and status in (2,3)", def.Id) if err_ != nil { panic(err_) } @@ -1703,21 +1581,21 @@ func RunTaskRetrain(b BasePack, task Task) (err error) { var epocs = 0 // TODO make max epochs come from db // TODO re increase the target accuracy - for acc*100 < defData.TargetAcuuracy-5 && epocs < 10 { + for acc*100 < float64(def.TargetAccuracy)-5 && epocs < 10 { // This is something I have to check - acc, err = trainDefinitionExpandExp(b, model, defData.Id, epocs > 0) + acc, err = trainDefinitionExpandExp(b, model, def, epocs > 0) if err != nil { failed() return } - l.Info("Retrained model", "accuracy", acc, "target", defData.TargetAcuuracy) + l.Info("Retrained model", "accuracy", acc, "target", def.TargetAccuracy) epocs += 1 } - if acc*100 < defData.TargetAcuuracy { - l.Error("Model never achived targetd accuracy", "acc", acc*100, "target", defData.TargetAcuuracy) + if acc*100 < float64(def.TargetAccuracy)-5 { + l.Error("Model never achived targetd accuracy", "acc", acc*100, "target", def.TargetAccuracy) failed() return } @@ -1731,7 +1609,14 @@ func RunTaskRetrain(b BasePack, task Task) (err error) { l.Info("Model updaded") - _, err = db.Exec("update model_classes set status=$1 where status=$2 and model_id=$3", MODEL_CLASS_STATUS_TRAINED, MODEL_CLASS_STATUS_TRAINING, model.Id) + _, err = db.Exec("update model_classes set status=$1 where status=$2 and model_id=$3", CLASS_STATUS_TRAINED, CLASS_STATUS_TRAINING, model.Id) + if err != nil { + l.Error("Error while updating the classes", "error", err) + failed() + return + } + + _, err = db.Exec("update exp_model_head set status=$1 where status=$2 and def_id=$3", MODEL_HEAD_STATUS_READY, MODEL_HEAD_STATUS_TRAINING, def.Id) if err != nil { l.Error("Error while updating the classes", "error", err) failed() @@ -1861,7 +1746,7 @@ func handleTrain(handle *Handle) { c, "model_classes where model_id=$1 and status=$2 order by class_order asc", model.Id, - MODEL_CLASS_STATUS_TO_TRAIN, + CLASS_STATUS_TO_TRAIN, ) if err != nil { _err := c.RollbackTx() @@ -1882,7 +1767,7 @@ func handleTrain(handle *Handle) { //Update the classes { - _, err = c.Exec("update model_classes set status=$1 where status=$2 and model_id=$3", MODEL_CLASS_STATUS_TRAINING, MODEL_CLASS_STATUS_TO_TRAIN, model.Id) + _, err = c.Exec("update model_classes set status=$1 where status=$2 and model_id=$3", CLASS_STATUS_TRAINING, CLASS_STATUS_TO_TRAIN, model.Id) if err != nil { _err := c.RollbackTx() if _err != nil { diff --git a/logic/tasks/index.go b/logic/tasks/index.go index 3c8d59e..d6af59c 100644 --- a/logic/tasks/index.go +++ b/logic/tasks/index.go @@ -8,4 +8,6 @@ func HandleTasks(handle *Handle) { handleUpload(handle) handleList(handle) handleRequests(handle) + handleRemoteRunner(handle) + handleRunnerData(handle) } diff --git a/logic/tasks/runner.go b/logic/tasks/runner.go new file mode 100644 index 0000000..8d18bd2 --- /dev/null +++ b/logic/tasks/runner.go @@ -0,0 +1,960 @@ +package tasks + +import ( + "os" + "path" + "sync" + "time" + + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/train" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/utils" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" + "github.com/charmbracelet/log" +) + +func verifyRunner(c *Context, dat *JustId) (runner *Runner, e *Error) { + runner, err := GetRunner(c, dat.Id) + if err == NotFoundError { + e = c.JsonBadRequest("Could not find runner, please register runner first") + return + } else if err != nil { + e = c.E500M("Failed to get information about the runner", err) + return + } + + if runner.Token != *c.Token { + return nil, c.SendJSONStatus(401, "Only runners can use this funcion") + } + + return +} + +type VerifyTask struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` +} + +type RunnerTrainDef struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` + DefId string `json:"defId" validate:"required"` +} + +func verifyTask(x *Handle, c *Context, dat *VerifyTask) (task *Task, error *Error) { + mutex := x.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + var runners map[string]interface{} = x.DataMap["runners"].(map[string]interface{}) + if runners[dat.Id] == nil { + return nil, c.JsonBadRequest("Runner not active") + } + + var runner_data map[string]interface{} = runners[dat.Id].(map[string]interface{}) + + if runner_data["task"] == nil { + return nil, c.SendJSONStatus(404, "No active task") + } + + return runner_data["task"].(*Task), nil +} + +func clearRunnerTask(x *Handle, runner_id string) { + mutex := x.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + var runners map[string]interface{} = x.DataMap["runners"].(map[string]interface{}) + var runner_data map[string]interface{} = runners[runner_id].(map[string]interface{}) + runner_data["task"] = nil + runners[runner_id] = runner_data + x.DataMap["runners"] = runners +} + +func handleRemoteRunner(x *Handle) { + + type RegisterRunner struct { + Token string `json:"token" validate:"required"` + Type RunnerType `json:"type" validate:"required"` + } + PostAuthJson(x, "/tasks/runner/register", User_Normal, func(c *Context, dat *RegisterRunner) *Error { + if *c.Token != dat.Token { + // TODO do admin + return c.E500M("Please make sure that the token is the same that is being registered", nil) + } + + c.Logger.Info("test", "dat", dat) + + var runner Runner + err := GetDBOnce(c, &runner, "remote_runner as ru where token=$1", dat.Token) + if err != NotFoundError && err != nil { + return c.E500M("Failed to get information remote runners", err) + } + if err != NotFoundError { + return c.JsonBadRequest("Token is already registered by a runner") + } + + // TODO get id from token passed by when doing admin + var userId = c.User.Id + + var new_runner = struct { + Type RunnerType + UserId string `db:"user_id"` + Token string + }{ + Type: dat.Type, + Token: dat.Token, + UserId: userId, + } + + id, err := InsertReturnId(c, &new_runner, "remote_runner", "id") + if err != nil { + return c.E500M("Failed to create remote runner", err) + } + + return c.SendJSON(struct { + Id string `json:"id"` + }{ + Id: id, + }) + }) + + // TODO remove runner + + PostAuthJson(x, "/tasks/runner/init", User_Normal, func(c *Context, dat *JustId) *Error { + runner, error := verifyRunner(c, dat) + if error != nil { + return error + } + + mutex := x.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + var runners map[string]interface{} = x.DataMap["runners"].(map[string]interface{}) + if runners[dat.Id] != nil { + c.Logger.Info("Logger trying to register but already registerd") + c.ShowMessage = false + return c.SendJSON("Ok") + } + + var new_runner = map[string]interface{}{} + new_runner["last_time_check"] = time.Now() + new_runner["runner_info"] = runner + + runners[dat.Id] = new_runner + + x.DataMap["runners"] = runners + + return c.SendJSON("Ok") + }) + + PostAuthJson(x, "/tasks/runner/active", User_Normal, func(c *Context, dat *JustId) *Error { + _, error := verifyRunner(c, dat) + if error != nil { + return error + } + + mutex := x.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + var runners map[string]interface{} = x.DataMap["runners"].(map[string]interface{}) + if runners[dat.Id] == nil { + return c.JsonBadRequest("Runner not active") + } + + var runner_data map[string]interface{} = runners[dat.Id].(map[string]interface{}) + + if runner_data["task"] == nil { + c.ShowMessage = false + return c.SendJSONStatus(404, "No active task") + } + + c.ShowMessage = false + // This should be a task obj + return c.SendJSON(runner_data["task"]) + }) + + PostAuthJson(x, "/tasks/runner/ready", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + err := task.UpdateStatus(c, TASK_RUNNING, "Task Running on Runner") + if err != nil { + return c.E500M("Failed to set task status", err) + } + + return c.SendJSON("Ok") + }) + + type TaskFail struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` + Reason string `json:"reason" validate:"required"` + } + PostAuthJson(x, "/tasks/runner/fail", User_Normal, func(c *Context, dat *TaskFail) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{Id: dat.Id, TaskId: dat.TaskId}) + if error != nil { + return error + } + + err := task.UpdateStatus(c, TASK_FAILED_RUNNING, dat.Reason) + if err != nil { + return c.E500M("Failed to set task status", err) + } + + // Do extra clean up on tasks + switch task.TaskType { + case int(TASK_TYPE_TRAINING): + CleanUpFailed(c, task) + case int(TASK_TYPE_RETRAINING): + CleanUpFailedRetrain(c, task) + case int(TASK_TYPE_CLASSIFICATION): + // DO nothing + default: + panic("Do not know how to handle this") + } + + mutex := x.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + var runners map[string]interface{} = x.DataMap["runners"].(map[string]interface{}) + var runner_data map[string]interface{} = runners[dat.Id].(map[string]interface{}) + runner_data["task"] = nil + + runners[dat.Id] = runner_data + x.DataMap["runners"] = runners + + return c.SendJSON("Ok") + }) + + PostAuthJson(x, "/tasks/runner/defs", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + var status DefinitionStatus + switch task.TaskType { + case int(TASK_TYPE_TRAINING): + status = DEFINITION_STATUS_INIT + case int(TASK_TYPE_RETRAINING): + fallthrough + case int(TASK_TYPE_CLASSIFICATION): + status = DEFINITION_STATUS_READY + default: + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + return c.E500M("Failed to get model information", err) + } + + defs, err := model.GetDefinitions(c, "and md.status=$2", status) + if err != nil { + return c.E500M("Failed to get the model definitions", err) + } + + return c.SendJSON(defs) + }) + + PostAuthJson(x, "/tasks/runner/classes", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + return c.E500M("Failed to get model information", err) + } + + switch task.TaskType { + case int(TASK_TYPE_TRAINING): + classes, err := model.GetClasses(c, "and status in ($2, $3) order by mc.class_order asc", CLASS_STATUS_TO_TRAIN, CLASS_STATUS_TRAINING) + if err != nil { + return c.E500M("Failed to get the model classes", err) + } + return c.SendJSON(classes) + case int(TASK_TYPE_RETRAINING): + classes, err := model.GetClasses(c, "and status=$2 order by mc.class_order asc", CLASS_STATUS_TRAINING) + if err != nil { + return c.E500M("Failed to get the model classes", err) + } + return c.SendJSON(classes) + case int(TASK_TYPE_CLASSIFICATION): + classes, err := model.GetClasses(c, "and status=$2 order by mc.class_order asc", CLASS_STATUS_TRAINED) + if err != nil { + return c.E500M("Failed to get the model classes", err) + } + return c.SendJSON(classes) + default: + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + }) + + type RunnerTrainDefStatus struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` + DefId string `json:"defId" validate:"required"` + Status DefinitionStatus `json:"status" validate:"required"` + } + PostAuthJson(x, "/tasks/runner/train/def/status", User_Normal, func(c *Context, dat *RunnerTrainDefStatus) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{Id: dat.Id, TaskId: dat.TaskId}) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_TRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + def, err := GetDefinition(c, dat.DefId) + if err != nil { + return c.E500M("Failed to get definition information", err) + } + + err = def.UpdateStatus(c, dat.Status) + if err != nil { + return c.E500M("Failed to update model status", err) + } + + return c.SendJSON("Ok") + }) + + type RunnerTrainDefHeadStatus struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` + DefId string `json:"defId" validate:"required"` + Status ModelHeadStatus `json:"status" validate:"required"` + } + PostAuthJson(x, "/tasks/runner/train/def/head/status", User_Normal, func(c *Context, dat *RunnerTrainDefHeadStatus) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{Id: dat.Id, TaskId: dat.TaskId}) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_TRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + def, err := GetDefinition(c, dat.DefId) + if err != nil { + return c.E500M("Failed to get definition information", err) + } + + _, err = c.Exec("update exp_model_head set status=$1 where def_id=$2;", dat.Status, def.Id) + if err != nil { + log.Error("Failed to train definition!") + return c.E500M("Failed to train definition", err) + } + + return c.SendJSON("Ok") + }) + + type RunnerRetrainDefHeadStatus struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` + HeadId string `json:"defId" validate:"required"` + Status ModelHeadStatus `json:"status" validate:"required"` + } + PostAuthJson(x, "/tasks/runner/retrain/def/head/status", User_Normal, func(c *Context, dat *RunnerRetrainDefHeadStatus) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{Id: dat.Id, TaskId: dat.TaskId}) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_RETRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + if err := UpdateStatus(c.GetDb(), "exp_model_head", dat.HeadId, MODEL_DEFINITION_STATUS_TRAINING); err != nil { + return c.E500M("Failed to update head status", err) + } + + return c.SendJSON("Ok") + }) + PostAuthJson(x, "/tasks/runner/train/def/layers", User_Normal, func(c *Context, dat *RunnerTrainDef) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{Id: dat.Id, TaskId: dat.TaskId}) + if error != nil { + return error + } + + switch task.TaskType { + case int(TASK_TYPE_TRAINING): + // Do nothing + case int(TASK_TYPE_RETRAINING): + // Do nothing + default: + c.Logger.Error("Task not is not the right type to get the layers", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the layers") + } + + def, err := GetDefinition(c, dat.DefId) + if err != nil { + return c.E500M("Failed to get definition information", err) + } + + layers, err := def.GetLayers(c, " order by layer_order asc") + if err != nil { + return c.E500M("Failed to get layers", err) + } + + return c.SendJSON(layers) + }) + + PostAuthJson(x, "/tasks/runner/train/datapoints", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + switch task.TaskType { + case int(TASK_TYPE_TRAINING): + // DO nothing + case int(TASK_TYPE_RETRAINING): + // DO nothing + default: + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + return c.E500M("Failed to get model information", err) + } + + training_points, err := model.DataPoints(c, DATA_POINT_MODE_TRAINING) + if err != nil { + return c.E500M("Failed to get the model classes", err) + } + testing_points, err := model.DataPoints(c, DATA_POINT_MODE_TRAINING) + if err != nil { + return c.E500M("Failed to get the model classes", err) + } + + return c.SendJSON(struct { + Testing []DataPoint `json:"testing"` + Training []DataPoint `json:"training"` + }{ + Testing: testing_points, + Training: training_points, + }) + }) + + type RunnerTrainDefEpoch struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` + DefId string `json:"defId" validate:"required"` + Epoch int `json:"epoch" validate:"required"` + Accuracy float64 `json:"accuracy" validate:"required"` + } + PostAuthJson(x, "/tasks/runner/train/epoch", User_Normal, func(c *Context, dat *RunnerTrainDefEpoch) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{ + Id: dat.Id, + TaskId: dat.TaskId, + }) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_TRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + def, err := GetDefinition(c, dat.DefId) + if err != nil { + return c.E500M("Failed to get definition information", err) + } + + err = def.UpdateAfterEpoch(c, dat.Accuracy, dat.Epoch) + if err != nil { + return c.E500M("Failed to update model", err) + } + + return c.SendJSON("Ok") + }) + + PostAuthJson(x, "/tasks/runner/train/mark-failed", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{ + Id: dat.Id, + TaskId: dat.TaskId, + }) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_TRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + _, err := c.Exec( + "update model_definition set status=$1 "+ + "where model_id=$2 and status in ($3, $4)", + MODEL_DEFINITION_STATUS_CANCELD_TRAINING, + task.ModelId, + MODEL_DEFINITION_STATUS_TRAINING, + MODEL_DEFINITION_STATUS_PAUSED_TRAINING, + ) + if err != nil { + return c.E500M("Failed to mark definition as failed", err) + } + + return c.SendJSON("Ok") + }) + + PostAuthJson(x, "/tasks/runner/model", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + switch task.TaskType { + case int(TASK_TYPE_TRAINING): + //DO NOTHING + case int(TASK_TYPE_RETRAINING): + //DO NOTHING + case int(TASK_TYPE_CLASSIFICATION): + //DO NOTHING + default: + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + return c.E500M("Failed to get model information", err) + } + + return c.SendJSON(model) + }) + + PostAuthJson(x, "/tasks/runner/heads", User_Normal, func(c *Context, dat *RunnerTrainDef) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{Id: dat.Id, TaskId: dat.TaskId}) + if error != nil { + return error + } + + type ExpHead struct { + Id string `json:"id"` + Start int `db:"range_start" json:"start"` + End int `db:"range_end" json:"end"` + } + + switch task.TaskType { + case int(TASK_TYPE_TRAINING): + fallthrough + case int(TASK_TYPE_RETRAINING): + // status = 2 (INIT) 3 (TRAINING) + heads, err := GetDbMultitple[ExpHead](c, "exp_model_head where def_id=$1 and status in (2,3)", dat.DefId) + if err != nil { + return c.E500M("Failed getting active heads", err) + } + return c.SendJSON(heads) + case int(TASK_TYPE_CLASSIFICATION): + heads, err := GetDbMultitple[ExpHead](c, "exp_model_head where def_id=$1", dat.DefId) + if err != nil { + return c.E500M("Failed getting active heads", err) + } + return c.SendJSON(heads) + default: + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + }) + + PostAuthJson(x, "/tasks/runner/train_exp/class/status/train", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_TRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + return c.E500M("Failed to get model", err) + } + + err = SetModelClassStatus(c, CLASS_STATUS_TRAINING, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TO_TRAIN) + if err != nil { + return c.E500M("Failed update status", err) + } + + return c.SendJSON("Ok") + }) + + PostAuthJson(x, "/tasks/runner/train/done", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_TRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + c.Logger.Error("Failed to get model", "err", err) + return c.E500M("Failed to get mode", err) + } + + var def Definition + err = GetDBOnce(c, &def, "model_definition as md where model_id=$1 and status=$2 order by accuracy desc limit 1;", task.ModelId, DEFINITION_STATUS_TRANIED) + if err == NotFoundError { + // TODO Make the Model status have a message + c.Logger.Error("All definitions failed to train!") + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "All definition failed to train!") + return c.SendJSON("Ok") + } else if err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to get model definition") + return c.E500M("Failed to get model definition", err) + } + + if err = def.UpdateStatus(c, DEFINITION_STATUS_READY); err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to update model definition") + return c.E500M("Failed to update model definition", err) + } + + to_delete, err := c.Query("select id from model_definition where status != $1 and model_id=$2", MODEL_DEFINITION_STATUS_READY, model.Id) + if err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to delete unsed definitions") + return c.E500M("Failed to delete unsed definitions", err) + } + defer to_delete.Close() + + for to_delete.Next() { + var id string + if err = to_delete.Scan(&id); err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to delete unsed definitions") + return c.E500M("Failed to delete unsed definitions", err) + } + os.RemoveAll(path.Join("savedData", model.Id, "defs", id)) + } + + // TODO Check if returning also works here + if _, err = c.Exec("delete from model_definition where status!=$1 and model_id=$2;", MODEL_DEFINITION_STATUS_READY, model.Id); err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to delete unsed definitions") + return c.E500M("Failed to delete unsed definitions", err) + } + + // Set the class status to trained + err = SetModelClassStatus(c, CLASS_STATUS_TRAINED, "model_id=$1;", model.Id) + if err != nil { + c.Logger.Error("Failed to set class status") + return c.E500M("Failed to set class status", err) + } + + if err = model.UpdateStatus(c, READY); err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to delete unsed definitions") + return c.E500M("Failed to update status of model", err) + } + + task.UpdateStatusLog(c, TASK_DONE, "Model finished training") + + clearRunnerTask(x, dat.Id) + return c.SendJSON("Ok") + }) + + type RunnerClassDone struct { + Id string `json:"id" validate:"required"` + TaskId string `json:"taskId" validate:"required"` + Result string `json:"result" validate:"required"` + } + PostAuthJson(x, "/tasks/runner/class/done", User_Normal, func(c *Context, dat *RunnerClassDone) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, &VerifyTask{ + Id: dat.Id, + TaskId: dat.TaskId, + }) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_CLASSIFICATION) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + err := task.SetResultText(c, dat.Result) + if err != nil { + return c.E500M("Failed to update the task", err) + } + + err = task.UpdateStatus(c, TASK_DONE, "Task completed") + if err != nil { + return c.E500M("Failed to update task", err) + } + + mutex := x.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + var runners map[string]interface{} = x.DataMap["runners"].(map[string]interface{}) + var runner_data map[string]interface{} = runners[dat.Id].(map[string]interface{}) + runner_data["task"] = nil + runners[dat.Id] = runner_data + x.DataMap["runners"] = runners + + return c.SendJSON("Ok") + }) + + PostAuthJson(x, "/tasks/runner/train_exp/done", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_TRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + c.Logger.Error("Failed to get model", "err", err) + return c.E500M("Failed to get mode", err) + } + + // TODO add check the to the model + + var def Definition + err = GetDBOnce(c, &def, "model_definition as md where model_id=$1 and status=$2 order by accuracy desc limit 1;", task.ModelId, DEFINITION_STATUS_TRANIED) + if err == NotFoundError { + // TODO Make the Model status have a message + c.Logger.Error("All definitions failed to train!") + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "All definition failed to train!") + clearRunnerTask(x, dat.Id) + return c.SendJSON("Ok") + } else if err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to get model definition") + return c.E500M("Failed to get model definition", err) + } + + if err = def.UpdateStatus(c, DEFINITION_STATUS_READY); err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to update model definition") + return c.E500M("Failed to update model definition", err) + } + + to_delete, err := GetDbMultitple[JustId](c, "model_definition where status!=$1 and model_id=$2", MODEL_DEFINITION_STATUS_READY, model.Id) + if err != nil { + c.GetLogger().Error("Failed to select model_definition to delete") + return c.E500M("Failed to select model definition to delete", err) + } + + for _, d := range to_delete { + os.RemoveAll(path.Join("savedData", model.Id, "defs", d.Id)) + } + + // TODO Check if returning also works here + if _, err = c.Exec("delete from model_definition where status!=$1 and model_id=$2;", MODEL_DEFINITION_STATUS_READY, model.Id); err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to delete unsed definitions") + return c.E500M("Failed to delete unsed definitions", err) + } + + if err = SplitModel(c, model); err != nil { + err = SetModelClassStatus(c, CLASS_STATUS_TO_TRAIN, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) + if err != nil { + c.Logger.Error("Failed to split the model! And Failed to set class status") + return c.E500M("Failed to split the model", err) + } + + c.Logger.Error("Failed to split the model") + return c.E500M("Failed to split the model", err) + } + + // Set the class status to trained + err = SetModelClassStatus(c, CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) + if err != nil { + c.Logger.Error("Failed to set class status") + return c.E500M("Failed to set class status", err) + } + + c.Logger.Warn("Removing base model for", "model", model.Id, "def", def.Id) + os.RemoveAll(path.Join("savedData", model.Id, "defs", def.Id, "model")) + os.RemoveAll(path.Join("savedData", model.Id, "defs", def.Id, "model.keras")) + + if err = model.UpdateStatus(c, READY); err != nil { + model.UpdateStatus(c, FAILED_TRAINING) + task.UpdateStatusLog(c, TASK_FAILED_RUNNING, "Failed to delete unsed definitions") + return c.E500M("Failed to update status of model", err) + } + + task.UpdateStatusLog(c, TASK_DONE, "Model finished training") + + mutex := x.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + var runners map[string]interface{} = x.DataMap["runners"].(map[string]interface{}) + var runner_data map[string]interface{} = runners[dat.Id].(map[string]interface{}) + runner_data["task"] = nil + runners[dat.Id] = runner_data + x.DataMap["runners"] = runners + + return c.SendJSON("Ok") + }) + + PostAuthJson(x, "/tasks/runner/retrain/done", User_Normal, func(c *Context, dat *VerifyTask) *Error { + _, error := verifyRunner(c, &JustId{Id: dat.Id}) + if error != nil { + return error + } + + task, error := verifyTask(x, c, dat) + if error != nil { + return error + } + + if task.TaskType != int(TASK_TYPE_RETRAINING) { + c.Logger.Error("Task not is not the right type to get the definitions", "task type", task.TaskType) + return c.JsonBadRequest("Task is not the right type go get the definitions") + } + + model, err := GetBaseModel(c, *task.ModelId) + if err != nil { + c.Logger.Error("Failed to get model", "err", err) + return c.E500M("Failed to get mode", err) + } + + err = SetModelClassStatus(c, CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, CLASS_STATUS_TRAINING) + if err != nil { + return c.E500M("Failed to set class status", err) + } + + defs, err := model.GetDefinitions(c, "") + if err != nil { + return c.E500M("Failed to get definitions", err) + } + + _, err = c.Exec("update exp_model_head set status=$1 where status=$2 and def_id=$3", MODEL_HEAD_STATUS_READY, MODEL_HEAD_STATUS_TRAINING, defs[0].Id) + if err != nil { + return c.E500M("Failed to set head status", err) + } + + err = model.UpdateStatus(c, READY) + if err != nil { + return c.E500M("Failed to set class status", err) + } + + task.UpdateStatusLog(c, TASK_DONE, "Model finished training") + clearRunnerTask(x, dat.Id) + + return c.SendJSON("Ok") + }) + +} diff --git a/logic/tasks/runner/runner.go b/logic/tasks/runner/runner.go index 4e6f967..99d4aff 100644 --- a/logic/tasks/runner/runner.go +++ b/logic/tasks/runner/runner.go @@ -5,6 +5,7 @@ import ( "math" "os" "runtime/debug" + "sync" "time" "github.com/charmbracelet/log" @@ -90,6 +91,57 @@ func runner(config Config, db db.Db, task_channel chan Task, index int, back_cha } } +/** +* Handle remote runner + */ +func handleRemoteTask(handler *Handle, base BasePack, runner_id string, task Task) { + logger := log.NewWithOptions(os.Stdout, log.Options{ + ReportCaller: true, + ReportTimestamp: true, + TimeFormat: time.Kitchen, + Prefix: fmt.Sprintf("Runner pre %s", runner_id), + }) + defer func() { + if r := recover(); r != nil { + logger.Error("Runner failed to setup for runner", "due to", r, "stack", string(debug.Stack())) + // TODO maybe create better failed task + task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Failed to setup task for runner") + } + }() + + err := task.UpdateStatus(base, TASK_PICKED_UP, "Failed to setup task for runner") + if err != nil { + logger.Error("Failed to mark task as PICK UP") + return + } + + mutex := handler.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + defer mutex.Unlock() + + switch task.TaskType { + case int(TASK_TYPE_RETRAINING): + runners := handler.DataMap["runners"].(map[string]interface{}) + runner := runners[runner_id].(map[string]interface{}) + runner["task"] = &task + runners[runner_id] = runner + handler.DataMap["runners"] = runners + case int(TASK_TYPE_TRAINING): + if err := PrepareTraining(handler, base, task, runner_id); err != nil { + logger.Error("Failed to prepare for training", "err", err) + } + case int(TASK_TYPE_CLASSIFICATION): + runners := handler.DataMap["runners"].(map[string]interface{}) + runner := runners[runner_id].(map[string]interface{}) + runner["task"] = &task + runners[runner_id] = runner + handler.DataMap["runners"] = runners + default: + logger.Error("Not sure what to do panicing", "taskType", task.TaskType) + panic("not sure what to do") + } +} + /** * Tells the orcchestator to look at the task list from time to time */ @@ -125,7 +177,7 @@ func attentionSeeker(config Config, back_channel chan int) { /** * Manages what worker should to Work */ -func RunnerOrchestrator(db db.Db, config Config) { +func RunnerOrchestrator(db db.Db, config Config, handler *Handle) { logger := log.NewWithOptions(os.Stdout, log.Options{ ReportCaller: true, ReportTimestamp: true, @@ -133,6 +185,14 @@ func RunnerOrchestrator(db db.Db, config Config) { Prefix: "Runner Orchestrator Logger", }) + setupHandle(handler) + + base := BasePackStruct{ + Db: db, + Logger: logger, + Host: config.Hostname, + } + gpu_workers := config.GpuWorker.NumberOfWorkers logger.Info("Starting runners") @@ -144,12 +204,12 @@ func RunnerOrchestrator(db db.Db, config Config) { defer func() { if r := recover(); r != nil { - logger.Error("Recovered in Orchestrator restarting", "due to", r) + logger.Error("Recovered in Orchestrator restarting", "due to", r, "stack", string(debug.Stack())) for x := range task_runners { close(task_runners[x]) } close(back_channel) - go RunnerOrchestrator(db, config) + go RunnerOrchestrator(db, config, handler) } }() @@ -159,6 +219,10 @@ func RunnerOrchestrator(db db.Db, config Config) { for i := 0; i < gpu_workers; i++ { task_runners[i] = make(chan Task, 10) task_runners_used[i] = false + AddLocalRunner(handler, LocalRunner{ + RunnerNum: i + 1, + Task: nil, + }) go runner(config, db, task_runners[i], i+1, back_channel) } @@ -166,14 +230,17 @@ func RunnerOrchestrator(db db.Db, config Config) { for i := range back_channel { - if i > 0 { - logger.Info("Runner freed", "runner", i) - task_runners_used[i-1] = false - } else if i < 0 { - logger.Error("Runner died! Restarting!", "runner", i) - i = int(math.Abs(float64(i)) - 1) - task_runners_used[i] = false - go runner(config, db, task_runners[i], i+1, back_channel) + if i != 0 { + if i > 0 { + logger.Info("Runner freed", "runner", i) + task_runners_used[i-1] = false + } else if i < 0 { + logger.Error("Runner died! Restarting!", "runner", i) + i = int(math.Abs(float64(i)) - 1) + task_runners_used[i] = false + go runner(config, db, task_runners[i], i+1, back_channel) + } + AddLocalTask(handler, int(math.Abs(float64(i))), nil) } if task_to_dispatch == nil { @@ -197,11 +264,38 @@ func RunnerOrchestrator(db db.Db, config Config) { } } + if task_to_dispatch != nil && task_to_dispatch.TaskType != int(TASK_TYPE_DELETE_USER) { + // TODO split tasks into cpu tasks and GPU tasks + mutex := handler.DataMap["runners_mutex"].(*sync.Mutex) + mutex.Lock() + remote_runners := handler.DataMap["runners"].(map[string]interface{}) + + for k, v := range remote_runners { + runner_data := v.(map[string]interface{}) + runner_info := runner_data["runner_info"].(*Runner) + + if runner_data["task"] != nil { + continue + } + + if runner_info.UserId != task_to_dispatch.UserId { + continue + } + + go handleRemoteTask(handler, base, k, *task_to_dispatch) + task_to_dispatch = nil + break + } + + mutex.Unlock() + } + if task_to_dispatch != nil { for i := 0; i < len(task_runners_used); i += 1 { if !task_runners_used[i] { task_runners[i] <- *task_to_dispatch task_runners_used[i] = true + AddLocalTask(handler, i+1, task_to_dispatch) task_to_dispatch = nil break } @@ -211,6 +305,6 @@ func RunnerOrchestrator(db db.Db, config Config) { } } -func StartRunners(db db.Db, config Config) { - go RunnerOrchestrator(db, config) +func StartRunners(db db.Db, config Config, handler *Handle) { + go RunnerOrchestrator(db, config, handler) } diff --git a/logic/tasks/runner/utils.go b/logic/tasks/runner/utils.go new file mode 100644 index 0000000..3df2a5e --- /dev/null +++ b/logic/tasks/runner/utils.go @@ -0,0 +1,51 @@ +package task_runner + +import ( + "sync" + + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/utils" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" +) + +type LocalRunner struct { + RunnerNum int `json:"id"` + Task *Task `json:"task"` +} + +type LocalRunners map[int]*LocalRunner + +func LockRunners(handler *Handle, t string) *sync.Mutex { + req := t + "_runners_mutex" + if t == "" { + req = "runners_mutex" + } + mutex := handler.DataMap[req].(*sync.Mutex) + mutex.Lock() + return mutex +} + +func setupHandle(handler *Handle) { + // Setup Remote Runner data + handler.DataMap["runners"] = map[string]interface{}{} + handler.DataMap["runners_mutex"] = &sync.Mutex{} + + // Setup Local Runner data + handler.DataMap["local_runners"] = &LocalRunners{} + handler.DataMap["local_runners_mutex"] = &sync.Mutex{} +} + +func AddLocalRunner(handler *Handle, runner LocalRunner) { + mutex := LockRunners(handler, "local") + defer mutex.Unlock() + + runners := handler.DataMap["local_runners"].(*LocalRunners) + (*runners)[runner.RunnerNum] = &runner +} + +func AddLocalTask(handler *Handle, runner_id int, task *Task) { + mutex := LockRunners(handler, "local") + defer mutex.Unlock() + + runners := handler.DataMap["local_runners"].(*LocalRunners) + (*(*runners)[runner_id]).Task = task +} diff --git a/logic/tasks/runner_data.go b/logic/tasks/runner_data.go new file mode 100644 index 0000000..be394e1 --- /dev/null +++ b/logic/tasks/runner_data.go @@ -0,0 +1,25 @@ +package tasks + +import ( + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/tasks/runner" + . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" +) + +func handleRunnerData(x *Handle) { + type NonType struct{} + PostAuthJson(x, "/tasks/runner/info", User_Admin, func(c *Context, dat *NonType) *Error { + mutex_remote := LockRunners(x, "") + defer mutex_remote.Unlock() + mutex_local := LockRunners(x, "local") + defer mutex_local.Unlock() + + return c.SendJSON(struct { + RemoteRunners map[string]interface{} `json:"remoteRunners"` + LocalRunner *LocalRunners `json:"localRunners"` + }{ + RemoteRunners: x.DataMap["runners"].(map[string]interface{}), + LocalRunner: x.DataMap["local_runners"].(*LocalRunners), + }) + }) +} diff --git a/logic/tasks/utils/runner.go b/logic/tasks/utils/runner.go new file mode 100644 index 0000000..9521644 --- /dev/null +++ b/logic/tasks/utils/runner.go @@ -0,0 +1,29 @@ +package tasks_utils + +import ( + "time" + + "git.andr3h3nriqu3s.com/andr3/fyp/logic/db" + dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" +) + +type RunnerType int64 + +const ( + RUNNER_TYPE_GPU RunnerType = iota + 1 +) + +type Runner struct { + Id string `json:"id" db:"ru.id"` + UserId string `json:"user_id" db:"ru.user_id"` + Token string `json:"token" db:"ru.token"` + Type RunnerType `json:"type" db:"ru.type"` + CreateOn time.Time `json:"createOn" db:"ru.created_on"` +} + +func GetRunner(db db.Db, id string) (ru *Runner, err error) { + var runner Runner + err = dbtypes.GetDBOnce(db, &runner, "remote_runner as ru where ru.id=$1", id) + ru = &runner + return +} diff --git a/logic/tasks/utils/utils.go b/logic/tasks/utils/utils.go index 8a7f105..e202717 100644 --- a/logic/tasks/utils/utils.go +++ b/logic/tasks/utils/utils.go @@ -101,7 +101,11 @@ func (t Task) SetResult(base BasePack, result any) (err error) { if err != nil { return } - _, err = base.GetDb().Exec("update tasks set result=$1 where id=$2", text, t.Id) + return t.SetResultText(base, string(text)) +} + +func (t Task) SetResultText(base BasePack, text string) (err error) { + _, err = base.GetDb().Exec("update tasks set result=$1 where id=$2", []byte(text), t.Id) return } diff --git a/logic/users/users.go b/logic/users/users.go index 9b542a6..0583e18 100644 --- a/logic/users/users.go +++ b/logic/users/users.go @@ -241,6 +241,17 @@ func UsersEndpints(db db.Db, handle *Handle) { return c.SendJSON(userReturn) }) + PostAuthJson(handle, "/user/info/get", User_Admin, func(c *Context, dat *JustId) *Error { + var user *User + user, err := UserFromId(c, dat.Id) + if err == NotFoundError { + return c.SendJSONStatus(404, "User not found") + } else if err != nil { + return c.E500M("Could not get user", err) + } + return c.SendJSON(user) + }) + // Handles updating users type UpdateUserData struct { Id string `json:"id"` diff --git a/logic/utils/config.go b/logic/utils/config.go index 085aa2b..dfb0c0b 100644 --- a/logic/utils/config.go +++ b/logic/utils/config.go @@ -114,12 +114,16 @@ func (c *Config) Cleanup(db db.Db) { tasks[i].UpdateStatus(base, TASK_FAILED_RUNNING, "Task inturupted by server restart please try again") _, err = db.Exec("update models set status=$1 where id=$2", READY_RETRAIN_FAILED, tasks[i].ModelId) failLog(err) + _, err = db.Exec("update model_classes set status=$1 where model_id=$2 and status=$3", CLASS_STATUS_TO_TRAIN, tasks[i].ModelId, CLASS_STATUS_TRAINING) + failLog(err) continue } if tasks[i].TaskType == int(TASK_TYPE_TRAINING) { tasks[i].UpdateStatus(base, TASK_FAILED_RUNNING, "Task inturupted by server restart please try again") _, err = db.Exec("update models set status=$1 where id=$2", FAILED_TRAINING, tasks[i].ModelId) failLog(err) + _, err = db.Exec("update model_classes set status=$1 where model_id=$2 and status=$3", CLASS_STATUS_TO_TRAIN, tasks[i].ModelId, CLASS_STATUS_TRAINING) + failLog(err) continue } } diff --git a/logic/utils/handler.go b/logic/utils/handler.go index 4b35f81..451bbd6 100644 --- a/logic/utils/handler.go +++ b/logic/utils/handler.go @@ -374,7 +374,7 @@ func (c Context) JsonBadRequest(dat any) *Error { c.SetReportCaller(true) c.Logger.Warn("Request failed with a bad request", "dat", dat) c.SetReportCaller(false) - return c.ErrorCode(nil, 404, dat) + return c.SendJSONStatus(http.StatusBadRequest, dat) } func (c Context) JsonErrorBadRequest(err error, dat any) *Error { @@ -449,7 +449,7 @@ func (x Handle) createContext(handler *Handle, r *http.Request, w http.ResponseW logger := log.NewWithOptions(os.Stdout, log.Options{ ReportCaller: true, ReportTimestamp: true, - TimeFormat: time.Kitchen, + TimeFormat: time.DateTime, Prefix: r.URL.Path, }) diff --git a/main.go b/main.go index 2433118..2ba1973 100644 --- a/main.go +++ b/main.go @@ -36,11 +36,11 @@ func main() { log.Info("Config loaded!", "config", config) config.GenerateToken(db) - StartRunners(db, config) - //TODO check if file structure exists to save data handle := NewHandler(db, config) + StartRunners(db, config, handle) + config.Cleanup(db) // TODO Handle this in other way diff --git a/nginx.dev.conf b/nginx.dev.conf index 242a37b..96e6229 100644 --- a/nginx.dev.conf +++ b/nginx.dev.conf @@ -13,7 +13,7 @@ http { server { listen 8000; - client_max_body_size 1G; + client_max_body_size 5G; location / { proxy_http_version 1.1; diff --git a/sql/tasks.sql b/sql/tasks.sql index 8248ade..e9aa445 100644 --- a/sql/tasks.sql +++ b/sql/tasks.sql @@ -38,3 +38,14 @@ create table if not exists tasks_dependencies ( main_id uuid references tasks (id) on delete cascade not null, dependent_id uuid references tasks (id) on delete cascade not null ); + +create table if not exists remote_runner ( + id uuid primary key default gen_random_uuid(), + user_id uuid references users (id) on delete cascade not null, + token text not null, + + -- 1: GPU + type integer, + + created_on timestamp default current_timestamp +); diff --git a/views/py/python_model_template.py b/views/py/python_model_template.py index 9542109..1935e98 100644 --- a/views/py/python_model_template.py +++ b/views/py/python_model_template.py @@ -82,7 +82,7 @@ def prepare_dataset(ds: tf.data.Dataset, size: int) -> tf.data.Dataset: def filterDataset(path): path = tf.strings.regex_replace(path, DATA_DIR_PREPARE, "") - + {{ if eq .Model.Format "png" }} path = tf.strings.regex_replace(path, ".png", "") {{ else if eq .Model.Format "jpeg" }} @@ -90,7 +90,7 @@ def filterDataset(path): {{ else }} ERROR {{ end }} - + return tf.reshape(table.lookup(tf.strings.as_string([path])), []) != -1 seed = random.randint(0, 100000000) @@ -135,9 +135,9 @@ def addBlock( model.add(layers.ReLU()) if top: if pooling_same: - model.add(pool_func(padding="same", strides=(1, 1))) + model.add(pool_func(pool_size=(2,2), padding="same", strides=(1, 1))) else: - model.add(pool_func()) + model.add(pool_func(pool_size=(2,2))) model.add(layers.BatchNormalization()) model.add(layers.LeakyReLU()) model.add(layers.Dropout(0.4)) @@ -172,7 +172,7 @@ model.compile( his = model.fit(dataset, validation_data= dataset_validation, epochs={{.EPOCH_PER_RUN}}, callbacks=[ NotifyServerCallback(), - tf.keras.callbacks.EarlyStopping("loss", mode="min", patience=5)], use_multiprocessing = True) + tf.keras.callbacks.EarlyStopping("loss", mode="min", patience=5)]) acc = his.history["accuracy"] diff --git a/webpage/bun.lockb b/webpage/bun.lockb index ab72073e27a1a432434b2f3bcf4a7a4e16495a05..cbe64952aa9ea483b8dffdfb498c14bc0da9cc98 100755 GIT binary patch delta 53001 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¥zxx2XANb#W0L_N~-3P!4|GN*! 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 M`-=bgpi7(o2B!an8UO$Q delta 35378 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 diff --git a/webpage/package-lock.json b/webpage/package-lock.json new file mode 100644 index 0000000..9727dec --- /dev/null +++ b/webpage/package-lock.json @@ -0,0 +1,4125 @@ +{ + "name": "webpage", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "webpage", + "version": "0.0.1", + "dependencies": { + "chart.js": "^4.4.2", + "d3": "^7.9.0", + "highlight.js": "^11.9.0" + }, + "devDependencies": { + "@sveltejs/adapter-auto": "^3.2.0", + "@sveltejs/kit": "^2.5.6", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/d3": "^7.4.3", + "@types/eslint": "^8.56.9", + "@typescript-eslint/eslint-plugin": "^7.7.0", + "@typescript-eslint/parser": "^7.7.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-svelte": "^2.37.0", + "prettier": "^3.2.5", + "prettier-plugin-svelte": "^3.2.3", + "sass": "^1.75.0", + "svelte": "^5.0.0-next.104", + "svelte-check": "^3.6.9", + "tslib": "^2.6.2", + "typescript": "^5.4.5", + "vite": "^5.2.8" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "dev": true + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@kurkle/color": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz", + "integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.25", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", + "integrity": "sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.17.2.tgz", + "integrity": "sha512-NM0jFxY8bB8QLkoKxIQeObCaDlJKewVlIEkuyYKm5An1tdVZ966w2+MPQ2l8LBZLjR+SgyV+nRkTIunzOYBMLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.17.2.tgz", + "integrity": "sha512-yeX/Usk7daNIVwkq2uGoq2BYJKZY1JfyLTaHO/jaiSwi/lsf8fTFoQW/n6IdAsx5tx+iotu2zCJwz8MxI6D/Bw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.17.2.tgz", + "integrity": "sha512-kcMLpE6uCwls023+kknm71ug7MZOrtXo+y5p/tsg6jltpDtgQY1Eq5sGfHcQfb+lfuKwhBmEURDga9N0ol4YPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.17.2.tgz", + "integrity": "sha512-AtKwD0VEx0zWkL0ZjixEkp5tbNLzX+FCqGG1SvOu993HnSz4qDI6S4kGzubrEJAljpVkhRSlg5bzpV//E6ysTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.17.2.tgz", + "integrity": "sha512-3reX2fUHqN7sffBNqmEyMQVj/CKhIHZd4y631duy0hZqI8Qoqf6lTtmAKvJFYa6bhU95B1D0WgzHkmTg33In0A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.17.2.tgz", + "integrity": "sha512-uSqpsp91mheRgw96xtyAGP9FW5ChctTFEoXP0r5FAzj/3ZRv3Uxjtc7taRQSaQM/q85KEKjKsZuiZM3GyUivRg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.17.2.tgz", + "integrity": "sha512-EMMPHkiCRtE8Wdk3Qhtciq6BndLtstqZIroHiiGzB3C5LDJmIZcSzVtLRbwuXuUft1Cnv+9fxuDtDxz3k3EW2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.17.2.tgz", + "integrity": "sha512-NMPylUUZ1i0z/xJUIx6VUhISZDRT+uTWpBcjdv0/zkp7b/bQDF+NfnfdzuTiB1G6HTodgoFa93hp0O1xl+/UbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.17.2.tgz", + "integrity": "sha512-T19My13y8uYXPw/L/k0JYaX1fJKFT/PWdXiHr8mTbXWxjVF1t+8Xl31DgBBvEKclw+1b00Chg0hxE2O7bTG7GQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.17.2.tgz", + "integrity": "sha512-BOaNfthf3X3fOWAB+IJ9kxTgPmMqPPH5f5k2DcCsRrBIbWnaJCgX2ll77dV1TdSy9SaXTR5iDXRL8n7AnoP5cg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.17.2.tgz", + "integrity": "sha512-W0UP/x7bnn3xN2eYMql2T/+wpASLE5SjObXILTMPUBDB/Fg/FxC+gX4nvCfPBCbNhz51C+HcqQp2qQ4u25ok6g==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.17.2.tgz", + "integrity": "sha512-Hy7pLwByUOuyaFC6mAr7m+oMC+V7qyifzs/nW2OJfC8H4hbCzOX07Ov0VFk/zP3kBsELWNFi7rJtgbKYsav9QQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.17.2.tgz", + "integrity": "sha512-h1+yTWeYbRdAyJ/jMiVw0l6fOOm/0D1vNLui9iPuqgRGnXA0u21gAqOyB5iHjlM9MMfNOm9RHCQ7zLIzT0x11Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.17.2.tgz", + "integrity": "sha512-tmdtXMfKAjy5+IQsVtDiCfqbynAQE/TQRpWdVataHmhMb9DCoJxp9vLcCBjEQWMiUYxO1QprH/HbY9ragCEFLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.17.2.tgz", + "integrity": "sha512-7II/QCSTAHuE5vdZaQEwJq2ZACkBpQDOmQsE6D6XUbnBHW8IAhm4eTufL6msLJorzrHDFv3CF8oCA/hSIRuZeQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.17.2.tgz", + "integrity": "sha512-TGGO7v7qOq4CYmSBVEYpI1Y5xDuCEnbVC5Vth8mOsW0gDSzxNrVERPc790IGHsrT2dQSimgMr9Ub3Y1Jci5/8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sveltejs/adapter-auto": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-auto/-/adapter-auto-3.2.0.tgz", + "integrity": "sha512-She5nKT47kwHE18v9NMe6pbJcvULr82u0V3yZ0ej3n1laWKGgkgdEABE9/ak5iDPs93LqsBkuIo51kkwCLBjJA==", + "dev": true, + "dependencies": { + "import-meta-resolve": "^4.0.0" + }, + "peerDependencies": { + "@sveltejs/kit": "^2.0.0" + } + }, + "node_modules/@sveltejs/kit": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.5.7.tgz", + "integrity": "sha512-6uedTzrb7nQrw6HALxnPrPaXdIN2jJJTzTIl96Z3P5NiG+OAfpdPbrWrvkJ3GN4CfWqrmU4dJqwMMRMTD/C7ow==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.6.0", + "devalue": "^5.0.0", + "esm-env": "^1.0.0", + "import-meta-resolve": "^4.0.0", + "kleur": "^4.1.5", + "magic-string": "^0.30.5", + "mrmime": "^2.0.0", + "sade": "^1.8.1", + "set-cookie-parser": "^2.6.0", + "sirv": "^2.0.4", + "tiny-glob": "^0.2.9" + }, + "bin": { + "svelte-kit": "svelte-kit.js" + }, + "engines": { + "node": ">=18.13" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.3" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-3.1.0.tgz", + "integrity": "sha512-sY6ncCvg+O3njnzbZexcVtUqOBE3iYmQPJ9y+yXSkOwG576QI/xJrBnQSRXFLGwJNBa0T78JEKg5cIR0WOAuUw==", + "dev": true, + "dependencies": { + "@sveltejs/vite-plugin-svelte-inspector": "^2.0.0", + "debug": "^4.3.4", + "deepmerge": "^4.3.1", + "kleur": "^4.1.5", + "magic-string": "^0.30.9", + "svelte-hmr": "^0.16.0", + "vitefu": "^0.2.5" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte-inspector": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-2.1.0.tgz", + "integrity": "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.0.0 || >=20" + }, + "peerDependencies": { + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "svelte": "^4.0.0 || ^5.0.0-next.0", + "vite": "^5.0.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte/node_modules/svelte-hmr": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.16.0.tgz", + "integrity": "sha512-Gyc7cOS3VJzLlfj7wKS0ZnzDVdv3Pn2IuVeJPk9m2skfhcu5bq3wtIZyQGggr7/Iim5rH5cncyQft/kRLupcnA==", + "dev": true, + "engines": { + "node": "^12.20 || ^14.13.1 || >= 16" + }, + "peerDependencies": { + "svelte": "^3.19.0 || ^4.0.0" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==", + "dev": true + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "dev": true + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "dev": true + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==", + "dev": true + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "dev": true + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "dev": true + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dev": true, + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==", + "dev": true + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "dev": true + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "dev": true + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==", + "dev": true + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "dev": true + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "dev": true + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "dev": true + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dev": true, + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", + "dev": true + }, + "node_modules/@types/d3-selection": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==", + "dev": true + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dev": true, + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==", + "dev": true + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "dev": true + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "dev": true + }, + "node_modules/@types/d3-transition": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", + "dev": true, + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dev": true, + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/geojson": { + "version": "7946.0.14", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", + "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/pug": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", + "integrity": "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", + "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/type-utils": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz", + "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz", + "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz", + "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.8.0", + "@typescript-eslint/utils": "7.8.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz", + "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz", + "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/visitor-keys": "7.8.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz", + "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.15", + "@types/semver": "^7.5.8", + "@typescript-eslint/scope-manager": "7.8.0", + "@typescript-eslint/types": "7.8.0", + "@typescript-eslint/typescript-estree": "7.8.0", + "semver": "^7.6.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz", + "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.8.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/acorn-typescript": { + "version": "1.4.13", + "resolved": "https://registry.npmjs.org/acorn-typescript/-/acorn-typescript-1.4.13.tgz", + "integrity": "sha512-xsc9Xv0xlVfwp2o7sQ+GCQ1PgbkdcpWdTzrwXxO3xDMTAywVS3oXVOcOHuRjAPkS4P9b+yc/qNF15460v+jp4Q==", + "dev": true, + "peerDependencies": { + "acorn": ">=8.9.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-+60uv1hiVFhHZeO+Lz0RYzsVHy5Wr1ayX0mwda9KPDVLNJgZ1T9Ny7VmFbLDzxsH0D87I86vgj3gFrjTJUYznw==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chart.js": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.2.tgz", + "integrity": "sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-indent": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-6.1.0.tgz", + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.0.0.tgz", + "integrity": "sha512-gO+/OMXF7488D+u3ue+G7Y4AA3ZmUnB3eHJXmBTgNHvr4ZNzl36A0ZtG+XCRNYCkYx/bFmw4qtkoFLa+wSrwAA==", + "dev": true + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/es6-promise": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", + "integrity": "sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-compat-utils": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.0.tgz", + "integrity": "sha512-dc6Y8tzEcSYZMHa+CMPLi/hyo1FzNeonbhJL7Ol0ccuKQkwopJcJBA9YL/xmMTLU1eKigXo9vj9nALElWYSowg==", + "dev": true, + "dependencies": { + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-svelte": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-svelte/-/eslint-plugin-svelte-2.38.0.tgz", + "integrity": "sha512-IwwxhHzitx3dr0/xo0z4jjDlb2AAHBPKt+juMyKKGTLlKi1rZfA4qixMwnveU20/JTHyipM6keX4Vr7LZFYc9g==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@jridgewell/sourcemap-codec": "^1.4.15", + "debug": "^4.3.4", + "eslint-compat-utils": "^0.5.0", + "esutils": "^2.0.3", + "known-css-properties": "^0.30.0", + "postcss": "^8.4.38", + "postcss-load-config": "^3.1.4", + "postcss-safe-parser": "^6.0.0", + "postcss-selector-parser": "^6.0.16", + "semver": "^7.6.0", + "svelte-eslint-parser": ">=0.35.0 <1.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0-0 || ^9.0.0-0", + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.112" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/esm-env": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.0.0.tgz", + "integrity": "sha512-Cf6VksWPsTuW01vU9Mk/3vRue91Zevka5SjyNf3nEpokFRuqt/KjUQoGAwq9qMmhpLTHmXzSIrFRw8zxWzmFBA==", + "dev": true + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrap": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.2.tgz", + "integrity": "sha512-F2pSJklxx1BlQIQgooczXCPHmcWpn6EsP5oo73LQfonG9fIlIENQ8vMmfGXeojP9MrkzUNAfyU5vdFlR9shHAw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.1" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalyzer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", + "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", + "dev": true + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-reference": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz", + "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/known-css-properties": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz", + "integrity": "sha512-VSWXYUnsPu9+WYKkfmJyLKtIvaRJi1kXUqVmBACORXZQxT5oZDsoZ2vQP+bQFDnWtpI/4eq3MLoRMjI2fnLzTQ==", + "dev": true + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-safe-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", + "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "dev": true, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.3.3" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.2.3.tgz", + "integrity": "sha512-wJq8RunyFlWco6U0WJV5wNCM7zpBFakS76UBSbmzMGpncpK98NZABaE+s7n8/APDCEVNHXC5Mpq+MLebQtsRlg==", + "dev": true, + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, + "node_modules/rollup": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.17.2.tgz", + "integrity": "sha512-/9ClTJPByC0U4zNLowV1tMBe8yMEAxewtR3cUNX5BoEpGH3dQEWpJLr6CLp0fPdYRF/fzVOgvDb1zXuakwF5kQ==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.17.2", + "@rollup/rollup-android-arm64": "4.17.2", + "@rollup/rollup-darwin-arm64": "4.17.2", + "@rollup/rollup-darwin-x64": "4.17.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.17.2", + "@rollup/rollup-linux-arm-musleabihf": "4.17.2", + "@rollup/rollup-linux-arm64-gnu": "4.17.2", + "@rollup/rollup-linux-arm64-musl": "4.17.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.17.2", + "@rollup/rollup-linux-riscv64-gnu": "4.17.2", + "@rollup/rollup-linux-s390x-gnu": "4.17.2", + "@rollup/rollup-linux-x64-gnu": "4.17.2", + "@rollup/rollup-linux-x64-musl": "4.17.2", + "@rollup/rollup-win32-arm64-msvc": "4.17.2", + "@rollup/rollup-win32-ia32-msvc": "4.17.2", + "@rollup/rollup-win32-x64-msvc": "4.17.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sander": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/sander/-/sander-0.5.1.tgz", + "integrity": "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==", + "dev": true, + "dependencies": { + "es6-promise": "^3.1.2", + "graceful-fs": "^4.1.3", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2" + } + }, + "node_modules/sander/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/sass": { + "version": "1.77.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.0.tgz", + "integrity": "sha512-eGj4HNfXqBWtSnvItNkn7B6icqH14i3CiCGbzMKs3BAPTq62pp9NBYsBgyN4cA+qssqo9r26lW4JSvlaUUWbgw==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz", + "integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==", + "dev": true + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/sorcery": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/sorcery/-/sorcery-0.11.0.tgz", + "integrity": "sha512-J69LQ22xrQB1cIFJhPfgtLuI6BpWRiWu1Y3vSsIwK/eAScqJxd/+CJlUuHQRdX2C9NGFamq+KqNywGgaThwfHw==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.14", + "buffer-crc32": "^0.2.5", + "minimist": "^1.2.0", + "sander": "^0.5.0" + }, + "bin": { + "sorcery": "bin/sorcery" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/svelte": { + "version": "5.0.0-next.126", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.0.0-next.126.tgz", + "integrity": "sha512-Qnxhf+LG/qxhXpYm6I5+o8msSFBba2QOfnybrOqWbbtQbgbfy4gDVr3p2IExCT4yPOSUcqZWJiiSPsMbCuVwtA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.1", + "@jridgewell/sourcemap-codec": "^1.4.15", + "@types/estree": "^1.0.5", + "acorn": "^8.11.3", + "acorn-typescript": "^1.4.13", + "aria-query": "^5.3.0", + "axobject-query": "^4.0.0", + "esm-env": "^1.0.0", + "esrap": "^1.2.2", + "is-reference": "^3.0.2", + "locate-character": "^3.0.0", + "magic-string": "^0.30.5", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-3.7.1.tgz", + "integrity": "sha512-U4uJoLCzmz2o2U33c7mPDJNhRYX/DNFV11XTUDlFxaKLsO7P+40gvJHMPpoRfa24jqZfST4/G9fGNcUGMO8NAQ==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "chokidar": "^3.4.1", + "fast-glob": "^3.2.7", + "import-fresh": "^3.2.1", + "picocolors": "^1.0.0", + "sade": "^1.7.4", + "svelte-preprocess": "^5.1.3", + "typescript": "^5.0.3" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "peerDependencies": { + "svelte": "^3.55.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0" + } + }, + "node_modules/svelte-eslint-parser": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/svelte-eslint-parser/-/svelte-eslint-parser-0.36.0.tgz", + "integrity": "sha512-/6YmUSr0FAVxW8dXNdIMydBnddPMHzaHirAZ7RrT21XYdgGGZMh0LQG6CZsvAFS4r2Y4ItUuCQc8TQ3urB30mQ==", + "dev": true, + "dependencies": { + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "postcss": "^8.4.38", + "postcss-scss": "^4.0.9" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + }, + "peerDependencies": { + "svelte": "^3.37.0 || ^4.0.0 || ^5.0.0-next.115" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + } + } + }, + "node_modules/svelte-preprocess": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/svelte-preprocess/-/svelte-preprocess-5.1.4.tgz", + "integrity": "sha512-IvnbQ6D6Ao3Gg6ftiM5tdbR6aAETwjhHV+UKGf5bHGYR69RQvF1ho0JKPcbUON4vy4R7zom13jPjgdOWCQ5hDA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@types/pug": "^2.0.6", + "detect-indent": "^6.1.0", + "magic-string": "^0.30.5", + "sorcery": "^0.11.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">= 16.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.10.2", + "coffeescript": "^2.5.1", + "less": "^3.11.3 || ^4.0.0", + "postcss": "^7 || ^8", + "postcss-load-config": "^2.1.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", + "pug": "^3.0.0", + "sass": "^1.26.8", + "stylus": "^0.55.0", + "sugarss": "^2.0.0 || ^3.0.0 || ^4.0.0", + "svelte": "^3.23.0 || ^4.0.0-next.0 || ^4.0.0 || ^5.0.0-next.0", + "typescript": ">=3.9.5 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "coffeescript": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "postcss-load-config": { + "optional": true + }, + "pug": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tiny-glob": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", + "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", + "dev": true, + "dependencies": { + "globalyzer": "0.1.0", + "globrex": "^0.1.2" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-0.2.5.tgz", + "integrity": "sha512-SgHtMLoqaeeGnd2evZ849ZbACbnwQCIwRH57t18FxcXoZop0uQu0uzlIhJBlF/eWVzuce0sHeqPcDo+evVcg8Q==", + "dev": true, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zimmerframe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz", + "integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==", + "dev": true + } + } +} diff --git a/webpage/package.json b/webpage/package.json index e370cac..262c7e6 100644 --- a/webpage/package.json +++ b/webpage/package.json @@ -15,7 +15,8 @@ "devDependencies": { "@sveltejs/adapter-auto": "^3.2.0", "@sveltejs/kit": "^2.5.6", - "@sveltejs/vite-plugin-svelte": "3.0.0", + "@sveltejs/vite-plugin-svelte": "^3.0.0", + "@types/d3": "^7.4.3", "@types/eslint": "^8.56.9", "@typescript-eslint/eslint-plugin": "^7.7.0", "@typescript-eslint/parser": "^7.7.0", @@ -25,7 +26,7 @@ "prettier": "^3.2.5", "prettier-plugin-svelte": "^3.2.3", "sass": "^1.75.0", - "svelte": "5.0.0-next.104", + "svelte": "^5.0.0-next.104", "svelte-check": "^3.6.9", "tslib": "^2.6.2", "typescript": "^5.4.5", @@ -33,6 +34,8 @@ }, "type": "module", "dependencies": { - "chart.js": "^4.4.2" + "chart.js": "^4.4.2", + "d3": "^7.9.0", + "highlight.js": "^11.9.0" } } diff --git a/webpage/src/NavBar.svelte b/webpage/src/NavBar.svelte index f90d75a..4f87812 100644 --- a/webpage/src/NavBar.svelte +++ b/webpage/src/NavBar.svelte @@ -15,8 +15,18 @@ {/if}
  • {#if userStore.user} + {#if userStore.user.user_type == 2} +
  • + + + Runner + +
  • + {/if}
  • {userStore.user.username} +
  • +
  • Logout
  • {:else} diff --git a/webpage/src/app.html b/webpage/src/app.html index 073c0c0..8152ef1 100644 --- a/webpage/src/app.html +++ b/webpage/src/app.html @@ -9,6 +9,8 @@ + + + import { goto } from '$app/navigation'; + import { notificationStore } from 'src/lib/NotificationsStore.svelte'; + import { post, showMessage } from 'src/lib/requests.svelte'; + import { userStore } from 'src/routes/UserStore.svelte'; + import { onMount } from 'svelte'; + import * as d3 from 'd3'; + + import type { Base } from './types'; + import CardInfo from './CardInfo.svelte'; + + let width = $state(0); + let height = $state(0); + + function drag(simulation: any) { + function dragstarted(event: any, d: any) { + if (!event.active) simulation.alphaTarget(0.3).restart(); + d.fx = d.x; + d.fy = d.y; + selected = d.data; + } + + function dragged(event: any, d: any) { + d.fx = event.x; + d.fy = event.y; + } + + function dragended(event: any, d: any) { + if (!event.active) simulation.alphaTarget(0); + d.fx = null; + d.fy = null; + } + + return d3.drag().on('start', dragstarted).on('drag', dragged).on('end', dragended); + } + + let graph: HTMLDivElement; + + let selected: Base | undefined = $state(); + + async function getData() { + const dataObj: Base = { + name: 'API', + type: 'api', + children: [] + }; + + if (!dataObj.children) throw new Error(); + + const localRunners: Base[] = []; + const remotePairs: Record = {}; + + try { + let data = await post('tasks/runner/info', {}); + + if (Object.keys(data.localRunners).length > 0) { + for (const objId of Object.keys(data.localRunners)) { + localRunners.push({ name: objId, type: 'local_runner' }); + } + + dataObj.children.push({ + name: 'local runners', + type: 'runner_group', + children: localRunners + }); + } + + for (const objId of Object.keys(data.remoteRunners)) { + let obj = data.remoteRunners[objId]; + if (remotePairs[obj.runner_info.user_id as string]) { + remotePairs[obj.runner_info.user_id as string].push({ + name: objId, + type: 'runner', + task: obj.task, + parent: data.remoteRunners[objId].runner_info.user_id + }); + } else { + remotePairs[data.remoteRunners[objId].runner_info.user_id] = [ + { + name: objId, + type: 'runner', + task: obj.task, + parent: data.remoteRunners[objId].runner_info.user_id + } + ]; + } + } + + dataObj.children.push({ + name: 'remote runners', + type: 'runner_group', + task: undefined, + children: Object.keys(remotePairs).map( + (name) => + ({ + name, + type: 'user_group', + task: undefined, + children: remotePairs[name] + }) as Base + ) + }); + } catch (ex) { + showMessage(ex, notificationStore, 'Failed to get Runner information'); + return; + } + + const root = d3.hierarchy(dataObj); + const links = root.links(); + const nodes = root.descendants(); + console.log(root, links, nodes); + + const simulation = d3 + .forceSimulation(nodes) + .force( + 'link', + d3 + .forceLink(links) + .id((d: any) => d.id) + .distance((d: any) => { + let data = d.source.data as Base; + switch (data.type) { + case 'api': + return 150; + case 'runner_group': + return 90; + case 'user_group': + return 80; + case 'runner': + case 'local_runner': + return 20; + default: + throw new Error(); + } + }) + .strength(1) + ) + .force('charge', d3.forceManyBody().strength(-1000)) + .force('x', d3.forceX()) + .force('y', d3.forceY()); + + const svg = d3 + .create('svg') + .attr('width', width) + .attr('height', height - 62) + .attr('viewBox', [-width / 2, -height / 2, width, height]) + .attr('style', 'max-width: 100%; height: auto;'); + // Append links. + const link = svg + .append('g') + .attr('stroke', '#999') + .attr('stroke-opacity', 0.6) + .selectAll('line') + .data(links) + .join('line'); + + const database_svg = ` + + + + `; + + const cpu_svg = ` + + + + + `; + + const user_svg = ` + + + + `; + + const inbox_fill = ` + + + + `; + + const node = svg + .append('g') + .attr('fill', '#fff') + .attr('stroke', '#000') + .attr('stroke-width', 1.5) + .selectAll('g') + .data(nodes) + .join('g') + .attr('style', 'cursor: pointer;') + .call(drag(simulation) as any) + .on('click', (e) => { + console.log('test'); + function findData(obj: HTMLElement) { + if ((obj as any).__data__) { + return (obj as any).__data__; + } + if (!obj.parentElement) { + throw new Error(); + } + return findData(obj.parentElement); + } + let obj = findData(e.srcElement); + console.log(obj); + selected = obj.data; + }); + + node + .append('circle') + .attr('fill', (d: any) => { + let data = d.data as Base; + switch (data.type) { + case 'api': + return '#caf0f8'; + case 'runner_group': + return '#00b4d8'; + case 'user_group': + return '#0000ff'; + case 'runner': + case 'local_runner': + return '#03045e'; + default: + throw new Error(); + } + }) + .attr('stroke', (d: any) => { + let data = d.data as Base; + switch (data.type) { + case 'api': + case 'user_group': + case 'runner_group': + return '#fff'; + case 'runner': + case 'local_runner': + // TODO make this relient on the stauts + return '#000'; + default: + throw new Error(); + } + }) + .attr('r', (d: any) => { + let data = d.data as Base; + switch (data.type) { + case 'api': + return 30; + case 'runner_group': + return 20; + case 'user_group': + return 25; + case 'runner': + case 'local_runner': + return 30; + default: + throw new Error(); + } + }) + .append('title') + .text((d: any) => d.data.name); + + node + .filter((d) => { + return ['api', 'local_runner', 'runner', 'user_group', 'runner_group'].includes( + d.data.type + ); + }) + .append('g') + .html((d) => { + switch (d.data.type) { + case 'api': + return database_svg; + case 'user_group': + return user_svg; + case 'runner_group': + return inbox_fill; + case 'local_runner': + case 'runner': + return cpu_svg; + default: + throw new Error(); + } + }); + + simulation.on('tick', () => { + link + .attr('x1', (d: any) => d.source.x) + .attr('y1', (d: any) => d.source.y) + .attr('x2', (d: any) => d.target.x) + .attr('y2', (d: any) => d.target.y); + + node + .select('circle') + .attr('cx', (d: any) => d.x) + .attr('cy', (d: any) => d.y); + node + .select('svg') + .attr('x', (d: any) => d.x - 16) + .attr('y', (d: any) => d.y - 16); + }); + + //invalidation.then(() => simulation.stop()); + + graph.appendChild(svg.node() as any); + } + + $effect(() => { + console.log(selected); + }); + + onMount(() => { + // Check if logged in and admin + if (!userStore.user || userStore.user.user_type != 2) { + goto('/'); + return; + } + getData(); + }); + + + + + Runners + + +
    +
    + {#if selected} +
    + +
    + {/if} +
    + + diff --git a/webpage/src/routes/admin/runners/CardInfo.svelte b/webpage/src/routes/admin/runners/CardInfo.svelte new file mode 100644 index 0000000..244ee6b --- /dev/null +++ b/webpage/src/routes/admin/runners/CardInfo.svelte @@ -0,0 +1,90 @@ + + +{#if item.type == 'api'} +

    API

    +{:else if item.type == 'runner_group'} +

    Runner Group

    + This reprents a the group of {item.name}. +{:else if item.type == 'user_group'} +

    User

    + {#if user_data} + All Runners connected to this node bellong to {user_data.username} + {:else} +
    + +
    + {/if} +{:else if item.type == 'local_runner'} +

    Local Runner

    + This is a local runner +
    + {#if item.task} + test + {:else} + Not running any task + {/if} +
    +{:else if item.type == 'runner'} +

    Runner

    + {#if user_data} +

    + This is a remote runner. This runner is owned by{user_data?.username} +

    +
    + {#if item.task} + This runner is runing a task + {:else} + Not running any task + {/if} +
    + {:else} +
    + +
    + {/if} +{:else} + {item.type} +{/if} + + diff --git a/webpage/src/routes/admin/runners/types.ts b/webpage/src/routes/admin/runners/types.ts new file mode 100644 index 0000000..e36cfc9 --- /dev/null +++ b/webpage/src/routes/admin/runners/types.ts @@ -0,0 +1,10 @@ +import type { Task } from 'src/routes/models/edit/tasks/types'; + +export type BaseType = 'api' | 'runner_group' | 'user_group' | 'runner' | 'local_runner'; +export type Base = { + name: string; + type: BaseType; + children?: Base[]; + task?: Task; + parent?: string; +}; diff --git a/webpage/src/routes/models/+page.svelte b/webpage/src/routes/models/+page.svelte index f802c4d..6240849 100644 --- a/webpage/src/routes/models/+page.svelte +++ b/webpage/src/routes/models/+page.svelte @@ -1,26 +1,22 @@ @@ -30,39 +26,44 @@
    - - {#if list.length > 0} -
    -

    My Models

    -
    - New -
    - - - - - - - - - {#each list as item} + {#if list} + {#if list.length > 0} +
    +

    My Models

    +
    + New +
    +
    Name - -
    + - - + + - {/each} - -
    - {item.name} - - Edit - Name + +
    + + + {#each list as item} + + + {item.name} + + + Edit + + + {/each} + + + {:else} +

    You don't have any models

    + + {/if} {:else} -

    You don't have any models

    -
    diff --git a/webpage/src/routes/models/add/+page.svelte b/webpage/src/routes/models/add/+page.svelte index 488b88a..9ef4e2d 100644 --- a/webpage/src/routes/models/add/+page.svelte +++ b/webpage/src/routes/models/add/+page.svelte @@ -1,15 +1,13 @@ -
    -
    - -
    Run image through them model and get the result
    + +
    + + +
    +
    +
    + To perform an image classfication please follow the example bellow: +
    {@html hljs.highlight(
    +					`let form = new FormData();
    +form.append('json_data', JSON.stringify({ id: '${model.id}' }));
    +form.append('file', file, 'file');
     
    -		
    -			
    -			 Upload image 
    -			
    - Image selected -
    -
    -
    - - - {#if run} - {#await _result} -

    - Processing Image! -

    - {:then result} - {#if result.status == 4} - {@const res = JSON.parse(result.result)} -
    -

    Result

    - The image was classified as {res.class} with confidence: {res.confidence} -
    - {:else} -
    -

    There was a problem running the task:

    - {result?.status_message} -
    +const headers = new Headers(); +headers.append('response-type', 'application/json'); +headers.append('token', token); + +const r = await fetch('${window.location.protocol}//${window.location.hostname}/api/tasks/start/image', { + method: 'POST', + headers: headers, + body: form +});`, + { language: 'javascript' } + ).value} + On Success the request will return a json with this format: +
    {@html hljs.highlight(
    +					`{ id	"00000000-0000-0000-0000-000000000000" }`,
    +					{ language: 'json' }
    +				).value}
    + This id can be used to query the API for the result of the task: +
    {@html hljs.highlight(
    +					`const headers = new Headers();
    +headers.append('content-type', 'application/json');
    +headers.append('token', token);
    +
    +const r = await fetch('${window.location.protocol}//${window.location.hostname}/api/tasks/task', {
    +	method: 'POST',
    +	headers: headers,
    +	body: JSON.stringify({ id: '00000000-0000-0000-0000-000000000000' })
    +});`,
    +					{ language: 'javascript' }
    +				).value}
    + Once the task shows the status as 4 then the data can be obatined in the result field: The successful + return value has this type: +
    {@html hljs.highlight(
    +					`{
    +    "id": string,
    +    "user_id": string,
    +    "model_id": string,
    +    "status": number,
    +    "status_message": string,
    +    "user_confirmed": number,
    +    "compacted": number,
    +    "type": number,
    +    "extra_task_info": string,
    +    "result": string,
    +    "created": string
    +}`,
    +					{ language: 'javascript' }
    +				).value}
    + + +
    + +
    + +
    Run image through them model and get the result
    + + + + Upload image +
    + Image selected +
    +
    +
    + + {#if run} + {#await _result} +

    + Processing Image! +

    + {:then result} + {#if result.status == 4} + {@const res = JSON.parse(result.result)} +
    +

    Result

    + The image was classified as {res.class} with confidence: {res.confidence} +
    + {:else} +
    +

    There was a problem running the task:

    + {result?.status_message} +
    + {/if} + {/await} {/if} - {/await} - {/if} - + +
    + + + diff --git a/webpage/src/routes/models/edit/tasks/Stats.svelte b/webpage/src/routes/models/edit/tasks/Stats.svelte index 9d25b1a..98055c6 100644 --- a/webpage/src/routes/models/edit/tasks/Stats.svelte +++ b/webpage/src/routes/models/edit/tasks/Stats.svelte @@ -18,7 +18,6 @@ PointElement, LineElement } from 'chart.js'; - import ModelData from '../ModelData.svelte'; Chart.register( Title, @@ -57,7 +56,9 @@ } let pie: HTMLCanvasElement; + let pie2: HTMLCanvasElement; let pieChart: Chart<'pie'> | undefined; + let pie2Chart: Chart<'pie'> | undefined; function createPie(s: TasksStatsDay) { if (pieChart) { pieChart.destroy(); @@ -72,23 +73,31 @@ 'Classfication Failure', 'Classfication Preparing', 'Classfication Running', - 'Classfication Unknown', - 'Non Classfication Error', - 'Non Classfication Success' + 'Classfication Unknown' ], datasets: [ { label: 'Total', - data: [ - t.c_error, - t.c_success, - t.c_failure, - t.c_pre_running, - t.c_running, - t.c_unknown, - t.nc_error, - t.nc_success - ] + data: [t.c_error, t.c_success, t.c_failure, t.c_pre_running, t.c_running, t.c_unknown] + } + ] + }, + options: { + animation: false + } + }); + + if (pie2Chart) { + pieChart.destroy(); + } + pie2Chart = new Chart(pie2, { + type: 'pie', + data: { + labels: ['Non Classfication Error', 'Non Classfication Success'], + datasets: [ + { + label: 'Total', + data: [t.nc_error, t.nc_success] } ] }, @@ -147,8 +156,13 @@

    Statistics (Day)

    Total

    -
    - +
    +
    + +
    +
    + +

    Hourly

    @@ -160,4 +174,12 @@ canvas { width: 100%; } + .pies { + display: flex; + align-content: stretch; + + div { + width: 50%; + } + } diff --git a/webpage/src/routes/models/edit/tasks/TasksTable.svelte b/webpage/src/routes/models/edit/tasks/TasksTable.svelte index c5029d5..5a2eca5 100644 --- a/webpage/src/routes/models/edit/tasks/TasksTable.svelte +++ b/webpage/src/routes/models/edit/tasks/TasksTable.svelte @@ -1,16 +1,4 @@