- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 233
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #112 from Mintplex-Labs/ui-v2
UI v2
Showing
146 changed files
with
6,557 additions
and
4,713 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,4 +6,6 @@ node_modules | |
__pycache__ | ||
v-env | ||
.DS_Store | ||
yarn-error.log | ||
yarn-error.log | ||
yarn.lock | ||
frontend/.env.development |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
import { useState, memo } from 'react'; | ||
import Organization from '../../models/organization'; | ||
import { titleCase } from 'title-case'; | ||
import paths from '../../utils/paths'; | ||
|
||
const SyncConnectorModal = memo( | ||
({ organization, connector }: { organization: any; connector: any }) => { | ||
const [synced, setSynced] = useState(false); | ||
const [loading, setLoading] = useState(false); | ||
const [error, setError] = useState<null | string>(null); | ||
const sync = async () => { | ||
setError(null); | ||
setLoading(true); | ||
const { job, error } = await Organization.syncConnector( | ||
organization.slug, | ||
connector.id | ||
); | ||
|
||
if (!job) { | ||
setError(error); | ||
setLoading(false); | ||
setSynced(false); | ||
return; | ||
} | ||
|
||
setLoading(false); | ||
setSynced(true); | ||
}; | ||
|
||
return ( | ||
<dialog | ||
id="sync-connector-modal" | ||
className="w-1/3 rounded-xl border-2 border-white/20 bg-main shadow" | ||
onClick={(event) => | ||
event.target == event.currentTarget && event.currentTarget?.close() | ||
} | ||
> | ||
<div className="overflow-y-scroll rounded-sm bg-main p-[20px]"> | ||
<div className="px-6.5 py-4"> | ||
<h3 className="text-lg font-medium text-white"> | ||
Sync Vector Database Connection | ||
</h3> | ||
<p className="mt-4 text-sm text-white/60"> | ||
Automatically sync existing information in your{' '} | ||
{titleCase(connector.type)}{' '} | ||
{connector.type === 'chroma' ? 'collections' : 'namespaces'} so | ||
you can manage it more easily. This process can take a long time | ||
to complete depending on how much data you have embedded already. | ||
<br /> | ||
<br /> | ||
Once you start this process you can check on its progress in the{' '} | ||
<a | ||
href={paths.jobs(organization)} | ||
className="font-semibold text-sky-400 hover:underline" | ||
> | ||
job queue. | ||
</a> | ||
</p> | ||
</div> | ||
<div className="w-full px-6"> | ||
{error && ( | ||
<p className="my-2 w-full rounded-lg border-red-800 bg-red-50 px-4 py-2 text-sm text-red-800"> | ||
{error} | ||
</p> | ||
)} | ||
{synced ? ( | ||
<button | ||
type="button" | ||
onClick={() => | ||
window.location.replace(paths.jobs(organization)) | ||
} | ||
className="w-full rounded-lg py-2 text-center text-gray-600 hover:bg-gray-400 hover:text-white" | ||
> | ||
Check progress | ||
</button> | ||
) : ( | ||
<button | ||
type="button" | ||
disabled={loading} | ||
onClick={sync} | ||
className="mb-4 h-11 w-full items-center rounded-lg bg-white p-2 text-center text-sm font-bold text-neutral-700 shadow-lg transition-all duration-300 hover:scale-105 hover:bg-opacity-90" | ||
> | ||
{loading ? 'Synchronizing...' : 'Synchronize embeddings'} | ||
</button> | ||
)} | ||
</div> | ||
</div> | ||
</dialog> | ||
); | ||
} | ||
); | ||
|
||
export default SyncConnectorModal; |
483 changes: 483 additions & 0 deletions
483
frontend/src/components/Modals/UpdateConnectorModal.tsx
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { AlertTriangle } from 'react-feather'; | ||
import { APP_NAME } from '../../utils/constants'; | ||
import System from '../../models/system'; | ||
import { ReactNode } from 'react'; | ||
|
||
export default function UploadModalNoKey() { | ||
const updateSystemSetting = async (e: any) => { | ||
e.preventDefault(); | ||
const form = new FormData(e.target); | ||
const open_ai_api_key = form.get('open_ai_api_key'); | ||
await System.updateSettings({ open_ai_api_key }); | ||
window.location.reload(); | ||
}; | ||
|
||
return ( | ||
<ModalWrapper> | ||
<div className="flex flex w-full flex-col items-center gap-y-2 rounded-lg border border-orange-500 bg-transparent px-4 py-2 text-orange-500"> | ||
<div className="flex w-full items-center gap-x-2 text-lg"> | ||
<AlertTriangle /> You cannot upload and embed documents without an | ||
OpenAI API Key. | ||
</div> | ||
<p> | ||
{APP_NAME} will automatically upload and embed your documents for you, | ||
but for this to happen we must have an OpenAI key set. | ||
</p> | ||
<form onSubmit={updateSystemSetting} className="w-full"> | ||
<div className=""> | ||
<div className="mb-4.5"> | ||
<label className="mb-2.5 block">Your OpenAI API Key</label> | ||
<input | ||
required={true} | ||
type="password" | ||
name="open_ai_api_key" | ||
placeholder="sk-xxxxxxxxxx" | ||
autoComplete="off" | ||
className="w-full rounded border-[1.5px] border-stroke bg-transparent px-5 py-3 font-medium outline-none transition focus:border-primary active:border-primary disabled:cursor-default disabled:bg-whiter dark:border-form-strokedark dark:bg-form-input dark:focus:border-primary" | ||
/> | ||
</div> | ||
<div className="flex flex-col gap-y-2"> | ||
<button | ||
type="submit" | ||
className="flex w-full justify-center rounded bg-orange-500 p-3 font-medium text-white" | ||
> | ||
Add OpenAI API Key | ||
</button> | ||
</div> | ||
</div> | ||
<p className="my-2 p-2 text-center text-sm text-orange-500"> | ||
This key will only be used for the embedding of documents you upload | ||
via {APP_NAME}. | ||
</p> | ||
</form> | ||
</div> | ||
</ModalWrapper> | ||
); | ||
} | ||
|
||
const ModalWrapper = ({ children }: { children: ReactNode }) => { | ||
return ( | ||
<dialog | ||
id="upload-document-modal" | ||
className="w-1/2 rounded-xl border-2 border-white/20 bg-main shadow" | ||
onClick={(event) => { | ||
event.target == event.currentTarget && event.currentTarget?.close(); | ||
}} | ||
> | ||
<div className="flex w-full flex-col gap-y-1 p-[20px]"> | ||
<p className="text-lg font-medium text-white">Upload new document</p> | ||
<p className="text-sm text-white/60"> | ||
Select a workspace and document you wish to upload and {APP_NAME} will | ||
process, embed and store the data for you automatically. | ||
</p> | ||
</div> | ||
<div className="my-2 flex w-full p-[20px]">{children}</div> | ||
</dialog> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,391 @@ | ||
import { ReactNode, useEffect, useRef, useState } from 'react'; | ||
import { useParams } from 'react-router-dom'; | ||
import Organization from '../../models/organization'; | ||
import { databaseTimestampFromNow } from '../../utils/data'; | ||
import ChromaLogo from '../../images/vectordbs/chroma.png'; | ||
import PineconeLogo from '../../images/vectordbs/pinecone-inverted.png'; | ||
import qDrantLogo from '../../images/vectordbs/qdrant.png'; | ||
import WeaviateLogo from '../../images/vectordbs/weaviate.png'; | ||
import { Bell, Info, Warning, WarningOctagon } from '@phosphor-icons/react'; | ||
|
||
const POLLING_INTERVAL = 30_000; | ||
|
||
export type INotification = { | ||
id: number; | ||
organization_id: number; | ||
seen: boolean; | ||
textContent: string; | ||
symbol?: | ||
| 'info' | ||
| 'warning' | ||
| 'error' | ||
| 'chroma' | ||
| 'pinecone' | ||
| 'weaviate' | ||
| 'qdrant'; | ||
link?: string; | ||
target?: '_blank' | 'self'; | ||
createdAt: string; | ||
lastUpdatedAt: string; | ||
}; | ||
|
||
export default function Notifications() { | ||
const { slug } = useParams(); | ||
const [loading, setLoading] = useState(true); | ||
const [notifications, setNotifications] = useState<INotification[]>([]); | ||
const [hasUnseen, setHasUnseen] = useState(false); | ||
const [showNotifs, setShowNotifs] = useState(false); | ||
const notificationRef = useRef(null); | ||
const bellButtonRef = useRef(null); | ||
|
||
async function handleClick() { | ||
if (!showNotifs) { | ||
!!slug && Organization.markNotificationsSeen(slug); | ||
setShowNotifs(true); | ||
setHasUnseen(false); | ||
} else { | ||
setShowNotifs(false); | ||
} | ||
} | ||
useEffect(() => { | ||
function handleClickOutside(event: any) { | ||
if ( | ||
bellButtonRef.current && | ||
bellButtonRef.current.contains(event.target) | ||
) { | ||
return; | ||
} | ||
if ( | ||
notificationRef.current && | ||
!notificationRef.current.contains(event.target) | ||
) { | ||
setShowNotifs(false); | ||
} | ||
} | ||
|
||
document.addEventListener('mousedown', handleClickOutside); | ||
return () => { | ||
document.removeEventListener('mousedown', handleClickOutside); | ||
}; | ||
}, []); | ||
|
||
async function fetchNotifications() { | ||
if (!slug) { | ||
setLoading(false); | ||
return; | ||
} | ||
|
||
const { notifications: _notifications } = await Organization.notifications( | ||
slug | ||
); | ||
setNotifications(_notifications); | ||
setHasUnseen(_notifications.some((notif) => notif.seen === false)); | ||
setLoading(false); | ||
} | ||
|
||
useEffect(() => { | ||
if (!slug) return; | ||
fetchNotifications(); | ||
setInterval(() => { | ||
fetchNotifications(); | ||
}, POLLING_INTERVAL); | ||
}, [slug]); | ||
|
||
if (loading) return null; | ||
|
||
return ( | ||
<div className="relative"> | ||
<button | ||
ref={bellButtonRef} | ||
type="button" | ||
onClick={handleClick} | ||
className={`group rounded-lg p-2 hover:bg-main-2 ${ | ||
showNotifs && 'bg-main-2' | ||
}`} | ||
> | ||
<div className="relative"> | ||
<p | ||
hidden={!hasUnseen} | ||
className="absolute -top-[4px] right-0 h-[12px] w-[12px] rounded-full bg-red-600" | ||
/> | ||
<div className="text-sky-400"> | ||
<Bell size={24} weight={showNotifs ? 'fill' : 'bold'} /> | ||
</div> | ||
</div> | ||
</button> | ||
|
||
<div | ||
hidden={!showNotifs} | ||
ref={notificationRef} | ||
className="absolute right-0 top-12 z-1 max-h-[50vh] w-[20rem] overflow-y-auto rounded-lg border border-neutral-600 bg-main shadow-2xl" | ||
> | ||
<div className="sticky left-0 top-0 z-10 block rounded-t-lg border-b border-neutral-600 bg-main px-5 py-2 text-lg text-white shadow-lg"> | ||
Notifications | ||
</div> | ||
<div className="divide-y divide-gray-100"> | ||
{notifications.length === 0 ? ( | ||
<div className="flex px-4 py-3 hover:bg-main-2"> | ||
<div className="w-full pl-3 text-center"> | ||
<div className="mb-1.5 text-xs text-white"> | ||
No notifications | ||
</div> | ||
</div> | ||
</div> | ||
) : ( | ||
<div> | ||
{notifications.map((notification) => ( | ||
<Notification | ||
key={notification.id} | ||
notification={notification} | ||
/> | ||
))} | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
function NotificationImage({ notification }: { notification: INotification }) { | ||
switch (notification.symbol) { | ||
case 'info': | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-2 text-white"> | ||
<Info size={24} weight="bold" /> | ||
</div> | ||
); | ||
case 'warning': | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-yellow-300 bg-opacity-20 p-2 text-yellow-300"> | ||
<WarningOctagon size={24} weight="bold" /> | ||
</div> | ||
); | ||
case 'error': | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-red-700/20 p-2 text-red-600"> | ||
<Warning size={24} weight="bold" /> | ||
</div> | ||
); | ||
case 'chroma': | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0"> | ||
<img alt="Chroma Logo" className="rounded-full" src={ChromaLogo} /> | ||
</div> | ||
); | ||
case 'pinecone': | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0"> | ||
<img | ||
alt="Pinecone Logo" | ||
className="rounded-full" | ||
src={PineconeLogo} | ||
/> | ||
</div> | ||
); | ||
case 'qdrant': | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0"> | ||
<img alt="qDrant Logo" className="rounded-full" src={qDrantLogo} /> | ||
</div> | ||
); | ||
case 'weaviate': | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-0"> | ||
<img | ||
alt="Weaviate Logo" | ||
className="rounded-full" | ||
src={WeaviateLogo} | ||
/> | ||
</div> | ||
); | ||
default: | ||
return ( | ||
<div className="flex h-[40px] w-[40px] items-center justify-center rounded-full bg-white/10 p-2 text-white"> | ||
<Info size={24} weight="bold" /> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
function NotificationWrapper({ | ||
notification, | ||
children, | ||
}: { | ||
notification: INotification; | ||
children: ReactNode; | ||
}) { | ||
if (!!notification.link) { | ||
return ( | ||
<a | ||
key={notification.id} | ||
href={notification?.link || '#'} | ||
target={notification?.target || 'self'} | ||
className={`flex px-4 py-3 transition-all duration-300 hover:bg-main-2 ${ | ||
!notification.seen | ||
? 'border-l-2 !border-l-sky-400 bg-sky-400/10' | ||
: 'border-l-2 !border-l-transparent' | ||
}`} | ||
> | ||
{children} | ||
</a> | ||
); | ||
} | ||
return ( | ||
<div | ||
key={notification.id} | ||
className={`flex px-4 py-3 hover:bg-main-2 ${ | ||
!notification.seen | ||
? 'border-l-2 !border-l-sky-400' | ||
: 'border-l-2 !border-l-transparent' | ||
}`} | ||
> | ||
{children} | ||
</div> | ||
); | ||
} | ||
|
||
function Notification({ notification }: { notification: INotification }) { | ||
return ( | ||
<NotificationWrapper notification={notification}> | ||
<div className="flex flex-shrink-0 items-center justify-center"> | ||
<NotificationImage notification={notification} /> | ||
</div> | ||
<div className="w-full pl-3"> | ||
<div className="mb-1 text-sm font-medium leading-tight text-white"> | ||
{notification.textContent} | ||
</div> | ||
<div className="text-sm text-white text-opacity-60"> | ||
{databaseTimestampFromNow(notification.createdAt)} | ||
</div> | ||
</div> | ||
</NotificationWrapper> | ||
); | ||
} | ||
|
||
// <Notification | ||
// key={'pinecone'} | ||
// notification={{ | ||
// id: 1, | ||
// organization_id: 1, | ||
// seen: false, | ||
// textContent: 'Pinecone is now available!', | ||
// symbol: 'pinecone', | ||
// link: 'https://pinecone.io', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// /> | ||
// <Notification | ||
// key={'chroma'} | ||
// notification={{ | ||
// id: 2, | ||
// organization_id: 1, | ||
// seen: false, | ||
// textContent: 'Chroma is now available!', | ||
// symbol: 'chroma', | ||
// link: 'https://chroma.ml', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// /> | ||
// <Notification | ||
// key={'qdrant'} | ||
// notification={{ | ||
// id: 3, | ||
// organization_id: 1, | ||
// seen: false, | ||
// textContent: 'qDrant is now available!', | ||
// symbol: 'qdrant', | ||
// link: 'https://qdrant.tech', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// /> | ||
// <Notification | ||
// key={'weaviate'} | ||
// notification={{ | ||
// id: 4, | ||
// organization_id: 1, | ||
// seen: false, | ||
// textContent: 'Weaviate is now available!', | ||
// symbol: 'weaviate', | ||
// link: 'https://weaviate.com', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// /> | ||
// <Notification | ||
// key={'error'} | ||
// notification={{ | ||
// id: 5, | ||
// organization_id: 1, | ||
// seen: true, | ||
// textContent: 'Something went wrong!', | ||
// symbol: 'error', | ||
// link: 'https://weaviate.com', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// /> | ||
// <Notification | ||
// key={'warning'} | ||
// notification={{ | ||
// id: 6, | ||
// organization_id: 1, | ||
// seen: true, | ||
// textContent: 'Something went wrong!', | ||
// symbol: 'warning', | ||
// link: 'https://weaviate.com', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// /> | ||
// <Notification | ||
// key={'info'} | ||
// notification={{ | ||
// id: 7, | ||
// organization_id: 1, | ||
// seen: false, | ||
// textContent: 'Something went wrong!', | ||
// symbol: 'info', | ||
// link: 'https://weaviate.com', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// />{' '} | ||
// <Notification | ||
// key={'info'} | ||
// notification={{ | ||
// id: 7, | ||
// organization_id: 1, | ||
// seen: true, | ||
// textContent: 'Something went wrong!', | ||
// symbol: 'info', | ||
// link: 'https://weaviate.com', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// />{' '} | ||
// <Notification | ||
// key={'info'} | ||
// notification={{ | ||
// id: 7, | ||
// organization_id: 1, | ||
// seen: false, | ||
// textContent: 'Something went wrong!', | ||
// symbol: 'info', | ||
// link: 'https://weaviate.com', | ||
// target: '_blank', | ||
// createdAt: '2021-10-12T12:00:00Z', | ||
// lastUpdatedAt: '2021-10-12T12:00:00Z', | ||
// }} | ||
// /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
238 changes: 238 additions & 0 deletions
238
frontend/src/components/Sidebar/OrganizationTab/index.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
import { NavLink, useParams } from 'react-router-dom'; | ||
import { useEffect, useState } from 'react'; | ||
import paths from '../../../utils/paths'; | ||
import { CaretDown, Plus, MagnifyingGlass } from '@phosphor-icons/react'; | ||
import truncate from 'truncate'; | ||
import Organization from '../../../models/organization'; | ||
import { debounce } from 'lodash'; | ||
import InfiniteScroll from 'react-infinite-scroll-component'; | ||
import CreateWorkspaceModal from '../../../pages/Dashboard/WorkspacesList/CreateWorkspaceModal'; | ||
|
||
type OrganizationTabProps = { | ||
organization: any; | ||
i: number; | ||
workspaces: any; | ||
hasMoreWorkspaces: boolean; | ||
loadMoreWorkspaces?: VoidFunction; | ||
}; | ||
|
||
const debouncedSearch = debounce( | ||
async (searchTerm, setResults, setIsSearching, slug) => { | ||
if (!slug) return; | ||
setIsSearching(true); | ||
|
||
const { workspacesResults = [] } = await Organization.searchWorkspaces( | ||
slug, | ||
1, // Page 1 | ||
30, // 30 results per page | ||
searchTerm | ||
); | ||
|
||
setResults(workspacesResults); | ||
setIsSearching(false); | ||
}, | ||
500 | ||
); | ||
|
||
export default function OrganizationTab({ | ||
organization, | ||
workspaces, | ||
i, | ||
hasMoreWorkspaces, | ||
loadMoreWorkspaces, | ||
}: OrganizationTabProps) { | ||
const { slug } = useParams(); | ||
const [isActive, setIsActive] = useState(false); | ||
const [menuOpen, setMenuOpen] = useState(true); | ||
const [searchTerm, setSearchTerm] = useState(''); | ||
const [searchResults, setSearchResults] = useState([]); | ||
const [isSearching, setIsSearching] = useState(false); | ||
|
||
const toggleMenu = () => { | ||
setMenuOpen(!menuOpen); | ||
}; | ||
|
||
const renderWorkspaceItem = (workspace: any) => ( | ||
<WorkspaceItem key={workspace.id} workspace={workspace} slug={slug} /> | ||
); | ||
|
||
const loadMoreWorkspacesAndScrollToBottom = async () => { | ||
loadMoreWorkspaces?.(); | ||
const organizationList = document.getElementById('organization-list'); | ||
if (organizationList) { | ||
organizationList.scrollTop = organizationList.scrollHeight; | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
if (searchTerm !== '') { | ||
setIsSearching(true); | ||
debouncedSearch(searchTerm, setSearchResults, setIsSearching, slug); | ||
} else { | ||
setSearchResults(workspaces); | ||
setIsSearching(false); | ||
} | ||
}, [searchTerm, slug]); | ||
|
||
return ( | ||
<li key={i}> | ||
<NavLink | ||
key={organization.id} | ||
reloadDocument={!isActive} | ||
to={paths.organization(organization)} | ||
className={({ isActive: active }) => { | ||
setIsActive(active); | ||
return `group relative flex w-full items-center justify-between rounded-lg border border-transparent bg-main-2 px-4 py-3 font-medium text-white duration-300 ease-in-out hover:border-sky-400 hover:text-white ${ | ||
active ? 'border-sky-400 !text-white' : '' | ||
}`; | ||
}} | ||
> | ||
<div className="flex w-full flex-col" onClick={toggleMenu}> | ||
<div className="flex w-full items-center justify-between"> | ||
<div className={`${isActive ? 'text-sky-400' : 'text-white/60'}`}> | ||
{truncate(organization.name, 19)} | ||
</div> | ||
<div | ||
className={`transition-all duration-300 ${ | ||
isActive && menuOpen ? 'text-sky-400' : 'rotate-180 ' | ||
}`} | ||
> | ||
<CaretDown weight="bold" /> | ||
</div> | ||
</div> | ||
</div> | ||
</NavLink> | ||
{isActive && ( | ||
<div | ||
className={`${ | ||
menuOpen | ||
? 'slide-down mb-4 mt-4 transition-all duration-300' | ||
: 'slide-up' | ||
}`} | ||
style={{ | ||
animationDuration: '0.15s', | ||
}} | ||
> | ||
<div className="mb-3.5 flex items-center justify-between px-3"> | ||
<div className="flex w-full items-center gap-x-2"> | ||
<svg | ||
xmlns="http://www.w3.org/2000/svg" | ||
width="16" | ||
height="12" | ||
viewBox="0 0 16 12" | ||
fill="none" | ||
> | ||
<path | ||
fill-rule="evenodd" | ||
clip-rule="evenodd" | ||
d="M7.20098 8.16845H13.601C14.0426 8.16845 14.401 8.52685 14.401 8.96845C14.401 9.41005 14.0426 9.76845 13.601 9.76845H7.20098C6.75938 9.76845 6.40098 9.41005 6.40098 8.96845C6.40098 8.52685 6.75938 8.16845 7.20098 8.16845ZM7.20098 2.56845H13.601C14.0426 2.56845 14.401 2.92685 14.401 3.36845C14.401 3.81005 14.0426 4.16845 13.601 4.16845H7.20098C6.75938 4.16845 6.40098 3.81005 6.40098 3.36845C6.40098 2.92685 6.75938 2.56845 7.20098 2.56845ZM4.80098 4.16845C4.80098 5.05245 5.51698 5.76845 6.40098 5.76845H14.401C15.285 5.76845 16.001 5.05245 16.001 4.16845V2.56845C16.001 1.68445 15.285 0.96845 14.401 0.96845H6.40098C5.51698 0.96845 4.80098 1.68445 4.80098 2.56845H1.60098L1.60098 0.854024H0.000976562V8.16767C0.000976562 9.05167 0.717782 9.76845 1.60178 9.76845H1.77617H4.80098C4.80098 10.6525 5.51698 11.3685 6.40098 11.3685H14.401C15.285 11.3685 16.001 10.6525 16.001 9.76845V8.16845C16.001 7.28445 15.285 6.56845 14.401 6.56845H6.40098C5.51698 6.56845 4.80098 7.28445 4.80098 8.16845H2.39778C1.95778 8.16845 1.60098 7.81158 1.60098 7.37158V4.16845H4.80098Z" | ||
fill="#A8A9AB" | ||
/> | ||
</svg> | ||
<div className="text-xs font-medium uppercase tracking-widest text-white/60"> | ||
Workspaces | ||
</div> | ||
</div> | ||
<button | ||
onClick={() => { | ||
document | ||
.getElementById('workspace-creation-modal') | ||
?.showModal(); | ||
}} | ||
> | ||
<Plus className="text-sky-400" size={17} weight="bold" /> | ||
</button> | ||
</div> | ||
<div className="mx-3 flex items-center rounded-full bg-main-2 p-2"> | ||
<MagnifyingGlass | ||
className="mx-1 h-4 w-4 text-white/60" | ||
weight="bold" | ||
/> | ||
<input | ||
type="text" | ||
placeholder="Search" | ||
onChange={(e) => setSearchTerm(e.target.value)} | ||
className="border-none bg-transparent text-sm text-white/60 placeholder-white/60 focus:outline-none" | ||
/> | ||
</div> | ||
|
||
{isSearching ? ( | ||
<LoadingWorkspaceItem /> | ||
) : searchTerm !== '' && searchResults.length > 0 ? ( | ||
<div className="mt-2 max-h-[150px] overflow-y-auto"> | ||
{searchResults.map((workspace, idx) => ( | ||
<WorkspaceItem key={idx} workspace={workspace} slug={slug} /> | ||
))} | ||
</div> | ||
) : searchTerm !== '' && searchResults.length === 0 ? ( | ||
<div className="mt-2"> | ||
<div className="flex w-full items-center justify-center rounded-sm text-xs text-white/60"> | ||
<p className="p-1">No results found.</p> | ||
</div> | ||
</div> | ||
) : workspaces.length > 0 ? ( | ||
<div className="mt-2"> | ||
<InfiniteScroll | ||
dataLength={workspaces.length} | ||
scrollableTarget="organization-list" | ||
height={workspaces.length > 5 ? 150 : workspaces.length * 30} | ||
next={loadMoreWorkspacesAndScrollToBottom} | ||
hasMore={hasMoreWorkspaces} | ||
loader={<LoadingWorkspaceItem />} | ||
> | ||
{workspaces.map(renderWorkspaceItem)} | ||
</InfiniteScroll> | ||
</div> | ||
) : ( | ||
<div className="mt-2"> | ||
<div className="flex w-48 items-center justify-center rounded-sm text-xs text-white/60"> | ||
<p className="p-1"> | ||
No workspaces,{' '} | ||
<button | ||
onClick={() => { | ||
document | ||
.getElementById('workspace-creation-modal') | ||
?.showModal(); | ||
}} | ||
className="italic underline hover:cursor-pointer" | ||
> | ||
create | ||
</button> | ||
. | ||
</p> | ||
</div> | ||
</div> | ||
)} | ||
<CreateWorkspaceModal organization={organization} /> | ||
</div> | ||
)} | ||
</li> | ||
); | ||
} | ||
|
||
function WorkspaceItem({ workspace, slug }: any) { | ||
return ( | ||
<li className="mx-5 mt-1"> | ||
<NavLink | ||
to={paths.workspace(slug, workspace.slug)} | ||
className={({ isActive }) => { | ||
return `text-sm font-normal leading-tight text-sky-400 hover:cursor-pointer hover:text-sky-400 hover:underline ${ | ||
isActive ? 'text-sky-400' : 'text-white/60' | ||
}`; | ||
}} | ||
> | ||
{truncate(workspace.name, 23)} | ||
</NavLink> | ||
</li> | ||
); | ||
} | ||
|
||
function LoadingWorkspaceItem() { | ||
return ( | ||
<div className="mt-2"> | ||
<div className="flex w-full animate-pulse items-center justify-center rounded-sm text-xs text-white/60"> | ||
<p className="p-1">Loading...</p> | ||
</div> | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { useState, useRef, useEffect } from 'react'; | ||
import useUser from '../../hooks/useUser'; | ||
import paths from '../../utils/paths'; | ||
import { STORE_TOKEN, STORE_USER } from '../../utils/constants'; | ||
|
||
export default function UserMenu() { | ||
const { user } = useUser(); | ||
const [showMenu, setShowMenu] = useState(false); | ||
const menuRef = useRef(null); | ||
const buttonRef = useRef(); | ||
const handleClose = (event: any) => { | ||
if ( | ||
menuRef.current && | ||
!menuRef.current.contains(event.target) && | ||
!buttonRef.current.contains(event.target) | ||
) { | ||
setShowMenu(false); | ||
} | ||
}; | ||
|
||
useEffect(() => { | ||
if (showMenu) { | ||
document.addEventListener('mousedown', handleClose); | ||
} | ||
return () => document.removeEventListener('mousedown', handleClose); | ||
}, [showMenu]); | ||
|
||
return ( | ||
<div> | ||
<button | ||
ref={buttonRef} | ||
onClick={() => setShowMenu(!showMenu)} | ||
className="flex h-[29px] w-[29px] items-center justify-center rounded-full bg-sky-400 bg-opacity-20 text-sm font-medium text-sky-400" | ||
> | ||
{user?.email?.slice(0, 2).toUpperCase()} | ||
</button> | ||
{showMenu && ( | ||
<div | ||
ref={menuRef} | ||
className="items-center-justify-center absolute right-0 top-12 flex w-fit rounded-lg border border-white/20 bg-main p-4" | ||
> | ||
<div className="flex flex-col gap-y-2"> | ||
<button | ||
onClick={() => { | ||
if (!window) return; | ||
window.localStorage.removeItem(STORE_USER); | ||
window.localStorage.removeItem(STORE_TOKEN); | ||
window.location.replace(paths.home()); | ||
}} | ||
type="button" | ||
className="w-full whitespace-nowrap rounded-md px-4 py-1.5 text-left text-white hover:bg-slate-200/20" | ||
> | ||
Sign out | ||
</button> | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
type VectorDBOptionProps = { | ||
name: string; | ||
link: string; | ||
description: string; | ||
value: string; | ||
image: string; | ||
checked?: boolean; | ||
onClick: (value: string) => void; | ||
}; | ||
|
||
export default function VectorDBOption({ | ||
name, | ||
link, | ||
description, | ||
value, | ||
image, | ||
checked = false, | ||
onClick, | ||
}: VectorDBOptionProps) { | ||
return ( | ||
<div | ||
onClick={() => onClick(value)} | ||
style={{ | ||
background: checked | ||
? `linear-gradient(180deg, #313236 0%, rgba(63, 65, 70, 0.00) 100%)` | ||
: `linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.06) 100%)`, | ||
}} | ||
className={`flex h-full w-60 cursor-pointer flex-col items-start justify-between rounded-2xl border-2 border-transparent px-5 py-4 text-white shadow-md transition-all duration-300 ${ | ||
checked ? 'border-white/60' : '' | ||
} hover:border-white/60 hover:shadow-lg md:max-w-sm`} | ||
> | ||
<input | ||
type="checkbox" | ||
value={value} | ||
className="peer hidden" | ||
checked={checked} | ||
readOnly={true} | ||
formNoValidate={true} | ||
/> | ||
<label className="flex h-full w-full cursor-pointer flex-col items-start justify-between"> | ||
<div className="flex items-center"> | ||
<img src={image} alt={name} className="h-10 w-10 rounded" /> | ||
<div className="ml-4 text-sm font-semibold">{name}</div> | ||
</div> | ||
<div className="font-base mt-2 text-xs tracking-wide text-white"> | ||
{description} | ||
</div> | ||
<a | ||
href={`https://${link}`} | ||
className="mt-2 text-xs font-medium text-white underline" | ||
> | ||
{link} | ||
</a> | ||
</label> | ||
</div> | ||
); | ||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.