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}
-
-
-
-
- Name |
-
-
- |
-
-
-
- {#each list as item}
+ {#if list}
+ {#if list.length > 0}
+
+
+
-
- {item.name}
- |
-
- Edit
- |
+ Name |
+
+
+ |
- {/each}
-
-
+
+
+ {#each list as item}
+
+
+ {item.name}
+ |
+
+ Edit
+ |
+
+ {/each}
+
+
+ {:else}
+ You don't have any models
+
+ {/if}
{:else}
- You don't have any models
-
-
Create a new model
+
+
{/if}
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 @@
-