From 08a0a51cdce2eedee614fc5d8f3a9fc0c521ba1e Mon Sep 17 00:00:00 2001
From: Ettore Di Giacinto <mudler@localai.io>
Date: Tue, 25 Feb 2025 20:29:55 +0100
Subject: [PATCH] feat(ui): small improvements to chat interface

- Change chat colors
- Improve layout on small windows

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
---
 core/http/routes/ui.go             |   5 -
 core/http/views/chat.html          | 343 +++++++++++++++++------------
 core/http/views/partials/head.html | 151 ++++++-------
 3 files changed, 260 insertions(+), 239 deletions(-)

diff --git a/core/http/routes/ui.go b/core/http/routes/ui.go
index 7fb6adfcc567..40919a83c865 100644
--- a/core/http/routes/ui.go
+++ b/core/http/routes/ui.go
@@ -209,11 +209,6 @@ func RegisterUIRoutes(app *fiber.App,
 				summary["TotalPages"] = totalPages
 				summary["CurrentPage"] = pageNum
 				summary["Models"] = template.HTML(elements.ListModels(models, processingModels, galleryService))
-
-				log.Debug().Msgf("totalPages : %+v\n", totalPages)
-				log.Debug().Msgf("prevPage : %+v\n", prevPage)
-				log.Debug().Msgf("nextPage : %+v\n", nextPage)
-				log.Debug().Msgf("CurrentPage : %+v\n", pageNum)
 			}
 
 			// Render index
diff --git a/core/http/views/chat.html b/core/http/views/chat.html
index b3e9bd4a23de..71e9b8d6abe8 100644
--- a/core/http/views/chat.html
+++ b/core/http/views/chat.html
@@ -29,143 +29,207 @@
 <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">
 
+  <!-- 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 }">
     {{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">
+    <!-- Container fills available space (flex-1) and allows scrolling if needed -->
+    <div class="flex flex-col flex-1 overflow-hidden">
 
-      <h1 class="text-lg font-semibold"> <i class="fa-solid fa-comments"></i> Chat {{ if .Model }} with {{.Model}} {{ end }} <a 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 pr-2"></i>
-      </a></h1>
-      <div x-show="component === 'menu'" id="menu">
-        <button
-          @click="$store.chat.clear()"
-          id="clear"
-          title="Clear chat history"
+      <!-- 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>
 
-          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"
+          <!-- 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"
           >
-          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 }}
-          {{ $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>
+            <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>
+                {{ end }}
+              {{ end }}
             {{ end }}
-          {{ end }}
-        {{ end }}
-        {{ range .ModelsWithoutConfig }}
-            <option value="chat/{{.}}" {{ if eq . $model }} selected {{ end }} class="bg-gray-700 text-white">{{.}}</option>
-        {{end}}
-      </select>
+            {{ range .ModelsWithoutConfig }}
+              <option
+                value="chat/{{.}}"
+                {{ if eq . $model }} selected {{ end }}
+                class="bg-gray-700 text-white"
+              >
+                {{.}}
+              </option>
+            {{end}}
+          </select>
+        </div>
       </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>
+      <!-- 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>
+            </div>
+          </template>
         </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">
+      <!-- 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 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>
+            <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>
           </div>
-      </form>
-  </div>
+        </form>
+      </div>
+    </div>
+
+    <!-- Alpine store initialization -->
     <script>
       document.addEventListener("alpine:init", () => {
         Alpine.store("chat", {
@@ -178,31 +242,21 @@ <h1 class="text-lg font-semibold"> <i class="fa-solid fa-comments"></i> Chat {{
             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),
+                marked.parse(this.history[N].content)
               );
             } else {
-              c =  ""
-              // split content newlines in content
+              let c = "";
               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,
-              });
+              this.history.push({ role, content, html: c, image });
             }
-
             const parser = new DOMParser();
             const html = parser.parseFromString(
               this.history[this.history.length - 1].html,
-              "text/html",
+              "text/html"
             );
             const code = html.querySelectorAll("pre code");
             if (!code.length) return;
@@ -216,17 +270,14 @@ <h1 class="text-lg font-semibold"> <i class="fa-solid fa-comments"></i> Chat {{
             });
           },
           messages() {
-            return this.history.map((message) => {
-              return {
-                role: message.role,
-                content: message.content,
-                image: message.image,
-              };
-            });
+            return this.history.map((message) => ({
+              role: message.role,
+              content: message.content,
+              image: message.image,
+            }));
           },
         });
       });
     </script>
-    </div>
   </body>
-</html>
+</html>
\ No newline at end of file
diff --git a/core/http/views/partials/head.html b/core/http/views/partials/head.html
index 57be385d0595..66afb22db6d9 100644
--- a/core/http/views/partials/head.html
+++ b/core/http/views/partials/head.html
@@ -1,36 +1,20 @@
 <head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <title>{{.Title}}</title>
-    <base href="{{.BaseURL}}" />
-    <link rel="icon" type="image/x-icon" href="favicon.ico" />
-    <link
-    rel="stylesheet"
-    href="static/assets/highlightjs.css"
-  />
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>{{.Title}}</title>
+  <base href="{{.BaseURL}}" />
+  <link rel="icon" type="image/x-icon" href="favicon.ico" />
+  <link rel="stylesheet" href="static/assets/highlightjs.css" />
   <script defer src="static/assets/highlightjs.js"></script>
-    <script
-    defer
-    src="static/assets/alpine.js"
-  ></script>
-  <script
-    defer
-    src="static/assets/marked.js"
-  ></script>
-  <script
-    defer
-    src="static/assets/purify.js"
-  ></script>
-
+  <script defer src="static/assets/alpine.js"></script>
+  <script defer src="static/assets/marked.js"></script>
+  <script defer src="static/assets/purify.js"></script>
   <link href="static/general.css" rel="stylesheet" />
-    <link href="static/assets/font1.css" rel="stylesheet">
-    <link
-    href="static/assets/font2.css"
-    rel="stylesheet" />
-  <link
-    rel="stylesheet"
-    href="static/assets/tw-elements.css" />
+  <link href="static/assets/font1.css" rel="stylesheet">
+  <link href="static/assets/font2.css" rel="stylesheet" />
+  <link rel="stylesheet" href="static/assets/tw-elements.css" />
   <script src="static/assets/tailwindcss.js"></script>
+
   <script>
     tailwind.config = {
       darkMode: "class",
@@ -48,84 +32,75 @@
     function copyClipboard(token) {
       navigator.clipboard.writeText(token)
       .then(() => {
-          console.log('Text copied to clipboard:', token);
-          alert('Text copied to clipboard!');
+        console.log('Text copied to clipboard:', token);
+        alert('Text copied to clipboard!');
       })
       .catch(err => {
-          console.error('Failed to copy token:', err);
+        console.error('Failed to copy token:', err);
       });
     }
   </script>
+
   <link href="static/assets/fontawesome/css/fontawesome.css" rel="stylesheet" />
   <link href="static/assets/fontawesome/css/brands.css" rel="stylesheet" />
   <link href="static/assets/fontawesome/css/solid.css" rel="stylesheet" />
   <script src="static/assets/flowbite.min.js"></script>
   <script src="static/assets/htmx.js" crossorigin="anonymous"></script>
-  <!-- P2P Animation START -->
+
+  <!-- Example responsive styling improvements -->
   <style>
     .animation-container {
-        position: relative;
-        width: 100%;
-        height: 25vh;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        overflow: hidden;
+      position: relative;
+      width: 100%;
+      height: 25vh;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      overflow: hidden;
     }
-
     canvas {
-        position: absolute;
-        top: 0;
-        left: 0;
+      position: absolute;
+      top: 0;
+      left: 0;
     }
-
     .text-overlay {
-        position: absolute;
-        top: 50%;
-        left: 50%;
-        transform: translate(-50%, -50%);
-        text-align: center;
-        z-index: 1;
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      text-align: center;
+      z-index: 1;
+    }
+    .fa-circle-nodes {
+      animation: rotateCircleNodes 8s linear infinite;
+      display: inline-block;
+    }
+    @keyframes rotateCircleNodes {
+      0% { transform: rotate(0deg); }
+      100% { transform: rotate(360deg); }
+    }
+    .fa-flask {
+      animation: shakeFlask 3s ease-in-out infinite;
+      transform-origin: bottom center;
+    }
+    @keyframes shakeFlask {
+      0%, 10% { transform: rotate(0deg); }
+      20% { transform: rotate(-10deg); }
+      30% { transform: rotate(10deg); }
+      40% { transform: rotate(-8deg); }
+      50% { transform: rotate(8deg); }
+      60% { transform: rotate(-5deg); }
+      70% { transform: rotate(5deg); }
+      80% { transform: rotate(-2deg); }
+      90% { transform: rotate(2deg); }
+      100% { transform: rotate(0deg); }
     }
-  </style>
-  <!-- P2P Animation END -->
-  <!-- Flask and node animation -->
-  <style>
-            .fa-circle-nodes {
-           /* font-size: 100px; /* Adjust the size as needed */
-            animation: rotateCircleNodes 8s linear infinite; /* Slow and fluid rotation */
-            display: inline-block;
-        }
-
-        @keyframes rotateCircleNodes {
-            0% { transform: rotate(0deg); }
-            100% { transform: rotate(360deg); }
-        }
-        /* Animation for the warning box */
-        .fa-flask {
-         /*  font-size: 100px; /* Adjust the size as needed */
-            animation: shakeFlask 3s ease-in-out infinite; /* Smooth easing and longer duration for fluidity */
-            transform-origin: bottom center;
-        }
-
-        @keyframes shakeFlask {
-            0%, 10% { transform: rotate(0deg); } /* Start and end still */
-            20% { transform: rotate(-10deg); } /* Smooth transition to left */
-            30% { transform: rotate(10deg); } /* Smooth transition to right */
-            40% { transform: rotate(-8deg); } /* Smooth transition to left */
-            50% { transform: rotate(8deg); } /* Smooth transition to right */
-            60% { transform: rotate(-5deg); } /* Smooth transition to left */
-            70% { transform: rotate(5deg); } /* Smooth transition to right */
-            80% { transform: rotate(-2deg); } /* Smooth transition to left */
-            90% { transform: rotate(2deg); } /* Smooth transition to right */
-            100% { transform: rotate(0deg); } /* Return to center */
-        }
   </style>
 
-  <!-- https://stackoverflow.com/questions/76051980/flowbite-component-not-working-when-loaded-via-htmx-django-project -->
+  <!-- Initialize Flowbite on HTMX content load -->
   <script>
-      htmx.onLoad(function(content) {
-          initFlowbite();
-      })
+    htmx.onLoad(function(content) {
+      initFlowbite();
+    });
   </script>
 </head>
\ No newline at end of file
