mirror of
https://github.com/mudler/LocalAI.git
synced 2025-03-11 15:04:02 +00:00
feat(ui): improve chat interface (#4910)
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
* feat(ui): show more informations in the chat view, minor adjustments to model gallery Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * fix(ui): UI improvements Visual improvements and bugfixes including: - disable pagination during search - fix scrolling on new message Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
This commit is contained in:
parent
5ad2be9c45
commit
c87870b18e
@ -29,6 +29,8 @@ func InstallModelFromGallery(galleries []config.Gallery, name string, basePath s
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Description = model.Description
|
||||
config.License = model.License
|
||||
} else if len(model.ConfigFile) > 0 {
|
||||
// TODO: is this worse than using the override method with a blank cfg yaml?
|
||||
reYamlConfig, err := yaml.Marshal(model.ConfigFile)
|
||||
|
@ -404,6 +404,15 @@ func RegisterUIRoutes(app *fiber.App,
|
||||
return c.Redirect(utils.BaseURL(c))
|
||||
}
|
||||
modelThatCanBeUsed := ""
|
||||
galleryConfigs := map[string]*gallery.Config{}
|
||||
|
||||
for _, m := range backendConfigs {
|
||||
cfg, err := gallery.GetLocalModelConfiguration(ml.ModelPath, m.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
galleryConfigs[m.Name] = cfg
|
||||
}
|
||||
|
||||
title := "LocalAI - Chat"
|
||||
|
||||
@ -419,6 +428,7 @@ func RegisterUIRoutes(app *fiber.App,
|
||||
"Title": title,
|
||||
"BaseURL": utils.BaseURL(c),
|
||||
"ModelsWithoutConfig": modelsWithoutConfig,
|
||||
"GalleryConfig": galleryConfigs,
|
||||
"ModelsConfig": backendConfigs,
|
||||
"Model": modelThatCanBeUsed,
|
||||
"Version": internal.PrintableVersion(),
|
||||
@ -434,10 +444,21 @@ func RegisterUIRoutes(app *fiber.App,
|
||||
backendConfigs := cl.GetAllBackendConfigs()
|
||||
modelsWithoutConfig, _ := services.ListModels(cl, ml, config.NoFilterFn, services.LOOSE_ONLY)
|
||||
|
||||
galleryConfigs := map[string]*gallery.Config{}
|
||||
|
||||
for _, m := range backendConfigs {
|
||||
cfg, err := gallery.GetLocalModelConfiguration(ml.ModelPath, m.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
galleryConfigs[m.Name] = cfg
|
||||
}
|
||||
|
||||
summary := fiber.Map{
|
||||
"Title": "LocalAI - Chat with " + c.Params("model"),
|
||||
"BaseURL": utils.BaseURL(c),
|
||||
"ModelsConfig": backendConfigs,
|
||||
"GalleryConfig": galleryConfigs,
|
||||
"ModelsWithoutConfig": modelsWithoutConfig,
|
||||
"Model": c.Params("model"),
|
||||
"Version": internal.PrintableVersion(),
|
||||
|
@ -49,7 +49,7 @@ function submitPrompt(event) {
|
||||
document.getElementById("input").value = "";
|
||||
const key = localStorage.getItem("key");
|
||||
const systemPrompt = localStorage.getItem("system_prompt");
|
||||
|
||||
Alpine.nextTick(() => { document.getElementById('messages').scrollIntoView(false); });
|
||||
promptGPT(systemPrompt, key, input);
|
||||
}
|
||||
|
||||
@ -74,7 +74,6 @@ function readInputImage() {
|
||||
// Make the "loader" visible
|
||||
document.getElementById("loader").style.display = "block";
|
||||
document.getElementById("input").disabled = true;
|
||||
document.getElementById('messages').scrollIntoView(false)
|
||||
|
||||
messages = Alpine.store("chat").messages();
|
||||
|
||||
@ -181,8 +180,8 @@ function readInputImage() {
|
||||
const chatStore = Alpine.store("chat");
|
||||
chatStore.add("assistant", token);
|
||||
// Efficiently scroll into view without triggering multiple reflows
|
||||
const messages = document.getElementById('messages');
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
// const messages = document.getElementById('messages');
|
||||
// messages.scrollTop = messages.scrollHeight;
|
||||
};
|
||||
|
||||
let buffer = "";
|
||||
|
@ -4,7 +4,7 @@ Part of this page is based on the OpenAI Chatbot example by David Härer:
|
||||
https://github.com/david-haerer/chatapi
|
||||
|
||||
MIT License Copyright (c) 2023 David Härer
|
||||
Copyright (c) 2024 Ettore Di Giacinto
|
||||
Copyright (c) 2024-2025 Ettore Di Giacinto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -29,212 +29,363 @@ SOFTWARE.
|
||||
<html lang="en">
|
||||
{{template "views/partials/head" .}}
|
||||
<script defer src="static/chat.js"></script>
|
||||
|
||||
<!-- Adjust layout classes to let the chat fill the viewport and be responsive -->
|
||||
<body class="bg-slate-900 text-gray-100 flex flex-col h-screen" x-data="{ key: $store.chat.key }">
|
||||
{{ $allGalleryConfigs:=.GalleryConfig }}
|
||||
{{ $model:=.Model}}
|
||||
<body class="bg-slate-900 text-gray-100 flex flex-col h-screen" x-data="{ key: $store.chat.key, sidebarOpen: true }">
|
||||
{{template "views/partials/navbar" .}}
|
||||
|
||||
<!-- Container fills available space (flex-1) and allows scrolling if needed -->
|
||||
<div class="flex flex-col flex-1 overflow-hidden">
|
||||
<!-- Main container with sidebar toggle -->
|
||||
<div class="flex flex-1 overflow-hidden relative">
|
||||
<!-- Sidebar -->
|
||||
<div
|
||||
class="sidebar bg-gray-800 fixed top-16 bottom-0 left-0 w-64 transform transition-transform duration-300 ease-in-out z-30 border-r border-gray-700 overflow-y-auto"
|
||||
:class="sidebarOpen ? 'translate-x-0' : '-translate-x-full'">
|
||||
|
||||
<!-- Header can stay at the top -->
|
||||
<div class="border-b border-gray-700 p-4" x-data="{ component: 'menu' }">
|
||||
<div class="flex items-center justify-between flex-wrap gap-2">
|
||||
<h1 class="text-lg font-semibold flex items-center">
|
||||
<i class="fa-solid fa-comments mr-2"></i>
|
||||
Chat
|
||||
{{ if .Model }} with {{.Model}} {{ end }}
|
||||
<a class="ml-2" href="browse?term={{.Model}}">
|
||||
<i class="fas fa-brain pr-2"></i>
|
||||
</a>
|
||||
<a href="https://localai.io/features/text-generation/" target="_blank">
|
||||
<i class="fas fa-circle-info"></i>
|
||||
</a>
|
||||
</h1>
|
||||
<div x-show="component === 'menu'" id="menu">
|
||||
<button
|
||||
@click="$store.chat.clear()"
|
||||
id="clear"
|
||||
title="Clear chat history"
|
||||
data-twe-ripple-init
|
||||
data-twe-ripple-color="light"
|
||||
class="m-2 inline-block rounded bg-primary px-4 py-1 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 focus:bg-primary-accent-300 focus:outline-none"
|
||||
>
|
||||
Clear chat 🔥
|
||||
</button>
|
||||
<button
|
||||
@click="component = 'key'"
|
||||
title="Update API key"
|
||||
class="m-2 inline-block rounded bg-primary px-4 py-1 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 focus:bg-primary-accent-300 focus:outline-none"
|
||||
>
|
||||
Set API Key🔑
|
||||
</button>
|
||||
<button
|
||||
@click="component = 'system_prompt'"
|
||||
title="System Prompt"
|
||||
class="m-2 inline-block rounded bg-primary px-4 py-1 text-xs font-medium uppercase leading-normal text-white shadow-primary-3 transition duration-150 ease-in-out hover:bg-primary-accent-300 focus:bg-primary-accent-300 focus:outline-none"
|
||||
>
|
||||
Set system prompt
|
||||
</button>
|
||||
</div>
|
||||
<!-- API Key form -->
|
||||
<form x-show="component === 'key'" id="key" class="flex items-center gap-2">
|
||||
<input
|
||||
type="password"
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
class="bg-gray-800 text-white border border-gray-600 focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none"
|
||||
placeholder="OpenAI API Key"
|
||||
x-model.lazy="key"
|
||||
/>
|
||||
<button
|
||||
@click="component = 'menu'"
|
||||
type="submit"
|
||||
title="Save API key"
|
||||
class="text-white"
|
||||
>
|
||||
<i class="fa-solid fa-arrow-right"></i>
|
||||
</button>
|
||||
</form>
|
||||
<!-- System Prompt form -->
|
||||
<form x-show="component === 'system_prompt'" id="system_prompt" class="flex flex-col gap-2 mt-2">
|
||||
<textarea
|
||||
type="text"
|
||||
id="systemPrompt"
|
||||
name="systemPrompt"
|
||||
class="bg-gray-800 text-white border border-gray-600 focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none"
|
||||
placeholder="System prompt"
|
||||
x-model.lazy="system_prompt"
|
||||
></textarea>
|
||||
<button
|
||||
@click="component = 'menu'"
|
||||
type="submit"
|
||||
title="Save Prompt"
|
||||
class="self-end text-white"
|
||||
>
|
||||
<i class="fa-solid fa-arrow-right"></i>
|
||||
</button>
|
||||
</form>
|
||||
<div class="p-4 flex justify-between items-center border-b border-gray-700">
|
||||
<h2 class="text-lg font-semibold">Chat Settings</h2>
|
||||
<button
|
||||
@click="sidebarOpen = false"
|
||||
class="text-gray-400 hover:text-white focus:outline-none">
|
||||
<i class="fa-solid fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Model selection -->
|
||||
<select
|
||||
x-data="{ link : '' }"
|
||||
x-model="link"
|
||||
x-init="$watch('link', value => window.location = link)"
|
||||
class="bg-gray-800 text-white border border-gray-600 focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none"
|
||||
>
|
||||
<option value="" disabled class="text-gray-400">Select a model</option>
|
||||
{{ $model:=.Model}}
|
||||
{{ range .ModelsConfig }}
|
||||
{{ $cfg := . }}
|
||||
{{ range .KnownUsecaseStrings }}
|
||||
{{ if eq . "FLAG_CHAT" }}
|
||||
<option
|
||||
value="chat/{{$cfg.Name}}"
|
||||
{{ if eq $cfg.Name $model }} selected {{end}}
|
||||
class="bg-gray-700 text-white"
|
||||
>
|
||||
{{$cfg.Name}}
|
||||
</option>
|
||||
<!-- Sidebar content -->
|
||||
<div class="p-4 space-y-6">
|
||||
<!-- Model selection - Fixed to properly select current model -->
|
||||
<div class="space-y-2">
|
||||
<label class="text-sm font-medium text-gray-300">Select Model</label>
|
||||
<select
|
||||
id="modelSelector"
|
||||
class="w-full bg-gray-700 text-white border border-gray-600 focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none"
|
||||
onchange="window.location = this.value"
|
||||
>
|
||||
<option value="" disabled class="text-gray-400">Select a model</option>
|
||||
|
||||
{{ range .ModelsConfig }}
|
||||
{{ $cfg := . }}
|
||||
{{ range .KnownUsecaseStrings }}
|
||||
{{ if eq . "FLAG_CHAT" }}
|
||||
<option
|
||||
value="chat/{{$cfg.Name}}"
|
||||
{{ if eq $cfg.Name $model }} selected {{end}}
|
||||
class="bg-gray-700 text-white"
|
||||
>
|
||||
{{$cfg.Name}}
|
||||
</option>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ range .ModelsWithoutConfig }}
|
||||
<option
|
||||
value="chat/{{.}}"
|
||||
{{ if eq . $model }} selected {{ end }}
|
||||
class="bg-gray-700 text-white"
|
||||
>
|
||||
{{.}}
|
||||
</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{{ range .ModelsWithoutConfig }}
|
||||
<option
|
||||
value="chat/{{.}}"
|
||||
{{ if eq . $model }} selected {{ end }}
|
||||
class="bg-gray-700 text-white"
|
||||
>
|
||||
{{.}}
|
||||
</option>
|
||||
{{end}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Main chat area (flex-1 for expansion) -->
|
||||
<div class="flex-1 p-4 overflow-auto" id="chat" x-data="{history: $store.chat.history}">
|
||||
<p id="usage" x-show="history.length === 0" class="text-gray-300">
|
||||
Start chatting with the AI by typing a prompt in the input field below and pressing Enter.
|
||||
For models that support images, you can upload an image by clicking the paperclip
|
||||
<i class="fa-solid fa-paperclip"></i> icon.
|
||||
</p>
|
||||
<div id="messages">
|
||||
<template x-for="message in history">
|
||||
<div class="message flex items-start space-x-2 my-2">
|
||||
<i
|
||||
class="fa-solid h-8 w-8"
|
||||
:class="message.role === 'user' ? 'fa-user' : 'fa-robot'"
|
||||
></i>
|
||||
<div class="flex flex-col flex-1">
|
||||
<span
|
||||
class="text-xs font-semibold text-gray-400"
|
||||
x-text="message.role === 'user' ? 'User' : 'Assistant ({{.Model}})'"
|
||||
></span>
|
||||
<template x-if="message.role === 'user'">
|
||||
<div class="p-2 flex-1 rounded bg-gray-700 text-white" x-html="message.html"></div>
|
||||
</template>
|
||||
<template x-if="message.role === 'assistant'">
|
||||
<div class="p-2 flex-1 rounded bg-gray-700 text-white" x-html="message.html"></div>
|
||||
</template>
|
||||
<template x-if="message.image">
|
||||
<img :src="message.image" alt="Image" class="rounded-lg mt-2 max-w-xs">
|
||||
</template>
|
||||
</div>
|
||||
{{ if $model }}
|
||||
{{ $galleryConfig:= index $allGalleryConfigs $model}}
|
||||
{{ if $galleryConfig }}
|
||||
<!-- Model info -->
|
||||
<div class="space-y-2">
|
||||
<div class="flex items-center">
|
||||
<img src="{{$galleryConfig.Icon}}" class="rounded-lg w-8 h-8 mr-2">
|
||||
<h3 class="text-md font-medium">{{ $model }}</h3>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</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">
|
||||
<div class="relative w-full">
|
||||
<textarea
|
||||
id="input"
|
||||
name="input"
|
||||
x-model="inputValue"
|
||||
placeholder="Send a message..."
|
||||
class="p-2 pr-16 border rounded w-full bg-gray-600 text-gray-100 placeholder-gray-300 focus:outline-none resize-none"
|
||||
required
|
||||
@keydown.shift="shiftPressed = true"
|
||||
@keyup.shift="shiftPressed = false"
|
||||
@keydown.enter="if (!shiftPressed) { submitPrompt($event); }"
|
||||
rows="3"
|
||||
></textarea>
|
||||
<span x-text="fileName" id="fileName" class="absolute right-16 top-3 text-gray-300 text-sm mr-2"></span>
|
||||
<button
|
||||
type="button"
|
||||
onclick="document.getElementById('input_image').click()"
|
||||
class="fa-solid fa-paperclip text-gray-300 absolute right-10 top-3 text-lg p-2 hover:text-gray-100"
|
||||
title="Attach an image"
|
||||
></button>
|
||||
<button
|
||||
type="submit"
|
||||
class="absolute right-2 top-3 text-lg p-2 text-gray-300 hover:text-gray-100"
|
||||
title="Send message"
|
||||
>
|
||||
<i class="fa-solid fa-circle-up"></i>
|
||||
<button data-twe-ripple-init data-twe-ripple-color="light" class="w-full text-left flex items-center px-3 py-2 text-xs rounded text-white bg-gray-700 hover:bg-gray-600 transition-colors" data-modal-target="model-info-modal" data-modal-toggle="model-info-modal">
|
||||
<i class="fas fa-info-circle mr-2"></i>
|
||||
Model Information
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<div x-data="{ activeTab: 'actions' }" class="space-y-4">
|
||||
<!-- Tab navigation -->
|
||||
<div class="flex border-b border-gray-700">
|
||||
<button
|
||||
@click="activeTab = 'actions'"
|
||||
:class="activeTab === 'actions' ? 'border-b-2 border-blue-500 text-white' : 'text-gray-400 hover:text-white'"
|
||||
class="py-2 px-4 text-sm font-medium">
|
||||
Actions
|
||||
</button>
|
||||
<button
|
||||
@click="activeTab = 'settings'"
|
||||
:class="activeTab === 'settings' ? 'border-b-2 border-blue-500 text-white' : 'text-gray-400 hover:text-white'"
|
||||
class="py-2 px-4 text-sm font-medium">
|
||||
Settings
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Actions tab -->
|
||||
<div x-show="activeTab === 'actions'" class="space-y-3">
|
||||
<button
|
||||
@click="$store.chat.clear()"
|
||||
id="clear"
|
||||
title="Clear chat history"
|
||||
class="w-full flex items-center px-3 py-2 text-sm rounded text-white bg-gray-700 hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
<i class="fa-solid fa-trash-can mr-2"></i> Clear chat
|
||||
</button>
|
||||
|
||||
<a
|
||||
href="https://localai.io/features/text-generation/"
|
||||
target="_blank"
|
||||
class="w-full flex items-center px-3 py-2 text-sm rounded text-white bg-gray-700 hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
<i class="fas fa-book mr-2"></i> Documentation
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="browse?term={{.Model}}"
|
||||
class="w-full flex items-center px-3 py-2 text-sm rounded text-white bg-gray-700 hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
<i class="fas fa-brain mr-2"></i> Browse Model
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Settings tab -->
|
||||
<div x-show="activeTab === 'settings'" x-data="{ showKeyForm: false, showPromptForm: false }" class="space-y-3">
|
||||
<button
|
||||
@click="showKeyForm = !showKeyForm; showPromptForm = false"
|
||||
class="w-full flex items-center justify-between px-3 py-2 text-sm rounded text-white bg-gray-700 hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
<span><i class="fa-solid fa-key mr-2"></i> API Key</span>
|
||||
<i :class="showKeyForm ? 'fa-chevron-up' : 'fa-chevron-down'" class="fa-solid"></i>
|
||||
</button>
|
||||
|
||||
<div x-show="showKeyForm" class="p-3 bg-gray-700 rounded">
|
||||
<form id="key" class="flex flex-col space-y-2">
|
||||
<input
|
||||
type="password"
|
||||
id="apiKey"
|
||||
name="apiKey"
|
||||
class="bg-gray-800 text-white border border-gray-600 focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none"
|
||||
placeholder="OpenAI API Key"
|
||||
x-model.lazy="key"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-3 py-2 text-sm rounded text-white bg-blue-600 hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Save API Key
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@click="showPromptForm = !showPromptForm; showKeyForm = false"
|
||||
class="w-full flex items-center justify-between px-3 py-2 text-sm rounded text-white bg-gray-700 hover:bg-gray-600 transition-colors"
|
||||
>
|
||||
<span><i class="fa-solid fa-message mr-2"></i> System Prompt</span>
|
||||
<i :class="showPromptForm ? 'fa-chevron-up' : 'fa-chevron-down'" class="fa-solid"></i>
|
||||
</button>
|
||||
|
||||
<div x-show="showPromptForm" class="p-3 bg-gray-700 rounded">
|
||||
<form id="system_prompt" class="flex flex-col space-y-2">
|
||||
<textarea
|
||||
type="text"
|
||||
id="systemPrompt"
|
||||
name="systemPrompt"
|
||||
class="bg-gray-800 text-white border border-gray-600 focus:border-blue-500 focus:ring focus:ring-blue-500 focus:ring-opacity-50 rounded-md shadow-sm p-2 appearance-none min-h-24"
|
||||
placeholder="System prompt"
|
||||
x-model.lazy="$store.chat.systemPrompt"
|
||||
></textarea>
|
||||
<button
|
||||
type="submit"
|
||||
class="px-3 py-2 text-sm rounded text-white bg-blue-600 hover:bg-blue-700 transition-colors"
|
||||
>
|
||||
Save System Prompt
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main chat container (shifts with sidebar) -->
|
||||
<div
|
||||
class="flex-1 flex flex-col transition-all duration-300 ease-in-out"
|
||||
:class="sidebarOpen ? 'ml-64' : 'ml-0'">
|
||||
|
||||
<!-- Chat header with toggle button -->
|
||||
<div class="border-b border-gray-700 p-4 flex items-center">
|
||||
<!-- Sidebar toggle button moved to be the first element in the header and with clear styling -->
|
||||
<button
|
||||
@click="sidebarOpen = !sidebarOpen"
|
||||
class="mr-4 text-gray-300 hover:text-white focus:outline-none bg-gray-800 hover:bg-gray-700 p-2 rounded"
|
||||
style="min-width: 36px;"
|
||||
title="Toggle settings">
|
||||
<i class="fa-solid" :class="sidebarOpen ? 'fa-times' : 'fa-bars'"></i>
|
||||
</button>
|
||||
|
||||
<div class="flex items-center">
|
||||
<i class="fa-solid fa-comments mr-2"></i>
|
||||
{{ if $model }}
|
||||
{{ $galleryConfig:= index $allGalleryConfigs $model}}
|
||||
<img src="{{$galleryConfig.Icon}}" class="rounded-lg w-8 h-8 mr-2">
|
||||
{{ end }}
|
||||
<h1 class="text-lg font-semibold">
|
||||
Chat {{ if .Model }} with {{.Model}} {{ end }}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat messages area -->
|
||||
<div class="flex-1 p-4 overflow-auto" id="chat" x-data="{history: $store.chat.history}">
|
||||
<p id="usage" x-show="history.length === 0" class="text-gray-300">
|
||||
Start chatting with the AI by typing a prompt in the input field below and pressing Enter.
|
||||
For models that support images, you can upload an image by clicking the paperclip
|
||||
<i class="fa-solid fa-paperclip"></i> icon.
|
||||
</p>
|
||||
<div id="messages" class="max-w-3xl mx-auto">
|
||||
<template x-for="message in history">
|
||||
<div :class="message.role === 'user' ? 'flex items-start space-x-2 my-2 justify-end' : 'flex items-start space-x-2 my-2'">
|
||||
{{ if .Model }}
|
||||
{{ $galleryConfig:= index $allGalleryConfigs .Model}}
|
||||
<template x-if="message.role === 'user'">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="flex flex-col flex-1 items-end">
|
||||
<span class="text-xs font-semibold text-gray-400">You</span>
|
||||
<div class="p-2 flex-1 rounded bg-gray-700 text-white" x-html="message.html"></div>
|
||||
<template x-if="message.image">
|
||||
<img :src="message.image" alt="Image" class="rounded-lg mt-2 max-w-xs">
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="message.role != 'user'">
|
||||
<div class="flex items-center space-x-2">
|
||||
<img src="{{$galleryConfig.Icon}}" class="rounded-lg mt-2 max-w-8 max-h-8">
|
||||
<div class="flex flex-col flex-1">
|
||||
<span class="text-xs font-semibold text-gray-400">{{if .Model}}{{.Model}}{{else}}Assistant{{end}}</span>
|
||||
<div class="flex-1 text-white flex items-center space-x-2">
|
||||
<div x-html="message.html"></div>
|
||||
<button @click="copyToClipboard(message.html)" title="Copy to clipboard" class="text-gray-400 hover:text-gray-100">
|
||||
<i class="fa-solid fa-copy"></i>
|
||||
</button>
|
||||
</div>
|
||||
<template x-if="message.image">
|
||||
<img :src="message.image" alt="Image" class="rounded-lg mt-2 max-w-xs">
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
{{ else }}
|
||||
<i
|
||||
class="fa-solid h-8 w-8"
|
||||
:class="message.role === 'user' ? 'fa-user' : 'fa-robot'"
|
||||
></i>
|
||||
{{ end }}
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</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>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal moved outside of sidebar to appear in center of page -->
|
||||
{{ if $model }}
|
||||
{{ $galleryConfig:= index $allGalleryConfigs $model}}
|
||||
{{ if $galleryConfig }}
|
||||
<div id="model-info-modal" tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 flex justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||
<div class="relative p-4 w-full max-w-2xl max-h-full">
|
||||
<div class="relative p-4 w-full max-w-2xl max-h-full bg-white rounded-lg shadow dark:bg-gray-700">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600">
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">{{ $model }}</h3>
|
||||
<button 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="model-info-modal">
|
||||
<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>
|
||||
<span class="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="p-4 md:p-5 space-y-4">
|
||||
<div class="flex justify-center items-center">
|
||||
<img class="lazy rounded-t-lg max-h-48 max-w-96 object-cover mt-3 entered loaded" src="{{$galleryConfig.Icon}}" loading="lazy"/>
|
||||
</div>
|
||||
<p class="text-base leading-relaxed text-gray-500 dark:text-gray-400">{{ $galleryConfig.Description }}</p>
|
||||
<hr>
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-white">Links</p>
|
||||
<ul>
|
||||
{{range $galleryConfig.URLs}}
|
||||
<li><a href="{{ . }}" target="_blank">{{ . }}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600">
|
||||
<button data-modal-hide="model-info-modal" 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">
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Alpine store initialization -->
|
||||
<script>
|
||||
document.addEventListener("alpine:init", () => {
|
||||
Alpine.store("chat", {
|
||||
history: [],
|
||||
languages: [undefined],
|
||||
systemPrompt: "",
|
||||
clear() {
|
||||
this.history.length = 0;
|
||||
},
|
||||
@ -253,6 +404,7 @@ SOFTWARE.
|
||||
});
|
||||
this.history.push({ role, content, html: c, image });
|
||||
}
|
||||
document.getElementById('messages').scrollIntoView(false);
|
||||
const parser = new DOMParser();
|
||||
const html = parser.parseFromString(
|
||||
this.history[this.history.length - 1].html,
|
||||
@ -277,6 +429,18 @@ SOFTWARE.
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
||||
window.copyToClipboard = (content) => {
|
||||
const tempElement = document.createElement('div');
|
||||
tempElement.innerHTML = content;
|
||||
const text = tempElement.textContent || tempElement.innerText;
|
||||
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert('Copied to clipboard!');
|
||||
}).catch(err => {
|
||||
console.error('Failed to copy: ', err);
|
||||
});
|
||||
};
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@ -6,6 +6,7 @@
|
||||
<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="models mt-12">
|
||||
@ -20,38 +21,45 @@
|
||||
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"}'
|
||||
hx-indicator=".htmx-indicator" >TTS</button>
|
||||
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"}'
|
||||
hx-indicator=".htmx-indicator" >Image generation</button>
|
||||
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"}'
|
||||
hx-indicator=".htmx-indicator" >Text generation</button>
|
||||
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"}'
|
||||
hx-indicator=".htmx-indicator" >Multimodal</button>
|
||||
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"}'
|
||||
hx-indicator=".htmx-indicator" >Embeddings</button>
|
||||
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"}'
|
||||
hx-indicator=".htmx-indicator" >Rerankers</button>
|
||||
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"}'
|
||||
hx-indicator=".htmx-indicator" >Audio transcription</button>
|
||||
onclick="hidePagination()"
|
||||
hx-indicator=".htmx-indicator" >Audio transcription</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center text-xs font-semibold text-gray-100">
|
||||
@ -59,6 +67,7 @@
|
||||
{{ 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>
|
||||
@ -72,16 +81,20 @@
|
||||
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 class="flex justify-center mt-5">
|
||||
<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}}disabled{{end}}
|
||||
<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}}disabled{{end}}
|
||||
<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.
|
||||
@ -103,11 +116,29 @@
|
||||
<i class="fas fa-arrow-right"></i>
|
||||
</button>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{template "views/partials/footer" .}}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function hidePagination() {
|
||||
const paginateDiv = document.getElementById('paginate');
|
||||
if (paginateDiv) {
|
||||
paginateDiv.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Listen for the htmx:afterSwap event to handle cases when the search results are updated
|
||||
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||
if (event.detail.target.id === 'search-results') {
|
||||
hidePagination();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user