From 2c041a2077854c955a4ae68d5ef486746eb6e356 Mon Sep 17 00:00:00 2001 From: Ettore Di Giacinto Date: Wed, 6 Nov 2024 18:25:59 +0100 Subject: [PATCH] feat(ui): move model detailed info to a modal (#4086) * feat(ui): move model detailed info to a modal Signed-off-by: Ettore Di Giacinto * chore: add static asset Signed-off-by: Ettore Di Giacinto --------- Signed-off-by: Ettore Di Giacinto --- core/http/elements/buttons.go | 97 ++++ core/http/elements/gallery.go | 612 ++++++++++-------------- core/http/elements/p2p.go | 147 ++++++ core/http/elements/progressbar.go | 89 ++++ core/http/static/assets/flowbite.min.js | 2 + core/http/views/partials/head.html | 8 + embedded/webui_static.yaml | 5 +- 7 files changed, 589 insertions(+), 371 deletions(-) create mode 100644 core/http/elements/buttons.go create mode 100644 core/http/elements/p2p.go create mode 100644 core/http/elements/progressbar.go create mode 100644 core/http/static/assets/flowbite.min.js diff --git a/core/http/elements/buttons.go b/core/http/elements/buttons.go new file mode 100644 index 00000000..7cfe968f --- /dev/null +++ b/core/http/elements/buttons.go @@ -0,0 +1,97 @@ +package elements + +import ( + "strings" + + "github.com/chasefleming/elem-go" + "github.com/chasefleming/elem-go/attrs" + "github.com/mudler/LocalAI/core/gallery" +) + +func installButton(galleryName string) elem.Node { + return elem.Button( + attrs.Props{ + "data-twe-ripple-init": "", + "data-twe-ripple-color": "light", + "class": "float-right inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong", + "hx-swap": "outerHTML", + // post the Model ID as param + "hx-post": "/browse/install/model/" + galleryName, + }, + elem.I( + attrs.Props{ + "class": "fa-solid fa-download pr-2", + }, + ), + elem.Text("Install"), + ) +} + +func reInstallButton(galleryName string) elem.Node { + return elem.Button( + attrs.Props{ + "data-twe-ripple-init": "", + "data-twe-ripple-color": "light", + "class": "float-right inline-block rounded bg-primary ml-2 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong", + "hx-target": "#action-div-" + dropBadChars(galleryName), + "hx-swap": "outerHTML", + // post the Model ID as param + "hx-post": "/browse/install/model/" + galleryName, + }, + elem.I( + attrs.Props{ + "class": "fa-solid fa-arrow-rotate-right pr-2", + }, + ), + elem.Text("Reinstall"), + ) +} + +func infoButton(m *gallery.GalleryModel) elem.Node { + return elem.Button( + attrs.Props{ + "data-twe-ripple-init": "", + "data-twe-ripple-color": "light", + "class": "float-left inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong", + "data-modal-target": modalName(m), + "data-modal-toggle": modalName(m), + }, + elem.P( + attrs.Props{ + "class": "flex items-center", + }, + elem.I( + attrs.Props{ + "class": "fas fa-info-circle pr-2", + }, + ), + elem.Text("Info"), + ), + ) +} + +func deleteButton(galleryID string) elem.Node { + return elem.Button( + attrs.Props{ + "data-twe-ripple-init": "", + "data-twe-ripple-color": "light", + "hx-confirm": "Are you sure you wish to delete the model?", + "class": "float-right inline-block rounded bg-red-800 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-red-accent-300 hover:shadow-red-2 focus:bg-red-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-red-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong", + "hx-target": "#action-div-" + dropBadChars(galleryID), + "hx-swap": "outerHTML", + // post the Model ID as param + "hx-post": "/browse/delete/model/" + galleryID, + }, + elem.I( + attrs.Props{ + "class": "fa-solid fa-cancel pr-2", + }, + ), + elem.Text("Delete"), + ) +} + +// Javascript/HTMX doesn't like weird IDs +func dropBadChars(s string) string { + return strings.ReplaceAll(s, "@", "__") +} diff --git a/core/http/elements/gallery.go b/core/http/elements/gallery.go index 06076bd9..c9d7a1cb 100644 --- a/core/http/elements/gallery.go +++ b/core/http/elements/gallery.go @@ -2,13 +2,11 @@ package elements import ( "fmt" - "strings" "github.com/chasefleming/elem-go" "github.com/chasefleming/elem-go/attrs" "github.com/microcosm-cc/bluemonday" "github.com/mudler/LocalAI/core/gallery" - "github.com/mudler/LocalAI/core/p2p" "github.com/mudler/LocalAI/core/services" ) @@ -16,231 +14,6 @@ const ( noImage = "https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg" ) -func renderElements(n []elem.Node) string { - render := "" - for _, r := range n { - render += r.Render() - } - return render -} - -func DoneProgress(galleryID, text string, showDelete bool) string { - var modelName = galleryID - // Split by @ and grab the name - if strings.Contains(galleryID, "@") { - modelName = strings.Split(galleryID, "@")[1] - } - - return elem.Div( - attrs.Props{ - "id": "action-div-" + dropBadChars(galleryID), - }, - elem.H3( - attrs.Props{ - "role": "status", - "id": "pblabel", - "tabindex": "-1", - "autofocus": "", - }, - elem.Text(bluemonday.StrictPolicy().Sanitize(text)), - ), - elem.If(showDelete, deleteButton(galleryID, modelName), reInstallButton(galleryID)), - ).Render() -} - -func ErrorProgress(err, galleryName string) string { - return elem.Div( - attrs.Props{}, - elem.H3( - attrs.Props{ - "role": "status", - "id": "pblabel", - "tabindex": "-1", - "autofocus": "", - }, - elem.Text("Error "+bluemonday.StrictPolicy().Sanitize(err)), - ), - installButton(galleryName), - ).Render() -} - -func ProgressBar(progress string) string { - return elem.Div(attrs.Props{ - "class": "progress", - "role": "progressbar", - "aria-valuemin": "0", - "aria-valuemax": "100", - "aria-valuenow": "0", - "aria-labelledby": "pblabel", - }, - elem.Div(attrs.Props{ - "id": "pb", - "class": "progress-bar", - "style": "width:" + progress + "%", - }), - ).Render() -} - -func P2PNodeStats(nodes []p2p.NodeData) string { - /* -
-

Total Workers Detected: {{ len .Nodes }}

- {{ $online := 0 }} - {{ range .Nodes }} - {{ if .IsOnline }} - {{ $online = add $online 1 }} - {{ end }} - {{ end }} -

Total Online Workers: {{$online}}

-
- */ - - online := 0 - for _, n := range nodes { - if n.IsOnline() { - online++ - } - } - - class := "text-green-500" - if online == 0 { - class = "text-red-500" - } - /* - - */ - circle := elem.I(attrs.Props{ - "class": "fas fa-circle animate-pulse " + class + " ml-2 mr-1", - }) - nodesElements := []elem.Node{ - elem.Span( - attrs.Props{ - "class": class, - }, - circle, - elem.Text(fmt.Sprintf("%d", online)), - ), - elem.Span( - attrs.Props{ - "class": "text-gray-200", - }, - elem.Text(fmt.Sprintf("/%d", len(nodes))), - ), - } - - return renderElements(nodesElements) -} - -func P2PNodeBoxes(nodes []p2p.NodeData) string { - /* -
-
- - {{.ID}} -
-

- Status: - - - {{ if .IsOnline }}Online{{ else }}Offline{{ end }} - -

-
- */ - - nodesElements := []elem.Node{} - - for _, n := range nodes { - - nodesElements = append(nodesElements, - elem.Div( - attrs.Props{ - "class": "bg-gray-700 p-6 rounded-lg shadow-lg text-left", - }, - elem.P( - attrs.Props{ - "class": "text-sm text-gray-400 mt-2 flex", - }, - elem.I( - attrs.Props{ - "class": "fas fa-desktop text-gray-400 mr-2", - }, - ), - elem.Text("Name: "), - elem.Span( - attrs.Props{ - "class": "text-gray-200 font-semibold ml-2 mr-1", - }, - elem.Text(bluemonday.StrictPolicy().Sanitize(n.ID)), - ), - elem.Text("Status: "), - elem.If( - n.IsOnline(), - elem.I( - attrs.Props{ - "class": "fas fa-circle animate-pulse text-green-500 ml-2 mr-1", - }, - ), - elem.I( - attrs.Props{ - "class": "fas fa-circle animate-pulse text-red-500 ml-2 mr-1", - }, - ), - ), - elem.If( - n.IsOnline(), - elem.Span( - attrs.Props{ - "class": "text-green-400", - }, - - elem.Text("Online"), - ), - elem.Span( - attrs.Props{ - "class": "text-red-400", - }, - elem.Text("Offline"), - ), - ), - ), - )) - } - - return renderElements(nodesElements) -} - -func StartProgressBar(uid, progress, text string) string { - if progress == "" { - progress = "0" - } - return elem.Div( - attrs.Props{ - "hx-trigger": "done", - "hx-get": "/browse/job/" + uid, - "hx-swap": "outerHTML", - "hx-target": "this", - }, - elem.H3( - attrs.Props{ - "role": "status", - "id": "pblabel", - "tabindex": "-1", - "autofocus": "", - }, - elem.Text(bluemonday.StrictPolicy().Sanitize(text)), //Perhaps overly defensive - elem.Div(attrs.Props{ - "hx-get": "/browse/job/progress/" + uid, - "hx-trigger": "every 600ms", - "hx-target": "this", - "hx-swap": "innerHTML", - }, - elem.Raw(ProgressBar(progress)), - ), - ), - ).Render() -} - func cardSpan(text, icon string) elem.Node { return elem.Span( attrs.Props{ @@ -268,7 +41,6 @@ func searchableElement(text, icon string) elem.Node { attrs.Props{ "class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2", }, - elem.A( attrs.Props{ // "name": "search", @@ -290,7 +62,8 @@ func searchableElement(text, icon string) elem.Node { ) } -func link(text, url string) elem.Node { +/* +func buttonLink(text, url string) elem.Node { return elem.A( attrs.Props{ "class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2 hover:bg-gray-300 hover:shadow-gray-2", @@ -303,163 +76,255 @@ func link(text, url string) elem.Node { elem.Text(bluemonday.StrictPolicy().Sanitize(text)), ) } -func installButton(galleryName string) elem.Node { - return elem.Button( - attrs.Props{ - "data-twe-ripple-init": "", - "data-twe-ripple-color": "light", - "class": "float-right inline-block rounded bg-primary px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong", - "hx-swap": "outerHTML", - // post the Model ID as param - "hx-post": "/browse/install/model/" + galleryName, - }, - elem.I( - attrs.Props{ - "class": "fa-solid fa-download pr-2", - }, - ), - elem.Text("Install"), - ) -} +*/ -func reInstallButton(galleryName string) elem.Node { - return elem.Button( +func link(text, url string) elem.Node { + return elem.A( attrs.Props{ - "data-twe-ripple-init": "", - "data-twe-ripple-color": "light", - "class": "float-right inline-block rounded bg-primary ml-2 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 hover:shadow-primary-2 focus:bg-primary-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-primary-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong", - "hx-target": "#action-div-" + dropBadChars(galleryName), - "hx-swap": "outerHTML", - // post the Model ID as param - "hx-post": "/browse/install/model/" + galleryName, + "class": "text-base leading-relaxed text-gray-500 dark:text-gray-400", + "href": url, + "target": "_blank", }, - elem.I( - attrs.Props{ - "class": "fa-solid fa-arrow-rotate-right pr-2", - }, - ), - elem.Text("Reinstall"), + elem.I(attrs.Props{ + "class": "fas fa-link pr-2", + }), + elem.Text(bluemonday.StrictPolicy().Sanitize(text)), ) } -func deleteButton(galleryID, modelName string) elem.Node { - return elem.Button( - attrs.Props{ - "data-twe-ripple-init": "", - "data-twe-ripple-color": "light", - "hx-confirm": "Are you sure you wish to delete the model?", - "class": "float-right inline-block rounded bg-red-800 px-6 pb-2.5 mb-3 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-red-accent-300 hover:shadow-red-2 focus:bg-red-accent-300 focus:shadow-primary-2 focus:outline-none focus:ring-0 active:bg-red-600 active:shadow-primary-2 dark:shadow-black/30 dark:hover:shadow-dark-strong dark:focus:shadow-dark-strong dark:active:shadow-dark-strong", - "hx-target": "#action-div-" + dropBadChars(galleryID), - "hx-swap": "outerHTML", - // post the Model ID as param - "hx-post": "/browse/delete/model/" + galleryID, - }, - elem.I( - attrs.Props{ - "class": "fa-solid fa-cancel pr-2", - }, - ), - elem.Text("Delete"), - ) -} - -// Javascript/HTMX doesn't like weird IDs -func dropBadChars(s string) string { - return strings.ReplaceAll(s, "@", "__") -} - type ProcessTracker interface { Exists(string) bool Get(string) string } -func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, galleryService *services.GalleryService) string { - modelsElements := []elem.Node{} - descriptionDiv := func(m *gallery.GalleryModel) elem.Node { - return elem.Div( - attrs.Props{ - "class": "p-6 text-surface dark:text-white", - }, - elem.H5( - attrs.Props{ - "class": "mb-2 text-xl font-bold leading-tight", - }, - elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)), - ), - elem.P( - attrs.Props{ - "class": "mb-4 text-sm [&:not(:hover)]:truncate text-base", - }, - elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)), - ), +func modalName(m *gallery.GalleryModel) string { + return m.Name + "-modal" +} + +func modelDescription(m *gallery.GalleryModel) elem.Node { + urls := []elem.Node{} + for _, url := range m.URLs { + urls = append(urls, + elem.Li(attrs.Props{}, link(url, url)), ) } - actionDiv := func(m *gallery.GalleryModel) elem.Node { - galleryID := fmt.Sprintf("%s@%s", m.Gallery.Name, m.Name) - currentlyProcessing := processTracker.Exists(galleryID) - jobID := "" - isDeletionOp := false - if currentlyProcessing { - status := galleryService.GetStatus(galleryID) - if status != nil && status.Deletion { - isDeletionOp = true - } - jobID = processTracker.Get(galleryID) - // TODO: - // case not handled, if status == nil : "Waiting" - } - - nodes := []elem.Node{ - cardSpan("Repository: "+m.Gallery.Name, "fa-brands fa-git-alt"), - } - - if m.License != "" { - nodes = append(nodes, - cardSpan("License: "+m.License, "fas fa-book"), - ) - } - - tagsNodes := []elem.Node{} - for _, tag := range m.Tags { - tagsNodes = append(tagsNodes, - searchableElement(tag, "fas fa-tag"), - ) - } - - nodes = append(nodes, - elem.Div( - attrs.Props{ - "class": "flex flex-row flex-wrap content-center", - }, - tagsNodes..., - ), + tagsNodes := []elem.Node{} + for _, tag := range m.Tags { + tagsNodes = append(tagsNodes, + searchableElement(tag, "fas fa-tag"), ) + } - for i, url := range m.URLs { - nodes = append(nodes, - link("Link #"+fmt.Sprintf("%d", i+1), url), - ) - } - - progressMessage := "Installation" - if isDeletionOp { - progressMessage = "Deletion" - } - - return elem.Div( + return elem.Div( + attrs.Props{ + "class": "p-6 text-surface dark:text-white", + }, + elem.H5( attrs.Props{ - "class": "px-6 pt-4 pb-2", + "class": "mb-2 text-xl font-bold leading-tight", + }, + elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)), + ), + elem.Div( // small description + attrs.Props{ + "class": "mb-4 text-sm truncate text-base", + }, + elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)), + ), + + elem.Div( + attrs.Props{ + "id": modalName(m), + "tabindex": "-1", + "aria-hidden": "true", + "class": "hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full", }, - elem.P( - attrs.Props{ - "class": "mb-4 text-base", - }, - nodes..., - ), elem.Div( attrs.Props{ - "id": "action-div-" + dropBadChars(galleryID), + "class": "relative p-4 w-full max-w-2xl max-h-full", + }, + elem.Div( + attrs.Props{ + "class": "relative p-4 w-full max-w-2xl max-h-full bg-white rounded-lg shadow dark:bg-gray-700", + }, + // header + elem.Div( + attrs.Props{ + "class": "flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600", + }, + elem.H3( + attrs.Props{ + "class": "text-xl font-semibold text-gray-900 dark:text-white", + }, + elem.Text(bluemonday.StrictPolicy().Sanitize(m.Name)), + ), + elem.Button( // close button + attrs.Props{ + "class": "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white", + "data-modal-hide": modalName(m), + }, + elem.Raw( + ``, + ), + elem.Span( + attrs.Props{ + "class": "sr-only", + }, + elem.Text("Close modal"), + ), + ), + ), + // body + elem.Div( + attrs.Props{ + "class": "p-4 md:p-5 space-y-4", + }, + elem.Div( + attrs.Props{ + "class": "flex justify-center items-center", + }, + elem.Img(attrs.Props{ + // "class": "rounded-t-lg object-fit object-center h-96", + "class": "lazy rounded-t-lg max-h-48 max-w-96 object-cover mt-3 entered loaded", + "src": m.Icon, + "loading": "lazy", + }), + ), + elem.P( + attrs.Props{ + "class": "text-base leading-relaxed text-gray-500 dark:text-gray-400", + }, + elem.Text(bluemonday.StrictPolicy().Sanitize(m.Description)), + ), + elem.Hr( + attrs.Props{}, + ), + elem.P( + attrs.Props{ + "class": "text-sm font-semibold text-gray-900 dark:text-white", + }, + elem.Text("Links"), + ), + elem.Ul( + attrs.Props{}, + urls..., + ), + elem.If( + len(m.Tags) > 0, + elem.Div( + attrs.Props{}, + elem.P( + attrs.Props{ + "class": "text-sm mb-5 font-semibold text-gray-900 dark:text-white", + }, + elem.Text("Tags"), + ), + elem.Div( + attrs.Props{ + "class": "flex flex-row flex-wrap content-center", + }, + tagsNodes..., + ), + ), + elem.Div(attrs.Props{}), + ), + ), + // Footer + elem.Div( + attrs.Props{ + "class": "flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600", + }, + elem.Button( + attrs.Props{ + "data-modal-hide": modalName(m), + "class": "py-2.5 px-5 ms-3 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700", + }, + elem.Text("Close"), + ), + ), + ), + ), + ), + ) +} + +func modelActionItems(m *gallery.GalleryModel, processTracker ProcessTracker, galleryService *services.GalleryService) elem.Node { + galleryID := fmt.Sprintf("%s@%s", m.Gallery.Name, m.Name) + currentlyProcessing := processTracker.Exists(galleryID) + jobID := "" + isDeletionOp := false + if currentlyProcessing { + status := galleryService.GetStatus(galleryID) + if status != nil && status.Deletion { + isDeletionOp = true + } + jobID = processTracker.Get(galleryID) + // TODO: + // case not handled, if status == nil : "Waiting" + } + + nodes := []elem.Node{ + cardSpan("Repository: "+m.Gallery.Name, "fa-brands fa-git-alt"), + } + + if m.License != "" { + nodes = append(nodes, + cardSpan("License: "+m.License, "fas fa-book"), + ) + } + /* + tagsNodes := []elem.Node{} + + for _, tag := range m.Tags { + tagsNodes = append(tagsNodes, + searchableElement(tag, "fas fa-tag"), + ) + } + + + nodes = append(nodes, + elem.Div( + attrs.Props{ + "class": "flex flex-row flex-wrap content-center", + }, + tagsNodes..., + ), + ) + + for i, url := range m.URLs { + nodes = append(nodes, + buttonLink("Link #"+fmt.Sprintf("%d", i+1), url), + ) + } + */ + + progressMessage := "Installation" + if isDeletionOp { + progressMessage = "Deletion" + } + + return elem.Div( + attrs.Props{ + "class": "px-6 pt-4 pb-2", + }, + elem.P( + attrs.Props{ + "class": "mb-4 text-base", + }, + nodes..., + ), + elem.Div( + attrs.Props{ + "id": "action-div-" + dropBadChars(galleryID), + "class": "flow-root", // To order buttons left and right + }, + infoButton(m), + elem.Div( + attrs.Props{ + "class": "float-right", }, elem.If( currentlyProcessing, @@ -470,14 +335,18 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g elem.Node(elem.Div( attrs.Props{}, reInstallButton(m.ID()), - deleteButton(m.ID(), m.Name), + deleteButton(m.ID()), )), installButton(m.ID()), ), ), ), - ) - } + ), + ) +} + +func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, galleryService *services.GalleryService) string { + modelsElements := []elem.Node{} for _, m := range models { elems := []elem.Node{} @@ -521,7 +390,10 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g )) } - elems = append(elems, descriptionDiv(m), actionDiv(m)) + elems = append(elems, + modelDescription(m), + modelActionItems(m, processTracker, galleryService), + ) modelsElements = append(modelsElements, elem.Div( attrs.Props{ diff --git a/core/http/elements/p2p.go b/core/http/elements/p2p.go new file mode 100644 index 00000000..7eb10df5 --- /dev/null +++ b/core/http/elements/p2p.go @@ -0,0 +1,147 @@ +package elements + +import ( + "fmt" + + "github.com/chasefleming/elem-go" + "github.com/chasefleming/elem-go/attrs" + "github.com/microcosm-cc/bluemonday" + "github.com/mudler/LocalAI/core/p2p" +) + +func renderElements(n []elem.Node) string { + render := "" + for _, r := range n { + render += r.Render() + } + return render +} + +func P2PNodeStats(nodes []p2p.NodeData) string { + /* +
+

Total Workers Detected: {{ len .Nodes }}

+ {{ $online := 0 }} + {{ range .Nodes }} + {{ if .IsOnline }} + {{ $online = add $online 1 }} + {{ end }} + {{ end }} +

Total Online Workers: {{$online}}

+
+ */ + + online := 0 + for _, n := range nodes { + if n.IsOnline() { + online++ + } + } + + class := "text-green-500" + if online == 0 { + class = "text-red-500" + } + /* + + */ + circle := elem.I(attrs.Props{ + "class": "fas fa-circle animate-pulse " + class + " ml-2 mr-1", + }) + nodesElements := []elem.Node{ + elem.Span( + attrs.Props{ + "class": class, + }, + circle, + elem.Text(fmt.Sprintf("%d", online)), + ), + elem.Span( + attrs.Props{ + "class": "text-gray-200", + }, + elem.Text(fmt.Sprintf("/%d", len(nodes))), + ), + } + + return renderElements(nodesElements) +} + +func P2PNodeBoxes(nodes []p2p.NodeData) string { + /* +
+
+ + {{.ID}} +
+

+ Status: + + + {{ if .IsOnline }}Online{{ else }}Offline{{ end }} + +

+
+ */ + + nodesElements := []elem.Node{} + + for _, n := range nodes { + + nodesElements = append(nodesElements, + elem.Div( + attrs.Props{ + "class": "bg-gray-700 p-6 rounded-lg shadow-lg text-left", + }, + elem.P( + attrs.Props{ + "class": "text-sm text-gray-400 mt-2 flex", + }, + elem.I( + attrs.Props{ + "class": "fas fa-desktop text-gray-400 mr-2", + }, + ), + elem.Text("Name: "), + elem.Span( + attrs.Props{ + "class": "text-gray-200 font-semibold ml-2 mr-1", + }, + elem.Text(bluemonday.StrictPolicy().Sanitize(n.ID)), + ), + elem.Text("Status: "), + elem.If( + n.IsOnline(), + elem.I( + attrs.Props{ + "class": "fas fa-circle animate-pulse text-green-500 ml-2 mr-1", + }, + ), + elem.I( + attrs.Props{ + "class": "fas fa-circle animate-pulse text-red-500 ml-2 mr-1", + }, + ), + ), + elem.If( + n.IsOnline(), + elem.Span( + attrs.Props{ + "class": "text-green-400", + }, + + elem.Text("Online"), + ), + elem.Span( + attrs.Props{ + "class": "text-red-400", + }, + elem.Text("Offline"), + ), + ), + ), + )) + } + + return renderElements(nodesElements) +} diff --git a/core/http/elements/progressbar.go b/core/http/elements/progressbar.go new file mode 100644 index 00000000..c9af98d9 --- /dev/null +++ b/core/http/elements/progressbar.go @@ -0,0 +1,89 @@ +package elements + +import ( + "github.com/chasefleming/elem-go" + "github.com/chasefleming/elem-go/attrs" + "github.com/microcosm-cc/bluemonday" +) + +func DoneProgress(galleryID, text string, showDelete bool) string { + return elem.Div( + attrs.Props{ + "id": "action-div-" + dropBadChars(galleryID), + }, + elem.H3( + attrs.Props{ + "role": "status", + "id": "pblabel", + "tabindex": "-1", + "autofocus": "", + }, + elem.Text(bluemonday.StrictPolicy().Sanitize(text)), + ), + elem.If(showDelete, deleteButton(galleryID), reInstallButton(galleryID)), + ).Render() +} + +func ErrorProgress(err, galleryName string) string { + return elem.Div( + attrs.Props{}, + elem.H3( + attrs.Props{ + "role": "status", + "id": "pblabel", + "tabindex": "-1", + "autofocus": "", + }, + elem.Text("Error "+bluemonday.StrictPolicy().Sanitize(err)), + ), + installButton(galleryName), + ).Render() +} + +func ProgressBar(progress string) string { + return elem.Div(attrs.Props{ + "class": "progress", + "role": "progressbar", + "aria-valuemin": "0", + "aria-valuemax": "100", + "aria-valuenow": "0", + "aria-labelledby": "pblabel", + }, + elem.Div(attrs.Props{ + "id": "pb", + "class": "progress-bar", + "style": "width:" + progress + "%", + }), + ).Render() +} + +func StartProgressBar(uid, progress, text string) string { + if progress == "" { + progress = "0" + } + return elem.Div( + attrs.Props{ + "hx-trigger": "done", + "hx-get": "/browse/job/" + uid, + "hx-swap": "outerHTML", + "hx-target": "this", + }, + elem.H3( + attrs.Props{ + "role": "status", + "id": "pblabel", + "tabindex": "-1", + "autofocus": "", + }, + elem.Text(bluemonday.StrictPolicy().Sanitize(text)), //Perhaps overly defensive + elem.Div(attrs.Props{ + "hx-get": "/browse/job/progress/" + uid, + "hx-trigger": "every 600ms", + "hx-target": "this", + "hx-swap": "innerHTML", + }, + elem.Raw(ProgressBar(progress)), + ), + ), + ).Render() +} diff --git a/core/http/static/assets/flowbite.min.js b/core/http/static/assets/flowbite.min.js new file mode 100644 index 00000000..e2c52c2c --- /dev/null +++ b/core/http/static/assets/flowbite.min.js @@ -0,0 +1,2 @@ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("Flowbite",[],e):"object"==typeof exports?exports.Flowbite=e():t.Flowbite=e()}(self,(function(){return function(){"use strict";var t={647:function(t,e,i){i.r(e)},853:function(t,e,i){i.r(e),i.d(e,{afterMain:function(){return w},afterRead:function(){return y},afterWrite:function(){return O},applyStyles:function(){return P},arrow:function(){return Q},auto:function(){return a},basePlacements:function(){return c},beforeMain:function(){return b},beforeRead:function(){return _},beforeWrite:function(){return L},bottom:function(){return o},clippingParents:function(){return u},computeStyles:function(){return it},createPopper:function(){return Pt},createPopperBase:function(){return Ht},createPopperLite:function(){return St},detectOverflow:function(){return mt},end:function(){return l},eventListeners:function(){return ot},flip:function(){return yt},hide:function(){return wt},left:function(){return s},main:function(){return E},modifierPhases:function(){return k},offset:function(){return Lt},placements:function(){return g},popper:function(){return h},popperGenerator:function(){return Tt},popperOffsets:function(){return It},preventOverflow:function(){return Ot},read:function(){return m},reference:function(){return f},right:function(){return r},start:function(){return d},top:function(){return n},variationPlacements:function(){return v},viewport:function(){return p},write:function(){return I}});var n="top",o="bottom",r="right",s="left",a="auto",c=[n,o,r,s],d="start",l="end",u="clippingParents",p="viewport",h="popper",f="reference",v=c.reduce((function(t,e){return t.concat([e+"-"+d,e+"-"+l])}),[]),g=[].concat(c,[a]).reduce((function(t,e){return t.concat([e,e+"-"+d,e+"-"+l])}),[]),_="beforeRead",m="read",y="afterRead",b="beforeMain",E="main",w="afterMain",L="beforeWrite",I="write",O="afterWrite",k=[_,m,y,b,E,w,L,I,O];function x(t){return t?(t.nodeName||"").toLowerCase():null}function A(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function C(t){return t instanceof A(t).Element||t instanceof Element}function T(t){return t instanceof A(t).HTMLElement||t instanceof HTMLElement}function H(t){return"undefined"!=typeof ShadowRoot&&(t instanceof A(t).ShadowRoot||t instanceof ShadowRoot)}var P={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},o=e.elements[t];T(o)&&x(o)&&(Object.assign(o.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?o.removeAttribute(t):o.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],o=e.attributes[t]||{},r=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});T(n)&&x(n)&&(Object.assign(n.style,r),Object.keys(o).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function S(t){return t.split("-")[0]}var j=Math.max,D=Math.min,z=Math.round;function M(){var t=navigator.userAgentData;return null!=t&&t.brands?t.brands.map((function(t){return t.brand+"/"+t.version})).join(" "):navigator.userAgent}function q(){return!/^((?!chrome|android).)*safari/i.test(M())}function V(t,e,i){void 0===e&&(e=!1),void 0===i&&(i=!1);var n=t.getBoundingClientRect(),o=1,r=1;e&&T(t)&&(o=t.offsetWidth>0&&z(n.width)/t.offsetWidth||1,r=t.offsetHeight>0&&z(n.height)/t.offsetHeight||1);var s=(C(t)?A(t):window).visualViewport,a=!q()&&i,c=(n.left+(a&&s?s.offsetLeft:0))/o,d=(n.top+(a&&s?s.offsetTop:0))/r,l=n.width/o,u=n.height/r;return{width:l,height:u,top:d,right:c+l,bottom:d+u,left:c,x:c,y:d}}function B(t){var e=V(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function R(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&H(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function W(t){return A(t).getComputedStyle(t)}function F(t){return["table","td","th"].indexOf(x(t))>=0}function K(t){return((C(t)?t.ownerDocument:t.document)||window.document).documentElement}function N(t){return"html"===x(t)?t:t.assignedSlot||t.parentNode||(H(t)?t.host:null)||K(t)}function U(t){return T(t)&&"fixed"!==W(t).position?t.offsetParent:null}function X(t){for(var e=A(t),i=U(t);i&&F(i)&&"static"===W(i).position;)i=U(i);return i&&("html"===x(i)||"body"===x(i)&&"static"===W(i).position)?e:i||function(t){var e=/firefox/i.test(M());if(/Trident/i.test(M())&&T(t)&&"fixed"===W(t).position)return null;var i=N(t);for(H(i)&&(i=i.host);T(i)&&["html","body"].indexOf(x(i))<0;){var n=W(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function Y(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}function G(t,e,i){return j(t,D(e,i))}function $(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function J(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}var Q={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,a=t.name,d=t.options,l=i.elements.arrow,u=i.modifiersData.popperOffsets,p=S(i.placement),h=Y(p),f=[s,r].indexOf(p)>=0?"height":"width";if(l&&u){var v=function(t,e){return $("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:J(t,c))}(d.padding,i),g=B(l),_="y"===h?n:s,m="y"===h?o:r,y=i.rects.reference[f]+i.rects.reference[h]-u[h]-i.rects.popper[f],b=u[h]-i.rects.reference[h],E=X(l),w=E?"y"===h?E.clientHeight||0:E.clientWidth||0:0,L=y/2-b/2,I=v[_],O=w-g[f]-v[m],k=w/2-g[f]/2+L,x=G(I,k,O),A=h;i.modifiersData[a]=((e={})[A]=x,e.centerOffset=x-k,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&R(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Z(t){return t.split("-")[1]}var tt={top:"auto",right:"auto",bottom:"auto",left:"auto"};function et(t){var e,i=t.popper,a=t.popperRect,c=t.placement,d=t.variation,u=t.offsets,p=t.position,h=t.gpuAcceleration,f=t.adaptive,v=t.roundOffsets,g=t.isFixed,_=u.x,m=void 0===_?0:_,y=u.y,b=void 0===y?0:y,E="function"==typeof v?v({x:m,y:b}):{x:m,y:b};m=E.x,b=E.y;var w=u.hasOwnProperty("x"),L=u.hasOwnProperty("y"),I=s,O=n,k=window;if(f){var x=X(i),C="clientHeight",T="clientWidth";if(x===A(i)&&"static"!==W(x=K(i)).position&&"absolute"===p&&(C="scrollHeight",T="scrollWidth"),c===n||(c===s||c===r)&&d===l)O=o,b-=(g&&x===k&&k.visualViewport?k.visualViewport.height:x[C])-a.height,b*=h?1:-1;if(c===s||(c===n||c===o)&&d===l)I=r,m-=(g&&x===k&&k.visualViewport?k.visualViewport.width:x[T])-a.width,m*=h?1:-1}var H,P=Object.assign({position:p},f&&tt),S=!0===v?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:z(e*n)/n||0,y:z(i*n)/n||0}}({x:m,y:b}):{x:m,y:b};return m=S.x,b=S.y,h?Object.assign({},P,((H={})[O]=L?"0":"",H[I]=w?"0":"",H.transform=(k.devicePixelRatio||1)<=1?"translate("+m+"px, "+b+"px)":"translate3d("+m+"px, "+b+"px, 0)",H)):Object.assign({},P,((e={})[O]=L?b+"px":"",e[I]=w?m+"px":"",e.transform="",e))}var it={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,o=void 0===n||n,r=i.adaptive,s=void 0===r||r,a=i.roundOffsets,c=void 0===a||a,d={placement:S(e.placement),variation:Z(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:o,isFixed:"fixed"===e.options.strategy};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,et(Object.assign({},d,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:s,roundOffsets:c})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,et(Object.assign({},d,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:c})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}},nt={passive:!0};var ot={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,o=n.scroll,r=void 0===o||o,s=n.resize,a=void 0===s||s,c=A(e.elements.popper),d=[].concat(e.scrollParents.reference,e.scrollParents.popper);return r&&d.forEach((function(t){t.addEventListener("scroll",i.update,nt)})),a&&c.addEventListener("resize",i.update,nt),function(){r&&d.forEach((function(t){t.removeEventListener("scroll",i.update,nt)})),a&&c.removeEventListener("resize",i.update,nt)}},data:{}},rt={left:"right",right:"left",bottom:"top",top:"bottom"};function st(t){return t.replace(/left|right|bottom|top/g,(function(t){return rt[t]}))}var at={start:"end",end:"start"};function ct(t){return t.replace(/start|end/g,(function(t){return at[t]}))}function dt(t){var e=A(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function lt(t){return V(K(t)).left+dt(t).scrollLeft}function ut(t){var e=W(t),i=e.overflow,n=e.overflowX,o=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+o+n)}function pt(t){return["html","body","#document"].indexOf(x(t))>=0?t.ownerDocument.body:T(t)&&ut(t)?t:pt(N(t))}function ht(t,e){var i;void 0===e&&(e=[]);var n=pt(t),o=n===(null==(i=t.ownerDocument)?void 0:i.body),r=A(n),s=o?[r].concat(r.visualViewport||[],ut(n)?n:[]):n,a=e.concat(s);return o?a:a.concat(ht(N(s)))}function ft(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function vt(t,e,i){return e===p?ft(function(t,e){var i=A(t),n=K(t),o=i.visualViewport,r=n.clientWidth,s=n.clientHeight,a=0,c=0;if(o){r=o.width,s=o.height;var d=q();(d||!d&&"fixed"===e)&&(a=o.offsetLeft,c=o.offsetTop)}return{width:r,height:s,x:a+lt(t),y:c}}(t,i)):C(e)?function(t,e){var i=V(t,!1,"fixed"===e);return i.top=i.top+t.clientTop,i.left=i.left+t.clientLeft,i.bottom=i.top+t.clientHeight,i.right=i.left+t.clientWidth,i.width=t.clientWidth,i.height=t.clientHeight,i.x=i.left,i.y=i.top,i}(e,i):ft(function(t){var e,i=K(t),n=dt(t),o=null==(e=t.ownerDocument)?void 0:e.body,r=j(i.scrollWidth,i.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),s=j(i.scrollHeight,i.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),a=-n.scrollLeft+lt(t),c=-n.scrollTop;return"rtl"===W(o||i).direction&&(a+=j(i.clientWidth,o?o.clientWidth:0)-r),{width:r,height:s,x:a,y:c}}(K(t)))}function gt(t,e,i,n){var o="clippingParents"===e?function(t){var e=ht(N(t)),i=["absolute","fixed"].indexOf(W(t).position)>=0&&T(t)?X(t):t;return C(i)?e.filter((function(t){return C(t)&&R(t,i)&&"body"!==x(t)})):[]}(t):[].concat(e),r=[].concat(o,[i]),s=r[0],a=r.reduce((function(e,i){var o=vt(t,i,n);return e.top=j(o.top,e.top),e.right=D(o.right,e.right),e.bottom=D(o.bottom,e.bottom),e.left=j(o.left,e.left),e}),vt(t,s,n));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function _t(t){var e,i=t.reference,a=t.element,c=t.placement,u=c?S(c):null,p=c?Z(c):null,h=i.x+i.width/2-a.width/2,f=i.y+i.height/2-a.height/2;switch(u){case n:e={x:h,y:i.y-a.height};break;case o:e={x:h,y:i.y+i.height};break;case r:e={x:i.x+i.width,y:f};break;case s:e={x:i.x-a.width,y:f};break;default:e={x:i.x,y:i.y}}var v=u?Y(u):null;if(null!=v){var g="y"===v?"height":"width";switch(p){case d:e[v]=e[v]-(i[g]/2-a[g]/2);break;case l:e[v]=e[v]+(i[g]/2-a[g]/2)}}return e}function mt(t,e){void 0===e&&(e={});var i=e,s=i.placement,a=void 0===s?t.placement:s,d=i.strategy,l=void 0===d?t.strategy:d,v=i.boundary,g=void 0===v?u:v,_=i.rootBoundary,m=void 0===_?p:_,y=i.elementContext,b=void 0===y?h:y,E=i.altBoundary,w=void 0!==E&&E,L=i.padding,I=void 0===L?0:L,O=$("number"!=typeof I?I:J(I,c)),k=b===h?f:h,x=t.rects.popper,A=t.elements[w?k:b],T=gt(C(A)?A:A.contextElement||K(t.elements.popper),g,m,l),H=V(t.elements.reference),P=_t({reference:H,element:x,strategy:"absolute",placement:a}),S=ft(Object.assign({},x,P)),j=b===h?S:H,D={top:T.top-j.top+O.top,bottom:j.bottom-T.bottom+O.bottom,left:T.left-j.left+O.left,right:j.right-T.right+O.right},z=t.modifiersData.offset;if(b===h&&z){var M=z[a];Object.keys(D).forEach((function(t){var e=[r,o].indexOf(t)>=0?1:-1,i=[n,o].indexOf(t)>=0?"y":"x";D[t]+=M[i]*e}))}return D}var yt={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,l=t.name;if(!e.modifiersData[l]._skip){for(var u=i.mainAxis,p=void 0===u||u,h=i.altAxis,f=void 0===h||h,_=i.fallbackPlacements,m=i.padding,y=i.boundary,b=i.rootBoundary,E=i.altBoundary,w=i.flipVariations,L=void 0===w||w,I=i.allowedAutoPlacements,O=e.options.placement,k=S(O),x=_||(k===O||!L?[st(O)]:function(t){if(S(t)===a)return[];var e=st(t);return[ct(t),e,ct(e)]}(O)),A=[O].concat(x).reduce((function(t,i){return t.concat(S(i)===a?function(t,e){void 0===e&&(e={});var i=e,n=i.placement,o=i.boundary,r=i.rootBoundary,s=i.padding,a=i.flipVariations,d=i.allowedAutoPlacements,l=void 0===d?g:d,u=Z(n),p=u?a?v:v.filter((function(t){return Z(t)===u})):c,h=p.filter((function(t){return l.indexOf(t)>=0}));0===h.length&&(h=p);var f=h.reduce((function(e,i){return e[i]=mt(t,{placement:i,boundary:o,rootBoundary:r,padding:s})[S(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}(e,{placement:i,boundary:y,rootBoundary:b,padding:m,flipVariations:L,allowedAutoPlacements:I}):i)}),[]),C=e.rects.reference,T=e.rects.popper,H=new Map,P=!0,j=A[0],D=0;D=0,B=V?"width":"height",R=mt(e,{placement:z,boundary:y,rootBoundary:b,altBoundary:E,padding:m}),W=V?q?r:s:q?o:n;C[B]>T[B]&&(W=st(W));var F=st(W),K=[];if(p&&K.push(R[M]<=0),f&&K.push(R[W]<=0,R[F]<=0),K.every((function(t){return t}))){j=z,P=!1;break}H.set(z,K)}if(P)for(var N=function(t){var e=A.find((function(e){var i=H.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return j=e,"break"},U=L?3:1;U>0;U--){if("break"===N(U))break}e.placement!==j&&(e.modifiersData[l]._skip=!0,e.placement=j,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function bt(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Et(t){return[n,r,o,s].some((function(e){return t[e]>=0}))}var wt={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,o=e.rects.popper,r=e.modifiersData.preventOverflow,s=mt(e,{elementContext:"reference"}),a=mt(e,{altBoundary:!0}),c=bt(s,n),d=bt(a,o,r),l=Et(c),u=Et(d);e.modifiersData[i]={referenceClippingOffsets:c,popperEscapeOffsets:d,isReferenceHidden:l,hasPopperEscaped:u},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":l,"data-popper-escaped":u})}};var Lt={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,o=t.name,a=i.offset,c=void 0===a?[0,0]:a,d=g.reduce((function(t,i){return t[i]=function(t,e,i){var o=S(t),a=[s,n].indexOf(o)>=0?-1:1,c="function"==typeof i?i(Object.assign({},e,{placement:t})):i,d=c[0],l=c[1];return d=d||0,l=(l||0)*a,[s,r].indexOf(o)>=0?{x:l,y:d}:{x:d,y:l}}(i,e.rects,c),t}),{}),l=d[e.placement],u=l.x,p=l.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=u,e.modifiersData.popperOffsets.y+=p),e.modifiersData[o]=d}};var It={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=_t({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}};var Ot={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,a=t.name,c=i.mainAxis,l=void 0===c||c,u=i.altAxis,p=void 0!==u&&u,h=i.boundary,f=i.rootBoundary,v=i.altBoundary,g=i.padding,_=i.tether,m=void 0===_||_,y=i.tetherOffset,b=void 0===y?0:y,E=mt(e,{boundary:h,rootBoundary:f,padding:g,altBoundary:v}),w=S(e.placement),L=Z(e.placement),I=!L,O=Y(w),k="x"===O?"y":"x",x=e.modifiersData.popperOffsets,A=e.rects.reference,C=e.rects.popper,T="function"==typeof b?b(Object.assign({},e.rects,{placement:e.placement})):b,H="number"==typeof T?{mainAxis:T,altAxis:T}:Object.assign({mainAxis:0,altAxis:0},T),P=e.modifiersData.offset?e.modifiersData.offset[e.placement]:null,z={x:0,y:0};if(x){if(l){var M,q="y"===O?n:s,V="y"===O?o:r,R="y"===O?"height":"width",W=x[O],F=W+E[q],K=W-E[V],N=m?-C[R]/2:0,U=L===d?A[R]:C[R],$=L===d?-C[R]:-A[R],J=e.elements.arrow,Q=m&&J?B(J):{width:0,height:0},tt=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},et=tt[q],it=tt[V],nt=G(0,A[R],Q[R]),ot=I?A[R]/2-N-nt-et-H.mainAxis:U-nt-et-H.mainAxis,rt=I?-A[R]/2+N+nt+it+H.mainAxis:$+nt+it+H.mainAxis,st=e.elements.arrow&&X(e.elements.arrow),at=st?"y"===O?st.clientTop||0:st.clientLeft||0:0,ct=null!=(M=null==P?void 0:P[O])?M:0,dt=W+rt-ct,lt=G(m?D(F,W+ot-ct-at):F,W,m?j(K,dt):K);x[O]=lt,z[O]=lt-W}if(p){var ut,pt="x"===O?n:s,ht="x"===O?o:r,ft=x[k],vt="y"===k?"height":"width",gt=ft+E[pt],_t=ft-E[ht],yt=-1!==[n,s].indexOf(w),bt=null!=(ut=null==P?void 0:P[k])?ut:0,Et=yt?gt:ft-A[vt]-C[vt]-bt+H.altAxis,wt=yt?ft+A[vt]+C[vt]-bt-H.altAxis:_t,Lt=m&&yt?function(t,e,i){var n=G(t,e,i);return n>i?i:n}(Et,ft,wt):G(m?Et:gt,ft,m?wt:_t);x[k]=Lt,z[k]=Lt-ft}e.modifiersData[a]=z}},requiresIfExists:["offset"]};function kt(t,e,i){void 0===i&&(i=!1);var n,o,r=T(e),s=T(e)&&function(t){var e=t.getBoundingClientRect(),i=z(e.width)/t.offsetWidth||1,n=z(e.height)/t.offsetHeight||1;return 1!==i||1!==n}(e),a=K(e),c=V(t,s,i),d={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(r||!r&&!i)&&(("body"!==x(e)||ut(a))&&(d=(n=e)!==A(n)&&T(n)?{scrollLeft:(o=n).scrollLeft,scrollTop:o.scrollTop}:dt(n)),T(e)?((l=V(e,!0)).x+=e.clientLeft,l.y+=e.clientTop):a&&(l.x=lt(a))),{x:c.left+d.scrollLeft-l.x,y:c.top+d.scrollTop-l.y,width:c.width,height:c.height}}function xt(t){var e=new Map,i=new Set,n=[];function o(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&o(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||o(t)})),n}var At={placement:"bottom",modifiers:[],strategy:"absolute"};function Ct(){for(var t=arguments.length,e=new Array(t),i=0;it._options.maxValue&&(i.value=t._options.maxValue.toString()),null!==t._options.minValue&&parseInt(i.value)=this._options.maxValue||(this._targetEl.value=(this.getCurrentValue()+1).toString(),this._options.onIncrement(this))},t.prototype.decrement=function(){null!==this._options.minValue&&this.getCurrentValue()<=this._options.minValue||(this._targetEl.value=(this.getCurrentValue()-1).toString(),this._options.onDecrement(this))},t.prototype.updateOnIncrement=function(t){this._options.onIncrement=t},t.prototype.updateOnDecrement=function(t){this._options.onDecrement=t},t}();function c(){document.querySelectorAll("[data-input-counter]").forEach((function(t){var e=t.id,i=document.querySelector('[data-input-counter-increment="'+e+'"]'),n=document.querySelector('[data-input-counter-decrement="'+e+'"]'),r=t.getAttribute("data-input-counter-min"),s=t.getAttribute("data-input-counter-max");t?o.default.instanceExists("InputCounter",t.getAttribute("id"))||new a(t,i||null,n||null,{minValue:r?parseInt(r):null,maxValue:s?parseInt(s):null}):console.error('The target element with id "'.concat(e,'" does not exist. Please check the data-input-counter attribute.'))}))}e.initInputCounters=c,"undefined"!=typeof window&&(window.InputCounter=a,window.initInputCounters=c),e.default=a},16:function(t,e,i){var n=this&&this.__assign||function(){return n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i + + + + \ No newline at end of file diff --git a/embedded/webui_static.yaml b/embedded/webui_static.yaml index fab448cb..8d691212 100644 --- a/embedded/webui_static.yaml +++ b/embedded/webui_static.yaml @@ -56,4 +56,7 @@ sha: "8a9a74f4455f392ec3e7499cfda6097b536bb4b7f1e529a079c3d953c08b54ca" - filename: "KFOlCnqEu92Fr1MmYUtfBBc9.ttf" url: "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmYUtfBBc9.ttf" - sha: "361a50f8a6c816ba4306c5290b7e487a726e1b4dcc3d8d7e4acf1fc2dae9f551" \ No newline at end of file + sha: "361a50f8a6c816ba4306c5290b7e487a726e1b4dcc3d8d7e4acf1fc2dae9f551" +- filename: "flowbite.min.js" + url: "https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js" + sha: "d2a1a72a4c2399e43c01412b86b9957c4df1845f2e0586607c7e55b9ae949cf8" \ No newline at end of file