Merge pull request #95 from Mintplex-Labs/v2-organization

v2 Organization page
This commit is contained in:
Timothy Carambat
2023-12-21 11:47:11 -08:00
committed by GitHub
9 changed files with 1426 additions and 238 deletions
+1
View File
@@ -7,6 +7,7 @@ export default function Header(props: {
sidebarOpen: string | boolean | undefined;
setSidebarOpen: (arg0: boolean) => void;
extendedItems?: any;
quickActions: boolean;
}) {
const [copied, setCopied] = useState(false);
if (!props.entity) return null;
+3
View File
@@ -13,6 +13,7 @@ interface DefaultLayoutProps {
children: ReactNode;
hasMoreWorkspaces?: boolean;
loadMoreWorkspaces?: VoidFunction;
hasQuickActions?: boolean;
}
const AppLayout = ({
@@ -26,6 +27,7 @@ const AppLayout = ({
children,
hasMoreWorkspaces,
loadMoreWorkspaces,
hasQuickActions = false,
}: DefaultLayoutProps) => {
const [sidebarOpen, setSidebarOpen] = useState(false);
@@ -52,6 +54,7 @@ const AppLayout = ({
sidebarOpen={sidebarOpen}
setSidebarOpen={setSidebarOpen}
extendedItems={headerExtendedItems}
quickActions={hasQuickActions}
/>
</div>
)}
@@ -1,8 +1,5 @@
import { Link } from 'react-router-dom';
import paths from '../../../utils/paths';
import moment from 'moment';
import { AlertOctagon, FileText } from 'react-feather';
// import { CodeBlock, vs2015 } from 'react-code-blocks';
import { useEffect, useState } from 'react';
import Organization from '../../../models/organization';
import truncate from 'truncate';
@@ -11,6 +8,8 @@ import UploadDocumentModal from './UploadModal';
import UploadModalNoKey from './UploadModal/UploadModalNoKey';
import DocumentListPagination from '../../../components/DocumentPaginator';
import useQuery from '../../../hooks/useQuery';
import Document from '../../../models/document';
import { File, Trash } from '@phosphor-icons/react';
export default function DocumentsList({
organization,
@@ -41,6 +40,18 @@ export default function DocumentsList({
setCurrentPage(setTo);
}
const deleteDocument = async (documentId: number) => {
if (
!confirm(
'Are you sure you want to delete this document? This will remove the document from your vector database and remove it from the cache. This process cannot be undone.'
)
)
return false;
const success = await Document.delete(documentId);
if (!success) return false;
document.getElementById(`document-row-${documentId}`)?.remove();
};
useEffect(() => {
async function getDocs(slug?: string) {
if (!slug) return false;
@@ -59,174 +70,245 @@ export default function DocumentsList({
if (loading) {
return (
<div className="col-span-12 flex-1 rounded-sm border border-stroke bg-main py-6 shadow-default dark:border-strokedark dark:bg-boxdark xl:col-span-4">
<div className="flex items-start justify-between px-4">
<div>
<h4 className="mb-6 px-4 text-xl font-semibold text-black dark:text-white">
Documents {totalDocuments! > 0 ? `(${totalDocuments})` : ''}
</h4>
</div>
</div>
<div className="flex h-60 w-full items-center justify-center px-7.5">
<div className="h-full w-full animate-pulse rounded-lg bg-slate-100" />
</div>
</div>
<div
className="mb-9 flex h-screen flex-col overflow-hidden bg-main py-6 transition-all duration-300"
style={{ height: `calc(100vh - 210px` }}
></div>
);
}
return (
<>
<div className="col-span-12 flex-1 rounded-sm border border-stroke bg-white py-6 shadow-default dark:border-strokedark dark:bg-boxdark xl:col-span-4">
<div className="flex items-start justify-between px-4">
<div>
<h4 className="mb-6 px-4 text-xl font-semibold text-black dark:text-white">
Documents {totalDocuments! > 0 ? `(${totalDocuments})` : ''}
</h4>
</div>
{workspaces.length > 0 ? (
<>
{!!knownConnector ? (
<div
className="mb-9 flex h-screen flex-col overflow-hidden bg-main transition-all duration-300"
style={{ height: `calc(100vh - 160px)` }}
>
<div className="flex-grow overflow-y-auto rounded-xl border-2 border-white/20 bg-main">
<table className="w-full rounded-xl text-left text-xs font-medium text-white text-opacity-80">
<thead className="sticky top-0 w-full border-b-2 border-white/20 bg-main ">
<tr className="mt-10">
<th
scope="col"
className="px-6 pb-2 pt-6 text-xs font-light text-white text-opacity-80"
>
Name
</th>
<th
scope="col"
className="px-6 pb-2 pt-6 text-xs font-light text-white text-opacity-80"
>
Workspace
</th>
<th
scope="col"
className="px-6 pb-2 pt-6 text-xs font-light text-white text-opacity-80"
>
Date
</th>
<th
scope="col"
className="px-6 pb-2 pt-6 text-xs font-light text-white text-opacity-80"
>
Vectors
</th>
<th
scope="col"
className="px-6 pb-2 pt-6 text-xs font-light text-white text-opacity-80"
>
{' '}
</th>
<th
hidden={workspaces.length === 0}
scope="col"
className="relative px-6 pb-2 pt-6"
>
<button
onClick={() => {
document
.getElementById('upload-document-modal')
?.showModal();
}}
className="inline-flex h-[26px] w-[98px] items-center justify-center gap-2.5 rounded-[100px] bg-white bg-opacity-10 px-2.5 py-1"
>
<div className="font-satoshi text-xs font-black tracking-tight text-sky-400">
UPLOAD
</div>
</button>
</th>
</tr>
</thead>
{documents?.length > 0 && (
<tbody className="bg-main">
{documents.map((document, index) => (
<Fragment
key={document?.id}
document={document}
index={index}
deleteDocument={deleteDocument}
organization={organization}
/>
))}
</tbody>
)}
</table>
{!!!knownConnector && (
<div className="-mt-10 flex h-full w-full items-center justify-center">
<div className="flex flex-col items-center justify-center gap-y-4 text-center">
<div className="text-center font-medium text-white text-opacity-40">
Connect a Vector Database to get started
</div>
<div className="text-center text-sm font-light text-white text-opacity-80">
Begin by connecting a Vector Database to your organization
</div>
<button
onClick={() => {
document
.getElementById('upload-document-modal')
window.document
?.getElementById('new-connector-modal')
?.showModal();
}}
className="rounded-lg px-2 py-1 text-sm text-slate-800 hover:bg-slate-200"
className="mt-4 inline-flex items-center justify-center gap-2.5 rounded-lg bg-white p-2.5 px-36 shadow"
>
Add Document
<div className="text-center text-sm font-bold leading-tight text-zinc-900">
Connect Vector Database
</div>
</button>
) : (
</div>
</div>
)}
{!!knownConnector && workspaces?.length === 0 && (
<div className="-mt-10 flex h-full w-full items-center justify-center">
<div className="flex flex-col items-center justify-center gap-y-4 text-center">
<div className="text-center font-medium text-white text-opacity-40">
Create a workspace to get started
</div>
<div className="text-center text-sm font-light text-white text-opacity-80">
Workspaces are used to organize your documents
</div>
<button
type="button"
disabled={true}
className="flex items-center gap-x-1 rounded-lg bg-red-50 px-2 py-1 text-sm text-red-800"
onClick={() => {
window.document
?.getElementById('workspace-creation-modal')
?.showModal();
}}
className="mt-4 inline-flex items-center justify-center gap-2.5 rounded-lg bg-white p-2.5 px-36 shadow"
>
<AlertOctagon className="h4- w-4" /> Requires Vector Database
Connection
<div className="text-center text-sm font-bold leading-tight text-zinc-900">
Create Workspace
</div>
</button>
)}
</>
) : (
<button
type="button"
disabled={true}
className="flex items-center gap-x-1 rounded-lg bg-red-50 px-2 py-1 text-sm text-red-800"
>
<AlertOctagon className="h4- w-4" /> Requires Workspace
</button>
</div>
</div>
)}
{documents?.length === 0 && workspaces?.length > 0 && (
<div className="-mt-10 flex h-full w-full items-center justify-center">
<div className="flex flex-col items-center justify-center gap-y-4 text-center">
<div className="text-center font-medium text-white text-opacity-40">
0 Documents
</div>
<div className="text-center text-sm font-light text-white text-opacity-80">
Upload documents to your workspace
</div>
<button
onClick={() => {
window.document
?.getElementById('upload-document-modal')
?.showModal();
}}
className="mt-4 inline-flex items-center justify-center gap-2.5 rounded-lg bg-white p-2.5 px-36 shadow"
>
<div className="text-center text-sm font-bold leading-tight text-zinc-900">
Upload Documents
</div>
</button>
</div>
</div>
)}
</div>
{documents.length > 0 ? (
<div>
<div className="border-b border-stroke px-4 pb-5 dark:border-strokedark md:px-6 xl:px-7.5">
<div className="flex items-center gap-3">
<div className="w-2/12 xl:w-3/12">
<span className="font-medium">Document Name</span>
</div>
<div className="w-6/12 2xsm:w-5/12 md:w-3/12">
<span className="font-medium">Workspace</span>
</div>
<div className="hidden w-4/12 md:block xl:w-3/12">
<span className="font-medium">Created</span>
</div>
<div className="w-5/12 2xsm:w-4/12 md:w-3/12 xl:w-2/12">
<span className="font-medium">Status</span>
</div>
<div className="hidden w-2/12 text-center 2xsm:block md:w-1/12">
<span className="font-medium"></span>
</div>
</div>
</div>
<>
{documents.map((document) => {
return (
<div
id={`document-row-${document.id}`}
key={document.id}
className="flex w-full items-center gap-5 px-7.5 py-3 text-gray-600 hover:bg-gray-3 dark:hover:bg-meta-4"
>
<div className="flex w-full items-center gap-3">
<div className="w-2/12 xl:w-3/12">
<div className="flex items-center gap-x-1">
<FileText className="h-4 w-4" />
<span className="hidden font-medium xl:block">
{truncate(document.name, 20)}
</span>
</div>
</div>
<div className="w-6/12 2xsm:w-5/12 md:w-3/12">
<a
href={paths.workspace(
organization.slug,
document.workspace.slug
)}
className="hover:text-blue-500 hover:underline"
>
<span className="font-medium">
{truncate(document.workspace.name, 20) || ''}
</span>
</a>
</div>
<div className="hidden w-3/12 overflow-x-scroll md:block xl:w-3/12">
<span className="font-medium">
{moment(document.createdAt).format('lll')}
</span>
</div>
<div className="w-5/12 2xsm:w-4/12 md:w-3/12 xl:w-2/12">
<span className="inline-block rounded bg-green-500 bg-opacity-25 px-2.5 py-0.5 text-sm font-medium text-green-500">
Cached
</span>
</div>
<div className=" flex items-center gap-x-2">
<a
href={paths.document(
organization.slug,
document.workspace.slug,
document.id
)}
className="rounded-lg px-2 py-1 text-blue-400 transition-all duration-300 hover:bg-blue-50 hover:text-blue-600"
>
Details
</a>
</div>
</div>
</div>
);
})}
</>
</div>
<div className="pt-20">
<DocumentListPagination
pageCount={Math.ceil(
totalDocuments! / Organization.documentPageSize
)}
currentPage={currentPage}
gotoPage={updatePage}
/>
</div>
{canUpload ? (
<UploadDocumentModal workspaces={workspaces} />
) : (
<div>
<div className="flex min-h-[40vh] w-full px-8">
<div className="flex flex h-auto w-full flex-col items-center justify-center gap-y-2 rounded-lg bg-slate-50">
<p>You have no documents in any workspaces!</p>
<p>
Get started managing documents by adding them to workspaces
via the UI or code.
</p>
<button
type="button"
className="text-xl text-blue-500 underline"
>
Show code example (coming soon)
</button>
</div>
</div>
</div>
<UploadModalNoKey />
)}
</div>
<DocumentListPagination
pageCount={Math.ceil(totalDocuments! / Organization.documentPageSize)}
currentPage={currentPage}
gotoPage={updatePage}
/>
{canUpload ? (
<UploadDocumentModal workspaces={workspaces} />
) : (
<UploadModalNoKey />
)}
</>
);
}
const Fragment = ({
document,
index,
deleteDocument,
organization,
}: {
document: any;
index: number;
deleteDocument: any;
organization: any;
}) => {
return (
<>
<tr
key={document?.id}
id={`document-row-${document?.id}`}
className={`h-9 hover:bg-white/10 ${
index % 2 === 0 ? 'bg-main-2' : 'bg-main'
}`}
>
<td className="flex items-center gap-x-1 px-6 py-2 text-sm font-light text-white">
<File className="flex-shrink-0" size={16} weight="fill" />
<p>{truncate(document?.name, 35)}</p>
</td>
<td className="px-6 ">
<a
href={paths.workspace(organization.slug, document.workspace.slug)}
className="hover:text-sky-400 hover:underline"
>
<span className="font-medium">
{truncate(document.workspace.name, 20) || ''}
</span>
</a>
</td>
<td className="px-6 ">{moment(document?.createdAt).format('lll')}</td>
<td className="px-6 ">Cached</td>
<td className="px-6 ">
<a
href={paths.document(
organization.slug,
document.workspace.slug,
document.id
)}
className="rounded-lg px-2 py-1 text-sky-400 transition-all duration-300 hover:bg-blue-50"
>
Details
</a>
</td>
<td className="px-6">
<div className="flex items-center gap-x-4">
<div className=" flex items-center gap-x-6">
<button
type="button"
onClick={() => deleteDocument(document?.id)}
className="rounded-lg px-2 py-1 text-white transition-all duration-300 hover:bg-red-50 hover:text-red-600"
>
<Trash size={16} />
</button>
</div>
</div>
</td>
</tr>
</>
);
};
@@ -0,0 +1,84 @@
import {
CaretDown,
Key,
SpinnerGap,
Toolbox,
User,
} from '@phosphor-icons/react';
import { useState } from 'react';
import { NavLink } from 'react-router-dom';
import paths from '../../../utils/paths';
import useUser from '../../../hooks/useUser';
export default function QuickActionsSidebar({
organization,
}: {
organization: any;
}) {
const { user } = useUser();
const [quickActionsOpen, setQuickActionsOpen] = useState(true);
return (
<div className="-mt-14 w-[217px]">
<button
onClick={() => setQuickActionsOpen(!quickActionsOpen)}
className="w-full text-white/80"
>
<div className="flex items-center justify-between">
<div className="font-['Plus Jakarta Sans'] whitespace-nowrap text-xs font-semibold uppercase leading-tight tracking-wide text-white text-opacity-80">
quick actions
</div>
<div
className={`${
quickActionsOpen ? '' : 'rotate-180'
} transition-all duration-300`}
>
<CaretDown size={18} weight="bold" />
</div>
</div>
</button>
<div
className={`${
quickActionsOpen
? 'slide-down mb-4 mt-4 transition-all duration-300'
: 'slide-up'
}`}
style={{
animationDuration: '0.15s',
}}
>
{user?.role === 'admin' && (
<>
<NavLink to={paths.toolsHome(organization)}>
<div className="mt-5 flex items-center gap-x-2 text-white hover:cursor-pointer hover:text-sky-400 hover:underline">
<Toolbox size={18} weight="bold" />
<div className="text-sm font-medium">Tools</div>
</div>
</NavLink>
<NavLink to={paths.users()}>
<div className="mt-5 flex items-center gap-x-2 text-white hover:cursor-pointer hover:text-sky-400 hover:underline">
<User size={18} weight="bold" />
<div className="text-sm font-medium">Add User</div>
</div>
</NavLink>
<NavLink to={paths.settings()}>
<div className="mt-5 flex items-center gap-x-2 text-white hover:cursor-pointer hover:text-sky-400 hover:underline">
<Key size={18} weight="bold" />
<div className="text-sm font-medium">OpenAI Key</div>
</div>
</NavLink>
</>
)}
<NavLink to={paths.jobs(organization)}>
<div className="mt-5 flex items-center gap-x-2 text-white hover:cursor-pointer hover:text-sky-400 hover:underline">
<SpinnerGap size={18} weight="bold" />
<div className="text-sm font-medium">Background Jobs</div>
</div>
</NavLink>
</div>
</div>
);
}
@@ -1,11 +1,16 @@
import { memo, useState, useEffect } from 'react';
import PreLoader from '../../../components/Preloader';
import { humanFileSize, nFormatter } from '../../../utils/numbers';
import moment from 'moment';
import Organization from '../../../models/organization';
import pluralize from 'pluralize';
import Workspace from '../../../models/workspace';
const Statistics = ({ organization }: { organization: any }) => {
const Statistics = ({
organization,
workspaces,
}: {
organization: any;
workspaces: any;
}) => {
const [documents, setDocuments] = useState({
status: 'loading',
value: 0,
@@ -18,6 +23,10 @@ const Statistics = ({ organization }: { organization: any }) => {
status: 'loading',
value: 0,
});
const [dimensions, setDimensions] = useState({
status: 'loading',
value: '-',
});
useEffect(() => {
async function collectStats() {
@@ -32,65 +41,49 @@ const Statistics = ({ organization }: { organization: any }) => {
Organization.stats(organization.slug, 'cache-size').then((json) => {
setCache({ status: 'complete', value: json.value });
});
Workspace.stats(organization.slug, workspaces[0].slug, 'dimensions').then(
(json) => {
setDimensions({ status: 'complete', value: json.value });
}
);
}
collectStats();
}, [organization?.slug]);
}, [organization?.slug, workspaces[0]?.slug]);
return (
<div className="col-span-12 rounded-md border border-stroke bg-white p-7.5 shadow-default dark:border-strokedark dark:bg-boxdark">
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-4 xl:gap-0">
<div className="flex items-center justify-center gap-2 border-b border-stroke pb-5 dark:border-strokedark xl:border-b-0 xl:border-r xl:pb-0">
{documents.status === 'loading' ? (
<PreLoader />
) : (
<div className="flex flex-col items-center">
<h4 className="mb-0.5 text-xl font-bold text-black dark:text-white md:text-title-lg">
{nFormatter(documents.value)}
</h4>
<p className="text-sm font-medium">
{pluralize('Document', documents.value)}
</p>
</div>
)}
<div className="-mt-6 flex w-full items-center pr-7">
<div className=" ml-4 flex items-center gap-x-6">
<div className="flex items-center gap-x-1">
<span className="font-['Plus Jakarta Sans'] text-sm font-bold uppercase leading-[18px] tracking-wide text-white">
Documents
</span>
<span className="font-['JetBrains Mono'] text-sm font-bold uppercase leading-[18px] tracking-wide text-white">
{' '}
</span>
<span className="font-['JetBrains Mono'] text-sm font-extrabold uppercase leading-[18px] tracking-wide text-white">
({nFormatter(documents.value)})
</span>
</div>
</div>
<div className="flex items-center justify-center gap-2 border-b border-stroke pb-5 dark:border-strokedark xl:border-b-0 xl:border-r xl:pb-0">
{vectors.status === 'loading' ? (
<PreLoader />
) : (
<div className="flex flex-col items-center">
<h4 className="mb-0.5 text-xl font-bold text-black dark:text-white md:text-title-lg">
{nFormatter(vectors.value)}
</h4>
<p className="text-sm font-medium">
{pluralize('Vector', vectors.value)}
</p>
</div>
)}
</div>
<div className="flex items-center justify-center gap-2 border-b border-stroke pb-5 dark:border-strokedark sm:border-b-0 sm:pb-0 xl:border-r">
{cache.status === 'loading' ? (
<PreLoader />
) : (
<div className="flex flex-col items-center">
<h4 className="mb-0.5 text-xl font-bold text-black dark:text-white md:text-title-lg">
{humanFileSize(cache.value)}
</h4>
<p className="text-sm font-medium">Vector Cache (MB)</p>
</div>
)}
</div>
<div className="flex items-center justify-center gap-2">
<div className="flex flex-col items-center">
<h4 className="mb-0.5 text-xl font-bold text-black dark:text-white md:text-title-lg">
{organization?.lastUpdated
? moment(organization.lastUpdated).fromNow()
: moment(organization.createdAt).fromNow()}
</h4>
<p className="text-sm font-medium">Last Modified</p>
</div>
<div className="ml-4 mr-48 w-full rounded-xl border-2 border-white/20 px-5 py-2 text-sky-400">
<div className="flex items-center justify-between whitespace-nowrap">
<span className="font-jetbrains uppercase text-white">
{pluralize('Vector', vectors.value)}:{' '}
<span className=" font-jetbrainsbold">
{nFormatter(vectors.value)}
</span>
</span>
<span className="font-jetbrains uppercase text-white">
Vector Cache:{' '}
<span className=" font-jetbrainsbold">
{humanFileSize(cache.value)}
</span>
</span>
<span className="font-jetbrains uppercase text-white">
Dimensions:{' '}
<span className=" font-jetbrainsbold">{dimensions.value}</span>
</span>
</div>
</div>
</div>
@@ -20,7 +20,7 @@ export default function WorkspacesList({
totalWorkspaces?: number;
}) {
return (
<div className="col-span-12 flex-1 rounded-sm border border-stroke bg-white py-6 shadow-default dark:border-strokedark dark:bg-boxdark xl:col-span-4">
<div className="col-span-12 max-w-[217px] flex-1 rounded-sm border border-stroke bg-white py-6 shadow-default dark:border-strokedark dark:bg-boxdark xl:col-span-4">
<div className=" top-0 z-10 bg-white">
<div className="mb-6 flex w-full items-center justify-between px-7.5">
<h4 className="text-xl font-semibold text-black dark:text-white">
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -169,7 +169,7 @@ function DocumentViewHeader({ organization, workspace, document }: any) {
return (
<>
<div className=" mr-10 w-full rounded-xl border-2 border-white/20 px-5 py-2 text-sky-400">
<div className="flex items-center gap-x-2">
<div className="flex items-center gap-x-2 text-lg">
<a
href={paths.organization(organization)}
className="text-sky-400 hover:cursor-pointer hover:underline"
@@ -188,7 +188,9 @@ function DocumentViewHeader({ organization, workspace, document }: any) {
<div className="text-sky-400" style={{ transform: 'rotate(270deg)' }}>
<CaretDown weight="bold" />
</div>
<span className="text-white">{truncate(document?.name, 30)}</span>
<span className="text-lg font-medium text-white">
{truncate(document?.name, 30)}
</span>
</div>
</div>
<div className="flex gap-x-3">
@@ -7,7 +7,6 @@ import paths from '../../utils/paths';
import AppLayout from '../../layout/AppLayout';
import { useParams } from 'react-router-dom';
import Organization from '../../models/organization';
import ApiKeyCard from './ApiKey';
import Statistics from './Statistics';
import DocumentsList from './DocumentsList';
import Workspace from '../../models/workspace';
@@ -17,7 +16,8 @@ import { CaretDown } from '@phosphor-icons/react';
import truncate from 'truncate';
import ChromaLogo from '../../images/vectordbs/chroma.png';
import PineconeLogo from '../../images/vectordbs/pinecone-inverted.png';
import PineconeLogoInverted from '../../images/vectordbs/pinecone-inverted.png';
import PineconeLogo from '../../images/vectordbs/pinecone.png';
import qDrantLogo from '../../images/vectordbs/qdrant.png';
import WeaviateLogo from '../../images/vectordbs/weaviate.png';
@@ -795,13 +795,13 @@ function WorkspaceViewHeader({
logo = WeaviateLogo;
break;
default:
logo = PineconeLogo;
logo = PineconeLogoInverted;
}
return (
<>
<div className=" mr-10 w-full rounded-xl border-2 border-white/20 px-5 py-2 text-sky-400">
<div className="flex items-center gap-x-2">
<div className="flex items-center gap-x-2 text-lg">
<a
href={paths.organization(organization)}
className="text-sky-400 hover:cursor-pointer hover:underline"
@@ -811,7 +811,9 @@ function WorkspaceViewHeader({
<div className="text-sky-400" style={{ transform: 'rotate(270deg)' }}>
<CaretDown weight="bold" />
</div>
<span className="text-white">{truncate(workspace?.name, 20)}</span>
<span className="text-lg font-medium text-white">
{truncate(workspace?.name, 20)}
</span>
</div>
</div>
<div className="flex gap-x-3">