Spaces:
Configuration error
Configuration error
| <!-- | |
| 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 | |
| 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. | |
| --> | |
| <html lang="en"> | |
| {{template "views/partials/head" .}} | |
| <script defer src="/static/chat.js"></script> | |
| <style> | |
| body { | |
| overflow: hidden; | |
| } | |
| </style> | |
| <body class="bg-gray-900 text-gray-200" x-data="{ key: $store.chat.key }"> | |
| <div class="flex flex-col min-h-screen"> | |
| {{template "views/partials/navbar" .}} | |
| <div class="chat-container mt-2 mr-2 ml-2 mb-2 bg-gray-800 shadow-lg rounded-lg" > | |
| <!-- Chat Header --> | |
| <div class="border-b border-gray-700 p-4" x-data="{ component: 'menu' }"> | |
| <div class="flex items-center justify-between"> | |
| <h1 class="text-lg font-semibold"> <i class="fa-solid fa-comments"></i> Chat with {{.Model}} <a href="https://localai.io/features/text-generation/" target="_blank" > | |
| <i class="fas fa-circle-info pr-2"></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 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" | |
| > | |
| Clear chat 🔥 | |
| </button> | |
| <button @click="component = 'key'" title="Update API key" | |
| class="m-2 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" | |
| >Set API Key🔑</button> | |
| <button @click="component = 'system_prompt'" title="System Prompt" | |
| class="m-2 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" | |
| >Set system prompt</button> | |
| </div> | |
| <form x-show="component === 'key'" id="key"> | |
| <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"> | |
| <i class="fa-solid fa-arrow-right"></i> | |
| </button> | |
| </form> | |
| <form x-show="component === 'system_prompt'" id="system_prompt"> | |
| <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"> | |
| <i class="fa-solid fa-arrow-right"></i> | |
| </button> | |
| </form> | |
| <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" | |
| > | |
| <!-- Options --> | |
| <option value="" disabled class="text-gray-400" >Select a model</option> | |
| {{ $model:=.Model}} | |
| {{ range .ModelsConfig }} | |
| {{ if eq . $model }} | |
| <option value="/chat/{{.}}" selected class="bg-gray-700 text-white">{{.}}</option> | |
| {{ else }} | |
| <option value="/chat/{{.}}" class="bg-gray-700 text-white">{{.}}</option> | |
| {{ end }} | |
| {{ end }} | |
| </select> | |
| </div> | |
| </div> | |
| <div class="chat-messages p-4" id="chat" x-data="{history: $store.chat.history}"> | |
| <p id="usage" x-show="history.length === 0"> | |
| 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" > | |
| <!--<img :src="message.role === 'user' ? '/path/to/user-icon.png' : '/path/to/bot-icon.png'" alt="" class="h-6 w-6">--> | |
| <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-600" x-text="message.role === 'user' ? 'User' : 'Assistant ({{.Model}})'"></span> | |
| <template x-if="message.role === 'user'"> | |
| <div class="p-2 flex-1 rounded" :class="message.role" x-html="message.html"></div> | |
| </template> | |
| <template x-if="message.role === 'assistant'"> | |
| <div class="p-2 flex-1 rounded" :class="message.role" x-html="message.html"></div> | |
| </template> | |
| <template x-if="message.image"> | |
| <img :src="message.image" alt="Image" class="rounded-lg mt-2 h-36 w-36"> | |
| </template> | |
| </div> | |
| </div> | |
| </template> | |
| </div> | |
| </div> | |
| <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 pl-2 border rounded w-full bg-gray-600 text-white placeholder-gray-300" | |
| required | |
| @keydown.shift="shiftPressed = true" | |
| @keyup.shift="shiftPressed = false" | |
| @keydown.enter="if (!shiftPressed) { submitPrompt($event); }" | |
| style="padding-right: 4rem;" | |
| ></textarea> | |
| <span x-text="fileName" id="fileName" class="absolute right-16 top-5 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 ml-2 absolute right-10 top-3 text-lg p-2"> | |
| </button> | |
| <button type=submit><i class="fa-solid fa-circle-up text-gray-300 absolute right-2 top-3 text-lg p-2"></i></button> | |
| </div> | |
| </form> | |
| </div> | |
| <script> | |
| document.addEventListener("alpine:init", () => { | |
| Alpine.store("chat", { | |
| history: [], | |
| languages: [undefined], | |
| 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; | |
| str = this.history[N].content; | |
| this.history[N].html = DOMPurify.sanitize( | |
| marked.parse(this.history[N].content), | |
| ); | |
| } else { | |
| c = "" | |
| // split content newlines in content | |
| const lines = content.split("\n"); | |
| // for each line, do DOMPurify.sanitize(marked.parse(line)) and add it to c | |
| lines.forEach((line) => { | |
| c += DOMPurify.sanitize(marked.parse(line)); | |
| }); | |
| this.history.push({ | |
| role: role, | |
| content: content, | |
| html: c, | |
| image: image, | |
| }); | |
| } | |
| 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) => { | |
| return { | |
| role: message.role, | |
| content: message.content, | |
| image: message.image, | |
| }; | |
| }); | |
| }, | |
| }); | |
| }); | |
| </script> | |
| </div> | |
| </body> | |
| </html> | |