diff --git a/core/http/elements/buttons.go b/core/http/elements/buttons.go index 2364a0b3..b2ce904b 100644 --- a/core/http/elements/buttons.go +++ b/core/http/elements/buttons.go @@ -13,7 +13,7 @@ func installButton(galleryName string) elem.Node { 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", + "class": "float-right inline-flex items-center rounded-lg bg-blue-600 hover:bg-blue-700 px-4 py-2 text-sm font-medium text-white transition duration-300 ease-in-out shadow hover:shadow-lg", "hx-swap": "outerHTML", // post the Model ID as param "hx-post": "browse/install/model/" + galleryName, @@ -52,7 +52,7 @@ func infoButton(m *gallery.GalleryModel) elem.Node { 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", + "class": "inline-flex items-center rounded-lg bg-gray-700 hover:bg-gray-600 px-4 py-2 text-sm font-medium text-white transition duration-300 ease-in-out", "data-modal-target": modalName(m), "data-modal-toggle": modalName(m), }, diff --git a/core/http/elements/gallery.go b/core/http/elements/gallery.go index 5ab68508..539627e4 100644 --- a/core/http/elements/gallery.go +++ b/core/http/elements/gallery.go @@ -17,7 +17,7 @@ const ( 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", + "class": "inline-flex items-center px-3 py-1 rounded-lg text-xs font-medium bg-gray-700/70 text-gray-300 border border-gray-600/50 mr-2 mb-2", }, elem.I(attrs.Props{ "class": icon + " pr-2", @@ -39,19 +39,20 @@ func searchableElement(text, icon string) elem.Node { ), 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", + "class": "inline-flex items-center text-xs px-3 py-1 rounded-full bg-gray-700/60 text-gray-300 border border-gray-600/50 hover:bg-gray-600 hover:text-gray-100 transition duration-200 ease-in-out", }, 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", + //"href": "#!", + "href": "browse?term=" + text, + //"hx-post": "browse/search/models", + //"hx-target": "#search-results", // TODO: this doesn't work // "hx-vals": `{ \"search\": \"` + text + `\" }`, - "hx-indicator": ".htmx-indicator", + //"hx-indicator": ".htmx-indicator", }, elem.I(attrs.Props{ "class": icon + " pr-2", @@ -101,7 +102,7 @@ func modalName(m *gallery.GalleryModel) string { return m.Name + "-modal" } -func modelDescription(m *gallery.GalleryModel) elem.Node { +func modelModal(m *gallery.GalleryModel) elem.Node { urls := []elem.Node{} for _, url := range m.URLs { urls = append(urls, @@ -116,6 +117,125 @@ func modelDescription(m *gallery.GalleryModel) elem.Node { ) } + return 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.Div( + attrs.Props{ + "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 modelDescription(m *gallery.GalleryModel) elem.Node { return elem.Div( attrs.Props{ "class": "p-6 text-surface dark:text-white", @@ -132,122 +252,6 @@ func modelDescription(m *gallery.GalleryModel) elem.Node { }, 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.Div( - attrs.Props{ - "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"), - ), - ), - ), - ), - ), ) } @@ -397,7 +401,7 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g modelsElements = append(modelsElements, elem.Div( attrs.Props{ - "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", + "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 bg-gray-800/90 border border-gray-700/50 rounded-xl overflow-hidden transition-all duration-300 hover:shadow-lg hover:shadow-blue-900/20 hover:-translate-y-1 hover:border-blue-700/50", }, elem.Div( attrs.Props{ @@ -406,6 +410,7 @@ func ListModels(models []*gallery.GalleryModel, processTracker ProcessTracker, g elems..., ), ), + modelModal(m), ) } diff --git a/core/http/static/chat.js b/core/http/static/chat.js index ac1e0ba8..b1e4e9cb 100644 --- a/core/http/static/chat.js +++ b/core/http/static/chat.js @@ -27,6 +27,21 @@ SOFTWARE. */ +function toggleLoader(show) { + const loader = document.getElementById('loader'); + const sendButton = document.getElementById('send-button'); + + if (show) { + loader.style.display = 'block'; + sendButton.style.display = 'none'; + document.getElementById("input").disabled = true; + } else { + document.getElementById("input").disabled = false; + loader.style.display = 'none'; + sendButton.style.display = 'block'; + } +} + function submitKey(event) { event.preventDefault(); localStorage.setItem("key", document.getElementById("apiKey").value); @@ -72,8 +87,8 @@ function readInputImage() { // Set class "loader" to the element with "loader" id //document.getElementById("loader").classList.add("loader"); // Make the "loader" visible - document.getElementById("loader").style.display = "block"; - document.getElementById("input").disabled = true; + toggleLoader(true); + messages = Alpine.store("chat").messages(); @@ -243,10 +258,8 @@ function readInputImage() { } // Remove class "loader" from the element with "loader" id - //document.getElementById("loader").classList.remove("loader"); - document.getElementById("loader").style.display = "none"; - // enable input - document.getElementById("input").disabled = false; + toggleLoader(false); + // scroll to the bottom of the chat document.getElementById('messages').scrollIntoView(false) // set focus to the input diff --git a/core/http/static/general.css b/core/http/static/general.css index 63007cf5..7caa384a 100644 --- a/core/http/static/general.css +++ b/core/http/static/general.css @@ -10,18 +10,6 @@ body { .htmx-request .htmx-indicator{ opacity:1 } -/* Loader (https://cssloaders.github.io/) */ -.loader { - width: 12px; - height: 12px; - border-radius: 50%; - display: block; - margin:15px auto; - position: relative; - color: #FFF; - box-sizing: border-box; - animation: animloader 2s linear infinite; -} @keyframes animloader { 0% { box-shadow: 14px 0 0 -2px, 38px 0 0 -2px, -14px 0 0 -2px, -38px 0 0 -2px; } diff --git a/core/http/views/chat.html b/core/http/views/chat.html index 3486e101..282d09c2 100644 --- a/core/http/views/chat.html +++ b/core/http/views/chat.html @@ -293,46 +293,61 @@ SOFTWARE. - -
- - - -
-
- - - - -
+ +
+ +
+ + + + + +
+ + + + + +
+
+ + + +
diff --git a/core/http/views/index.html b/core/http/views/index.html index b9fd7074..4ae17fdc 100644 --- a/core/http/views/index.html +++ b/core/http/views/index.html @@ -2,125 +2,185 @@ {{template "views/partials/head" .}} - +
{{template "views/partials/navbar" .}} -
-
-

Welcome to your LocalAI instance!

-

The FOSS alternative to OpenAI, Claude, ...

- - Documentation - +
+ +
+
+

+ + Welcome to your LocalAI instance! + +

+

The FOSS alternative to OpenAI, Claude, and more

+ +
-
+ +
{{template "views/partials/inprogress" .}} + {{ if eq (len .ModelsConfig) 0 }} -

Ouch! seems you don't have any models installed from the LocalAI gallery!

-

..install something from the 🖼️ Gallery or check the Getting started documentation

+
+
+
+ +
+

No models installed from the LocalAI gallery

+

Install models from the 🖼️ Gallery or check the Getting started documentation

- {{ if ne (len .Models) 0 }} -
-

- However, It seems you have installed some models installed without a configuration file: -

- {{ range .Models }} -
-

{{.Name}}

-
- {{end}} - {{end}} + {{ if ne (len .Models) 0 }} +
+

Models installed without a configuration file:

+
+ {{ range .Models }} +
+ +

{{.Name}}

+
+ {{end}} +
+
+ {{end}} +
+
{{ else }} {{ $modelsN := len .ModelsConfig}} {{ $modelsN = add $modelsN (len .Models)}} -

{{$modelsN}} Installed model(s)

- - - - - - - - - - +
+

+ {{$modelsN}} Installed Model +

+ +
+ +
{{$galleryConfig:=.GalleryConfig}} {{$noicon:="https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg"}} + {{ range .ModelsConfig }} {{ $backendCfg := . }} {{ $cfg:= index $galleryConfig .Name}} -
- - - - - + +
+
+

{{.Name}}

+ + + +
+ +
+ {{ if .Backend }} + + {{.Backend}} + + {{ else }} + + auto + + {{ end }} +
+
+ + +
+
+ {{ range .KnownUsecaseStrings }} + {{ if eq . "FLAG_CHAT" }} + + Chat + + {{ end }} + {{ if eq . "FLAG_IMAGE" }} + + Image + + {{ end }} + {{ if eq . "FLAG_TTS" }} + + TTS + + {{ end }} + {{ end }} +
+ +
+ +
+
+ {{ end }} + {{ range .Models }} - - - - - - +
+
+
+ Model icon +
+
+
+

{{.}}

+
+ +
+ + auto + + + No Configuration + +
+
+
+
{{end}} - -
Model NameBackendActions
- {{ with $cfg }} - +
+
+ {{.Name}} icon - {{ else}} - - {{ end }} -
-

{{.Name}}

- {{ range .KnownUsecaseStrings }} - {{ if eq . "FLAG_CHAT" }} - Chat - {{ end }} - {{ if eq . "FLAG_IMAGE" }} - Image - {{ end }} - {{ if eq . "FLAG_TTS" }} - TTS - {{ end }} - {{ end }} -
- {{ if .Backend }} - - - {{.Backend}} - - {{ else }} - - auto - - {{ end }} - - -
- - -

{{.}}

-
- - auto - - - - No Configuration - -
+
{{ end }}
@@ -129,4 +189,4 @@
- + \ No newline at end of file diff --git a/core/http/views/models.html b/core/http/views/models.html index c3910bdc..f744832f 100644 --- a/core/http/views/models.html +++ b/core/http/views/models.html @@ -2,124 +2,164 @@ {{template "views/partials/head" .}} - +
{{template "views/partials/navbar" .}} {{ $numModelsPerPage := 21 }} -
+
-
-

- 🖼️ Available {{.AvailableModels}} models from {{ len .Repositories }} repositories - -

- -
-

Filter by type:

- - - - - - - + +
+
+

+ + Model Gallery + +

+

+ {{.AvailableModels}} models from + {{ len .Repositories }} repositories + + + +

- -
- Filter by tags: - {{ range .AllTags }} - - {{ end }} +
+ + +
+ +
+
+ +
+ + + + + + +
- - - {{template "views/partials/inprogress" .}} - - - -
{{.Models}}
- {{ if gt .AvailableModels $numModelsPerPage }} - -
-
- - - +
+

Filter by type:

+
+ - + + + + + - -->
- {{ end }} + + +
+

Filter by tags:

+
+ {{ range .AllTags }} + + {{ end }} +
+
+ + +
+ {{.Models}} +
+ + + {{ if gt .AvailableModels $numModelsPerPage }} +
+
+ +
+ Page {{add .PrevPage 1}} +
+ +
+
+ {{ end }} +
{{template "views/partials/footer" .}}
diff --git a/core/http/views/text2image.html b/core/http/views/text2image.html index 10eef2d7..1eb16b11 100644 --- a/core/http/views/text2image.html +++ b/core/http/views/text2image.html @@ -7,21 +7,31 @@
{{template "views/partials/navbar" .}} -
+
+ +
+
+

+ + Image generation {{ if .Model }} with {{.Model}} {{ end }} + +

+ +
+
+ +
-
- - 🖼️ Text to Image {{ if .Model }} (using {{.Model}}) {{ end }} - - - - - -
- -
+
-
+
+ + + + + +
-
-
-