mirror of
https://github.com/mudler/LocalAI.git
synced 2025-03-22 20:15:33 +00:00
Some checks failed
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
generate and publish GRPC docker caches / generate_caches (ubuntu:22.04, linux/amd64,linux/arm64, arc-runner-set) (push) Has been cancelled
* chore(ui): drop set api key button Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * chore(ui): shore in-progress installs in model view Signed-off-by: Ettore Di Giacinto <mudler@localai.io> * feat(ui): improve text to image view Signed-off-by: Ettore Di Giacinto <mudler@localai.io> --------- Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
439 lines
21 KiB
HTML
439 lines
21 KiB
HTML
<!--
|
|
|
|
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-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
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
-->
|
|
<!doctype html>
|
|
<html lang="en">
|
|
{{template "views/partials/head" .}}
|
|
<script defer src="static/chat.js"></script>
|
|
{{ $allGalleryConfigs:=.GalleryConfig }}
|
|
{{ $model:=.Model}}
|
|
<body class="bg-slate-900 text-gray-100 flex flex-col h-screen" x-data="{ sidebarOpen: true }">
|
|
{{template "views/partials/navbar" .}}
|
|
|
|
<!-- 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'">
|
|
|
|
<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>
|
|
|
|
<!-- 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 }}
|
|
{{ range .ModelsWithoutConfig }}
|
|
<option
|
|
value="chat/{{.}}"
|
|
{{ if eq . $model }} selected {{ end }}
|
|
class="bg-gray-700 text-white"
|
|
>
|
|
{{.}}
|
|
</option>
|
|
{{end}}
|
|
</select>
|
|
</div>
|
|
|
|
{{ if $model }}
|
|
{{ $galleryConfig:= index $allGalleryConfigs $model}}
|
|
{{ if $galleryConfig }}
|
|
<!-- Model info -->
|
|
<div class="space-y-2">
|
|
<div class="flex items-center">
|
|
{{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg w-8 h-8 mr-2">{{end}}
|
|
<h3 class="text-md font-medium">{{ $model }}</h3>
|
|
</div>
|
|
<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>
|
|
{{ 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="{ showPromptForm: false }" class="space-y-3">
|
|
<button
|
|
@click="showPromptForm = !showPromptForm"
|
|
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}}
|
|
{{ if $galleryConfig }}
|
|
{{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg w-8 h-8 mr-2">{{end}}
|
|
{{ end }}
|
|
{{ 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">
|
|
{{ if $galleryConfig }}
|
|
{{ if $galleryConfig.Icon }}<img src="{{$galleryConfig.Icon}}" class="rounded-lg mt-2 max-w-8 max-h-8">{{end}}
|
|
{{ end }}
|
|
<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: '', 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>
|
|
</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">
|
|
{{ if $galleryConfig.Icon }}<img class="lazy rounded-t-lg max-h-48 max-w-96 object-cover mt-3 entered loaded" src="{{$galleryConfig.Icon}}" loading="lazy"/>{{end}}
|
|
</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;
|
|
},
|
|
add(role, content, image) {
|
|
const N = this.history.length - 1;
|
|
if (this.history.length && this.history[N].role === role) {
|
|
this.history[N].content += content;
|
|
this.history[N].html = DOMPurify.sanitize(
|
|
marked.parse(this.history[N].content)
|
|
);
|
|
} else {
|
|
let c = "";
|
|
const lines = content.split("\n");
|
|
lines.forEach((line) => {
|
|
c += DOMPurify.sanitize(marked.parse(line));
|
|
});
|
|
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,
|
|
"text/html"
|
|
);
|
|
const code = html.querySelectorAll("pre code");
|
|
if (!code.length) return;
|
|
code.forEach((el) => {
|
|
const language = el.className.split("language-")[1];
|
|
if (this.languages.includes(language)) return;
|
|
const script = document.createElement("script");
|
|
script.src = `https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.8.0/build/languages/${language}.min.js`;
|
|
document.head.appendChild(script);
|
|
this.languages.push(language);
|
|
});
|
|
},
|
|
messages() {
|
|
return this.history.map((message) => ({
|
|
role: message.role,
|
|
content: message.content,
|
|
image: message.image,
|
|
}));
|
|
},
|
|
});
|
|
|
|
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>
|
|
</html> |