2024-04-23 07:22:58 +00:00
|
|
|
package elements
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-05-06 23:17:07 +00:00
|
|
|
"strings"
|
2024-04-23 07:22:58 +00:00
|
|
|
|
|
|
|
"github.com/chasefleming/elem-go"
|
|
|
|
"github.com/chasefleming/elem-go/attrs"
|
|
|
|
"github.com/go-skynet/LocalAI/pkg/gallery"
|
2024-04-27 07:08:33 +00:00
|
|
|
"github.com/go-skynet/LocalAI/pkg/xsync"
|
2024-04-23 07:22:58 +00:00
|
|
|
)
|
|
|
|
|
2024-04-23 18:10:58 +00:00
|
|
|
const (
|
|
|
|
NoImage = "https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg"
|
|
|
|
)
|
|
|
|
|
2024-05-06 23:17:07 +00:00
|
|
|
func DoneProgress(galleryID, text string, showDelete bool) string {
|
|
|
|
// Split by @ and grab the name
|
|
|
|
if strings.Contains(galleryID, "@") {
|
|
|
|
galleryID = strings.Split(galleryID, "@")[1]
|
|
|
|
}
|
|
|
|
|
2024-04-23 07:22:58 +00:00
|
|
|
return elem.Div(
|
|
|
|
attrs.Props{},
|
|
|
|
elem.H3(
|
|
|
|
attrs.Props{
|
|
|
|
"role": "status",
|
|
|
|
"id": "pblabel",
|
|
|
|
"tabindex": "-1",
|
|
|
|
"autofocus": "",
|
|
|
|
},
|
2024-04-28 21:42:46 +00:00
|
|
|
elem.Text(text),
|
2024-04-23 07:22:58 +00:00
|
|
|
),
|
2024-05-06 23:17:07 +00:00
|
|
|
elem.If(showDelete, deleteButton(galleryID), reInstallButton(galleryID)),
|
2024-04-23 07:22:58 +00:00
|
|
|
).Render()
|
|
|
|
}
|
|
|
|
|
2024-05-06 23:17:07 +00:00
|
|
|
func ErrorProgress(err, galleryName string) string {
|
2024-04-23 07:22:58 +00:00
|
|
|
return elem.Div(
|
|
|
|
attrs.Props{},
|
|
|
|
elem.H3(
|
|
|
|
attrs.Props{
|
|
|
|
"role": "status",
|
|
|
|
"id": "pblabel",
|
|
|
|
"tabindex": "-1",
|
|
|
|
"autofocus": "",
|
|
|
|
},
|
2024-05-06 23:17:07 +00:00
|
|
|
elem.Text("Error "+err),
|
2024-04-23 07:22:58 +00:00
|
|
|
),
|
2024-05-06 23:17:07 +00:00
|
|
|
installButton(galleryName),
|
2024-04-23 07:22:58 +00:00
|
|
|
).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()
|
|
|
|
}
|
|
|
|
|
2024-04-28 21:42:46 +00:00
|
|
|
func StartProgressBar(uid, progress, text string) string {
|
2024-04-23 07:22:58 +00:00
|
|
|
if progress == "" {
|
|
|
|
progress = "0"
|
|
|
|
}
|
|
|
|
return elem.Div(attrs.Props{
|
|
|
|
"hx-trigger": "done",
|
|
|
|
"hx-get": "/browse/job/" + uid,
|
2024-05-06 23:17:07 +00:00
|
|
|
"hx-swap": "innerHTML",
|
2024-04-23 07:22:58 +00:00
|
|
|
"hx-target": "this",
|
|
|
|
},
|
|
|
|
elem.H3(
|
|
|
|
attrs.Props{
|
|
|
|
"role": "status",
|
|
|
|
"id": "pblabel",
|
|
|
|
"tabindex": "-1",
|
|
|
|
"autofocus": "",
|
|
|
|
},
|
2024-04-28 21:42:46 +00:00
|
|
|
elem.Text(text),
|
2024-04-23 07:22:58 +00:00
|
|
|
// This is a simple example of how to use the HTMLX library to create a progress bar that updates every 600ms.
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2024-04-23 16:43:25 +00:00
|
|
|
func cardSpan(text, icon string) elem.Node {
|
|
|
|
return elem.Span(
|
|
|
|
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",
|
|
|
|
},
|
|
|
|
elem.I(attrs.Props{
|
|
|
|
"class": icon + " pr-2",
|
|
|
|
}),
|
2024-05-06 23:17:07 +00:00
|
|
|
|
|
|
|
elem.Text(text),
|
|
|
|
|
|
|
|
//elem.Text(text),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func searchableElement(text, icon string) elem.Node {
|
|
|
|
return elem.Form(
|
|
|
|
attrs.Props{},
|
|
|
|
elem.Input(
|
|
|
|
attrs.Props{
|
|
|
|
"type": "hidden",
|
|
|
|
"name": "search",
|
|
|
|
"value": text,
|
|
|
|
},
|
|
|
|
),
|
|
|
|
elem.Span(
|
|
|
|
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",
|
|
|
|
// "value": text,
|
|
|
|
//"class": "inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2",
|
|
|
|
"href": "#!",
|
|
|
|
"hx-post": "/browse/search/models",
|
|
|
|
"hx-target": "#search-results",
|
|
|
|
// TODO: this doesn't work
|
|
|
|
// "hx-vals": `{ \"search\": \"` + text + `\" }`,
|
|
|
|
"hx-indicator": ".htmx-indicator",
|
|
|
|
},
|
|
|
|
elem.I(attrs.Props{
|
|
|
|
"class": icon + " pr-2",
|
|
|
|
}),
|
|
|
|
elem.Text(text),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
|
|
|
|
//elem.Text(text),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func link(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",
|
|
|
|
"href": url,
|
|
|
|
"target": "_blank",
|
|
|
|
},
|
|
|
|
elem.I(attrs.Props{
|
|
|
|
"class": "fas fa-link pr-2",
|
|
|
|
}),
|
2024-04-23 16:43:25 +00:00
|
|
|
elem.Text(text),
|
|
|
|
)
|
|
|
|
}
|
2024-05-06 23:17:07 +00:00
|
|
|
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-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 deleteButton(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-swap": "outerHTML",
|
|
|
|
// post the Model ID as param
|
|
|
|
"hx-post": "/browse/delete/model/" + modelName,
|
|
|
|
},
|
|
|
|
elem.I(
|
|
|
|
attrs.Props{
|
|
|
|
"class": "fa-solid fa-cancel pr-2",
|
|
|
|
},
|
|
|
|
),
|
|
|
|
elem.Text("Delete"),
|
|
|
|
)
|
|
|
|
}
|
2024-04-23 16:43:25 +00:00
|
|
|
|
2024-04-27 07:08:33 +00:00
|
|
|
func ListModels(models []*gallery.GalleryModel, installing *xsync.SyncedMap[string, string]) string {
|
|
|
|
//StartProgressBar(uid, "0")
|
2024-04-23 07:22:58 +00:00
|
|
|
modelsElements := []elem.Node{}
|
2024-04-28 21:42:46 +00:00
|
|
|
// span := func(s string) elem.Node {
|
|
|
|
// return elem.Span(
|
|
|
|
// attrs.Props{
|
|
|
|
// "class": "float-right inline-block bg-green-500 text-white py-1 px-3 rounded-full text-xs",
|
|
|
|
// },
|
|
|
|
// elem.Text(s),
|
|
|
|
// )
|
|
|
|
// }
|
2024-04-23 07:22:58 +00:00
|
|
|
|
|
|
|
descriptionDiv := func(m *gallery.GalleryModel) elem.Node {
|
|
|
|
|
|
|
|
return elem.Div(
|
|
|
|
attrs.Props{
|
2024-04-23 16:43:25 +00:00
|
|
|
"class": "p-6 text-surface dark:text-white",
|
2024-04-23 07:22:58 +00:00
|
|
|
},
|
|
|
|
elem.H5(
|
|
|
|
attrs.Props{
|
|
|
|
"class": "mb-2 text-xl font-medium leading-tight",
|
|
|
|
},
|
|
|
|
elem.Text(m.Name),
|
|
|
|
),
|
|
|
|
elem.P(
|
|
|
|
attrs.Props{
|
|
|
|
"class": "mb-4 text-base",
|
|
|
|
},
|
|
|
|
elem.Text(m.Description),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
actionDiv := func(m *gallery.GalleryModel) elem.Node {
|
2024-04-27 07:08:33 +00:00
|
|
|
galleryID := fmt.Sprintf("%s@%s", m.Gallery.Name, m.Name)
|
|
|
|
currentlyInstalling := installing.Exists(galleryID)
|
|
|
|
|
2024-04-23 16:43:25 +00:00
|
|
|
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"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-05-06 23:17:07 +00:00
|
|
|
tagsNodes := []elem.Node{}
|
2024-04-23 16:43:25 +00:00
|
|
|
for _, tag := range m.Tags {
|
2024-05-06 23:17:07 +00:00
|
|
|
tagsNodes = append(tagsNodes,
|
|
|
|
searchableElement(tag, "fas fa-tag"),
|
2024-04-23 16:43:25 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-05-06 23:17:07 +00:00
|
|
|
nodes = append(nodes,
|
|
|
|
elem.Div(
|
|
|
|
attrs.Props{
|
|
|
|
"class": "flex flex-row flex-wrap content-center",
|
|
|
|
},
|
|
|
|
tagsNodes...,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2024-04-23 16:43:25 +00:00
|
|
|
for i, url := range m.URLs {
|
|
|
|
nodes = append(nodes,
|
2024-05-06 23:17:07 +00:00
|
|
|
link("Link #"+fmt.Sprintf("%d", i+1), url),
|
|
|
|
)
|
2024-04-23 16:43:25 +00:00
|
|
|
}
|
|
|
|
|
2024-04-23 07:22:58 +00:00
|
|
|
return elem.Div(
|
|
|
|
attrs.Props{
|
|
|
|
"class": "px-6 pt-4 pb-2",
|
|
|
|
},
|
2024-04-23 16:43:25 +00:00
|
|
|
elem.P(
|
2024-04-23 07:22:58 +00:00
|
|
|
attrs.Props{
|
2024-04-23 16:43:25 +00:00
|
|
|
"class": "mb-4 text-base",
|
2024-04-23 07:22:58 +00:00
|
|
|
},
|
2024-04-23 16:43:25 +00:00
|
|
|
nodes...,
|
2024-04-23 07:22:58 +00:00
|
|
|
),
|
2024-04-27 07:08:33 +00:00
|
|
|
elem.If(
|
|
|
|
currentlyInstalling,
|
|
|
|
elem.Node( // If currently installing, show progress bar
|
2024-04-28 21:42:46 +00:00
|
|
|
elem.Raw(StartProgressBar(installing.Get(galleryID), "0", "Installing")),
|
2024-04-27 07:08:33 +00:00
|
|
|
), // Otherwise, show install button (if not installed) or display "Installed"
|
|
|
|
elem.If(m.Installed,
|
2024-05-06 23:17:07 +00:00
|
|
|
elem.Node(elem.Div(
|
|
|
|
attrs.Props{},
|
|
|
|
reInstallButton(m.ID()),
|
|
|
|
deleteButton(m.Name),
|
|
|
|
)),
|
|
|
|
installButton(m.ID()),
|
2024-04-27 07:08:33 +00:00
|
|
|
),
|
|
|
|
),
|
2024-04-23 07:22:58 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range models {
|
2024-04-23 16:43:25 +00:00
|
|
|
|
|
|
|
elems := []elem.Node{}
|
|
|
|
|
2024-04-23 18:10:58 +00:00
|
|
|
if m.Icon == "" {
|
|
|
|
m.Icon = NoImage
|
|
|
|
}
|
|
|
|
|
2024-05-07 06:39:23 +00:00
|
|
|
divProperties := attrs.Props{
|
|
|
|
"class": "flex justify-center items-center",
|
|
|
|
}
|
|
|
|
|
|
|
|
_, trustRemoteCodeExists := m.Overrides["trust_remote_code"]
|
|
|
|
if trustRemoteCodeExists {
|
|
|
|
// should this be checking for trust_remote_code: false? I don't think we ever use that value.
|
|
|
|
divProperties["class"] = divProperties["class"] + " remote-code"
|
|
|
|
}
|
|
|
|
|
2024-04-23 18:10:58 +00:00
|
|
|
elems = append(elems,
|
2024-04-23 16:43:25 +00:00
|
|
|
|
2024-05-07 06:39:23 +00:00
|
|
|
elem.Div(divProperties,
|
2024-04-23 18:10:58 +00:00
|
|
|
elem.A(attrs.Props{
|
|
|
|
"href": "#!",
|
|
|
|
// "class": "justify-center items-center",
|
2024-04-23 16:43:25 +00:00
|
|
|
},
|
2024-04-23 18:10:58 +00:00
|
|
|
elem.Img(attrs.Props{
|
|
|
|
// "class": "rounded-t-lg object-fit object-center h-96",
|
|
|
|
"class": "rounded-t-lg max-h-48 max-w-96 object-cover mt-3",
|
|
|
|
"src": m.Icon,
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
))
|
2024-04-23 16:43:25 +00:00
|
|
|
|
|
|
|
elems = append(elems, descriptionDiv(m), actionDiv(m))
|
2024-04-23 07:22:58 +00:00
|
|
|
modelsElements = append(modelsElements,
|
|
|
|
elem.Div(
|
|
|
|
attrs.Props{
|
2024-04-23 16:43:25 +00:00
|
|
|
"class": " me-4 mb-2 block rounded-lg bg-white shadow-secondary-1 dark:bg-gray-800 dark:bg-surface-dark dark:text-white text-surface pb-2",
|
2024-04-23 07:22:58 +00:00
|
|
|
},
|
|
|
|
elem.Div(
|
|
|
|
attrs.Props{
|
2024-04-23 16:43:25 +00:00
|
|
|
// "class": "p-6",
|
2024-04-23 07:22:58 +00:00
|
|
|
},
|
2024-04-23 16:43:25 +00:00
|
|
|
elems...,
|
2024-04-23 07:22:58 +00:00
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
wrapper := elem.Div(attrs.Props{
|
2024-04-23 16:43:25 +00:00
|
|
|
"class": "dark grid grid-cols-1 grid-rows-1 md:grid-cols-3 block rounded-lg shadow-secondary-1 dark:bg-surface-dark",
|
|
|
|
//"class": "block rounded-lg bg-white shadow-secondary-1 dark:bg-surface-dark",
|
2024-04-23 07:22:58 +00:00
|
|
|
}, modelsElements...)
|
|
|
|
|
|
|
|
return wrapper.Render()
|
|
|
|
}
|