From b701660f880634ff9babb2f4bf49a19666647293 Mon Sep 17 00:00:00 2001
From: Timothy Carambat <rambat1010@gmail.com>
Date: Wed, 13 Nov 2024 11:11:13 -0800
Subject: [PATCH] Frontend performance improvements (#2627)

* Frontend performance improvements

* test docker build
---
 .github/workflows/dev-build.yaml              |   2 +-
 frontend/src/components/Footer/index.jsx      |  35 +--
 .../Documents/Directory/FileRow/index.jsx     |  26 +-
 .../Documents/Directory/index.jsx             | 279 ++++++++++--------
 .../WorkspaceFileRow/index.jsx                |  50 +---
 .../Documents/WorkspaceDirectory/index.jsx    |  55 ++++
 .../src/components/SettingsButton/index.jsx   |  13 +-
 .../ChatHistory/Citation/index.jsx            |  26 +-
 .../Actions/ActionMenu/index.jsx              |   7 -
 .../Actions/EditMessage/index.jsx             |   7 -
 .../Actions/TTSButton/asyncTts.jsx            |   7 -
 .../Actions/TTSButton/native.jsx              |   7 -
 .../Actions/TTSButton/piperTTS.jsx            |   7 -
 .../HistoricalMessage/Actions/index.jsx       |  24 +-
 .../ChatContainer/ChatTooltips/index.jsx      |  66 +++++
 .../WorkspaceChat/ChatContainer/index.jsx     |   2 +
 .../src/components/WorkspaceChat/index.jsx    |   4 +-
 .../src/pages/Admin/Agents/Badges/default.jsx |  10 +-
 frontend/src/pages/Admin/Agents/index.jsx     |  76 +++--
 .../BrowserExtensionApiKeyRow/index.jsx       |  17 +-
 .../BrowserExtensionApiKey/index.jsx          |  13 +
 21 files changed, 391 insertions(+), 342 deletions(-)
 create mode 100644 frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx

diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml
index 86e10bbcd..e7acd4e53 100644
--- a/.github/workflows/dev-build.yaml
+++ b/.github/workflows/dev-build.yaml
@@ -6,7 +6,7 @@ concurrency:
 
 on:
   push:
-    branches: ['agent-skill-plugins'] # put your current branch to create a build. Core team only.
+    branches: ['2602-page-load-speed'] # put your current branch to create a build. Core team only.
     paths-ignore:
       - '**.md'
       - 'cloud-deployments/*'
diff --git a/frontend/src/components/Footer/index.jsx b/frontend/src/components/Footer/index.jsx
index 2dfccc3f9..095aeba91 100644
--- a/frontend/src/components/Footer/index.jsx
+++ b/frontend/src/components/Footer/index.jsx
@@ -15,7 +15,6 @@ import React, { useEffect, useState } from "react";
 import SettingsButton from "../SettingsButton";
 import { isMobile } from "react-device-detect";
 import { Tooltip } from "react-tooltip";
-import { v4 } from "uuid";
 
 export const MAX_ICONS = 3;
 export const ICON_COMPONENTS = {
@@ -49,40 +48,40 @@ export default function Footer() {
     return (
       <div className="flex justify-center mb-2">
         <div className="flex space-x-4">
-          <ToolTipWrapper id="open-github">
+          <div className="flex w-fit">
             <a
               href={paths.github()}
               target="_blank"
               rel="noreferrer"
               className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
               aria-label="Find us on Github"
-              data-tooltip-id="open-github"
+              data-tooltip-id="footer-item"
               data-tooltip-content="View source code on Github"
             >
               <GithubLogo weight="fill" className="h-5 w-5 " />
             </a>
-          </ToolTipWrapper>
-          <ToolTipWrapper id="open-documentation">
+          </div>
+          <div className="flex w-fit">
             <a
               href={paths.docs()}
               target="_blank"
               rel="noreferrer"
               className="w-fit transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
               aria-label="Docs"
-              data-tooltip-id="open-documentation"
+              data-tooltip-id="footer-item"
               data-tooltip-content="Open AnythingLLM help docs"
             >
               <BookOpen weight="fill" className="h-5 w-5 " />
             </a>
-          </ToolTipWrapper>
-          <ToolTipWrapper id="open-discord">
+          </div>
+          <div className="flex w-fit">
             <a
               href={paths.discord()}
               target="_blank"
               rel="noreferrer"
               className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
               aria-label="Join our Discord server"
-              data-tooltip-id="open-discord"
+              data-tooltip-id="footer-item"
               data-tooltip-content="Join the AnythingLLM Discord"
             >
               <DiscordLogo
@@ -90,9 +89,15 @@ export default function Footer() {
                 className="h-5 w-5 stroke-slate-200 group-hover:stroke-slate-200"
               />
             </a>
-          </ToolTipWrapper>
+          </div>
           {!isMobile && <SettingsButton />}
         </div>
+        <Tooltip
+          id="footer-item"
+          place="top"
+          delayShow={300}
+          className="tooltip !text-xs z-99"
+        />
       </div>
     );
   }
@@ -119,16 +124,8 @@ export default function Footer() {
         ))}
         {!isMobile && <SettingsButton />}
       </div>
-    </div>
-  );
-}
-
-export function ToolTipWrapper({ id = v4(), children }) {
-  return (
-    <div className="flex w-fit">
-      {children}
       <Tooltip
-        id={id}
+        id="footer-item"
         place="top"
         delayShow={300}
         className="tooltip !text-xs z-99"
diff --git a/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/FileRow/index.jsx b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/FileRow/index.jsx
index fc3546c17..663c73d56 100644
--- a/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/FileRow/index.jsx
+++ b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/FileRow/index.jsx
@@ -5,7 +5,6 @@ import {
   middleTruncate,
 } from "@/utils/directories";
 import { File } from "@phosphor-icons/react";
-import { Tooltip } from "react-tooltip";
 
 export default function FileRow({ item, selected, toggleSelection }) {
   return (
@@ -16,8 +15,13 @@ export default function FileRow({ item, selected, toggleSelection }) {
       }`}
     >
       <div
-        data-tooltip-id={`directory-item-${item.url}`}
+        data-tooltip-id={`directory-item`}
         className="col-span-10 w-fit flex gap-x-[4px] items-center relative"
+        data-tooltip-content={JSON.stringify({
+          title: item.title,
+          date: formatDate(item?.published),
+          extension: getFileExtension(item.url).toUpperCase(),
+        })}
       >
         <div
           className="shrink-0 w-3 h-3 rounded border-[1px] border-white flex justify-center items-center cursor-pointer"
@@ -42,24 +46,6 @@ export default function FileRow({ item, selected, toggleSelection }) {
           </div>
         )}
       </div>
-      <Tooltip
-        id={`directory-item-${item.url}`}
-        place="bottom"
-        delayShow={800}
-        className="tooltip invert z-99"
-      >
-        <div className="text-xs ">
-          <p className="text-white">{item.title}</p>
-          <div className="flex mt-1 gap-x-2">
-            <p className="">
-              Date: <b>{formatDate(item?.published)}</b>
-            </p>
-            <p className="">
-              Type: <b>{getFileExtension(item.url).toUpperCase()}</b>
-            </p>
-          </div>
-        </div>
-      </Tooltip>
     </tr>
   );
 }
diff --git a/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx
index d1c5eba78..d87ae66c8 100644
--- a/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx
+++ b/frontend/src/components/Modals/ManageWorkspace/Documents/Directory/index.jsx
@@ -13,6 +13,8 @@ import NewFolderModal from "./NewFolderModal";
 import debounce from "lodash.debounce";
 import { filterFileSearchResults } from "./utils";
 import ContextMenu from "./ContextMenu";
+import { Tooltip } from "react-tooltip";
+import { safeJsonParse } from "@/utils/request";
 
 function Directory({
   files,
@@ -188,140 +190,175 @@ function Directory({
   };
 
   return (
-    <div className="px-8 pb-8" onContextMenu={handleContextMenu}>
-      <div className="flex flex-col gap-y-6">
-        <div className="flex items-center justify-between w-[560px] px-5 relative">
-          <h3 className="text-white text-base font-bold">My Documents</h3>
-          <div className="relative">
-            <input
-              type="search"
-              placeholder="Search for document"
-              onChange={handleSearch}
-              className="search-input bg-zinc-900 text-white placeholder-white/40 text-sm rounded-lg pl-9 pr-2.5 py-2 w-[250px] h-[32px]"
-            />
-            <MagnifyingGlass
-              size={14}
-              className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white"
-              weight="bold"
-            />
-          </div>
-          <button
-            className="flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-[#222628]/60 z-20 relative"
-            onClick={openFolderModal}
-          >
-            <Plus size={18} weight="bold" color="#D3D4D4" />
-            <div className="text-[#D3D4D4] text-xs font-bold leading-[18px]">
-              New Folder
+    <>
+      <div className="px-8 pb-8" onContextMenu={handleContextMenu}>
+        <div className="flex flex-col gap-y-6">
+          <div className="flex items-center justify-between w-[560px] px-5 relative">
+            <h3 className="text-white text-base font-bold">My Documents</h3>
+            <div className="relative">
+              <input
+                type="search"
+                placeholder="Search for document"
+                onChange={handleSearch}
+                className="search-input bg-zinc-900 text-white placeholder-white/40 text-sm rounded-lg pl-9 pr-2.5 py-2 w-[250px] h-[32px]"
+              />
+              <MagnifyingGlass
+                size={14}
+                className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white"
+                weight="bold"
+              />
             </div>
-          </button>
-        </div>
-
-        <div className="relative w-[560px] h-[310px] bg-zinc-900 rounded-2xl overflow-hidden">
-          <div className="absolute top-0 left-0 right-0 z-10 rounded-t-2xl text-white/80 text-xs grid grid-cols-12 py-2 px-8 border-b border-white/20 shadow-lg bg-zinc-900">
-            <p className="col-span-6">Name</p>
+            <button
+              className="flex items-center gap-x-2 cursor-pointer px-[14px] py-[7px] -mr-[14px] rounded-lg hover:bg-[#222628]/60 z-20 relative"
+              onClick={openFolderModal}
+            >
+              <Plus size={18} weight="bold" color="#D3D4D4" />
+              <div className="text-[#D3D4D4] text-xs font-bold leading-[18px]">
+                New Folder
+              </div>
+            </button>
           </div>
 
-          <div className="overflow-y-auto h-full pt-8">
-            {loading ? (
-              <div className="w-full h-full flex items-center justify-center flex-col gap-y-5">
-                <PreLoader />
-                <p className="text-white/80 text-sm font-semibold animate-pulse text-center w-1/3">
-                  {loadingMessage}
-                </p>
-              </div>
-            ) : filteredFiles.length > 0 ? (
-              filteredFiles.map(
-                (item, index) =>
-                  item.type === "folder" && (
-                    <FolderRow
-                      key={index}
-                      item={item}
-                      selected={isSelected(
-                        item.id,
-                        item.type === "folder" ? item : null
+          <div className="relative w-[560px] h-[310px] bg-zinc-900 rounded-2xl overflow-hidden">
+            <div className="absolute top-0 left-0 right-0 z-10 rounded-t-2xl text-white/80 text-xs grid grid-cols-12 py-2 px-8 border-b border-white/20 shadow-lg bg-zinc-900">
+              <p className="col-span-6">Name</p>
+            </div>
+
+            <div className="overflow-y-auto h-full pt-8">
+              {loading ? (
+                <div className="w-full h-full flex items-center justify-center flex-col gap-y-5">
+                  <PreLoader />
+                  <p className="text-white/80 text-sm font-semibold animate-pulse text-center w-1/3">
+                    {loadingMessage}
+                  </p>
+                </div>
+              ) : filteredFiles.length > 0 ? (
+                filteredFiles.map(
+                  (item, index) =>
+                    item.type === "folder" && (
+                      <FolderRow
+                        key={index}
+                        item={item}
+                        selected={isSelected(
+                          item.id,
+                          item.type === "folder" ? item : null
+                        )}
+                        onRowClick={() => toggleSelection(item)}
+                        toggleSelection={toggleSelection}
+                        isSelected={isSelected}
+                        autoExpanded={index === 0}
+                      />
+                    )
+                )
+              ) : (
+                <div className="w-full h-full flex items-center justify-center">
+                  <p className="text-white text-opacity-40 text-sm font-medium">
+                    No Documents
+                  </p>
+                </div>
+              )}
+            </div>
+            {amountSelected !== 0 && (
+              <div className="absolute bottom-[12px] left-0 right-0 flex justify-center pointer-events-none">
+                <div className="mx-auto bg-white/40 rounded-lg py-1 px-2 pointer-events-auto">
+                  <div className="flex flex-row items-center gap-x-2">
+                    <button
+                      onClick={moveToWorkspace}
+                      onMouseEnter={() => setHighlightWorkspace(true)}
+                      onMouseLeave={() => setHighlightWorkspace(false)}
+                      className="border-none text-sm font-semibold bg-white h-[30px] px-2.5 rounded-lg hover:text-white hover:bg-neutral-800/80"
+                    >
+                      Move to Workspace
+                    </button>
+                    <div className="relative">
+                      <button
+                        onClick={() =>
+                          setShowFolderSelection(!showFolderSelection)
+                        }
+                        className="border-none text-sm font-semibold bg-white h-[32px] w-[32px] rounded-lg text-dark-text hover:bg-neutral-800/80 flex justify-center items-center group"
+                      >
+                        <MoveToFolderIcon className="text-dark-text group-hover:text-white" />
+                      </button>
+                      {showFolderSelection && (
+                        <FolderSelectionPopup
+                          folders={files.items.filter(
+                            (item) => item.type === "folder"
+                          )}
+                          onSelect={moveToFolder}
+                          onClose={() => setShowFolderSelection(false)}
+                        />
                       )}
-                      onRowClick={() => toggleSelection(item)}
-                      toggleSelection={toggleSelection}
-                      isSelected={isSelected}
-                      autoExpanded={index === 0}
-                    />
-                  )
-              )
-            ) : (
-              <div className="w-full h-full flex items-center justify-center">
-                <p className="text-white text-opacity-40 text-sm font-medium">
-                  No Documents
-                </p>
-              </div>
-            )}
-          </div>
-          {amountSelected !== 0 && (
-            <div className="absolute bottom-[12px] left-0 right-0 flex justify-center pointer-events-none">
-              <div className="mx-auto bg-white/40 rounded-lg py-1 px-2 pointer-events-auto">
-                <div className="flex flex-row items-center gap-x-2">
-                  <button
-                    onClick={moveToWorkspace}
-                    onMouseEnter={() => setHighlightWorkspace(true)}
-                    onMouseLeave={() => setHighlightWorkspace(false)}
-                    className="border-none text-sm font-semibold bg-white h-[30px] px-2.5 rounded-lg hover:text-white hover:bg-neutral-800/80"
-                  >
-                    Move to Workspace
-                  </button>
-                  <div className="relative">
+                    </div>
                     <button
-                      onClick={() =>
-                        setShowFolderSelection(!showFolderSelection)
-                      }
-                      className="border-none text-sm font-semibold bg-white h-[32px] w-[32px] rounded-lg text-dark-text hover:bg-neutral-800/80 flex justify-center items-center group"
+                      onClick={deleteFiles}
+                      className="border-none text-sm font-semibold bg-white h-[32px] w-[32px] rounded-lg text-dark-text hover:text-white hover:bg-neutral-800/80 flex justify-center items-center"
                     >
-                      <MoveToFolderIcon className="text-dark-text group-hover:text-white" />
+                      <Trash size={18} weight="bold" />
                     </button>
-                    {showFolderSelection && (
-                      <FolderSelectionPopup
-                        folders={files.items.filter(
-                          (item) => item.type === "folder"
-                        )}
-                        onSelect={moveToFolder}
-                        onClose={() => setShowFolderSelection(false)}
-                      />
-                    )}
                   </div>
-                  <button
-                    onClick={deleteFiles}
-                    className="border-none text-sm font-semibold bg-white h-[32px] w-[32px] rounded-lg text-dark-text hover:text-white hover:bg-neutral-800/80 flex justify-center items-center"
-                  >
-                    <Trash size={18} weight="bold" />
-                  </button>
                 </div>
               </div>
-            </div>
-          )}
-        </div>
+            )}
+          </div>
 
-        <UploadFile
-          workspace={workspace}
-          fetchKeys={fetchKeys}
-          setLoading={setLoading}
-          setLoadingMessage={setLoadingMessage}
-        />
-      </div>
-      {isFolderModalOpen && (
-        <div className="bg-black/60 backdrop-blur-sm fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center z-30">
-          <NewFolderModal
-            closeModal={closeFolderModal}
-            files={files}
-            setFiles={setFiles}
+          <UploadFile
+            workspace={workspace}
+            fetchKeys={fetchKeys}
+            setLoading={setLoading}
+            setLoadingMessage={setLoadingMessage}
           />
         </div>
-      )}
-      <ContextMenu
-        contextMenu={contextMenu}
-        closeContextMenu={closeContextMenu}
-        files={files}
-        selectedItems={selectedItems}
-        setSelectedItems={setSelectedItems}
-      />
-    </div>
+        {isFolderModalOpen && (
+          <div className="bg-black/60 backdrop-blur-sm fixed top-0 left-0 outline-none w-screen h-screen flex items-center justify-center z-30">
+            <NewFolderModal
+              closeModal={closeFolderModal}
+              files={files}
+              setFiles={setFiles}
+            />
+          </div>
+        )}
+        <ContextMenu
+          contextMenu={contextMenu}
+          closeContextMenu={closeContextMenu}
+          files={files}
+          selectedItems={selectedItems}
+          setSelectedItems={setSelectedItems}
+        />
+      </div>
+      <DirectoryTooltips />
+    </>
+  );
+}
+
+/**
+ * Tooltips for the directory components. Renders when the directory is shown
+ * or updated so that tooltips are attached as the items are changed.
+ */
+function DirectoryTooltips() {
+  return (
+    <Tooltip
+      id="directory-item"
+      place="bottom"
+      delayShow={800}
+      className="tooltip invert z-99"
+      render={({ content }) => {
+        const data = safeJsonParse(content, null);
+        if (!data) return null;
+        return (
+          <div className="text-xs">
+            <p className="text-white">{data.title}</p>
+            <div className="flex mt-1 gap-x-2">
+              <p className="">
+                Date: <b>{data.date}</b>
+              </p>
+              <p className="">
+                Type: <b>{data.extension}</b>
+              </p>
+            </div>
+          </div>
+        );
+      }}
+    />
   );
 }
 
diff --git a/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/WorkspaceFileRow/index.jsx b/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/WorkspaceFileRow/index.jsx
index 505c4c22c..496702fc2 100644
--- a/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/WorkspaceFileRow/index.jsx
+++ b/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/WorkspaceFileRow/index.jsx
@@ -8,7 +8,6 @@ import { ArrowUUpLeft, Eye, File, PushPin } from "@phosphor-icons/react";
 import Workspace from "@/models/workspace";
 import showToast from "@/utils/toast";
 import System from "@/models/system";
-import { Tooltip } from "react-tooltip";
 
 export default function WorkspaceFileRow({
   item,
@@ -64,7 +63,12 @@ export default function WorkspaceFileRow({
     >
       <div
         className="col-span-10 w-fit flex gap-x-[2px] items-center relative"
-        data-tooltip-id={`ws-directory-item-${item.url}`}
+        data-tooltip-id="ws-directory-item"
+        data-tooltip-content={JSON.stringify({
+          title: item.title,
+          date: formatDate(item?.published),
+          extension: getFileExtension(item.url).toUpperCase(),
+        })}
       >
         <div className="shrink-0 w-3 h-3">
           {!disableSelection ? (
@@ -106,24 +110,6 @@ export default function WorkspaceFileRow({
           </div>
         )}
       </div>
-      <Tooltip
-        id={`ws-directory-item-${item.url}`}
-        place="bottom"
-        delayShow={800}
-        className="tooltip invert z-99"
-      >
-        <div className="text-xs ">
-          <p className="text-white">{item.title}</p>
-          <div className="flex mt-1 gap-x-2">
-            <p className="">
-              Date: <b>{formatDate(item?.published)}</b>
-            </p>
-            <p className="">
-              Type: <b>{getFileExtension(item.url).toUpperCase()}</b>
-            </p>
-          </div>
-        </div>
-      </Tooltip>
     </div>
   );
 }
@@ -175,7 +161,7 @@ const PinItemToWorkspace = memo(({ workspace, docPath, item }) => {
       className="flex gap-x-2 items-center hover:bg-main-gradient p-[2px] rounded ml-2"
     >
       <PushPin
-        data-tooltip-id={`pin-${item.id}`}
+        data-tooltip-id="pin-document"
         data-tooltip-content={
           pinned ? "Un-Pin from workspace" : "Pin to workspace"
         }
@@ -184,12 +170,6 @@ const PinItemToWorkspace = memo(({ workspace, docPath, item }) => {
         weight={hover || pinned ? "fill" : "regular"}
         className="outline-none text-base font-bold flex-shrink-0 cursor-pointer"
       />
-      <Tooltip
-        id={`pin-${item.id}`}
-        place="bottom"
-        delayShow={300}
-        className="tooltip invert !text-xs"
-      />
     </div>
   );
 });
@@ -247,7 +227,7 @@ const WatchForChanges = memo(({ workspace, docPath, item }) => {
       className="flex gap-x-2 items-center hover:bg-main-gradient p-[2px] rounded ml-2"
     >
       <Eye
-        data-tooltip-id={`watch-changes-${item.id}`}
+        data-tooltip-id="watch-changes"
         data-tooltip-content={
           watched ? "Stop watching for changes" : "Watch document for changes"
         }
@@ -256,12 +236,6 @@ const WatchForChanges = memo(({ workspace, docPath, item }) => {
         weight={hover || watched ? "fill" : "regular"}
         className="outline-none text-base font-bold flex-shrink-0 cursor-pointer"
       />
-      <Tooltip
-        id={`watch-changes-${item.id}`}
-        place="bottom"
-        delayShow={300}
-        className="tooltip invert !text-xs"
-      />
     </div>
   );
 });
@@ -270,17 +244,11 @@ const RemoveItemFromWorkspace = ({ item, onClick }) => {
   return (
     <div>
       <ArrowUUpLeft
-        data-tooltip-id={`remove-${item.id}`}
+        data-tooltip-id="remove-document"
         data-tooltip-content="Remove document from workspace"
         onClick={onClick}
         className="text-base font-bold w-4 h-4 ml-2 flex-shrink-0 cursor-pointer"
       />
-      <Tooltip
-        id={`remove-${item.id}`}
-        place="bottom"
-        delayShow={300}
-        className="tooltip invert !text-xs"
-      />
     </div>
   );
 };
diff --git a/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/index.jsx b/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/index.jsx
index e9864f12c..b54f29cc1 100644
--- a/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/index.jsx
+++ b/frontend/src/components/Modals/ManageWorkspace/Documents/WorkspaceDirectory/index.jsx
@@ -8,6 +8,8 @@ import { SEEN_DOC_PIN_ALERT, SEEN_WATCH_ALERT } from "@/utils/constants";
 import paths from "@/utils/paths";
 import { Link } from "react-router-dom";
 import Workspace from "@/models/workspace";
+import { Tooltip } from "react-tooltip";
+import { safeJsonParse } from "@/utils/request";
 
 function WorkspaceDirectory({
   workspace,
@@ -241,6 +243,7 @@ function WorkspaceDirectory({
       </div>
       <PinAlert />
       <DocumentWatchAlert />
+      <WorkspaceDocumentTooltips />
     </>
   );
 }
@@ -396,4 +399,56 @@ function RenderFileRows({ files, movedItems, children }) {
     });
 }
 
+/**
+ * Tooltips for the workspace directory components. Renders when the workspace directory is shown
+ * or updated so that tooltips are attached as the items are changed.
+ */
+function WorkspaceDocumentTooltips() {
+  return (
+    <>
+      <Tooltip
+        id="ws-directory-item"
+        place="bottom"
+        delayShow={800}
+        className="tooltip invert z-99"
+        render={({ content }) => {
+          const data = safeJsonParse(content, null);
+          if (!data) return null;
+          return (
+            <div className="text-xs">
+              <p className="text-white">{data.title}</p>
+              <div className="flex mt-1 gap-x-2">
+                <p className="">
+                  Date: <b>{data.date}</b>
+                </p>
+                <p className="">
+                  Type: <b>{data.extension}</b>
+                </p>
+              </div>
+            </div>
+          );
+        }}
+      />
+      <Tooltip
+        id="watch-changes"
+        place="bottom"
+        delayShow={300}
+        className="tooltip invert !text-xs"
+      />
+      <Tooltip
+        id="pin-document"
+        place="bottom"
+        delayShow={300}
+        className="tooltip invert !text-xs"
+      />
+      <Tooltip
+        id="remove-document"
+        place="bottom"
+        delayShow={300}
+        className="tooltip invert !text-xs"
+      />
+    </>
+  );
+}
+
 export default memo(WorkspaceDirectory);
diff --git a/frontend/src/components/SettingsButton/index.jsx b/frontend/src/components/SettingsButton/index.jsx
index f53e675f1..6ce4fad15 100644
--- a/frontend/src/components/SettingsButton/index.jsx
+++ b/frontend/src/components/SettingsButton/index.jsx
@@ -3,7 +3,6 @@ import paths from "@/utils/paths";
 import { ArrowUUpLeft, Wrench } from "@phosphor-icons/react";
 import { Link } from "react-router-dom";
 import { useMatch } from "react-router-dom";
-import { ToolTipWrapper } from "../Footer";
 
 export default function SettingsButton() {
   const isInSettings = !!useMatch("/settings/*");
@@ -13,30 +12,30 @@ export default function SettingsButton() {
 
   if (isInSettings)
     return (
-      <ToolTipWrapper id="go-home">
+      <div className="flex w-fit">
         <Link
           to={paths.home()}
           className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
           aria-label="Home"
-          data-tooltip-id="go-home"
+          data-tooltip-id="footer-item"
           data-tooltip-content="Back to workspaces"
         >
           <ArrowUUpLeft className="h-5 w-5" weight="fill" />
         </Link>
-      </ToolTipWrapper>
+      </div>
     );
 
   return (
-    <ToolTipWrapper id="open-settings">
+    <div className="flex w-fit">
       <Link
         to={paths.settings.appearance()}
         className="transition-all duration-300 p-2 rounded-full text-white bg-sidebar-button hover:bg-menu-item-selected-gradient hover:border-slate-100 hover:border-opacity-50 border-transparent border"
         aria-label="Settings"
-        data-tooltip-id="open-settings"
+        data-tooltip-id="footer-item"
         data-tooltip-content="Open settings"
       >
         <Wrench className="h-5 w-5" weight="fill" />
       </Link>
-    </ToolTipWrapper>
+    </div>
   );
 }
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx
index 1f726b781..6f81b17c5 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/Citation/index.jsx
@@ -15,7 +15,6 @@ import {
   YoutubeLogo,
 } from "@phosphor-icons/react";
 import ConfluenceLogo from "@/media/dataConnectors/confluence.png";
-import { Tooltip } from "react-tooltip";
 import { toPercentString } from "@/utils/numbers";
 
 function combineLikeSources(sources) {
@@ -176,23 +175,16 @@ function CitationDetailModal({ source, onClose }) {
                   </p>
 
                   {!!score && (
-                    <>
-                      <div className="w-full flex items-center text-xs text-white/60 gap-x-2 cursor-default">
-                        <div
-                          data-tooltip-id="similarity-score"
-                          data-tooltip-content={`This is the semantic similarity score of this chunk of text compared to your query calculated by the vector database.`}
-                          className="flex items-center gap-x-1"
-                        >
-                          <Info size={14} />
-                          <p>{toPercentString(score)} match</p>
-                        </div>
+                    <div className="w-full flex items-center text-xs text-white/60 gap-x-2 cursor-default">
+                      <div
+                        data-tooltip-id="similarity-score"
+                        data-tooltip-content={`This is the semantic similarity score of this chunk of text compared to your query calculated by the vector database.`}
+                        className="flex items-center gap-x-1"
+                      >
+                        <Info size={14} />
+                        <p>{toPercentString(score)} match</p>
                       </div>
-                      <Tooltip
-                        id="similarity-score"
-                        place="top"
-                        delayShow={100}
-                      />
-                    </>
+                    </div>
                   )}
                 </div>
                 {[...Array(3)].map((_, idx) => (
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/ActionMenu/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/ActionMenu/index.jsx
index de7df30f8..acd0c4fa5 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/ActionMenu/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/ActionMenu/index.jsx
@@ -1,6 +1,5 @@
 import React, { useState, useEffect, useRef } from "react";
 import { Trash, DotsThreeVertical, TreeView } from "@phosphor-icons/react";
-import { Tooltip } from "react-tooltip";
 
 function ActionMenu({ chatId, forkThread, isEditing, role }) {
   const [open, setOpen] = useState(false);
@@ -37,12 +36,6 @@ function ActionMenu({ chatId, forkThread, isEditing, role }) {
 
   return (
     <div className="mt-2 -ml-0.5 relative" ref={menuRef}>
-      <Tooltip
-        id="action-menu"
-        place="top"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
       <button
         onClick={toggleMenu}
         className="border-none text-zinc-300 hover:text-zinc-100 transition-colors duration-200"
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/EditMessage/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/EditMessage/index.jsx
index 811de87d6..c99dd5b92 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/EditMessage/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/EditMessage/index.jsx
@@ -1,7 +1,6 @@
 import { AI_BACKGROUND_COLOR, USER_BACKGROUND_COLOR } from "@/utils/constants";
 import { Pencil } from "@phosphor-icons/react";
 import { useState, useEffect, useRef } from "react";
-import { Tooltip } from "react-tooltip";
 
 const EDIT_EVENT = "toggle-message-edit";
 
@@ -55,12 +54,6 @@ export function EditMessageAction({ chatId = null, role, isEditing }) {
       >
         <Pencil size={21} className="mb-1" />
       </button>
-      <Tooltip
-        id="edit-input-text"
-        place="bottom"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
     </div>
   );
 }
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/asyncTts.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/asyncTts.jsx
index 1947f0057..3fa99174f 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/asyncTts.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/asyncTts.jsx
@@ -1,6 +1,5 @@
 import { useEffect, useState, useRef } from "react";
 import { SpeakerHigh, PauseCircle, CircleNotch } from "@phosphor-icons/react";
-import { Tooltip } from "react-tooltip";
 import Workspace from "@/models/workspace";
 import showToast from "@/utils/toast";
 
@@ -83,12 +82,6 @@ export default function AsyncTTSMessage({ slug, chatId }) {
           controls={false}
         />
       </button>
-      <Tooltip
-        id="message-to-speech"
-        place="bottom"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
     </div>
   );
 }
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/native.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/native.jsx
index 5f3bd3f69..daa9ebda9 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/native.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/native.jsx
@@ -1,6 +1,5 @@
 import React, { useEffect, useState } from "react";
 import { SpeakerHigh, PauseCircle } from "@phosphor-icons/react";
-import { Tooltip } from "react-tooltip";
 
 export default function NativeTTSMessage({ message }) {
   const [speaking, setSpeaking] = useState(false);
@@ -50,12 +49,6 @@ export default function NativeTTSMessage({ message }) {
           <SpeakerHigh size={18} className="mb-1" />
         )}
       </button>
-      <Tooltip
-        id="message-to-speech"
-        place="bottom"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
     </div>
   );
 }
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/piperTTS.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/piperTTS.jsx
index f8431a3bd..1ec39874c 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/piperTTS.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/TTSButton/piperTTS.jsx
@@ -1,6 +1,5 @@
 import { useEffect, useState, useRef } from "react";
 import { SpeakerHigh, PauseCircle, CircleNotch } from "@phosphor-icons/react";
-import { Tooltip } from "react-tooltip";
 import PiperTTSClient from "@/utils/piperTTS";
 
 export default function PiperTTS({ voiceId = null, message }) {
@@ -80,12 +79,6 @@ export default function PiperTTS({ voiceId = null, message }) {
           controls={false}
         />
       </button>
-      <Tooltip
-        id="message-to-speech"
-        place="bottom"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
     </div>
   );
 }
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx
index e383b4a03..e396fd11b 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatHistory/HistoricalMessage/Actions/index.jsx
@@ -1,7 +1,6 @@
 import React, { memo, useState } from "react";
 import useCopyText from "@/hooks/useCopyText";
 import { Check, ThumbsUp, ArrowsClockwise, Copy } from "@phosphor-icons/react";
-import { Tooltip } from "react-tooltip";
 import Workspace from "@/models/workspace";
 import { EditMessageAction } from "./EditMessage";
 import ActionMenu from "./ActionMenu";
@@ -46,7 +45,7 @@ const Actions = ({
             <FeedbackButton
               isSelected={selectedFeedback === true}
               handleFeedback={() => handleFeedback(true)}
-              tooltipId={`${chatId}-thumbs-up`}
+              tooltipId="feedback-button"
               tooltipContent="Good response"
               IconComponent={ThumbsUp}
             />
@@ -66,7 +65,6 @@ const Actions = ({
 function FeedbackButton({
   isSelected,
   handleFeedback,
-  tooltipId,
   tooltipContent,
   IconComponent,
 }) {
@@ -74,7 +72,7 @@ function FeedbackButton({
     <div className="mt-3 relative">
       <button
         onClick={handleFeedback}
-        data-tooltip-id={tooltipId}
+        data-tooltip-id="feedback-button"
         data-tooltip-content={tooltipContent}
         className="text-zinc-300"
         aria-label={tooltipContent}
@@ -85,12 +83,6 @@ function FeedbackButton({
           weight={isSelected ? "fill" : "regular"}
         />
       </button>
-      <Tooltip
-        id={tooltipId}
-        place="bottom"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
     </div>
   );
 }
@@ -114,12 +106,6 @@ function CopyMessage({ message }) {
             <Copy size={20} className="mb-1" />
           )}
         </button>
-        <Tooltip
-          id="copy-assistant-text"
-          place="bottom"
-          delayShow={300}
-          className="tooltip !text-xs"
-        />
       </div>
     </>
   );
@@ -138,12 +124,6 @@ function RegenerateMessage({ regenerateMessage, chatId }) {
       >
         <ArrowsClockwise size={20} className="mb-1" weight="fill" />
       </button>
-      <Tooltip
-        id="regenerate-assistant-text"
-        place="bottom"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
     </div>
   );
 }
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx
new file mode 100644
index 000000000..99571ba33
--- /dev/null
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/ChatTooltips/index.jsx
@@ -0,0 +1,66 @@
+import { Tooltip } from "react-tooltip";
+
+/**
+ * Set the tooltips for the chat container in bulk.
+ * Why do this?
+ *
+ * React-tooltip rendering on _each_ chat will attach an event listener to the body.
+ * This will add up if we have many chats open resulting in the browser crashing
+ * so we batch them together in a single component that renders at the top most level with
+ * a static id the content can change, but this prevents the React-tooltip library from adding
+ * hundreds of event listeners to the DOM.
+ *
+ * In general, anywhere we have iterative rendering the Tooltip should be rendered at the highest level to prevent
+ * hundreds of event listeners from being added to the DOM in the worst case scenario.
+ * @returns
+ */
+export function ChatTooltips() {
+  return (
+    <>
+      <Tooltip
+        id="message-to-speech"
+        place="bottom"
+        delayShow={300}
+        className="tooltip !text-xs"
+      />
+      <Tooltip
+        id="regenerate-assistant-text"
+        place="bottom"
+        delayShow={300}
+        className="tooltip !text-xs"
+      />
+      <Tooltip
+        id="copy-assistant-text"
+        place="bottom"
+        delayShow={300}
+        className="tooltip !text-xs"
+      />
+      <Tooltip
+        id="feedback-button"
+        place="bottom"
+        delayShow={300}
+        className="tooltip !text-xs"
+      />
+      <Tooltip
+        id="action-menu"
+        place="top"
+        delayShow={300}
+        className="tooltip !text-xs"
+      />
+      <Tooltip
+        id="edit-input-text"
+        place="bottom"
+        delayShow={300}
+        className="tooltip !text-xs"
+      />
+      <Tooltip
+        id="similarity-score"
+        place="top"
+        delayShow={100}
+        // z-[99] to ensure it renders above the chat history
+        // as the citation modal is z-indexed above the chat history
+        className="tooltip !text-xs z-[99]"
+      />
+    </>
+  );
+}
diff --git a/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx b/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx
index 4f09c0b44..6b2a51fcd 100644
--- a/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx
+++ b/frontend/src/components/WorkspaceChat/ChatContainer/index.jsx
@@ -17,6 +17,7 @@ import DnDFileUploaderWrapper from "./DnDWrapper";
 import SpeechRecognition, {
   useSpeechRecognition,
 } from "react-speech-recognition";
+import { ChatTooltips } from "./ChatTooltips";
 
 export default function ChatContainer({ workspace, knownHistory = [] }) {
   const { threadSlug = null } = useParams();
@@ -284,6 +285,7 @@ export default function ChatContainer({ workspace, knownHistory = [] }) {
           attachments={files}
         />
       </DnDFileUploaderWrapper>
+      <ChatTooltips />
     </div>
   );
 }
diff --git a/frontend/src/components/WorkspaceChat/index.jsx b/frontend/src/components/WorkspaceChat/index.jsx
index c37ecb0c8..61dc43df5 100644
--- a/frontend/src/components/WorkspaceChat/index.jsx
+++ b/frontend/src/components/WorkspaceChat/index.jsx
@@ -5,9 +5,7 @@ import ChatContainer from "./ChatContainer";
 import paths from "@/utils/paths";
 import ModalWrapper from "../ModalWrapper";
 import { useParams } from "react-router-dom";
-import DnDFileUploaderWrapper, {
-  DnDFileUploaderProvider,
-} from "./ChatContainer/DnDWrapper";
+import { DnDFileUploaderProvider } from "./ChatContainer/DnDWrapper";
 
 export default function WorkspaceChat({ loading, workspace }) {
   const { threadSlug = null } = useParams();
diff --git a/frontend/src/pages/Admin/Agents/Badges/default.jsx b/frontend/src/pages/Admin/Agents/Badges/default.jsx
index 238155508..13a37abad 100644
--- a/frontend/src/pages/Admin/Agents/Badges/default.jsx
+++ b/frontend/src/pages/Admin/Agents/Badges/default.jsx
@@ -1,11 +1,9 @@
-import { Tooltip } from "react-tooltip";
-
 export function DefaultBadge({ title }) {
   return (
     <>
       <span
         className="w-fit"
-        data-tooltip-id={`default-skill-${title}`}
+        data-tooltip-id="default-skill"
         data-tooltip-content="This skill is enabled by default and cannot be turned off."
       >
         <div className="flex items-center gap-x-1 w-fit rounded-full bg-[#F4FFD0]/10 px-2.5 py-0.5 text-sm font-medium text-sky-400 shadow-sm cursor-pointer">
@@ -14,12 +12,6 @@ export function DefaultBadge({ title }) {
           </div>
         </div>
       </span>
-      <Tooltip
-        id={`default-skill-${title}`}
-        place="bottom"
-        delayShow={300}
-        className="tooltip !text-xs"
-      />
     </>
   );
 }
diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx
index 99c093d5c..8f758647b 100644
--- a/frontend/src/pages/Admin/Agents/index.jsx
+++ b/frontend/src/pages/Admin/Agents/index.jsx
@@ -12,6 +12,7 @@ import { defaultSkills, configurableSkills } from "./skills";
 import { DefaultBadge } from "./Badges/default";
 import ImportedSkillList from "./Imported/SkillList";
 import ImportedSkillConfig from "./Imported/ImportedSkillConfig";
+import { Tooltip } from "react-tooltip";
 
 export default function AdminAgents() {
   const [hasChanges, setHasChanges] = useState(false);
@@ -361,38 +362,49 @@ function SkillList({
   if (skills.length === 0) return null;
 
   return (
-    <div
-      className={`bg-white/5 text-white rounded-xl ${
-        isMobile ? "w-full" : "min-w-[360px] w-fit"
-      }`}
-    >
-      {Object.entries(skills).map(([skill, settings], index) => (
-        <div
-          key={skill}
-          className={`py-3 px-4 flex items-center justify-between ${
-            index === 0 ? "rounded-t-xl" : ""
-          } ${
-            index === Object.keys(skills).length - 1
-              ? "rounded-b-xl"
-              : "border-b border-white/10"
-          } cursor-pointer transition-all duration-300  hover:bg-white/5 ${
-            selectedSkill === skill ? "bg-white/10" : ""
-          }`}
-          onClick={() => handleClick?.(skill)}
-        >
-          <div className="text-sm font-light">{settings.title}</div>
-          <div className="flex items-center gap-x-2">
-            {isDefault ? (
-              <DefaultBadge title={skill} />
-            ) : (
-              <div className="text-sm text-white/60 font-medium">
-                {activeSkills.includes(skill) ? "On" : "Off"}
-              </div>
-            )}
-            <CaretRight size={14} weight="bold" className="text-white/80" />
+    <>
+      <div
+        className={`bg-white/5 text-white rounded-xl ${
+          isMobile ? "w-full" : "min-w-[360px] w-fit"
+        }`}
+      >
+        {Object.entries(skills).map(([skill, settings], index) => (
+          <div
+            key={skill}
+            className={`py-3 px-4 flex items-center justify-between ${
+              index === 0 ? "rounded-t-xl" : ""
+            } ${
+              index === Object.keys(skills).length - 1
+                ? "rounded-b-xl"
+                : "border-b border-white/10"
+            } cursor-pointer transition-all duration-300  hover:bg-white/5 ${
+              selectedSkill === skill ? "bg-white/10" : ""
+            }`}
+            onClick={() => handleClick?.(skill)}
+          >
+            <div className="text-sm font-light">{settings.title}</div>
+            <div className="flex items-center gap-x-2">
+              {isDefault ? (
+                <DefaultBadge title={skill} />
+              ) : (
+                <div className="text-sm text-white/60 font-medium">
+                  {activeSkills.includes(skill) ? "On" : "Off"}
+                </div>
+              )}
+              <CaretRight size={14} weight="bold" className="text-white/80" />
+            </div>
           </div>
-        </div>
-      ))}
-    </div>
+        ))}
+      </div>
+      {/* Tooltip for default skills - only render when skill list is passed isDefault */}
+      {isDefault && (
+        <Tooltip
+          id="default-skill"
+          place="bottom"
+          delayShow={300}
+          className="tooltip !text-xs"
+        />
+      )}
+    </>
   );
 }
diff --git a/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/BrowserExtensionApiKeyRow/index.jsx b/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/BrowserExtensionApiKeyRow/index.jsx
index e43996a41..8017aa4b2 100644
--- a/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/BrowserExtensionApiKeyRow/index.jsx
+++ b/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/BrowserExtensionApiKeyRow/index.jsx
@@ -3,7 +3,6 @@ import BrowserExtensionApiKey from "@/models/browserExtensionApiKey";
 import showToast from "@/utils/toast";
 import { Trash, Copy, Check, Plug } from "@phosphor-icons/react";
 import { POPUP_BROWSER_EXTENSION_EVENT } from "@/utils/constants";
-import { Tooltip } from "react-tooltip";
 
 export default function BrowserExtensionApiKeyRow({
   apiKey,
@@ -66,7 +65,7 @@ export default function BrowserExtensionApiKeyRow({
         <div className="flex items-center space-x-2">
           <button
             onClick={handleCopy}
-            data-tooltip-id={`copy-connection-text-${apiKey.id}`}
+            data-tooltip-id="copy-connection-text"
             data-tooltip-content="Copy connection string"
             className="text-white hover:text-white/80 transition-colors duration-200 p-1 rounded"
           >
@@ -75,27 +74,15 @@ export default function BrowserExtensionApiKeyRow({
             ) : (
               <Copy className="h-5 w-5" />
             )}
-            <Tooltip
-              id={`copy-connection-text-${apiKey.id}`}
-              place="bottom"
-              delayShow={300}
-              className="allm-tooltip !allm-text-xs"
-            />
           </button>
 
           <button
             onClick={handleConnect}
-            data-tooltip-id={`auto-connection-${apiKey.id}`}
+            data-tooltip-id="auto-connection"
             data-tooltip-content="Automatically connect to extension"
             className="text-white hover:text-white/80 transition-colors duration-200 p-1 rounded"
           >
             <Plug className="h-5 w-5" />
-            <Tooltip
-              id={`auto-connection-${apiKey.id}`}
-              place="bottom"
-              delayShow={300}
-              className="allm-tooltip !allm-text-xs"
-            />
           </button>
         </div>
       </td>
diff --git a/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/index.jsx b/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/index.jsx
index 1bcb7c13f..2641aa64a 100644
--- a/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/index.jsx
+++ b/frontend/src/pages/GeneralSettings/BrowserExtensionApiKey/index.jsx
@@ -11,6 +11,7 @@ import NewBrowserExtensionApiKeyModal from "./NewBrowserExtensionApiKeyModal";
 import ModalWrapper from "@/components/ModalWrapper";
 import { useModal } from "@/hooks/useModal";
 import { fullApiUrl } from "@/utils/constants";
+import { Tooltip } from "react-tooltip";
 
 export default function BrowserExtensionApiKeys() {
   const [loading, setLoading] = useState(true);
@@ -128,6 +129,18 @@ export default function BrowserExtensionApiKeys() {
           isMultiUser={isMultiUser}
         />
       </ModalWrapper>
+      <Tooltip
+        id="auto-connection"
+        place="bottom"
+        delayShow={300}
+        className="allm-tooltip !allm-text-xs"
+      />
+      <Tooltip
+        id="copy-connection-text"
+        place="bottom"
+        delayShow={300}
+        className="allm-tooltip !allm-text-xs"
+      />
     </div>
   );
 }
-- 
GitLab