Frontend performance improvements ()

* Frontend performance improvements

* test docker build
This commit is contained in:
Timothy Carambat 2024-11-13 11:11:13 -08:00 committed by GitHub
parent 2f56327962
commit b701660f88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 394 additions and 345 deletions
.github/workflows
frontend/src
components
Footer
Modals/ManageWorkspace/Documents
Directory
WorkspaceDirectory
WorkspaceFileRow
index.jsx
SettingsButton
WorkspaceChat
ChatContainer
ChatHistory
Citation
HistoricalMessage/Actions
ChatTooltips
index.jsx
index.jsx
pages
Admin/Agents
GeneralSettings/BrowserExtensionApiKey
BrowserExtensionApiKeyRow
index.jsx

View file

@ -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/*'

View file

@ -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"

View file

@ -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>
);
}

View file

@ -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>
<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>
{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)}
/>
)}
</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>
<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>
);
}}
/>
);
}

View file

@ -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>
);
};

View file

@ -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);

View file

@ -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>
);
}

View file

@ -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) => (

View file

@ -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"

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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]"
/>
</>
);
}

View file

@ -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>
);
}

View file

@ -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();

View file

@ -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"
/>
</>
);
}

View file

@ -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"
/>
)}
</>
);
}

View file

@ -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>

View file

@ -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>
);
}