From 143ad3b02b2d3a2dbd7c4d433cedf8f5595e8189 Mon Sep 17 00:00:00 2001 From: Andre Henriques Date: Tue, 9 Apr 2024 17:28:59 +0100 Subject: [PATCH] feat: improved visibility of dataset and improved speed for expand import zip file --- go.mod | 11 +- go.sum | 18 + logic/models/data.go | 91 ++-- logic/models/list.go | 30 +- logic/utils/handler.go | 22 +- webpage/bun.lockb | Bin 99104 -> 100603 bytes webpage/package.json | 9 +- webpage/src/lib/Tabs.svelte | 15 +- webpage/src/routes/models/edit/+page.svelte | 507 +++++++++--------- .../src/routes/models/edit/ModelData.svelte | 344 ++++++------ .../routes/models/edit/ModelDataPage.svelte | 48 ++ .../edit/ModelDataPageStatsGraph.svelte | 89 +++ .../src/routes/models/edit/ModelTable.svelte | 2 - webpage/src/routes/models/edit/types.ts | 5 + 14 files changed, 727 insertions(+), 464 deletions(-) create mode 100644 webpage/src/routes/models/edit/ModelDataPage.svelte create mode 100644 webpage/src/routes/models/edit/ModelDataPageStatsGraph.svelte create mode 100644 webpage/src/routes/models/edit/types.ts diff --git a/go.mod b/go.mod index 1bfffc2..bd54c89 100644 --- a/go.mod +++ b/go.mod @@ -8,15 +8,20 @@ require ( github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99 github.com/google/uuid v1.6.0 github.com/lib/pq v1.10.9 - golang.org/x/crypto v0.18.0 + golang.org/x/crypto v0.19.0 ) 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 github.com/go-logfmt/logfmt v0.6.0 // indirect + 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/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect @@ -24,6 +29,8 @@ require ( github.com/muesli/termenv v0.15.2 // indirect github.com/rivo/uniseg v0.4.6 // indirect golang.org/x/exp v0.0.0-20240119083558-1b970713d09a // indirect - golang.org/x/sys v0.16.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect google.golang.org/protobuf v1.32.0 // indirect ) diff --git a/go.sum b/go.sum index e170f8b..ff10af9 100644 --- a/go.sum +++ b/go.sum @@ -10,6 +10,8 @@ github.com/charmbracelet/log v0.2.5 h1:1yVvyKCKVV639RR4LIq1iy1Cs1AKxuNO+Hx2LJtk7 github.com/charmbracelet/log v0.2.5/go.mod h1:nQGK8tvc4pS9cvVEH/pWJiZ50eUq1aoXUOjGpXvdD0k= github.com/charmbracelet/log v0.3.1 h1:TjuY4OBNbxmHWSwO3tosgqs5I3biyY8sQPny/eCMTYw= github.com/charmbracelet/log v0.3.1/go.mod h1:OR4E1hutLsax3ZKpXbgUqPtTjQfrh1pG3zwHGWuuq8g= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e h1:9+2AEFZymTi25FIIcDwuzcOPH04z9+fV6XeLiGORPDI= github.com/galeone/tensorflow/tensorflow/go v0.0.0-20221023090153-6b7fa0680c3e/go.mod h1:TelZuq26kz2jysARBwOrTv16629hyUsHmIoj54QqyFo= github.com/galeone/tensorflow/tensorflow/go v0.0.0-20240119075110-6ad3cf65adfe h1:7yELf1NFEwECpXMGowkoftcInMlVtLTCdwWLmxKgzNM= @@ -18,6 +20,12 @@ github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99 h1:8Bt1P/zy1gb37L4n8C github.com/galeone/tfgo v0.0.0-20230715013254-16113111dc99/go.mod h1:3YgYBeIX42t83uP27Bd4bSMxTnQhSbxl0pYSkCDB1tc= github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4= +github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -26,6 +34,8 @@ github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -52,10 +62,14 @@ golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -63,6 +77,10 @@ golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= diff --git a/logic/models/data.go b/logic/models/data.go index 0212af4..1d03c3d 100644 --- a/logic/models/data.go +++ b/logic/models/data.go @@ -309,58 +309,69 @@ func processZipFileExpand(c *Context, model *BaseModel) { ids[name] = id } + back_channel := make(chan int, c.Handle.Config.NumberOfWorkers) + + file_chans := make([]chan *zip.File, c.Handle.Config.NumberOfWorkers) + + for i := 0; i < c.Handle.Config.NumberOfWorkers; i++ { + file_chans[i] = make(chan *zip.File, 2) + go fileProcessor(c, model, reader, ids, base_path, i, file_chans[i], back_channel) + } + + clean_up_channels := func() { + for i := 0; i < c.Handle.Config.NumberOfWorkers; i++ { + close(file_chans[i]) + } + for i := 0; i < c.Handle.Config.NumberOfWorkers - 1; i++ { + _ = <- back_channel + } + close(back_channel) + } + + first_round := true + + channel_to_send := 0 + + // Parelalize this + for _, file := range reader.Reader.File { + // Skip if dir if file.Name[len(file.Name)-1] == '/' { continue } - data, err := reader.Open(file.Name) - if err != nil { - failed(fmt.Sprintf("Could not open file in zip %s\n", file.Name)) - return - } - defer data.Close() - file_data, err := io.ReadAll(data) - if err != nil { - failed(fmt.Sprintf("Could not read file file in zip %s\n", file.Name)) - return - } + file_chans[channel_to_send] <- file - // TODO check if the file is a valid photo that matched the defined photo on the database - parts := strings.Split(file.Name, "/") + if first_round { + channel_to_send += 1 + if c.Handle.Config.NumberOfWorkers == channel_to_send { + first_round = false + } + } + + // Can not do else if because need to handle the case where the value changes in + // previous if + if !first_round { + new_id, ok := <- back_channel + if !ok { + c.Logger.Fatal("Something is very wrong please check as this line should be unreachable") + } - mode := model_classes.DATA_POINT_MODE_TRAINING - if parts[0] == "testing" { - mode = model_classes.DATA_POINT_MODE_TESTING - } + if new_id < 0 { + c.Logger.Error("Worker failed", "worker id", -(new_id + 1)) + clean_up_channels() + failed("One of the workers failed due to db error") + return + } - data_point_id, err := model_classes.AddDataPoint(c.Db, ids[parts[1]], "id://", mode) - if err != nil { - failed(fmt.Sprintf("Failed to add data point for %s\n", model.Id)) - return - } + channel_to_send = new_id + } - file_path := path.Join(base_path, data_point_id+"."+model.Format) - f, err := os.Create(file_path) - if err != nil { - failed(fmt.Sprintf("Could not create file %s\n", file_path)) - return - } - defer f.Close() - f.Write(file_data) - - if !testImgForModel(c, model, file_path) { - c.Logger.Errorf("Image did not have valid format for model %s (in zip: %s)!", file_path, file.Name) - c.Logger.Warn("Not failling updating data point to status -1") - message := "Image did not have valid format for the model" - if err = model_classes.UpdateDataPointStatus(c.Db, data_point_id, -1, &message); err != nil { - failed(fmt.Sprintf("Failed to update data point status")) - return - } - } } + clean_up_channels() + c.Logger.Info("Added data to model", "id", model.Id) ModelUpdateStatus(c, model.Id, READY) } diff --git a/logic/models/list.go b/logic/models/list.go index 8094217..2a5c4c9 100644 --- a/logic/models/list.go +++ b/logic/models/list.go @@ -5,6 +5,31 @@ import ( . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" ) +// Auth level set when path is definied as 1 +func handleStats(c *Context) *Error { + var b struct { + Id string `json:"id" validate:"required"` + } + + if _err := c.ToJSON(&b); _err != nil { + return _err; + } + + type Row struct { + Name string `db:"mc.name" json:"name"` + Training string `db:"count(mdp.id) filter (where mdp.model_mode=1)" json:"training"` + Testing string `db:"count(mdp.id) filter (where mdp.model_mode=2)" json:"testing"` + } + + rows, err := GetDbMultitple[Row](c, "model_data_point as mdp inner join model_classes as mc on mc.id=mdp.class_id where mc.model_id=$1 group by mc.name order by mc.name asc;", b.Id) + if err != nil { + return c.Error500(err) + } + + c.ShowMessage = false + return c.SendJSON(rows) +} + func handleList(handle *Handle) { handle.Get("/models", func(c *Context) *Error { if !c.CheckAuthLevel(1) { @@ -20,7 +45,10 @@ func handleList(handle *Handle) { if err != nil { return c.Error500(nil) } - + + c.ShowMessage = true return c.SendJSON(got) }) + + handle.PostAuth("/models/class/stats", 1, handleStats) } diff --git a/logic/utils/handler.go b/logic/utils/handler.go index 94c9edc..5e49567 100644 --- a/logic/utils/handler.go +++ b/logic/utils/handler.go @@ -15,6 +15,7 @@ import ( dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils" "github.com/charmbracelet/log" + "github.com/go-playground/validator/v10" "github.com/goccy/go-json" ) @@ -43,6 +44,7 @@ type Handle struct { posts []HandleFunc deletes []HandleFunc Config Config + validate *validator.Validate } func decodeBody(r *http.Request) (string, *Error) { @@ -79,6 +81,17 @@ func (x *Handle) Post(path string, fn func(c *Context) *Error) { x.posts = append(x.posts, HandleFunc{path, fn}) } + +func (x *Handle) PostAuth(path string, authLevel int, fn func(c *Context) *Error) { + inner_fn := func(c *Context) *Error { + if !c.CheckAuthLevel(authLevel) { + return nil + } + return fn(c) + } + x.posts = append(x.posts, HandleFunc{path, inner_fn}) +} + func (x *Handle) Delete(path string, fn func(c *Context) *Error) { x.deletes = append(x.deletes, HandleFunc{path, fn}) } @@ -195,6 +208,12 @@ func (c Context) ToJSON(dat any) *Error { return c.Error500(err) } + err = c.Handle.validate.Struct(dat) + if err != nil { + c.Logger.Error("Failed invalid json passed", "dat", dat, "err", err) + return c.JsonBadRequest("Bad Request! Invalid body passed!") + } + return nil } @@ -476,7 +495,8 @@ func NewHandler(db *sql.DB, config Config) *Handle { var gets []HandleFunc var posts []HandleFunc var deletes []HandleFunc - x := &Handle{db, gets, posts, deletes, config} + validate := validator.New() + x := &Handle{db, gets, posts, deletes, config, validate} http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { diff --git a/webpage/bun.lockb b/webpage/bun.lockb index 3db0581a0ef8efe79df21f1efd6cdc30693252f2..b93f148886ba3bde78ece8fd5fd1da072f7ded9e 100755 GIT binary patch delta 21995 zcmeHPcU%=$_MbcQl&7K~ARt9R1gQd21oSC(9eY$Pu{_}+C`H}_6)f*rFlu5UR~<|2 z8a4LbqN^tPnV4wQ7_+HLc2i6=i7~E`SbpC#MciyQzr^^<{@fx?~HJn z)iZ1UR8ez5OoXY~p~wY8&VP~UalXr$ul=vrjC&Awy7BtprGuB8JF>2%@0eLkpsNO} z7#Lt5FQ`lq##=^?HIK3g|3F?nL2zW|n61SzqiqXNzy*9=Qllz{Zr3fvV{eg%>m zd=1hU@*pG)WNe|WIMZemii|2B1z8*V0&_uOZk8ZeY$Ho@^Rk4Lx`NOE`cO!B$Oe!e zkb3eiKNlUti;QFep`CYo@Zgv3}KnULgw-H@)3FKT*=&1%L3 zz~KC7OTjo{3G_5`S(c&_bDki~14qk_DOx>qW}&qJ<$HqDI96j&G(!$=)viXWv^yf9 zr2=PBzzy;Xa2iNeBeg+Wp*7QDEfs9$f-H+w5YAw9G+=9PzSW#92&3`}N1`8LKBkWb zTv(8)gwA-3cWkL3%!D43<|s$p)Q7wZNdx^5vYucU9JvKK7He*?Ew>;$FLzW<@kCUh z4)VujaCFE>Q^BtKi^^&e+Odl!uR_uknF~tu&Bd8HHZxWRE`4zvX~z3Hh1x%gQpa&Ylf$A_{<_9pV$|{F zS;kR>2sOaTfmgJ0UqQmB2rbLl=vAPPsQClX9y!>WpblIQ#W9xBap)gANupXXJI`EPY{?QjqXJoAv6U3(VgMFf zQ3=I#k0iALi*2mcf`KNW9HPQ;3VIq~Gw`~QyOUM!0gmURA zM{NKnPfSSDVm6I;s~KP)&`RyU6(p9Y+HJGskE1oe8yxcLVWuma;mFH5qBnY9jS8hiP&p0I zYMCJ12B-42T{EWowT;+H*%a^u@b2as=;pYLmq9!SR=3UFo{!)Jk`@= zJdR1g>Tod5nk9?1YxBdNCb4U6Ugc>rNOc9FBfsaFW>|n!XC?I>QeBl)AiS#PT98t8 z+mTYs-Fldtiak!vosLuwrS7?hIv?yay%b#zQflr#q|{dKBBj>th;^v;HU}wH_YP8O zjX;DzXWrVk4U?3%c7v;BenLtu(*^OW>MD>@+A@BM6j^WJ%q>|AaObIhCUJy2FN0{{ z!4E^c@4>4eygYfTzeybE$;FaKm?rN#srv7s$CflmCMUz#}6ho=UajEAw2s6(OYbn@kggG{32%c~&f`SR3Y zllWI(UIyXi#}7kDe!ME!WLV_~gZR;)H1-8AZPLczg+-mfTYIJ%C8Q{>mF~rb{ya6r zWV{I_`3=Dylx%2@y|e=r7Ht8%D#Rpi3E-(sO~$`Lj~!Kut;ojwa8r{wyfLqWkQ?*V zP?Pv>V_pW)FpwXH7#PT_Am#<~)G(8HERdIlnT*#0b(7K?CyO0}comf8L4tt2l$!3@ z(iub@;)zhia4erYK8E!2$L}_M70Z1gBd9WqltFn4C6Z#!c!wnhFeg!;rE)RiJhDB z!;vOqS<{E%&@@@x*_5Y7nG7rxrt!&PX~y12(agi1CdtOdVAxSHT8v$MH!mcVX3KbE%QnIl_Gj*SWl|jialfdGr2kctiP#U{dd!rMS?Pvg^ zM>wyFF&TS?3qn7XLLEfl2jRS|IaWdhKMc_)l2(@*rpZPNSS)%F_|%AG<03FMkft_CHk>EMTL+|xF3ovryvdl+T=g2_ z%r)6C7pxPX+$7ET4pJ26PCVT&+2D@$tQ5RPj+8nVCz4t^gU~(=-(X%DjPIaWhG{Z( z1f$SI!|5%ZK`0yy6dZ;FV5!uLcq5KiC78tcc%GVQGUmstRcb0hvjvQLLJTxZHr%E> zTHeOQ7J4a~9vc`9K`CY20!DReD3f(fD~Tas>LM@z+EG+LP68vh)KDh!AQ+8L30Lu2 z0A+E=P(YNtty+rJ}zFk9sBX)D)AL0f7yNC-K8z=aP6; zipdy&_ce-gOdEC$YcelOH5rd+O8B^Ovf&n3Z^gonFoU$;vU1o#JewAzXvGIG$1U^H6P1G@v(4ouxv(=a5pqx49)5{z2GGD8o? zz-TsWk%J5(Ui(@qgG)tFM?*ut#e*E-xDt%I zRJVysV7tqaw(wL4 zXrnBir?9Lka4{lRSP#|{409EkEdJ1jr?xX0TO+?Olo(NyWbvi8{BS#y7?;kgkT)e= z9c?Y19-VAF0!GtTQ;ES_U`fg&CbAu`YHuCBSpp^) z3N5b`Y^cr*pMtfe6>khib?VZn#LUxRYFyCTT?>XlcGOY&`V@>JL5&g*tQs{+XdUzi zOI0FaK2j73iV4OuVEw_=@de|pwVG`OdpJJPwHr_EW-?0HM`-{E;SR~h3b0;!b@56! zUWFolc!eDRrLruIC&5O8sgWGjUG+G8)jU}&>&~krlks&Z)ey#Oq_GN&3`VG8{YyO_ zMqY5Tc(ezv>TWVJxSKW%JnRwk$zX^`+OrUVHn3QJFC@))1gVD}Fx~{CxmLy@hV|lQ zJx#_e3|@UOAZ`rX!FnoAx{DNbfQKXkBObv{RRtxwvca%oR3@(J&8vE)+THQ=Ac%k` zKu?o~09_>O06|1>k*p738&<9-Anm`op&BTO4OY2+N#ae5Jfd8`U8?;*M#bOT(Z4Al zqUwKf0Crry^8mU?GJdA7mz}zOo>aJgNmBko zC0CWy+9Hkr7D?q7YvoB&drLG98Ltd)siq)l09FE2a5X^JYJeJALz2%OqI!Rwrd_Ye z4Ulw^q}m$+s1VXl%p6uU|17DG zmo+`fdcbKQ6!;FHizKyqg9t8?G(oqC;37%w+ySUu6+qV`vcb<47}m%ARM-5ZFGw2X;M^_D68=rkmQb<453JQwX_UpBDj7{QW2w8jwCfy zSK}lp>4F;#tiHxwNr{UjCEYaHfbqjaYr9bso|>v5<@4+Py@DVGFEDDzN6T$QxwuF& zes8Fkoz_pFmK&ty{uW7nhG_MgYV{tKw0*>A`bQ;=CKh^{{v?LqPEeU-+(@?6WC{^n zBt?E^fR~*Vt#G5^w$*ZfNmBFekWWS0<3`<+HN~Tn8XgY43uGQ7HC(KfdsG^rFNL1UPlP1@%zz|==(j}pC(PF593r@?C3Ze~ z^esgOHLzSO@TjB-S*hv&h@|%Ea@RoIlXhNe`%io}qqpj3z5Ul7_GI}lJ(H;c(m&p_ znQA|LrrUq<5&1}g|L1&|@aS8`f1k~8J;md1c_z~=Q8_CA-)HlGpUv7H`Ag4e%KGoK z`M=L*gu!orHsilDf6B9&`-~{IS8Ut!%#k|{qZ+fxIdOLeohx;D-ucvXgB?ju);eFG zF^|7-z<>Rb9X{)>&zZV=rPJh%QoqW2o1KLwH+}sM_UhPZH7sMo{rdM2>H32nD#7W? z5kJk^YFK|iD?jkZxWD)5)bERScZa9D#b4esV}HK+tw|rHZf$s~laoE*>+r+VExU&t zTk-m{fdQB2rPZpC9(ct)jh{R`Y?z+M`sFm`YrhifWZ%KduNQh2PdL`ZY4n`dpVjOZ zB^Jb&)akHvf68~)P7PZhbnNNT4Z5{>Co(E=(Cr%s!aL>&Kis_Ea&)lzzY}%9cva%l zNAza~{{DyzR+Gn>GgvJ?8+T`3iMx@fjLcxQc?Iru_*LBN^7PCM=E9faUXS0vy*}@n zmBC#38rX3 zyX9msU!IG*A3uS+KljPaU;(@c_s0AU+yi;g=nNLbOGjt$zm9R?ACHz;6COS$gN5*^ zxHskR;~vW6@-kQ$pN)GnUWt1+Psz_<5xfHTNPZRfD4t%B!J_$6+++9++?(^Rg&8cC zufaWz-zm)C1B+bv(6JJ0!8ebE9ah*;B(X$3s0em|9R*9~Mr#JovAOVktHe_HAu#V^ z7w&J9SQ^i@W$@R*&VaS%KE)Y)LWv8XTr9CR{0*>X<6L-jiNw-*X$dR>`xvY}41VnzH8*uZCC-%N?w_~x0gZx-x(Mq(v=&@-?P z>?qiHZkz@CX2ZT&5-a70z`WPP|Xb$}SoC|*!Y$^|W7XAh+ ze^z4C`FXHeb6t4ia}q1(Q=fys=eh9Dz@Fi8bK!5WMRO%Kn^%G@neW0o&Xd@)ykZ{w zy}*Uv0-MXz=fmG%8|F)FKEDCBsltW#Um&pxzGeaZ{k#h|R7i~TJ{9mccj5cM7IF4G z{0(M#UScxe1(vhWg?n;|E#+noe=l<3uYoP+ZVTaWu#$xmTft9&O<3&0n=X>rDqge* z_Q|jhYz+@u4Ew;!7fWm%KMyu*3G9<4wt-KTVc$~N2ey&NErESti>m>FDuLN7N z0rstz*qgj!J?wiC_JN(@=^J1l*oF-fdz;??+q4n(y(qEse9eonZxig>D6w~VpN+6@ zGwcI_hJM66^yjc}Zd)^Alhbw!*$G z61&Wcw!pq^un(+~2W^FYVC7pS_BVbWY}R(zw@qT7@u}Nj-wxOZ_63jI4*S3sZI{?p zUJ15jC+yoHv9Ec>4%oK~_JLjJ={sQ`*oK`F`;o&=C$W3{1lWWF zuy4P_9`K_586tlEb9(;(kue^8V1OtxUUneEaOL_Z2;?HyhP+Et4}zV35JGPX`jOQ4 z@a9m5evJPMIx43hRV@a=Ymrj!59sVqy20Ps9)9rgge>`;9c%rf|6g<=xmrJjdlx?r zz)xKrq|*=Nnrc^1(;v~<|48L0P}eRzDN7!2FgWuDuRd|N{^r5hwFB0=7o2D`xJc?c zMe#Eo#ljV!F5;p$ew62`#nJE8+5br8Cs5b!fDw;v33)+3iM<3rL&HK*+WYM~yLycK z@hU%FT^h80>bq^MZ}mEUsQeY3$SaP1$QK;-;CaXD+JhRary9|spr7ad6Lsq3{RcW$ zCOLnMT|L46Ct9p^to3XE&${1%hsS?p-gPf@Q~rS9Nw{C+eNKe&-X{|5r^Bl^Gn^VP zF3z=Bb)EVE%*!n(#z~dP>T|jl+Y{5NA^ij!RDAW@+p|BOCFB|X?*+>7yA*m|fa>?_ z=oc}MUHJ*rRYLlSSn_y-siz#N*Ig*?33~M@#z(y7ZEuFNW_agv#6!Aj-u)AG_WxAJ zTEE(R(w}JYKO1cS&${1X|Go3BJms~&?@H=G3i4N6+c_KRdlWy>Lwx{e%PsEbn*)beSUbX-Kxb-&=2KLkL zr;XVtt_5KP zlU?JP2VZkHQi-?w57Z0eSI#!kuP-~&4VQk#i9CH)(WvL0D4Tvkb5hgYha{a7a6r>N z&~ye!oGMi>6rytU^Pt{Lh*XGvK&lp$Q`KS*4@oVcOk6J*_^^AS+;Oi%e>YG%$3@3e z=xocgz;nP{U_P(_r~sY^IIs{{1kibzNdTP!asX2SI!jXq4Cg`j-P0{dWdoyt93U6y z0FXCMqsO;^GXQzwZQvYm9=HI!16<^#_Z!vika!vx3JeFj0W<+v zA#_xlPLC@`aJ(UX08fC9HqbdB`hzh#AG8dh^I-)5ovZ2zL<4lFYXi!@2>2r%05k^Z zh?qCffZ1{A2MG^=4xrToY6EoY?H(#or2GV|2k2nlZQw^>KJYzo6Zi-49S{r9ajn)s z3XlpU0Cf0pE3h5d3G4z!0Tv)P1h=KY3&0A14r|Cj0Z<4O0oFx{OmgdpR41S_&;{ta zsDd@I<3l@bTV=bumpgw1P3q`m;&Ie*cj!gr7tSeaX_36R2Iw{i0Qx#U?wmNm<`MUo&)9r zv{KeWasX>fXad9|y$x6n^aC%4e4R#yt4m!hso)%%m|LI}dfxyPmLCDrfDeJGfCHeh zP685u763IJ2RH#DK#giU$phLN^vJ4-v;mNr$h>4%kr~`PG?hDK1Heu87g?ky#iJL{ z5TL0i&&S9^MfQ}v9g^dKEMNpc#fAYieKZIfR639hQ0r}hHh>9e1*8G=%jFcHC6ELp z0(yB}wuX+-7U%#_zwN0DDnRi-h0H8n62j~U#1bPE~fquYK0O?6b?F|P;0%jl+C;fF8g)U=z?5*aDCxl->rA zoXB+UjvU0ST}?QPi@iQuK-kszEz@BBIOUjO&|&Q9=HK~2P6Pa z0G0Xn5l%Ytmhu9`kOof!?gM%|KOsE~xCh(;{sBDFMws$sy$$7EfC^Uu)W9uZf_7f~ zV3Hi*%pwkScV>~ynGK7JNsfz&lkG+}-JYp^QbzxKbWub_<)W_YVUg>9u`aS5yRu;kF|jeR>N}eYO6Y&@j@DvhTEwu} z#kJWCv4JShtj!#Lap=7cN>wx;JU4V->l9ZO0F65|2Squ!4vY7D4cr&}h)2T)L+3A@ z=EM?W;^L`?E22EM4)adZzg5#`**D*`U48z3bqW1zHZ_YcWaXW`DOTsC)R2!+UF8Ef zXuaik6MNNjedc_13H=i}&(6R1!ht4kUsdNUts&Q~%e?*ckLo0cZEG3-)45L7CElwc zw?&C0{WCoNWxd#Eliqbym(V}dQ~%TRaZz{Qyjz_U?j+Bv3%m6%>eyy?xxaTq$RzkL zPW5b&QT~YfpITda);d1<#2-y@VzDuCDR7R!Ys;=K=pP@qQU1r(*K{t@&);oh zbqW3RH{MlMaeYrGS*ml+*OrH&uJ7llOY;!hZrShyHS5E7dh2)O3K!-h)~_RPMM1wN zn7;-nn0u(zri7aLpH~-5tRr8cVd&rJS-W}4=x>~xUZ^gif5m4@r`(~|HeoMR=UD5= zG4)Va|L#vjgJvz*vwvGtT|)mdP~M2O4=z<4O0Uk@Q%9bJx_+m$k$tha`1>_&{&A$b z#QSyRozyygeh4et!PBla89jH|tm+c<3o^3m#Ag+o+`T&AuFh#tSH4emLs5bpt6{HvX8xbeL>Mj2!i;S_!w-DnI3X)SJcEZ8SRRiQ#|CA;C}k|hPrGh z*#6hXHEW?pB9^&U(lNs4Hr{`{|#9G7S@7zWA;4WsyZ_o5>^NyX3~Q#YWz;sR4Rj z4a za9M&fv?x@69RKz9_3sh!!G89qe(ZVyb+L`aVDtXKN3Q2iv*95(b7%4F3wek;^Ir%zL1@k-Ag;>z+6XIW(O+ zAZfSTdne$71&);mv0Em`#Kp$M3;I``^6DNb+0o_PY0WSgKh0O(?ty7j9oo<-K{)9v zH}r(&jIW&KiK(CHCodsB(@$<2$~@(tJej{Z-%oDT5Siuv^4O+W&gy5K1V=`ni#>*Z z|3+nX2Uq2(4cSz`s37&HEn9*HuB|G65K>(rB}fkN!qlBY2_r`Tb=^RpI@6lQql6YP z9fH-T!LwNlcfI_<8wdl%wefg>2g`Y=D`o}DGrcgJ!eIF@$$i1{H`J#7EvMEukMBC| znu!0pz$40p>R)$q8W`X2OwE^GLr$Wa^J){hl{ebh5h9QEM%xKZG zFX5Ja!y7wRdZ=92hjsVM!f8pQw zs{e><52_ZJ|DhPBtiP*r{YK2kPyZCuq?$vQw|;Iz0Sah7frT9!v52H?&D5vr5lHf0 zi=JO@8yFg8LP_mOsedL){}znMUTr3?Z^YtjuMHOjRKs_G{ zz8LHB2zex=-_HY78A@CXUPg9A$UA*0z9Zz*DCwbpPH64TwJ%0}b+1Zf??=e4e#|?h zMkM}|i4OJOJ00rhn7G4r(~qt!6gdIaL-CslPV2aG)`!KrdY*Px%t@k^xGGY1psghR z<3`cP0}MuUg?h-`X`~5FAX`?gKzu+HIh*SbD9t(U+|}8 z8YQm|VV(vo!~s!qzW}%nQ8|lbc9i@w$-*f4JCbEla!6yySyA#plG~!>=NiL->I(A~ z4@Jqx!INH(Qh!nzIIi1Ht4FuviXka6=t2L+mw#|&ljFwVL&$;YSS0 z!R&<(JKbJ27A3U!z7j2ef)euJK-S1l|LE7jwyVxc!ppZ%j68uZYQ)G30x|FUXTv5O zx|M85?Yadew0%u$$gf6>I?R@fqZbCf(f~6tB{l}ZhgT@AtB4@hNDPmcI|ZS^N;r*{ z$ACU>_B6ZhJ*ABu#jnN73xlwLD$zZaXui>O{PXgWlTiY;QG?b5`EC$%h<=Iklwf$Q zN0PiG7#=*8B)>%*lhHjKw%reA4MGMctDno#KjURS8T4A`B|cn26AVEAURUj_8UAfnKZgj`m)(X`IUoczcBaZLL(uVrG&v94?-X9N@kZk~Kk%h< zU4K~h8GL|%qt&6tg}0yn@va$dF8X#m_j+fvNq&S;16s)!LlA4@O>%HkI7t84Rb%nf zVST>&hNg}dJ$4#(w_T<^*W>ko@zX!-)oa`N@z>w;NmOc}e+~{I=@D<5%PO zWGF@6V{A5Sj5zgY}hT)iVJyV;Phg>a`O$L04!s4Y)@+YV)Cq$qUtn3{DpZ@!2%6pyV z84;+azUO$#XChc1v3nOeF!GTBseNF8k#Yg)wE_F--}!Ev)hMvnvprLeH8 z{4$l)OwiVCa|32kTaxmCC=5^^?VKo4%J2_FCmAjUB~~*o6(J~a#Rd_W0m9)kbeLE zZEK<=Z>N&wlKg25W_*hz$Hp;#IkGwH`jC8PWbUD^Y(#f;E&c1u#{uNHA@Zm9kl$*K zHMF3IoDhpv@wSr_i?~_WL*7q(Zx8vaSk^&nI8eOZL+%^L;v4B-K$}!?VsNeFfh$C| zuBZA)lO3_Fk$f-?W~0%IC|ui_ezJ_KOEUdNqHRru2y*Xu7Tf4j1NF6XM$5#v*o1ob zCun6eXB}7;&-_?Db?ni~|29Y4qfH>}cixrZL5%%CPtn+9m^G;1d)dDmEk*o+zkTP@=%^d!`Vc&!10__x#8C@SStNx1MwExp#Iqm-(s6 zb?pq-g|Sf|WjohTzjFQy^V12dV+M?RWK8O)x0Y@>z9p|-c;Tn+@0?jUg$Z=s5~}(I zc}@LUA-IjP4jq|0%qmo^^>uWvBM40e!5IS?2-!g6*F-_^1AhxL1o8l65abfbFvu~G z!I1qlX@;co0FB?m(A3{2kO7cyLwZ1tEVY;C+3mtYH-$T#GmvmcK}l{&sV!d+toET5 zwxWEYUp+zaLVia`Z^(E^AIM-Wzn;c_L}Mzy2ubzdfTViQK{kfmtnn(yCg9_>{6a`? zAzN_v(JHiqqz+;=?h8pB7$9kYuZ@Dx2=aYM8sKk`RQ`;{*BZGfhC8mHE>v??L)M3! z0!a=y1KAMrB`x1-FUuW?$*|gsTUkp+3;U7(0Lt^NqbhQX1YsLEMs}{!+U4ezmX)A> zF*vDl6uQy`t$|TwSDGsQon3v^3Rh6U69ulQMT+?G<6;xXWq(z@GFx$3Zh;^SD=Hm| zUWM(@k<=?K$+Hq41MNnR6NHV(hk?!wn9K%{ZU`1qs|K9ZwUrcF%WUO#TS-BYZCGLX za?~gDi$~R*H;h0dXzq z&E86J!s%w3BFHCC6y(~=M?sM?YlYobFiz;*T#W;t7OFx+AjuQl!W_nkHVjpp_ri zXqHiK=0G%#E;)8;$V=;W`SaVnI=EdAT)TXpRPCfz@77Zi50=`=||M9zroChy9PND)7A0)AQA4)*DxNf(Or)UaLJ% z5auGE9M~JZl2qaY_RVRhI&iEufK{tE9c8rF6+x0BYdWaAe;y>(uX8*kmWOk9M^Xrj zOt(zmHYlWqjuQlXQLddFjs>Euy2uCB0rzxL9oZfog`lHs)FC;lvpT>9NLu};A+hkC zeIV(v1qUekSi8#OW@uMI2uEZ&N88G+1r>tO4;k*@BZ{mQ@Q~00+z1Y9l!jFpi8v`( zW*sY>1*iIUTNz?h5Xwu-bIFy(WyQ8)gvk7EYB_1>iF(D5VTA=O_=`MKzM!A6;44>w-Z$Re(COm!S4XG|lDzm;`It?i`?-WvMJs&tnZQCCywRAmF-ITUp-OfwJR@Fnv zn~9WKdKM|QmjG-RYTJCI)c&^I&ie)_wM8bDliFeiQj*f&8Kl&<{+P*bd}2T*Q~6GC zr7t5xu(iSL894JYi5Xtp+|(jEym%!c_Kue zb@=39lhLb@AhcCjR%4U+P$O;*wusXj@ycL}@es;7pv;A8iZ>ha8k8k8=H?KKSkjnR zLL6(%4~1Ba-y)2#Oq?RmLd~8{xH;5f+=u0jkj6kP$Yi9&NK7Dg4THft@f%Ik#f`q) z9A+`TgSD&bL1Q)Z=ZC^9;&6Xn1F_AYo10n0AN+YGL`ncZ1Tj2-*EF*jj$pIu%nyg9 zGgm&Yd8Q!+5C0^d(Ky{W94YL5>hR+3KyGefF?wNTlG6l!Jj`V14%UGxi;p+uH7zXS zD^0mM++q;16Izs@>Keokgm#p5ka#z;J^RW{kvWLyeHn!51gjZDT5 zv@$ptHJjiALy}?|Ce7FvjB3^;n~ZC~XwnP{y9`Es)>Wo3ycw^IwipLsOTzX|k%Ng@ z3YG%q!dZw(JlBkyV=M-r=7NyPZ-l3d1jWkGN=~gEZkXh)W$eE|f5Yht1 z@`(}Y#!{px4p6JP$+#B`yC7=81o68T+#G9Roq1YprqLO$YN-a{iyA{kSm)PLjTDhj zjxZVX!0^b!G-idFJXV0=(e7jdEt=ykqxshj-NdUcxjD{ajE)e5K5A9O(EbR1D9$2& z6~SvDVxzb@-eP9XQOgw1iIZcvIl*GAK_;yhI39MlYb6MAU;>{U zZ8DaDslhb4xyi7d7|*~?^m!{@nP@R4#i}kttiegc!8-AY&C`uLk!p)hTzFPfli_Qy zG9?;Dw$>+tyznF#*$tmyR?mZV0K-~BJcU6Ann0yz;}9^41_Q;0VG)>_#t~1)adV1A zY!J^YQ!K`=a2vI8hR-9scu?D zr~!rRCaRuNo!K9Ze1i#wqZfnS4p{MIA~&a5j4sF_jnEg?Vmq)Dp3yShI2I`iB4x%6 zN0j^TD_)X^e;k zdkd@`m>S!@Y5G7}G4LiB^@7EQ5mtcFtYUS-Ngrx8)mdl)&1rO1`JrIcry330!N|Fc zB1`<-%*|~r#s~3<4!%MIip}w0(1X;)-0TJ;+ts+OgSL+-Wtca^V6YyvdJWr=>Pw+0 z*2&;CSr&0*1~<2}7@xvtz+P%!ct-q$Osu3hC`VBYo0C)BK)Qoq%3@6784Is$Z!!8~ z?LUM}DAe2}&dlUB?JeR1ZMnIF#W)a6N!hwQD;8T27)(NJlK)_Vs6xqNb{QzXGF?)utKnk*WtdW>Gs{*~wyj37OQpvb%`4+VL7>_H3`#R6Vj8 ztPiRfl}P>>>_IRnhp9e-8R~tTd1BkcKdtOxBnRota1^X9?EuDGNYRi+C3Mmd8)|sa zQg$GTXm!?8#ySc{0ijxR6^v$0SrWzsC_%lb0Z{}-0ikY^Pl5FXQ+2%cFKjk8tlK(@ zS32`b$zqJ?qAGy~9Zkj(upWAI@o*Pz&bAn@BlBToVisZd8a%CsE923m7$w!~SnF{n zF-PL&ZWiN4WNOijC)V3ws=5?%5!tr`u$f6*p3TkOEynZ6q^ZS+0EE9!H+}!4IP3`) z$8WSqH!ej=^+Hx;sv8JRH{vWTm38NbdRUBEm}B}Zf#62;%mJfGSKagul3MgYrv@;R zS_?|e_Fz~EDibI5;O1Utho_)?T&6Ee^f_I*NHzeXRr=qQ_(t%Hf$D(AD!IQd@v(}g zSGoSURL8%M9p0s4hzRa76iB1!dD zYH}5=XUbR&&~>Mz{51f{wE$gf0g<12q)9L8b|XNgPXct^Dc!)g15~~Ppo=8&okVbv zq<)?O>hO%dsx$V3JpjA}gacmybdjXimx@3H zQqqm#69oCgUCXGaRUk>p`kM66@<~!h4Yhnv%I8-eZW0P9c!5!GK3ZubD#b+->u_)r z=EA$>s52O-mHnEe5rVZoLbQ5!N+Zf6wfs9JjUA1An(=t8eS#(vHJQYOC=}xnfDCGo z0!i1MlCn&=x$ve#n(&w*ZVsxMfgCEa;6_7qfTa9Rl&MJy>F(f^)kBj#HQ5W2+T~E8 zCMnrZ<9ABxcL4Hf`y0ma8x?h2s#UyG8jxR(d}=Tnl6*tk9qmw4A@NU`rpf6Phbv!}Th1Hh zNlfIqc{!{OKZv^v_sq{>25!S0KfmE#m-|_Bm>VC3yOE#7-JORPK~XXe=xmC$aYYAeh@Y58kv=Vja1yGKU`kI|cS2_Zy$X3oAYN#PJgTtaB2~cf1F0 zH9=w$A2%U~p8)#+tQ(JdNIbk2z4Y2;a%VgN+gng4GHjrNj>pU6uO_A6jzGVvR1M`?F zvBA9mRMR?H)3!M>TWZ_89D& z4f`IGSOvcXmiid%n2XzB*(bFQo!wBY6RM`LqNojMTy)|s3( zhx|hJ32`e**HwJd;ONb_KK}8|k2ZvTP_}XS!JFH*_c$LFlze_#r{LuY^N%Flc!tlL z74_27HKCUQ0(egd=3mzaa^0^2;#gEv|rvB^Al0sJ)IgTDkem3uCP zpB8xViiHxJ&X0o~0Bcbtv6*~S75uc&gTDzjn}5ofn4nG5S0xX$JY#yJ? z;itzv_@`hCc>E&xiF@$HizHUXtHI8JbzCel&Z`!~Pm4VG_h5^8))M$>u?OF{L}D_( z3icUTUs+6$*>P>6=%y}ADDHS#Mba#V4Ii1zU2~I$8(p% zzGbiv>ux}&mds1RA@#A0zz*=mQ*vov>CfN5R>;pT=LpQ^|O|WmX#Qx6D zfSmwK-XgJ6eDW69w;A?hs0fpy#}v48NYt+4MY*a!9|&w3j6 zZH0YLOYCia73?#xzS|`B4qvwo_B{>zwoB|i-fKJT+Xnl;tpzkl2TO7ue?Q zuy3cts(J2C*tY}rfql$9cfr1$uy2>dKIO;34uG|IMq>Zuqn?3%yI>#KB_6sP_B{jp zc1!FFeg^CWSn?i;UE!1Wz`otE59})*zZdrHfqi==c9mCyodfH*Ph#Kls(r9;FYE*R zo@eccefwbFeu@3SuY!FB*7tzKuJd&VVBdb&cTi$K@m>dE-vQWnP!ewlTs-tJ{}I%B zDBH32$eX#^&x6W0W1O#`9e{qWsYk!|;-WJbB=tP1S5{h7gd;=vEm}QUL*@69=fKx$ z=e;(*4{aUyr1gCmSQ{7L*MG}}{v>2O&?NrhCf6_=@}QsquYi z>-e(<*2Z-Rf7pe8$O|}Ah3Wja`E;R7TqN&Q@RxZM3m1U;P@>dJJzYk1es>=IUUbim z??YS1J!$=mfyoQ{Iokb=)q`Ki`aO9L=kKg|-|Lam_EJ+>9-V|7>|eW%?WO-Y4}Zrz z_A(!GJju~ENIkelX6T1=|A%?1|Nex5mH8+h;ZTnw{|_zJ2G+)P{JHLT()#@~=3Vzf zSLHPL{cu12M*U(-?tIaQZ+@}9V^c)!W`@JT<>fYOnVzRU0E=uT<$@53zfs_)e?=Xu z{l4X?VSOJvyh{Zg_ht!sMn4ulw6*$i0aL&>X-|BhRKw=%Gwc0Wy<|ELVbf@ikcCIC`@Qr~kP`Tw39--ovC z$ofBK$?o%~-;Aqwf=;sF>T^R_JcU^tH*cvId zx_$yEkG?eGOrvt?2WAXl5(=qYKQu!}EG8(m@Lyf%1Pv8BwWBo?m^_@ZaCs49Z5{L% z7di_u1DFZS0v-eA0CRzPzj~GJ&?hOLQdhWoUK+AoE`V{tmnfoB~b*uK{O(H-LWtXMs26i6RSje2C-)pc?oH z_!#&E_!PJZdXC~ypT9w1Ne2KE4SE}$3i5YQXw13V1m0R4ag zKu21!G!>nJE`S7h0^WcRun?d_BufE0T(TITqeM1<{$|((hydvD&sx-72lyiG2lxYY zkjNXL<3PZEX zfMlRG@Fem#0b78rz|+7`U=Xz%F0~umdOnI8X_U2PpjMj~;Z=Xd`4}NagGpB$iWe<#6956u^zbWMC>V4VVth z1ZDxW##RCI0a{~0z-FYE06ma)LT;v$X}EmcXjz5zA!T)cQ{;ULoC9dAw}FWODMw0@ zg5)Z48M%&JNWI|+OaEz3*#ksKIo_28xZd(aQj`a|vgk&xNE!eapbtWxXAK zNcsXzfK0#++8~tJObKh z2w6zbz-|$XyMTU z>-U8MG zZvy0%H-Iz1Y2fd`Nr2i=Orf1a*+pLhc@a1c90Op8vc(;O+yiU}sPnDBQ^026Nnj)J z1V96>16BZwfknXMKovl{*#dwJn-9zd<^a=x$7l(W$7cgm04IPfWmV09oCVN|nhsEd zsQ~2@Cpiz`0Cg?{OMs=oGGIBd5?BkY237%Gfi=JefC6?C4Mvs_*#b}jfh^hv>;`rL zv`_2=o&oj)dx3qx0pK7&)*k^51J44_0WSdLwdVl}Bl0G3@)~)EJmVOI8@YtM@Hc>z zp}D!!#a`gA05orueib+cybinuP~KVK9{|NYahhxDhwusTF7O`k4nRs!KJpyOfK;d# ze26r;^8%d+Lm|C#9&3CqQi|?`z;IAQAWyxB^@T$ZHIsI$zwuDesm8?z(}* z&%jSWDo_LH9sPjxK;Sy?J@5^1XD8o*e+&EwP~B?)wZ95WKBj;@=i%x$l)>h~z$|D;x-xU1?AgN!E@9{vxnP+Ni=&uNw zv*50O*ow9!s24%)8HCGf0xIJx;^DipA|n}TkTmz>t*MEB=#>;} zZH=#uaziiluD>rNuHEv%`&}Erb$ajm8$~>tbhxy7`{|t~ zp60Q&CH3U--k37|jV;GNt=jC}t!6)$w8Y*p#ak|EjFwnPfsN2|mABlp5nQallcn>< zlI`b$UF)Jna%>`n)_!k!S|gSnsK4FiKKuyb|;&W%ea{EwZQQB^BhkmmfvjQ=# ziTpLSgiq!+!5I2GVTN?cs~0xA@4Jc?SOkh?7OJ7YB&Pc2sqGO-9!pV!7Rhbf8@t2_ zdT;t0TKIrKNADlIyn(vdL}F901NzFx(3@E8Ex+4@B`{an!CYqD6o_IEgiKUx6e|(rG_A z#t#bn`KznCsQ$ARJ37Dqil#73TIVkh@nhNKHZo1D_Lol(zw9qJ_Q&Kc50Jb1gKrFw zmlEF|Aiqj-L*SACRIdq?16wm+<|Zcwu*rc^i0bsMq5am^OuyBE-WvB+ z)$dNL`*ET&YCKJ4@8)uwrYt+<-}R#TOGYs1@3NU$H*$5iC!X1awrClfhR?4phlnZ`6ovAKEm=(Du>RB0Amj#ggh_=6Oqeeb|La`F$wk zUI(i(s8+d@Ja7cBvwGHh<-5AS;-(mwLi3@&*2OyM(wMUT)#Pn>OwlY#&JIPCFF{*+ zVrO(8Ij-sS=i8tL7A2-6aaWY=q#AZD#*W6UcsISTZ&%ca$EJnPD6d7ydqOc|7o%h` z4DxD}+%pU<^q1qTSl0YVH@@jRv@oFsqS7;3o)(7X*F0LjjGAI%wCvgpp2VD+Nw$xc zOGsu%%j-$zM9XiHv_{MB%^@F)mMzUOg8s6XVDY0zdwu!kNQ^)p#|TTJ<*(z=^Tud- zF4fdub<{Mhy4f*f*g@2!8GvsNM$5;jhW-+y0c_=aoqkwDeQEK2I7VLBg89nzTQL7X z{oO|g+O9b*3D13xYUBp&7#CvXb}g_h^tUICJ@~!JVD7RNHMCtz>nKovpHk|QR*OPk z@H#27NpZ1=MSNn{hI$`EiLUYTkLXZ55--Pu!-FG@r}%<3v1OTK+SOB{F9>xoZkDQ)e?!!@Z~WRc>Lz;>pZMek+B=$ZjSU=l{K@ t`blqEYVxaW>;pHCX%%Np+5eD<1+x7;(%5U^EM5*C!aD8WG=%ku_&<4(-wXf% diff --git a/webpage/package.json b/webpage/package.json index 39ec920..6fbfc05 100644 --- a/webpage/package.json +++ b/webpage/package.json @@ -3,7 +3,8 @@ "version": "0.0.1", "private": true, "scripts": { - "dev": "vite dev", + "dev:raw": "vite dev", + "dev": "vite dev --port 5001 --host", "build": "vite build", "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", @@ -30,5 +31,9 @@ "typescript": "^5.0.0", "vite": "^5.0.3" }, - "type": "module" + "type": "module", + "dependencies": { + "chart.js": "^4.4.2", + "svelte-chartjs": "^3.1.5" + } } diff --git a/webpage/src/lib/Tabs.svelte b/webpage/src/lib/Tabs.svelte index 2bffee5..825065a 100644 --- a/webpage/src/lib/Tabs.svelte +++ b/webpage/src/lib/Tabs.svelte @@ -1,5 +1,5 @@ -
+
@@ -58,5 +58,16 @@ display: block; box-shadow: 0 2px 2px 1px #66666655; } + + &.nobox { + box-shadow: none; + overflow: visible; + + + :global(.content.selected) { + box-shadow: none; + } + } + } diff --git a/webpage/src/routes/models/edit/+page.svelte b/webpage/src/routes/models/edit/+page.svelte index 86348e1..52b1ee4 100644 --- a/webpage/src/routes/models/edit/+page.svelte +++ b/webpage/src/routes/models/edit/+page.svelte @@ -6,8 +6,8 @@ width: number; height: number; status: number; - model_type: number; - format: string; + model_type: number; + format: string; }; export type Layer = { @@ -25,85 +25,88 @@ @@ -127,186 +130,208 @@ -->
- {#await model} - Loading - {:then m} - {#if m.status == 1} -
-

- {m.name} -

- -

Preparing the model

-
- {:else if m.status == -1} -
-

- {m.name} -

- -

Failed to prepare model

+ +
+ + {#if _model && [2, 3, 4, 5, 6, 7].includes(_model.status)} + + {/if} +
+ {#if _model} + + {/if} +
+ {#await model} + Loading + {:then m} + {#if m.status == 1} +
+

+ {m.name} +

+ +

Preparing the model

+
+ {:else if m.status == -1} +
+

+ {m.name} +

+ +

Failed to prepare model

- -
- - {:else if m.status == 2} - - - - - {:else if m.status == -2} - - - - {:else if m.status == 3} - -
- - Processing zip file... -
- {:else if m.status == -3 || m.status == -4} - -
- Failed Prepare for training.
-
- - - - - {:else if m.status == 4} - - -
- - Training the model...
- - {#await definitions} - Loading - {:then defs} - - - - - - - - - - - {#each defs as def} - - - - - - - {#if def.status == 3 && def.layers} + + + + {:else if m.status == 2} + + + + + {:else if m.status == -2} + + + + {:else if m.status == 3} + +
+ + Processing zip file... +
+ {:else if m.status == -3 || m.status == -4} + +
+ Failed Prepare for training.
+
+ + + + + {:else if m.status == 4} + + +
+ + Training the model...
+ + {#await definitions} + Loading + {:then defs} +
Done Progress Training Round Progress Accuracy Status
- {def.epoch} - - {def.epoch_progress}/20 - - {def.accuracy}% - - {#if def.status == 2} - - {:else if [3, 6, -3].includes(def.status)} - - {:else} - {def.status} - {/if} -
+ - + + + + - {/if} - {/each} - -
- - {#each def.layers as layer, i} - {@const sep_mod = - def.layers.length > 8 - ? Math.max(10, 100 - (def.layers.length - 8) * 10) - : 100} - {#if layer.layer_type == 1} - - {:else if layer.layer_type == 4} - - - {:else if layer.layer_type == 3} - - - {:else if layer.layer_type == 2} - - - {:else} -
- {layer.layer_type} - {layer.shape} -
- {/if} - {/each} - -
Done Progress Training Round Progress Accuracy Status
- {/await} - -
- {:else if [5, 6, -6, 7, -7].includes(m.status)} - - - {#if m.status == 6} -
- Model expading... Processing ZIP file -
- {/if} - {#if m.status == -6} - - {/if} - {#if m.status == -7} -
- - Failed to train the model! - Try to retrain -
- {/if} - {#if m.model_type == 2} - - {/if} - - {:else} -

Unknown Status of the model.

- {/if} - {/await} + + + {#each defs as def} + + + {def.epoch} + + + {def.epoch_progress}/20 + + + {def.accuracy}% + + + {#if def.status == 2} + + {:else if [3, 6, -3].includes(def.status)} + + {:else} + {def.status} + {/if} + + + {#if def.status == 3 && def.layers} + + + + {#each def.layers as layer, i} + {@const sep_mod = + def.layers.length > 8 + ? Math.max(10, 100 - (def.layers.length - 8) * 10) + : 100} + {#if layer.layer_type == 1} + + {:else if layer.layer_type == 4} + + + {:else if layer.layer_type == 3} + + + {:else if layer.layer_type == 2} + + + {:else} +
+ {layer.layer_type} + {layer.shape} +
+ {/if} + {/each} + + + + {/if} + {/each} + + + {/await} + +
+ {:else if [5, 6, -6, 7, -7].includes(m.status)} + + + {#if m.status == 6} +
Model expading... Processing ZIP file
+ {/if} + {#if m.status == -6} + + {/if} + {#if m.status == -7} +
+ + Failed to train the model! Try to retrain +
+ {/if} + {#if m.model_type == 2} + + {/if} + + {:else} +

Unknown Status of the model.

+ {/if} + {/await} +
+
diff --git a/webpage/src/routes/models/edit/ModelTable.svelte b/webpage/src/routes/models/edit/ModelTable.svelte index fa802b9..e831dde 100644 --- a/webpage/src/routes/models/edit/ModelTable.svelte +++ b/webpage/src/routes/models/edit/ModelTable.svelte @@ -44,8 +44,6 @@ let res = await get('models/data/list?' + url.toString()); showNext = res.showNext; image_list = res.image_list; - - console.log(image_list); } catch (e) { console.error('TODO notify user', e); } diff --git a/webpage/src/routes/models/edit/types.ts b/webpage/src/routes/models/edit/types.ts new file mode 100644 index 0000000..92bee0e --- /dev/null +++ b/webpage/src/routes/models/edit/types.ts @@ -0,0 +1,5 @@ +export type ModelStats = Array<{ + name: string, + training: number, + testing: number, +}>