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 ab72073..cbe6495 100755 Binary files a/webpage/bun.lockb and b/webpage/bun.lockb differ 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 @@