mirror of
https://github.com/Mintplex-Labs/anything-llm.git
synced 2025-04-17 18:18:11 +00:00
Frontend performance improvements (#2627)
* Frontend performance improvements * test docker build
This commit is contained in:
parent
2f56327962
commit
b701660f88
21 changed files with 394 additions and 345 deletions
2
.github/workflows/dev-build.yaml
vendored
2
.github/workflows/dev-build.yaml
vendored
|
@ -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/*'
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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]"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue