mirror of
https://github.com/mudler/LocalAI.git
synced 2025-04-10 21:00:37 +00:00
feat(ui): improvements to index and models page (#4918)
Some checks are pending
Explorer deployment / build-linux (push) Waiting to run
GPU tests / ubuntu-latest (1.21.x) (push) Waiting to run
generate and publish intel docker caches / generate_caches (intel/oneapi-basekit:2025.0.0-0-devel-ubuntu22.04, linux/amd64, ubuntu-latest) (push) Waiting to run
build container images / hipblas-jobs (-aio-gpu-hipblas, rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, extras, latest-gpu-hipblas, latest-aio-gpu-hipblas, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -hipblas-ffmpeg) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-core) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f16, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, extras, latest-gpu-intel-f16, latest-aio-gpu-intel-f16, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f16-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f32, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, extras, latest-gpu-intel-f32, latest-aio-gpu-intel-f32, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f32-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-11, ubuntu:22.04, cublas, 11, 7, true, extras, latest-gpu-nvidia-cuda-11, latest-aio-gpu-nvidia-cuda-11, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda11-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-12, ubuntu:22.04, cublas, 12, 0, true, extras, latest-gpu-nvidia-cuda-12, latest-aio-gpu-nvidia-cuda-12, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda12-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, ) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , true, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 11, 7, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda11) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 12, 0, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda12) (push) Waiting to run
build container images / core-image-build (-aio-cpu, ubuntu:22.04, , true, core, latest-cpu, latest-aio-cpu, --jobs=4 --output-sync=target, linux/amd64,linux/arm64, arc-runner-set, false, auto, -ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, vulkan, true, core, latest-vulkan-ffmpeg-core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -vulkan-ffmpeg-core) (push) Waiting to run
build container images / gh-runner (nvcr.io/nvidia/l4t-jetpack:r36.4.0, cublas, 12, 0, true, core, latest-nvidia-l4t-arm64-core, --jobs=4 --output-sync=target, linux/arm64, ubuntu-24.04-arm, true, false, -nvidia-l4t-arm64-core) (push) Waiting to run
Security Scan / tests (push) Waiting to run
Tests extras backends / tests-transformers (push) Waiting to run
Tests extras backends / tests-rerankers (push) Waiting to run
Tests extras backends / tests-diffusers (push) Waiting to run
Tests extras backends / tests-coqui (push) Waiting to run
tests / tests-linux (1.21.x) (push) Waiting to run
tests / tests-aio-container (push) Waiting to run
tests / tests-apple (1.21.x) (push) Waiting to run
Some checks are pending
Explorer deployment / build-linux (push) Waiting to run
GPU tests / ubuntu-latest (1.21.x) (push) Waiting to run
generate and publish intel docker caches / generate_caches (intel/oneapi-basekit:2025.0.0-0-devel-ubuntu22.04, linux/amd64, ubuntu-latest) (push) Waiting to run
build container images / hipblas-jobs (-aio-gpu-hipblas, rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, extras, latest-gpu-hipblas, latest-aio-gpu-hipblas, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -hipblas-ffmpeg) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-core) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, false, ubuntu:22.04, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas) (push) Waiting to run
build container images / hipblas-jobs (rocm/dev-ubuntu-22.04:6.1, hipblas, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -hipblas-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f16, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, extras, latest-gpu-intel-f16, latest-aio-gpu-intel-f16, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f16-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-intel-f32, quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, extras, latest-gpu-intel-f32, latest-aio-gpu-intel-f32, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -sycl-f32-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-11, ubuntu:22.04, cublas, 11, 7, true, extras, latest-gpu-nvidia-cuda-11, latest-aio-gpu-nvidia-cuda-11, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda11-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (-aio-gpu-nvidia-cuda-12, ubuntu:22.04, cublas, 12, 0, true, extras, latest-gpu-nvidia-cuda-12, latest-aio-gpu-nvidia-cuda-12, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -cublas-cuda12-ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f16, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f16-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, false, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-core) (push) Waiting to run
build container images / self-hosted-jobs (quay.io/go-skynet/intel-oneapi-base:latest, sycl_f32, true, ubuntu:22.04, core, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -sycl-f32-ffmpeg-core) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, ) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, , true, extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, auto, -ffmpeg) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 11, 7, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda11) (push) Waiting to run
build container images / self-hosted-jobs (ubuntu:22.04, cublas, 12, 0, , extras, --jobs=3 --output-sync=target, linux/amd64, arc-runner-set, false, -cublas-cuda12) (push) Waiting to run
build container images / core-image-build (-aio-cpu, ubuntu:22.04, , true, core, latest-cpu, latest-aio-cpu, --jobs=4 --output-sync=target, linux/amd64,linux/arm64, arc-runner-set, false, auto, -ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 11, 7, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda11-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, , core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, cublas, 12, 0, true, core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -cublas-cuda12-ffmpeg-core) (push) Waiting to run
build container images / core-image-build (ubuntu:22.04, vulkan, true, core, latest-vulkan-ffmpeg-core, --jobs=4 --output-sync=target, linux/amd64, arc-runner-set, false, false, -vulkan-ffmpeg-core) (push) Waiting to run
build container images / gh-runner (nvcr.io/nvidia/l4t-jetpack:r36.4.0, cublas, 12, 0, true, core, latest-nvidia-l4t-arm64-core, --jobs=4 --output-sync=target, linux/arm64, ubuntu-24.04-arm, true, false, -nvidia-l4t-arm64-core) (push) Waiting to run
Security Scan / tests (push) Waiting to run
Tests extras backends / tests-transformers (push) Waiting to run
Tests extras backends / tests-rerankers (push) Waiting to run
Tests extras backends / tests-diffusers (push) Waiting to run
Tests extras backends / tests-coqui (push) Waiting to run
tests / tests-linux (1.21.x) (push) Waiting to run
tests / tests-aio-container (push) Waiting to run
tests / tests-apple (1.21.x) (push) Waiting to run
- mobile-friendly index - adjust color palette - improve search experience Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
parent
e4fdde158f
commit
755e4fb5f4
@ -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),
|
||||
},
|
||||
|
@ -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(
|
||||
`<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||
</svg>`,
|
||||
),
|
||||
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(
|
||||
`<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||
</svg>`,
|
||||
),
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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; }
|
||||
|
@ -293,46 +293,61 @@ SOFTWARE.
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Chat Input -->
|
||||
<div class="p-4 border-t border-gray-700" x-data="{ inputValue: '', shiftPressed: false, fileName: '' }">
|
||||
<div id="loader" class="my-2 loader" style="display: none;"></div>
|
||||
<input id="chat-model" type="hidden" value="{{.Model}}">
|
||||
<input
|
||||
id="input_image"
|
||||
type="file"
|
||||
style="display: none;"
|
||||
@change="fileName = $event.target.files[0].name"
|
||||
/>
|
||||
<form id="prompt" action="chat/{{.Model}}" method="get" @submit.prevent="submitPrompt" class="max-w-3xl mx-auto">
|
||||
<div class="relative w-full bg-gray-800 rounded-xl shadow-md">
|
||||
<textarea
|
||||
id="input"
|
||||
name="input"
|
||||
x-model="inputValue"
|
||||
placeholder="Send a message..."
|
||||
class="p-4 pr-16 w-full bg-gray-800 text-gray-100 placeholder-gray-400 focus:outline-none resize-none border-0 rounded-xl transition-colors duration-200"
|
||||
required
|
||||
@keydown.shift="shiftPressed = true"
|
||||
@keyup.shift="shiftPressed = false"
|
||||
@keydown.enter="if (!shiftPressed) { submitPrompt($event); }"
|
||||
rows="3"
|
||||
style="box-shadow: 0 0 0 1px rgba(75, 85, 99, 0.4) inset;"
|
||||
></textarea>
|
||||
<span x-text="fileName" id="fileName" class="absolute right-16 top-4 text-gray-400 text-sm mr-2"></span>
|
||||
<button
|
||||
type="button"
|
||||
onclick="document.getElementById('input_image').click()"
|
||||
class="fa-solid fa-paperclip text-gray-400 absolute right-12 top-4 text-lg p-2 hover:text-blue-400 transition-colors duration-200"
|
||||
title="Attach an image"
|
||||
></button>
|
||||
<button
|
||||
type="submit"
|
||||
class="absolute right-3 top-4 text-lg p-2 text-gray-400 hover:text-blue-400 transition-colors duration-200"
|
||||
title="Send message"
|
||||
>
|
||||
<i class="fa-solid fa-paper-plane"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Chat Input -->
|
||||
<div class="p-4 border-t border-gray-700" x-data="{ inputValue: '', shiftPressed: false, fileName: '', isLoading: false }">
|
||||
<form id="prompt" action="chat/{{.Model}}" method="get" @submit.prevent="submitPrompt" class="max-w-3xl mx-auto">
|
||||
<div class="relative w-full bg-gray-800 rounded-xl shadow-md">
|
||||
<textarea
|
||||
id="input"
|
||||
name="input"
|
||||
x-model="inputValue"
|
||||
placeholder="Send a message..."
|
||||
class="p-4 pr-16 w-full bg-gray-800 text-gray-100 placeholder-gray-400 focus:outline-none resize-none border-0 rounded-xl transition-colors duration-200"
|
||||
required
|
||||
@keydown.shift="shiftPressed = true"
|
||||
@keyup.shift="shiftPressed = false"
|
||||
@keydown.enter="if (!shiftPressed) { submitPrompt($event); }"
|
||||
rows="3"
|
||||
style="box-shadow: 0 0 0 1px rgba(75, 85, 99, 0.4) inset;"
|
||||
></textarea>
|
||||
<span x-text="fileName" id="fileName" class="absolute right-16 top-4 text-gray-400 text-sm mr-2"></span>
|
||||
<button
|
||||
type="button"
|
||||
onclick="document.getElementById('input_image').click()"
|
||||
class="fa-solid fa-paperclip text-gray-400 absolute right-12 top-4 text-lg p-2 hover:text-blue-400 transition-colors duration-200"
|
||||
title="Attach an image"
|
||||
></button>
|
||||
|
||||
<!-- Send button and loader in the same position -->
|
||||
<div class="absolute right-3 top-4">
|
||||
<!-- Loader (hidden by default) -->
|
||||
<div id="loader" class="text-lg p-2" style="display: none;">
|
||||
<svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<!-- Send button -->
|
||||
<button
|
||||
id="send-button"
|
||||
type="submit"
|
||||
class="text-lg p-2 text-gray-400 hover:text-blue-400 transition-colors duration-200"
|
||||
title="Send message"
|
||||
>
|
||||
<i class="fa-solid fa-paper-plane"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<input id="chat-model" type="hidden" value="{{.Model}}">
|
||||
<input
|
||||
id="input_image"
|
||||
type="file"
|
||||
style="display: none;"
|
||||
@change="fileName = $event.target.files[0].name"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -2,125 +2,185 @@
|
||||
<html lang="en">
|
||||
{{template "views/partials/head" .}}
|
||||
|
||||
<body class="bg-gray-900 text-gray-200">
|
||||
<body class="bg-gradient-to-br from-gray-900 to-gray-950 text-gray-200">
|
||||
<div class="flex flex-col min-h-screen">
|
||||
|
||||
{{template "views/partials/navbar" .}}
|
||||
|
||||
<div class="container mx-auto px-4 flex-grow">
|
||||
<div class="header text-center py-12">
|
||||
<h1 class="text-5xl font-bold text-gray-100">Welcome to <i>your</i> LocalAI instance!</h1>
|
||||
<p class="mt-4 text-lg">The FOSS alternative to OpenAI, Claude, ...</p>
|
||||
<a href="https://localai.io" target="_blank" class="mt-4 inline-block bg-blue-500 text-white py-2 px-4 rounded-lg shadow transition duration-300 ease-in-out hover:bg-blue-700 hover:shadow-lg">
|
||||
<i class="fas fa-book-reader pr-2"></i>Documentation
|
||||
</a>
|
||||
<div class="container mx-auto px-4 py-8 flex-grow">
|
||||
<!-- Hero Section -->
|
||||
<div class="bg-gradient-to-r from-blue-900/30 to-indigo-900/30 rounded-2xl shadow-xl p-8 mb-10">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<h1 class="text-4xl md:text-5xl font-bold text-white mb-4">
|
||||
<span class="bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-indigo-400">
|
||||
Welcome to <i>your</i> LocalAI instance!
|
||||
</span>
|
||||
</h1>
|
||||
<p class="text-xl text-gray-300 mb-6">The FOSS alternative to OpenAI, Claude, and more</p>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="https://localai.io" target="_blank"
|
||||
class="group flex items-center bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-lg">
|
||||
<i class="fas fa-book-reader mr-2"></i>
|
||||
<span>Documentation</span>
|
||||
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
|
||||
</a>
|
||||
<a href="browse"
|
||||
class="group flex items-center bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-lg">
|
||||
<i class="fas fa-images mr-2"></i>
|
||||
<span>Gallery</span>
|
||||
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="models mt-4">
|
||||
<!-- Models Section -->
|
||||
<div class="models mt-8">
|
||||
{{template "views/partials/inprogress" .}}
|
||||
|
||||
{{ if eq (len .ModelsConfig) 0 }}
|
||||
<h2 class="text-center text-3xl font-semibold text-gray-100"> <i class="text-yellow-200 ml-2 fa-solid fa-triangle-exclamation animate-pulse"></i> Ouch! seems you don't have any models installed from the LocalAI gallery!</h2>
|
||||
<p class="text-center mt-4 text-xl">..install something from the <a class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded" href="browse">🖼️ Gallery</a> or check the <a href="https://localai.io/basics/getting_started/" class="text-gray-400 hover:text-white ml-1 px-3 py-2 rounded"> <i class="fa-solid fa-book"></i> Getting started documentation </a></p>
|
||||
<div class="bg-gray-800/50 border border-gray-700/50 rounded-xl p-8 shadow-md backdrop-blur-sm">
|
||||
<div class="text-center max-w-3xl mx-auto">
|
||||
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-yellow-500/20 mb-4">
|
||||
<i class="text-yellow-400 text-2xl fa-solid fa-triangle-exclamation"></i>
|
||||
</div>
|
||||
<h2 class="text-2xl md:text-3xl font-semibold text-gray-100 mb-4">No models installed from the LocalAI gallery</h2>
|
||||
<p class="text-lg text-gray-300 mb-6">Install models from the <a class="text-blue-400 hover:text-blue-300 underline underline-offset-2" href="browse">🖼️ Gallery</a> or check the <a href="https://localai.io/basics/getting_started/" class="text-blue-400 hover:text-blue-300 underline underline-offset-2"> <i class="fa-solid fa-book"></i> Getting started documentation</a></p>
|
||||
|
||||
{{ if ne (len .Models) 0 }}
|
||||
<hr class="my-4">
|
||||
<h3 class="text-center text-xl font-semibold text-gray-100">
|
||||
However, It seems you have installed some models installed without a configuration file:
|
||||
</h3>
|
||||
{{ range .Models }}
|
||||
<div class="bg-gray-800 border-b border-gray-700 p-4 mt-4">
|
||||
<h4 class="text-md font-bold text-gray-200">{{.Name}}</h4>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{ if ne (len .Models) 0 }}
|
||||
<div class="mt-8 pt-8 border-t border-gray-700/50">
|
||||
<h3 class="text-xl font-semibold text-gray-100 mb-4">Models installed without a configuration file:</h3>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{{ range .Models }}
|
||||
<div class="bg-gray-800/80 border border-gray-700 rounded-lg p-4 flex items-center">
|
||||
<i class="fas fa-brain text-lg text-gray-400 mr-3"></i>
|
||||
<p class="font-medium text-gray-200">{{.Name}}</p>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ $modelsN := len .ModelsConfig}}
|
||||
{{ $modelsN = add $modelsN (len .Models)}}
|
||||
<h2 class="text-center text-3xl font-semibold text-gray-100">{{$modelsN}} Installed model(s)</h2>
|
||||
<table class="table-auto mt-4 w-full text-left text-gray-200">
|
||||
<thead class="text-xs text-gray-400 uppercase bg-gray-700">
|
||||
<tr>
|
||||
<th class="px-4 py-2"></th>
|
||||
<th class="px-4 py-2"><i class="fas fa-brain pr-2"></i>Model Name</th>
|
||||
<th class="px-4 py-2">Backend</th>
|
||||
<th class="px-4 py-2 float-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<div class="mb-6 flex flex-col md:flex-row md:items-center md:justify-between">
|
||||
<h2 class="text-2xl md:text-3xl font-bold text-white mb-4 md:mb-0">
|
||||
<span class="text-blue-400">{{$modelsN}}</span> Installed Model<span class="{{if gt $modelsN 1}}s{{end}}">
|
||||
</h2>
|
||||
<!--
|
||||
<div class="flex gap-4">
|
||||
<button class="text-sm bg-gray-800 hover:bg-gray-700 text-gray-300 py-2 px-4 rounded-lg transition flex items-center gap-2">
|
||||
<i class="fas fa-filter"></i> Filter
|
||||
</button>
|
||||
<button class="text-sm bg-gray-800 hover:bg-gray-700 text-gray-300 py-2 px-4 rounded-lg transition flex items-center gap-2">
|
||||
<i class="fas fa-sort"></i> Sort
|
||||
</button>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{{$galleryConfig:=.GalleryConfig}}
|
||||
{{$noicon:="https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg"}}
|
||||
|
||||
{{ range .ModelsConfig }}
|
||||
{{ $backendCfg := . }}
|
||||
{{ $cfg:= index $galleryConfig .Name}}
|
||||
<tr class="bg-gray-800 border-b border-gray-700">
|
||||
<td class="px-4 py-3">
|
||||
{{ with $cfg }}
|
||||
<img {{ if $cfg.Icon }}
|
||||
src="{{$cfg.Icon}}"
|
||||
{{ else }}
|
||||
src="{{$noicon}}"
|
||||
{{ end }}
|
||||
class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3"
|
||||
<div class="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">
|
||||
<div class="flex p-5">
|
||||
<div class="w-20 h-20 rounded-lg overflow-hidden flex-shrink-0 bg-gray-700/50 flex items-center justify-center">
|
||||
<img {{ if and $cfg $cfg.Icon }}
|
||||
src="{{$cfg.Icon}}"
|
||||
{{ else }}
|
||||
src="{{$noicon}}"
|
||||
{{ end }}
|
||||
class="w-full h-full object-contain"
|
||||
alt="{{.Name}} icon"
|
||||
>
|
||||
{{ else}}
|
||||
<img src="{{$noicon}}" class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3">
|
||||
{{ end }}
|
||||
</td>
|
||||
<td class="px-4 py-3 font-bold">
|
||||
<p class="font-bold text-white flex items-center">{{.Name}} <a href="browse?term={{.Name}}" class="ml-2 text-blue-400 hover:text-blue-600"><i class="fas fa-search"></i></a></p>
|
||||
{{ range .KnownUsecaseStrings }}
|
||||
{{ if eq . "FLAG_CHAT" }}
|
||||
<a href="chat/{{$backendCfg.Name}}" class="ml-2 bg-blue-500 text-white py-1 px-3 rounded-lg shadow transition duration-300 ease-in-out hover:bg-blue-700 hover:shadow-lg"><i class="fas fa-comments pr-1"></i>Chat</a>
|
||||
{{ end }}
|
||||
{{ if eq . "FLAG_IMAGE" }}
|
||||
<a href="text2image/{{$backendCfg.Name}}" class="ml-2 bg-green-500 text-white py-1 px-3 rounded-lg shadow transition duration-300 ease-in-out hover:bg-green-700 hover:shadow-lg"><i class="fas fa-image pr-1"></i>Image</a>
|
||||
{{ end }}
|
||||
{{ if eq . "FLAG_TTS" }}
|
||||
<a href="tts/{{$backendCfg.Name}}" class="ml-2 bg-purple-500 text-white py-1 px-3 rounded-lg shadow transition duration-300 ease-in-out hover:bg-purple-700 hover:shadow-lg"><i class="fas fa-microphone pr-1"></i>TTS</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</td>
|
||||
<td class="px-4 py-3 font-bold">
|
||||
{{ if .Backend }}
|
||||
<!-- Badge for Backend -->
|
||||
<span class="inline-block bg-blue-500 text-white py-1 px-3 rounded-full text-xs">
|
||||
{{.Backend}}
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="inline-block bg-yellow-500 text-white py-1 px-3 rounded-full text-xs">
|
||||
auto
|
||||
</span>
|
||||
{{ end }}
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-3">
|
||||
<button
|
||||
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"
|
||||
data-twe-ripple-color="light" data-twe-ripple-init="" hx-confirm="Are you sure you wish to delete the model?" hx-post="browse/delete/model/{{.Name}}" hx-swap="outerHTML"><i class="fa-solid fa-cancel pr-2"></i>Delete</button>
|
||||
</td>
|
||||
</div>
|
||||
<div class="ml-4 flex-1 min-w-0">
|
||||
<div class="flex items-center">
|
||||
<h3 class="font-bold text-lg text-white truncate">{{.Name}}</h3>
|
||||
<a href="browse?term={{.Name}}" class="ml-2 text-gray-400 hover:text-blue-400 transition" title="Search for similar models">
|
||||
<i class="fas fa-search text-xs"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
{{ if .Backend }}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-blue-900/50 text-blue-300 border border-blue-700/50">
|
||||
{{.Backend}}
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-yellow-900/50 text-yellow-300 border border-yellow-700/50">
|
||||
auto
|
||||
</span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-5 pb-5 pt-2">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{{ range .KnownUsecaseStrings }}
|
||||
{{ if eq . "FLAG_CHAT" }}
|
||||
<a href="chat/{{$backendCfg.Name}}" class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-blue-900/60 text-blue-200 border border-blue-700/50 hover:bg-blue-800 transition duration-200 ease-in-out">
|
||||
<i class="fas fa-comment-alt text-xs mr-1.5"></i>Chat
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ if eq . "FLAG_IMAGE" }}
|
||||
<a href="text2image/{{$backendCfg.Name}}" class="inline-flex items-center text-sm bg-green-600/80 hover:bg-green-700 text-white py-1.5 px-3 rounded-lg shadow transition duration-300 ease-in-out">
|
||||
<i class="fas fa-image text-xs mr-1.5"></i>Image
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ if eq . "FLAG_TTS" }}
|
||||
<a href="tts/{{$backendCfg.Name}}" class="inline-flex items-center text-sm bg-purple-600/80 hover:bg-purple-700 text-white py-1.5 px-3 rounded-lg shadow transition duration-300 ease-in-out">
|
||||
<i class="fas fa-microphone text-xs mr-1.5"></i>TTS
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<button
|
||||
class="inline-flex items-center text-xs font-medium text-red-400 hover:text-red-300 hover:bg-red-900/20 rounded-md px-2 py-1 transition-colors duration-200"
|
||||
data-twe-ripple-init=""
|
||||
hx-confirm="Are you sure you wish to delete this model?"
|
||||
hx-post="browse/delete/model/{{.Name}}"
|
||||
hx-swap="outerHTML">
|
||||
<i class="fas fa-trash-alt mr-1.5"></i>Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ range .Models }}
|
||||
<tr class="bg-gray-800 border-b border-gray-700">
|
||||
<td class="px-4 py-3">
|
||||
<img src="{{$noicon}}" class="rounded-t-lg max-h-24 max-w-24 object-cover mt-3">
|
||||
</td>
|
||||
<td class="px-4 py-3 font-bold">
|
||||
<p class="font-bold text-white flex items-center"><i class="fas fa-brain pr-2"></i>{{.}}</p>
|
||||
</td>
|
||||
<td class="px-4 py-3 font-bold">
|
||||
<span class="inline-block bg-yellow-500 text-white py-1 px-3 rounded-full text-xs">
|
||||
auto
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<td class="px-4 py-3">
|
||||
<span class="float-right inline-block bg-red-800 text-white py-1 px-3 rounded-full text-xs">
|
||||
No Configuration
|
||||
</span>
|
||||
</td>
|
||||
<div class="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">
|
||||
<div class="flex p-5">
|
||||
<div class="w-20 h-20 rounded-lg overflow-hidden flex-shrink-0 bg-gray-700/50 flex items-center justify-center">
|
||||
<img src="{{$noicon}}" class="w-full h-full object-contain" alt="Model icon">
|
||||
</div>
|
||||
<div class="ml-4 flex-1 min-w-0">
|
||||
<div class="flex items-center">
|
||||
<h3 class="font-bold text-lg text-white truncate"><i class="fas fa-brain mr-2 text-gray-400"></i>{{.}}</h3>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 flex flex-wrap gap-2">
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-yellow-900/50 text-yellow-300 border border-yellow-700/50">
|
||||
auto
|
||||
</span>
|
||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-md text-xs font-medium bg-red-900/50 text-red-300 border border-red-700/50">
|
||||
No Configuration
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
@ -129,4 +189,4 @@
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
@ -2,124 +2,164 @@
|
||||
<html lang="en">
|
||||
{{template "views/partials/head" .}}
|
||||
|
||||
<body class="bg-gray-900 text-gray-200">
|
||||
<body class="bg-gradient-to-br from-gray-900 to-gray-950 text-gray-200">
|
||||
<div class="flex flex-col min-h-screen">
|
||||
|
||||
{{template "views/partials/navbar" .}}
|
||||
{{ $numModelsPerPage := 21 }}
|
||||
<div class="container mx-auto px-4 flex-grow">
|
||||
<div class="container mx-auto px-4 py-8 flex-grow">
|
||||
|
||||
<div class="models mt-12">
|
||||
<h2 class="text-center text-3xl font-semibold text-gray-100">
|
||||
🖼️ Available {{.AvailableModels}} models from <i>{{ len .Repositories }}</i> repositories <a href="https://localai.io/models/" target="_blank" >
|
||||
<i class="fas fa-circle-info pr-2"></i>
|
||||
</a></h2>
|
||||
|
||||
<div class="text-center font-semibold text-gray-100">
|
||||
<h2>Filter by type:</h2>
|
||||
<button hx-post="browse/search/models"
|
||||
class="text-white-500 inline-block bg-blue-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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "tts"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >TTS</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="text-white-500 inline-block bg-blue-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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "stablediffusion"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >Image generation</button>
|
||||
<button hx-post="browse/search/models" \
|
||||
class="text-white-500 inline-block bg-blue-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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "llm"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >Text generation</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="text-white-500 inline-block bg-blue-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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "multimodal"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >Multimodal</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="text-white-500 inline-block bg-blue-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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "embedding"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >Embeddings</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="text-white-500 inline-block bg-blue-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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "rerank"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >Rerankers</button>
|
||||
<button
|
||||
hx-post="browse/search/models"
|
||||
class="text-white-500 inline-block bg-blue-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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "whisper"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >Audio transcription</button>
|
||||
<!-- Hero Header -->
|
||||
<div class="bg-gradient-to-r from-indigo-900/30 to-purple-900/30 rounded-2xl shadow-xl p-6 mb-8">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-white mb-3">
|
||||
<span class="bg-clip-text text-transparent bg-gradient-to-r from-indigo-400 to-purple-400">
|
||||
Model Gallery
|
||||
</span>
|
||||
</h1>
|
||||
<p class="text-lg text-gray-300 mb-2">
|
||||
<span class="font-semibold text-indigo-300">{{.AvailableModels}}</span> models from
|
||||
<span class="font-semibold text-purple-300">{{ len .Repositories }}</span> repositories
|
||||
<a href="https://localai.io/models/" target="_blank" class="ml-2 text-blue-400 hover:text-blue-300 transition">
|
||||
<i class="fas fa-circle-info"></i>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-xs font-semibold text-gray-100">
|
||||
Filter by tags:
|
||||
{{ range .AllTags }}
|
||||
<button hx-post="browse/search/models" class="text-blue-500" hx-target="#search-results"
|
||||
hx-vals='{"search": "{{.}}"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >{{.}}</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<!-- Search and Filter Section -->
|
||||
<div class="bg-gray-800/70 rounded-xl p-6 mb-8 shadow-lg border border-gray-700/50">
|
||||
<!-- Search Input -->
|
||||
<div class="relative mb-6">
|
||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3 pointer-events-none">
|
||||
<i class="fas fa-search text-gray-400"></i>
|
||||
</div>
|
||||
<input class="form-control block w-full pl-10 px-4 py-3 text-base font-normal text-gray-300 bg-gray-900/80 bg-clip-padding border border-gray-700/70 rounded-lg transition ease-in-out focus:text-gray-200 focus:bg-gray-900 focus:border-blue-500 focus:ring-1 focus:ring-blue-500/50 focus:outline-none"
|
||||
type="search"
|
||||
name="search"
|
||||
placeholder="Search models by name, tag, or description..."
|
||||
hx-post="browse/search/models"
|
||||
hx-trigger="input changed delay:500ms, search"
|
||||
hx-target="#search-results"
|
||||
oninput="hidePagination()"
|
||||
onchange="hidePagination()"
|
||||
onsearch="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<span class="htmx-indicator absolute right-3 top-3">
|
||||
<svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<span class="htmx-indicator loader"></span>
|
||||
{{template "views/partials/inprogress" .}}
|
||||
|
||||
<input class="form-control appearance-none block w-full mt-5 px-3 py-2 text-base font-normal text-gray-300 pb-2 mb-5 bg-gray-800 bg-clip-padding border border-solid border-gray-600 rounded transition ease-in-out m-0 focus:text-gray-300 focus:bg-gray-900 focus:border-blue-500 focus:outline-none" type="search"
|
||||
name="search" placeholder="Begin Typing To Search models..."
|
||||
hx-post="browse/search/models"
|
||||
hx-trigger="input changed delay:500ms, search"
|
||||
hx-target="#search-results"
|
||||
oninput="hidePagination()"
|
||||
onchange="hidePagination()"
|
||||
onsearch="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
|
||||
<div id="search-results">{{.Models}}</div>
|
||||
{{ if gt .AvailableModels $numModelsPerPage }}
|
||||
<!-- Pagination -->
|
||||
<div id="paginate" class="flex justify-center mt-5">
|
||||
<div class="flex items
|
||||
-center">
|
||||
<button onclick="window.location.href='browse?page={{.PrevPage}}'" class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-l-md {{if not .PrevPage}}invisible{{end}}"
|
||||
><i class="fas fa-arrow-left"></i></button>
|
||||
<button onclick="window.location.href='browse?page={{.NextPage}}'" class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-r-md {{if not .NextPage}}invisible{{end}}"
|
||||
><i class="fas fa-arrow-right"></i></button>
|
||||
<!--
|
||||
TODO: do not refresh the page, but use htmx.
|
||||
This however requires the page calculation to be here instead that in the golang backend.
|
||||
<button class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-l-md"
|
||||
hx-post="browse/search/models?page={{.PrevPage}}"
|
||||
<!-- Filter by Type -->
|
||||
<div class="mb-4">
|
||||
<h3 class="text-gray-200 font-medium mb-3">Filter by type:</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button hx-post="browse/search/models"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-indigo-900/60 text-indigo-200 border border-indigo-700/50 hover:bg-indigo-800 transition duration-200 ease-in-out"
|
||||
hx-target="#search-results"
|
||||
hx-indicator=".htmx-indicator"
|
||||
{{if not .PrevPage}}disabled{{end}}
|
||||
>
|
||||
<i class="fas fa-arrow-left"></i>
|
||||
hx-vals='{"search": "tts"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-microphone mr-2"></i>TTS
|
||||
</button>
|
||||
<button class="bg-gray-800 text-gray-300 hover:bg-gray-700 hover:text-gray-200 px-3 py-1 rounded-r-md"
|
||||
hx-post="browse/search/models?page={{.NextPage}}"
|
||||
<button hx-post="browse/search/models"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-purple-900/60 text-purple-200 border border-purple-700/50 hover:bg-purple-800 transition duration-200 ease-in-out"
|
||||
hx-target="#search-results"
|
||||
hx-indicator=".htmx-indicator"
|
||||
{{if not .NextPage}}disabled{{end}}
|
||||
>
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
hx-vals='{"search": "stablediffusion"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-image mr-2"></i>Image generation
|
||||
</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-blue-900/60 text-blue-200 border border-blue-700/50 hover:bg-blue-800 transition duration-200 ease-in-out"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "llm"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-comment-alt mr-2"></i>Text generation
|
||||
</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-green-900/60 text-green-200 border border-green-700/50 hover:bg-green-800 transition duration-200 ease-in-out"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "multimodal"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-object-group mr-2"></i>Multimodal
|
||||
</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-cyan-900/60 text-cyan-200 border border-cyan-700/50 hover:bg-cyan-800 transition duration-200 ease-in-out"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "embedding"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-vector-square mr-2"></i>Embeddings
|
||||
</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-amber-900/60 text-amber-200 border border-amber-700/50 hover:bg-amber-800 transition duration-200 ease-in-out"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "rerank"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-sort-amount-up mr-2"></i>Rerankers
|
||||
</button>
|
||||
<button hx-post="browse/search/models"
|
||||
class="inline-flex items-center rounded-full px-4 py-2 text-sm font-medium bg-teal-900/60 text-teal-200 border border-teal-700/50 hover:bg-teal-800 transition duration-200 ease-in-out"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "whisper"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-headphones mr-2"></i>Audio transcription
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<!-- Filter by Tags -->
|
||||
<div class="mt-5">
|
||||
<h3 class="text-gray-200 font-medium mb-2">Filter by tags:</h3>
|
||||
<div class="flex flex-wrap gap-2 max-h-24 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-700 scrollbar-track-gray-900 pr-2">
|
||||
{{ range .AllTags }}
|
||||
<button hx-post="browse/search/models"
|
||||
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"
|
||||
hx-target="#search-results"
|
||||
hx-vals='{"search": "{{.}}"}'
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator">
|
||||
<i class="fas fa-tag text-xs mr-1.5"></i>{{.}}
|
||||
</button>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Results Section -->
|
||||
<div id="search-results" class="transition-all duration-300">
|
||||
{{.Models}}
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{{ if gt .AvailableModels $numModelsPerPage }}
|
||||
<div id="paginate" class="flex justify-center mt-8">
|
||||
<div class="flex items-center gap-4">
|
||||
<button onclick="window.location.href='browse?page={{.PrevPage}}'"
|
||||
class="flex items-center justify-center h-10 w-10 bg-gray-800/80 text-gray-300 hover:bg-indigo-900/70 hover:text-white rounded-lg shadow transition duration-300 ease-in-out {{if not .PrevPage}}opacity-50 cursor-not-allowed{{end}}"
|
||||
{{if not .PrevPage}}disabled{{end}}>
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</button>
|
||||
<div class="text-gray-400 text-sm">
|
||||
Page <span class="text-white font-medium">{{add .PrevPage 1}}</span>
|
||||
</div>
|
||||
<button onclick="window.location.href='browse?page={{.NextPage}}'"
|
||||
class="flex items-center justify-center h-10 w-10 bg-gray-800/80 text-gray-300 hover:bg-indigo-900/70 hover:text-white rounded-lg shadow transition duration-300 ease-in-out {{if not .NextPage}}opacity-50 cursor-not-allowed{{end}}"
|
||||
{{if not .NextPage}}disabled{{end}}>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
{{template "views/partials/footer" .}}
|
||||
</div>
|
||||
|
@ -7,21 +7,31 @@
|
||||
<div class="flex flex-col min-h-screen">
|
||||
|
||||
{{template "views/partials/navbar" .}}
|
||||
<div class="container mx-auto px-4 flex-grow " x-data="{ component: 'menu' }">
|
||||
<div class="container mx-auto px-4 py-8 flex-grow " x-data="{ component: 'menu' }">
|
||||
|
||||
|
||||
<!-- Hero Header -->
|
||||
<div class="bg-gradient-to-r from-indigo-900/30 to-purple-900/30 rounded-2xl shadow-xl p-6 mb-8">
|
||||
<div class="max-w-4xl mx-auto text-center">
|
||||
<h1 class="text-3xl md:text-4xl font-bold text-white mb-3">
|
||||
<span class="bg-clip-text text-transparent bg-gradient-to-r from-indigo-400 to-purple-400">
|
||||
Image generation {{ if .Model }} with {{.Model}} {{ end }}
|
||||
</span>
|
||||
</h1>
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
<a href="https://localai.io/features/image-generation/" target="_blank"
|
||||
class="group flex items-center bg-blue-600 hover:bg-blue-700 text-white py-2 px-6 rounded-lg transition duration-300 ease-in-out transform hover:scale-105 hover:shadow-lg">
|
||||
<i class="fas fa-book-reader mr-2"></i>
|
||||
<span>Documentation</span>
|
||||
<i class="fas fa-arrow-right opacity-0 group-hover:opacity-100 group-hover:translate-x-2 ml-2 transition-all duration-300"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mt-12">
|
||||
<div class="flex items-center justify-center text-center pb-2">
|
||||
<span class="text-3xl font-semibold text-gray-100">
|
||||
🖼️ Text to Image {{ if .Model }} (using {{.Model}}) {{ end }}
|
||||
<a href="https://localai.io/features/image-generation" target="_blank" >
|
||||
<i class="fas fa-circle-info pr-2"></i>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="text-center font-semibold text-gray-100">
|
||||
<div class="text-center font-semibold text-gray-100">
|
||||
<div class="flex items-center justify-between">
|
||||
|
||||
<div x-show="component === 'menu'" id="menu">
|
||||
@ -64,7 +74,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-12">
|
||||
<div class="mt-12 relative">
|
||||
<input id="image-model" type="hidden" value="{{.Model}}">
|
||||
<form id="genimage" action="text2image/{{.Model}}" method="get">
|
||||
<input
|
||||
@ -73,13 +83,16 @@
|
||||
name="input"
|
||||
placeholder="Prompt…"
|
||||
autocomplete="off"
|
||||
class="p-2 border rounded w-full bg-gray-600 text-white placeholder-gray-300"
|
||||
class="form-control block w-full pl-10 px-4 py-3 text-base font-normal text-gray-300 bg-gray-900/80 bg-clip-padding border border-gray-700/70 rounded-lg transition ease-in-out focus:text-gray-200 focus:bg-gray-900 focus:border-blue-500 focus:ring-1 focus:ring-blue-500/50 focus:outline-none"
|
||||
required
|
||||
/>
|
||||
<span id="loader" class="my-2 loader absolute right-3 top-2" >
|
||||
<svg class="animate-spin h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</form>
|
||||
<div class="container max-w-screen-lg mx-auto mt-4 pb-10 flex justify-center">
|
||||
<div id="loader" class="my-2 loader" ></div>
|
||||
</div>
|
||||
<div class="container max-w-screen-lg mx-auto mt-4 pb-10 flex justify-center">
|
||||
<div id="result" class="mx-auto"></div>
|
||||
</div>
|
||||
|
Loading…
x
Reference in New Issue
Block a user