Merge pull request #89 from Mintplex-Labs/v2-workspace

v2 Workspace
This commit is contained in:
Timothy Carambat
2023-12-21 11:35:02 -08:00
committed by GitHub
77 changed files with 2413 additions and 1059 deletions
+1
View File
@@ -305,6 +305,7 @@ function workspaceEndpoints(app) {
documents: "countForEntity",
vectors: "calcVectors",
"cache-size": "calcVectorCache",
dimensions: "calcDimensions",
};
if (!Object.keys(methods).includes(statistic)) {
+2 -2
View File
@@ -17,9 +17,9 @@ const Organization = {
message: "No Organization name provided.",
};
// If the new name contains bad characters
// If the new name contains bad characters
// replace them with spaces and continue creation.
const newOrgName = orgName.replace(/[:\.,<>@]/g, ' ');
const newOrgName = orgName.replace(/[:\.,<>@]/g, " ");
var slug = slugify(newOrgName, { lower: true });
const existingBySlug = await this.get({ slug });
+25
View File
@@ -4,6 +4,7 @@ const { v5 } = require("uuid");
const { fetchMetadata } = require("../utils/storage");
const { DocumentVectors } = require("./documentVectors");
const { selectConnector } = require("../utils/vectordatabases/providers");
const { OrganizationWorkspace } = require("./organizationWorkspace");
const WorkspaceDocument = {
vectorFilenameRaw: function (documentName, workspaceId) {
@@ -159,6 +160,30 @@ const WorkspaceDocument = {
return totalBytes;
},
calcDimensions: async function (field = "workspace_id", value = null) {
try {
const { OrganizationConnection } = require("./organizationConnection");
const workspace = await prisma.organization_workspaces.findUnique({
where: { id: value },
include: { organization: true },
});
const connector = await OrganizationConnection.get({
organization_id: workspace.organization.id,
});
const vectorDb = selectConnector(connector);
const dimensions = await vectorDb.indexDimensions(workspace.fname);
return dimensions;
} catch (e) {
console.error(e);
return 0;
}
},
// Will get both the remote and local count of vectors to see if the numbers match.
vectorCount: async function (field = "organization_id", value = null) {
try {
+256 -256
View File
@@ -611,6 +611,154 @@
"readonly": false
}
},
"document_vectors": {
"slug": "document_vectors",
"table": {
"name": "document_vectors",
"pk": "id",
"verbose": "document_vectors"
},
"columns": [
{
"name": "id",
"verbose": "id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "docId",
"verbose": "docId",
"control": {
"text": true
},
"type": "text",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "vectorId",
"verbose": "vectorId",
"control": {
"text": true
},
"type": "text",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "document_id",
"verbose": "document_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "workspace_id",
"verbose": "workspace_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "organization_id",
"verbose": "organization_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "createdAt",
"verbose": "createdAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "lastUpdatedAt",
"verbose": "lastUpdatedAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
}
],
"mainview": {
"show": true
},
"listview": {
"order": {},
"page": 25
},
"editview": {
"readonly": false
}
},
"users": {
"slug": "users",
"table": {
@@ -727,12 +875,12 @@
"readonly": false
}
},
"document_vectors": {
"slug": "document_vectors",
"system_settings": {
"slug": "system_settings",
"table": {
"name": "document_vectors",
"name": "system_settings",
"pk": "id",
"verbose": "document_vectors"
"verbose": "system_settings"
},
"columns": [
{
@@ -752,8 +900,8 @@
}
},
{
"name": "docId",
"verbose": "docId",
"name": "label",
"verbose": "label",
"control": {
"text": true
},
@@ -768,8 +916,8 @@
}
},
{
"name": "vectorId",
"verbose": "vectorId",
"name": "value",
"verbose": "value",
"control": {
"text": true
},
@@ -783,54 +931,6 @@
"show": true
}
},
{
"name": "document_id",
"verbose": "document_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "workspace_id",
"verbose": "workspace_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "organization_id",
"verbose": "organization_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "createdAt",
"verbose": "createdAt",
@@ -1039,206 +1139,6 @@
"readonly": false
}
},
"system_settings": {
"slug": "system_settings",
"table": {
"name": "system_settings",
"pk": "id",
"verbose": "system_settings"
},
"columns": [
{
"name": "id",
"verbose": "id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "label",
"verbose": "label",
"control": {
"text": true
},
"type": "text",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "value",
"verbose": "value",
"control": {
"text": true
},
"type": "text",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "createdAt",
"verbose": "createdAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "lastUpdatedAt",
"verbose": "lastUpdatedAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
}
],
"mainview": {
"show": true
},
"listview": {
"order": {},
"page": 25
},
"editview": {
"readonly": false
}
},
"organization_users": {
"slug": "organization_users",
"table": {
"name": "organization_users",
"pk": "id",
"verbose": "organization_users"
},
"columns": [
{
"name": "id",
"verbose": "id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "user_id",
"verbose": "user_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "organization_id",
"verbose": "organization_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "createdAt",
"verbose": "createdAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "lastUpdatedAt",
"verbose": "lastUpdatedAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
}
],
"mainview": {
"show": true
},
"listview": {
"order": {},
"page": 25
},
"editview": {
"readonly": false
}
},
"organization_notifications": {
"slug": "organization_notifications",
"table": {
@@ -1403,6 +1303,106 @@
"readonly": false
}
},
"organization_users": {
"slug": "organization_users",
"table": {
"name": "organization_users",
"pk": "id",
"verbose": "organization_users"
},
"columns": [
{
"name": "id",
"verbose": "id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "user_id",
"verbose": "user_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "organization_id",
"verbose": "organization_id",
"control": {
"text": true
},
"type": "integer",
"allowNull": false,
"defaultValue": null,
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "createdAt",
"verbose": "createdAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
},
{
"name": "lastUpdatedAt",
"verbose": "lastUpdatedAt",
"control": {
"text": true
},
"type": "timestamp",
"allowNull": false,
"defaultValue": "CURRENT_TIMESTAMP",
"listview": {
"show": true
},
"editview": {
"show": true
}
}
],
"mainview": {
"show": true
},
"listview": {
"order": {},
"page": 25
},
"editview": {
"readonly": false
}
},
"organization_rag_tests": {
"slug": "organization_rag_tests",
"table": {
@@ -72,6 +72,12 @@ class Chroma {
return { result: totalVectors, error: null };
}
// TODO: Solve this issue
async indexDimensions() {
// Chroma does not support this, defaulting to openai's 1536
return 1536;
}
// Collections === namespaces for Chroma to normalize interfaces
async collections() {
return await this.namespaces();
@@ -43,8 +43,10 @@ export default function DocumentListPagination({
if (currentPage < pageCount) gotoPage(currentPage + 1);
};
if (pageCount < 2) return <div className="mb-18"></div>;
return (
<div className="my-4 mb-8 flex justify-center">
<div className="my-4 -mt-8 mb-8 flex justify-center">
{hasPrevious && (
<button
onClick={goToPrevious}
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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 31 KiB

+35
View File
@@ -1,6 +1,41 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;700&display=swap');
@font-face {
font-family: 'Satoshi-Bold';
src: url('./fonts/Satoshi-Bold.ttf') format('truetype');
font-weight: 700;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Regular.ttf') format('truetype');
font-weight: 300;
font-display: swap;
font-style: normal;
}
.font-satoshi.font-bold {
font-family: 'Satoshi-Bold', sans-serif;
}
.font-satoshi {
font-family: 'Satoshi', sans-serif;
}
.font-jetbrains {
font-family: 'JetBrains Mono', monospace;
font-weight: 300;
}
.font-jetbrainsbold {
font-family: 'JetBrains Mono', monospace;
font-weight: 700;
}
/* Chrome, Safari and Opera */
.no-scrollbar::-webkit-scrollbar {
-1
View File
@@ -2,7 +2,6 @@ import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
import './index.css';
import './satoshi.css';
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Router>
@@ -49,7 +49,7 @@ function FileUploadProgressComponent({
<XCircle className="h-6 h-full w-6 w-full rounded-full bg-red-500 stroke-white p-1" />
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-red-700 dark:text-red-400">
@@ -70,7 +70,7 @@ function FileUploadProgressComponent({
)}
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-gray-700 dark:text-stone-400">
@@ -180,7 +180,7 @@ export default function UploadDocumentModal({
</div>
<p className="text-xs text-gray-600 dark:text-stone-400 ">
supported file extensions are{' '}
<code className="font-mono rounded-sm bg-gray-200 px-1 text-xs text-gray-800 dark:bg-stone-800 dark:text-slate-400">
<code className="rounded-sm bg-gray-200 px-1 font-mono text-xs text-gray-800 dark:bg-stone-800 dark:text-slate-400">
{Object.values(fileTypes).flat().join(' ')}
</code>
</p>
@@ -59,7 +59,7 @@ export default function DocumentsList({
if (loading) {
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 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">
@@ -36,7 +36,7 @@ const DeleteEmbeddingConfirmation = memo(
</p>
</div>
<div className="flex w-full flex-col overflow-y-scroll px-4">
<pre className="font-mono w-full whitespace-pre-line rounded-lg bg-slate-100 p-2">
<pre className="w-full whitespace-pre-line rounded-lg bg-slate-100 p-2 font-mono">
{data.metadata.text}
</pre>
<div className="mt-4 flex flex-col gap-y-2">
@@ -125,7 +125,7 @@ const EditEmbeddingConfirmation = memo(
</form>
</div>
</div>
<pre className="font-mono whitespace-pre-line rounded-lg bg-slate-100 p-2">
<pre className="whitespace-pre-line rounded-lg bg-slate-100 p-2 font-mono">
{data.metadata.text}
</pre>
</dialog>
@@ -165,7 +165,7 @@ const EditEmbeddingConfirmation = memo(
name="embeddingText"
defaultValue={data.metadata.text}
spellCheck="true"
className="font-mono h-fit w-full overflow-y-scroll rounded-lg bg-slate-100 p-2"
className="h-fit w-full overflow-y-scroll rounded-lg bg-slate-100 p-2 font-mono"
/>
<div className="mt-4 flex flex-col gap-y-2">
<button
@@ -106,7 +106,7 @@ export default function FragmentList({
</div>
) : (
<table className="w-full rounded-xl text-left text-xs font-medium text-white text-opacity-80">
<thead className="sticky top-0 w-full bg-main">
<thead className="sticky top-0 w-full border-b-2 border-white/20 bg-main">
<tr className="mt-10">
<th
scope="col"
@@ -174,11 +174,13 @@ export default function FragmentList({
)}
</div>
{!searchMode && (
<DocumentListPagination
pageCount={totalPages}
currentPage={currentPage}
gotoPage={handlePageChange}
/>
<div className="mt-14">
<DocumentListPagination
pageCount={totalPages}
currentPage={currentPage}
gotoPage={handlePageChange}
/>
</div>
)}
</div>
</>
@@ -219,7 +221,7 @@ const Fragment = ({
<>
<tr
id={`embedding-row-${fragment.id}`}
className={`h-9 transition-all duration-300 ${
className={`h-9 hover:bg-white/10 ${
index % 2 === 0 ? 'bg-main-2' : 'bg-main'
}`}
>
@@ -240,7 +242,7 @@ const Fragment = ({
) : (
<>
{loading ? (
<div className="h-[20px] w-[80px] animate-pulse rounded-md bg-slate-200" />
<div className="h-[20px] w-[80px] animate-pulse rounded-md bg-white/20" />
) : (
<p>no text found.</p>
)}
@@ -266,7 +268,7 @@ const Fragment = ({
) : (
<>
{loading ? (
<div className="h-[20px] w-[80px] animate-pulse rounded-md bg-slate-200" />
<div className="h-[20px] w-[80px] animate-pulse rounded-md bg-white/20" />
) : (
<button
onClick={() => {
@@ -339,7 +341,7 @@ const FullTextWindow = memo(
return (
<dialog id={`${fragment.id}-text`} className="w-1/2 rounded-lg">
<div className="flex flex-col overflow-y-scroll p-[20px]">
<pre className="font-mono whitespace-pre-line rounded-lg bg-slate-100 p-2">
<pre className="whitespace-pre-line rounded-lg bg-slate-100 p-2 font-mono">
{data?.metadata?.text ||
'[ERROR] Could not parse text key from embedding'}
</pre>
@@ -49,7 +49,7 @@ function FileUploadProgressComponent({
<XCircle className="h-6 h-full w-6 w-full rounded-full bg-red-500 stroke-white p-1" />
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-red-700 dark:text-red-400">
@@ -70,7 +70,7 @@ function FileUploadProgressComponent({
)}
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-gray-700 dark:text-stone-400">
@@ -180,7 +180,7 @@ export default function UploadDocumentModal({
</div>
<p className="text-xs text-gray-600 dark:text-stone-400 ">
supported file extensions are{' '}
<code className="font-mono rounded-sm bg-gray-200 px-1 text-xs text-gray-800 dark:bg-stone-800 dark:text-slate-400">
<code className="rounded-sm bg-gray-200 px-1 font-mono text-xs text-gray-800 dark:bg-stone-800 dark:text-slate-400">
{Object.values(fileTypes).flat().join(' ')}
</code>
</p>
+2 -2
View File
@@ -196,7 +196,7 @@ function DocumentViewHeader({ organization, workspace, document }: any) {
onClick={cloneDocument}
className="inline-flex h-11 w-[74px] flex-col items-center justify-center gap-2.5 rounded-lg bg-white bg-opacity-10 px-5 py-2.5 transition-all duration-300 hover:bg-opacity-5"
>
<div className="h-[25.53px] w-11 text-center font-['Satoshi'] text-base font-bold text-white">
<div className="font-satoshi h-[25.53px] w-11 text-center text-base font-bold text-white">
Clone
</div>
</button>
@@ -204,7 +204,7 @@ function DocumentViewHeader({ organization, workspace, document }: any) {
onClick={deleteDocument}
className="inline-flex h-11 w-[74px] flex-col items-center justify-center gap-2.5 rounded-lg border border-white border-opacity-20 px-3.5 py-2.5 transition-all duration-300 hover:bg-red-500"
>
<div className="h-[25.53px] w-[59px] text-center font-['Satoshi'] text-base font-bold text-white">
<div className="font-satoshi h-[25.53px] w-[59px] text-center text-base font-bold text-white">
Delete
</div>
</button>
File diff suppressed because it is too large Load Diff
@@ -1,262 +1,148 @@
import { SyntheticEvent, useRef, useState } from 'react';
import { ChevronDown, FileText, Search, Loader } from 'react-feather';
import { CopyDocToModal } from '..';
import truncate from 'truncate';
import moment from 'moment';
import paths from '../../../../utils/paths';
import Workspace from '../../../../models/workspace';
import { SEARCH_MODES, ISearchTypes } from '../../../../utils/constants';
import { CaretDown, MagnifyingGlass, X } from '@phosphor-icons/react';
export default function SearchView({
organization,
workspace,
workspaces,
stopSearching,
deleteDocument,
searchMode,
setSearchMode,
handleSearchResults,
setLoading,
}: {
organization: object;
workspace: object;
workspaces: object[];
stopSearching: VoidFunction;
deleteDocument: (documentId: number) => void;
searchMode: boolean;
setSearchMode: (searchMode: boolean) => void;
handleSearchResults: (results: any) => void;
setLoading: (loading: boolean) => void;
}) {
const formEl = useRef<HTMLFormElement>(null);
const [searching, setSearching] = useState(false);
const [showSearchMethods, setShowSearchMethods] = useState(false);
const [searchBy, setSearchBy] = useState<ISearchTypes>('exactText');
const [searchTerm, setSearchTerm] = useState<string>('');
const [documents, setDocuments] = useState([]);
const clearSearch = () => {
setSearchBy('exactText');
setSearchTerm('');
setDocuments([]);
setSearching(false);
stopSearching();
(formEl.current as HTMLFormElement).reset();
};
const handleSearch = async (e: SyntheticEvent<HTMLElement, SubmitEvent>) => {
e.preventDefault();
setSearchMode(true);
setLoading(true);
const formData = new FormData(e.target as any);
const query = formData.get('query') as string;
setSearching(true);
setSearchTerm(query);
const matches = await Workspace.searchDocuments(
workspace.id,
searchBy,
query
);
setDocuments(matches);
setSearching(false);
setLoading(false);
handleSearchResults(matches);
};
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="mx-4 mb-6 flex items-center">
<form onSubmit={handleSearch} className="w-full">
<div className="relative flex">
<button
onClick={() => setShowSearchMethods(!showSearchMethods)}
className="z-10 inline-flex flex-shrink-0 items-center rounded-l-lg border border-gray-300 bg-gray-100 px-4 py-2.5 text-center text-sm font-medium text-gray-900 hover:bg-gray-200 focus:outline-none focus:ring-4 focus:ring-gray-100 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-700"
type="button"
>
{SEARCH_MODES[searchBy].display}
<ChevronDown size={18} />
</button>
<div className="relative -mt-6 ml-6 flex w-full items-center">
<form ref={formEl} onSubmit={handleSearch} className="flex w-full flex-1">
<div className="flex w-full">
<button
onClick={() => setShowSearchMethods(!showSearchMethods)}
className="z-10 inline-flex h-9 flex-shrink-0 items-center rounded-[100px] bg-zinc-700 px-5 text-center text-sm font-medium text-white transition-all duration-300 hover:bg-zinc-800 focus:outline-none"
type="button"
>
{SEARCH_MODES['exactText'].display}
<div
hidden={!showSearchMethods}
className="absolute left-0 top-12 z-99 w-44 divide-y divide-gray-100 rounded-lg bg-white shadow dark:bg-gray-700"
className={`ml-2 transition-all duration-300 ${
showSearchMethods ? '' : 'rotate-180'
}`}
>
<ul
className="py-2 text-sm text-gray-700 dark:text-gray-200"
aria-labelledby="dropdown-button"
>
{Object.keys(SEARCH_MODES).map((_key, i) => {
const method = _key as ISearchTypes;
return (
<li key={i}>
<button
onClick={() => {
setSearchBy(method);
setShowSearchMethods(false);
setDocuments([]);
}}
type="button"
className="inline-flex w-full px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"
>
{SEARCH_MODES[method].display}
</button>
</li>
);
})}
</ul>
<CaretDown size={16} weight="bold" />
</div>
<div className="relative w-full">
<input
type="search"
name="query"
className="z-20 block w-full rounded-r-lg border border-l-2 border-gray-300 border-l-gray-50 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:border-l-gray-700 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500"
placeholder={SEARCH_MODES[searchBy].placeholder}
required
/>
</button>
<div
className={`absolute left-0 top-12 z-99 w-44 divide-y divide-gray-100 rounded-lg bg-zinc-700 shadow ${
showSearchMethods ? 'slide-down' : 'slide-up'
}`}
style={{
animationDuration: '0.15s',
}}
>
<ul
className="py-2 text-sm text-white"
aria-labelledby="dropdown-button"
>
{Object.keys(SEARCH_MODES).map((_key, i) => {
const method = _key as ISearchTypes;
return (
<li key={i}>
<button
onClick={() => {
setSearchBy(method);
setShowSearchMethods(false);
setSearchMode(false);
}}
type="button"
className="inline-flex w-full px-4 py-2 hover:bg-zinc-800"
>
{SEARCH_MODES[method].display}
</button>
</li>
);
})}
</ul>
</div>
<div className="relative w-full">
<input
name="query"
placeholder={SEARCH_MODES[searchBy].placeholder}
className=" z-20 -ml-4 block h-9 w-full rounded-r-[100px] bg-main-2 pl-8 text-sm text-white focus:outline-none"
required
/>
{false ? (
<div className="absolute right-0 top-0 mr-4.5 flex h-full p-2.5 text-sm font-medium text-white focus:outline-none">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-solid border-white border-t-transparent" />
</div>
) : searchMode ? (
<button
onClick={clearSearch}
className="absolute right-0 top-0 mr-4.5 flex h-full items-center justify-center p-2.5 text-sm font-medium text-white focus:outline-none"
>
<X
size={16}
className="text-sky-400 transition-all duration-300 hover:text-sky-700"
weight="bold"
/>
</button>
) : (
<button
type="submit"
disabled={searching}
className="absolute right-0 top-0 h-full rounded-r-lg border border-blue-700 bg-blue-700 p-2.5 text-sm font-medium text-white hover:bg-blue-800 focus:outline-none focus:ring-4 focus:ring-blue-300 dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
className="absolute -right-1 top-0 mr-4.5 flex h-full items-center justify-center rounded-r-[100px] bg-[#303237] p-2.5 text-sm font-medium text-white focus:outline-none"
>
{searching ? (
<Loader size={18} className="animate-spin" />
) : (
<Search size={18} />
)}
<span className="sr-only">Search</span>
<MagnifyingGlass
className="text-sky-400 transition-all duration-300 hover:text-sky-700"
size={18}
weight="bold"
/>
</button>
</div>
<button
onClick={clearSearch}
type="button"
className="ml-2 flex items-center rounded-lg px-4 py-2 text-center text-black hover:bg-gray-200"
>
X
</button>
)}
</div>
</form>
</div>
{searching ? (
<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">
<Loader size={15} className="animate-spin rounded-sm" />
<p className="text-sm">
Running {SEARCH_MODES[searchBy].display} for{' '}
<code className="bg-gray-200 px-2">"{searchTerm}"</code>
</p>
</div>
</div>
<button
onClick={(e) => {
e.preventDefault();
window.document
?.getElementById('upload-document-modal')
?.showModal();
}}
className="ml-2 flex w-18 items-center justify-center rounded-[100px] bg-sky-400 px-2.5 text-xs"
>
<div className="font-bold uppercase text-black">Upload</div>
</button>
</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">
<span className="font-medium">
{workspace.name || ''}
</span>
</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">
<button
type="button"
onClick={() =>
window.document
.getElementById(
`copy-document-${document.id}-modal`
)
?.showModal()
}
className="rounded-lg px-2 py-1 text-gray-400 transition-all duration-300 hover:bg-gray-50 hover:text-gray-600"
>
Clone
</button>
<a
href={paths.document(
organization.slug,
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>
<button
type="button"
onClick={() => deleteDocument(document.id)}
className="rounded-lg px-2 py-1 text-red-400 transition-all duration-300 hover:bg-red-50 hover:text-red-600"
>
Delete
</button>
</div>
</div>
<CopyDocToModal
key={`copy-document-${document.id}`}
document={document}
workspace={workspace}
workspaces={workspaces}
/>
</div>
);
})}
</>
</div>
) : (
<>
<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">
{!!searchTerm ? (
<p className="text-sm">
No results on {SEARCH_MODES[searchBy].display} for{' '}
<code className="bg-gray-200 px-2">"{searchTerm}"</code>
</p>
) : (
<p className="text-sm">
Type in a query to search for a document
</p>
)}
</div>
</div>
</div>
</>
)}
</>
)}
</form>
</div>
);
}
@@ -49,7 +49,7 @@ function FileUploadProgressComponent({
<XCircle className="h-6 h-full w-6 w-full rounded-full bg-red-500 stroke-white p-1" />
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-red-700 dark:text-red-400">
@@ -70,7 +70,7 @@ function FileUploadProgressComponent({
)}
</div>
<div className="flex flex-col">
<p className="font-mono overflow-x-scroll text-sm text-black dark:text-stone-200">
<p className="overflow-x-scroll font-mono text-sm text-black dark:text-stone-200">
{truncate(file.name, 30)}
</p>
<p className="font-mono text-xs text-gray-700 dark:text-stone-400">
@@ -138,7 +138,7 @@ export default function UploadDocumentModal({ workspace }: { workspace: any }) {
</div>
<p className="text-xs text-gray-600 dark:text-stone-400 ">
supported file extensions are{' '}
<code className="font-mono rounded-sm bg-gray-200 px-1 text-xs text-gray-800 dark:bg-stone-800 dark:text-slate-400">
<code className="rounded-sm bg-gray-200 px-1 font-mono text-xs text-gray-800 dark:bg-stone-800 dark:text-slate-400">
{Object.values(fileTypes).flat().join(' ')}
</code>
</p>
@@ -1,7 +1,6 @@
import { memo } from 'react';
import { memo, useRef } from 'react';
import paths from '../../../utils/paths';
import moment from 'moment';
import { AlertOctagon, FileText, Search } from 'react-feather';
// import { CodeBlock, vs2015 } from 'react-code-blocks';
import { useEffect, useState } from 'react';
import truncate from 'truncate';
@@ -11,10 +10,12 @@ import UploadDocumentModal from './UploadModal';
import UploadModalNoKey from './UploadModal/UploadModalNoKey';
import Document from '../../../models/document';
import useQuery from '../../../hooks/useQuery';
import { APP_NAME } from '../../../utils/constants';
import { APP_NAME, ISearchTypes, SEARCH_MODES } from '../../../utils/constants';
import { useParams } from 'react-router-dom';
import DocumentListPagination from '../../../components/DocumentPaginator';
import SearchView from './SearchView';
import { File, Trash } from '@phosphor-icons/react';
import PreLoader from '../../../components/Preloader';
export default function DocumentsList({
knownConnector,
@@ -33,6 +34,7 @@ export default function DocumentsList({
const [documents, setDocuments] = useState([]);
const [totalDocuments, setTotalDocuments] = useState(0);
const [canUpload, setCanUpload] = useState(false);
const [searchResults, setSearchResults] = useState<any[]>([]);
const [currentPage, setCurrentPage] = useState(
Number(query.get('docPage')) || 1
);
@@ -60,6 +62,11 @@ export default function DocumentsList({
document.getElementById(`document-row-${documentId}`)?.remove();
};
const handleSearchResults = (results: any[]) => {
setSearchResults(results);
setSearchMode(true);
};
useEffect(() => {
async function getDocs(orgSlug: string, wsSlug?: string) {
if (!orgSlug || !wsSlug) return false;
@@ -78,200 +85,216 @@ export default function DocumentsList({
if (loading) {
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
className="flex h-screen flex-col overflow-hidden bg-main py-6 transition-all duration-300"
style={{ height: `calc(100vh - ${searchMode ? '130px' : '130px'})` }}
>
<div className="flex items-start justify-between px-4">
<div className="mb-6 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">
({workspace?.documentCount})
</span>
</div>
<SearchView
searchMode={searchMode}
organization={organization}
workspace={workspace}
workspaces={workspaces}
stopSearching={() => setSearchMode(false)}
deleteDocument={deleteDocument}
setSearchMode={setSearchMode}
handleSearchResults={handleSearchResults}
/>
</div>
</div>
<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"
>
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>
</tr>
</thead>
</table>
<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">
<PreLoader />
</div>
</div>
</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>
</>
);
}
if (searchMode)
return (
<SearchView
organization={organization}
workspace={workspace}
workspaces={workspaces}
stopSearching={() => setSearchMode(false)}
deleteDocument={deleteDocument}
/>
);
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 className="mb-6 flex items-center gap-x-2">
<h4 className="px-4 text-xl font-semibold text-black dark:text-white">
Documents {totalDocuments! > 0 ? `(${totalDocuments})` : ''}
</h4>
<button
onClick={() => setSearchMode(true)}
className="group flex items-center gap-2 rounded-lg bg-transparent px-4 py-2 hover:bg-blue-300/20"
>
<Search
size={20}
className="text-gray-600 group-hover:text-blue-600"
/>
<p className="pointer-events-none text-gray-600 transition-all duration-[300ms] group-hover:text-blue-600">
Search documents
</p>
</button>
<div
className="mb-9 flex h-screen flex-col overflow-hidden bg-main py-6 transition-all duration-300"
style={{ height: `calc(100vh - ${searchMode ? '210px' : '210px'})` }}
>
<div className="flex items-start items-center justify-between px-4">
<div className="mb-6 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">
({workspace?.documentCount})
</span>
</div>
</div>
{!!knownConnector ? (
<button
onClick={() => {
document.getElementById('upload-document-modal')?.showModal();
}}
className="rounded-lg px-2 py-1 text-sm text-slate-800 hover:bg-slate-200"
>
Add Document
</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 Vector Database
Connection
</button>
)}
<SearchView
searchMode={searchMode}
organization={organization}
workspace={workspace}
stopSearching={() => setSearchMode(false)}
setSearchMode={setSearchMode}
handleSearchResults={handleSearchResults}
setLoading={setLoading}
/>
</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">
<span className="font-medium">
{document.workspace.name || ''}
</span>
</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-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"
>
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>
</tr>
</thead>
{!searchMode && documents?.length > 0 && (
<tbody className="bg-main">
{documents.map((document, index) => (
<Fragment
key={document?.id}
document={document}
index={index}
deleteDocument={deleteDocument}
organization={organization}
workspace={workspace}
workspaces={workspaces}
/>
))}
</tbody>
)}
{searchMode && searchResults?.length > 0 && (
<tbody className="bg-main">
{searchResults.map((document, index) => (
<Fragment
key={document?.id}
document={document}
index={index}
deleteDocument={deleteDocument}
organization={organization}
workspace={workspace}
workspaces={workspaces}
/>
))}
</tbody>
)}
</table>
<div className=" flex items-center gap-x-2">
<button
type="button"
onClick={() =>
window.document
.getElementById(
`copy-document-${document.id}-modal`
)
?.showModal()
}
className="rounded-lg px-2 py-1 text-gray-400 transition-all duration-300 hover:bg-gray-50 hover:text-gray-600"
>
Clone
</button>
<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>
<button
type="button"
onClick={() => deleteDocument(document.id)}
className="rounded-lg px-2 py-1 text-red-400 transition-all duration-300 hover:bg-red-50 hover:text-red-600"
>
Delete
</button>
</div>
</div>
<CopyDocToModal
key={`copy-document-${document.id}`}
document={document}
workspace={workspace}
workspaces={workspaces}
/>
</div>
);
})}
</>
</div>
) : (
<>
<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>
{searchMode && searchResults?.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">
No documents found
</div>
</div>
</div>
{/* <CodeExampleModal organization={organization} /> */}
</>
)}
)}
{documents?.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>
</div>
<div className="pb-4">
<DocumentListPagination
pageCount={Math.ceil(totalDocuments! / Workspace.documentPageSize)}
currentPage={currentPage}
gotoPage={updatePage}
/>
</div>
<DocumentListPagination
pageCount={Math.ceil(totalDocuments! / Workspace.documentPageSize)}
currentPage={currentPage}
gotoPage={updatePage}
/>
{canUpload ? (
<UploadDocumentModal workspace={workspace} />
) : (
@@ -281,6 +304,81 @@ export default function DocumentsList({
);
}
const Fragment = ({
document,
index,
deleteDocument,
organization,
workspace,
workspaces,
}: {
document: any;
index: number;
deleteDocument: any;
organization: any;
workspace: any;
workspaces: 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 size={16} weight="fill" />
<p>{truncate(document?.name, 35)}</p>
</td>
<td className="px-6 ">{moment(document?.createdAt).format('lll')}</td>
<td className="px-6 ">Cached</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={() =>
window.document
.getElementById(`copy-document-${document?.id}-modal`)
?.showModal()
}
className="rounded-lg px-2 py-1 text-gray-400 transition-all duration-300 hover:bg-gray-50 hover:text-gray-600"
>
Clone
</button>
<a
href={paths.document(
organization.slug,
workspace?.slug,
document?.id
)}
className="rounded-lg px-2 py-1 text-sky-400 transition-all duration-300 hover:bg-blue-50"
>
Details
</a>
<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>
<CopyDocToModal
key={`copy-document-${document?.id}`}
document={document}
workspace={workspace}
workspaces={workspaces}
/>
</>
);
};
export const CopyDocToModal = memo(
({
document,
@@ -376,95 +474,3 @@ export const CopyDocToModal = memo(
);
}
);
// const CodeExampleModal = ({ organization }: { organization: any }) => {
// // Rework this to be an upload modal.
// return (
// <dialog id="document-code-modal" className="w-1/2 rounded-lg">
// <div className="rounded-sm bg-white dark:border-strokedark dark:bg-boxdark">
// <div className="px-6.5 py-4 dark:border-strokedark">
// <h3 className="font-medium text-black dark:text-white">
// Adding documents to Conifer
// </h3>
// <p className="text-sm text-gray-500">
// You can begin managing documents with the code you have already
// written. Our library currently only supports NodeJS environments.
// </p>
// </div>
// <p className="my-2 rounded-lg border border-orange-800 bg-orange-100 p-2 text-center text-sm text-orange-800">
// During the Pinecone Hackathon the library is a standalone fork of
// langchainJS, but ideally it would eventually be live in the main
// LangchainJS repo :)
// <br />
// We werent able to add uploading or deleting docs via the UI but how
// cool would that be. It can be done via the library though.
// </p>
// <div className="max-h-[50vh] w-full overflow-y-scroll bg-slate-50">
// <CodeBlock
// theme={vs2015}
// text={`/* How to sync documents to Pinecone and Conifer with LangchainJS
// Be sure you have the correct packages installed!
// example: package.json
// {
// "dependencies": {
// "@mintplex-labs/langchain": "https://gitpkg.now.sh/Mintplex-Labs/langchainjs/langchain?conifer",
// ...other deps
// }
// */
// // Now write code as you usually would!
// import { PineconeClient } from "@pinecone-database/pinecone";
// import { PineconeStore } from "@mintplex-labs/langchain/dist/vectorstores/pinecone.js"
// import { ConiferVDBMS } from "@mintplex-labs/langchain/dist/vdbms/conifer.js"
// import { OpenAIEmbeddings } from "langchain/embeddings/openai";
// const client = new PineconeClient();
// await client.init({
// apiKey: 'my-pinecone-api-key',
// environment: 'us-central-gcp',
// });
// const pineconeIndex = client.Index('hackathon');
// const coniferInstance = new ConiferVDBMS({
// orgId: '${organization.orgId}',
// workspaceId: 'workspace-xxxx', // Get from workspace page.
// apiKey: 'ck-xxx' // Get from the api key at the top of the page.
// })
// // Split documents with LangChain text splitter as you normally would
// await PineconeStore.fromDocumentsVerbose(
// documents,
// new OpenAIEmbeddings({ openAIApiKey: 'sk-xxxxxxxx' }),
// {
// pineconeIndex,
// namespace:' testing-collection',
// },
// coniferInstance
// )
// // Documents will now exist in Conifer!
// // More CRUD methods available at ${window.location.origin}/api-docs
// `}
// language="javascript"
// showLineNumbers={false}
// />
// </div>
// <div className="mt-4 flex flex-col gap-y-2">
// <button
// type="button"
// onClick={() => {
// document.getElementById('document-code-modal')?.close();
// }}
// className="flex w-full justify-center rounded bg-transparent p-3 font-medium text-slate-500 hover:bg-slate-200"
// >
// Close Preview
// </button>
// </div>
// </div>
// </dialog>
// );
// };
@@ -1,9 +1,9 @@
import { memo, useState, useEffect } from 'react';
import PreLoader from '../../../components/Preloader';
import { humanFileSize, nFormatter } from '../../../utils/numbers';
import moment from 'moment';
import pluralize from 'pluralize';
import Workspace from '../../../models/workspace';
import { Copy } from '@phosphor-icons/react';
import truncate from 'truncate';
const Statistics = ({
organization,
@@ -12,10 +12,6 @@ const Statistics = ({
organization: any;
workspace: any;
}) => {
const [documents, setDocuments] = useState({
status: 'loading',
value: 0,
});
const [vectors, setVectors] = useState({
status: 'loading',
value: 0,
@@ -24,16 +20,26 @@ const Statistics = ({
status: 'loading',
value: 0,
});
const [dimensions, setDimensions] = useState({
status: 'loading',
value: '-',
});
const [clipboardMessage, setClipboardMessage] = useState('');
const handleCopyToClipboard = async (text: any) => {
if ('clipboard' in navigator) {
await navigator.clipboard.writeText(text);
setClipboardMessage('Copied to clipboard!');
setTimeout(() => {
setClipboardMessage('');
}, 2000);
}
};
useEffect(() => {
async function collectStats() {
if (!workspace?.slug) return false;
Workspace.stats(organization.slug, workspace.slug, 'documents').then(
(json) => {
setDocuments({ status: 'complete', value: json.value });
}
);
Workspace.stats(organization.slug, workspace.slug, 'vectors').then(
(json) => {
setVectors({ status: 'complete', value: json.value });
@@ -44,65 +50,56 @@ const Statistics = ({
setCache({ status: 'complete', value: json.value });
}
);
Workspace.stats(organization.slug, workspace.slug, 'dimensions').then(
(json) => {
setDimensions({ status: 'complete', value: json.value });
}
);
}
collectStats();
}, [organization?.slug, workspace?.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="flex w-full justify-between">
<div className="-mt-6 ml-4 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 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=" -mt-6 ml-4 mr-24 w-fit min-w-[303px] rounded-xl border-2 border-white/20 px-5 py-2 text-sky-400">
<div className="flex items-center justify-between">
<span className="font-jetbrains whitespace-nowrap text-white">
ID:{' '}
<span
className={`font-jetbrainsbold transition-all duration-300 ${
clipboardMessage ? 'animate-pulse text-sky-400' : ''
}`}
>
{clipboardMessage || truncate(workspace?.uuid, 18)}
</span>
</span>
<button
onClick={() => handleCopyToClipboard(workspace?.uuid)}
className="pl-2 text-white transition-all duration-300 hover:cursor-pointer hover:text-sky-400"
>
<Copy size={20} weight="fill" />
</button>
</div>
</div>
</div>
+628 -35
View File
@@ -7,13 +7,19 @@ import paths from '../../utils/paths';
import AppLayout from '../../layout/AppLayout';
import { useParams } from 'react-router-dom';
import Organization from '../../models/organization';
import ConnectorCard from './Connector';
import ApiKeyCard from './ApiKey';
import Statistics from './Statistics';
import DocumentsList from './DocumentsList';
import Workspace from '../../models/workspace';
import { APP_NAME } from '../../utils/constants';
import { titleCase } from 'title-case';
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 qDrantLogo from '../../images/vectordbs/qdrant.png';
import WeaviateLogo from '../../images/vectordbs/weaviate.png';
export default function WorkspaceDashboard() {
const { user } = useUser();
@@ -116,54 +122,559 @@ export default function WorkspaceDashboard() {
hasMoreWorkspaces={hasMoreWorkspaces}
loadMoreWorkspaces={fetchWorkspaces}
headerExtendedItems={
<div className="flex items-center gap-x-4">
<button
type="button"
onClick={() =>
document
.getElementById(`clone-workspace-${workspace?.id}-modal`)
?.showModal()
}
className="rounded-lg px-4 py-2 text-sm text-blue-400 hover:bg-blue-50 hover:text-blue-600"
>
Clone Workspace
</button>
<button
onClick={deleteWorkspace}
className="rounded-lg px-4 py-2 text-sm text-slate-400 hover:bg-red-50 hover:text-red-600"
>
Delete Workspace
</button>
</div>
<WorkspaceViewHeader
organization={organization}
workspace={workspace}
connector={connector}
deleteWorkspace={deleteWorkspace}
/>
}
>
<Statistics organization={organization} workspace={workspace} />
{!!organization && (
<div className="mb-4 grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-6 xl:grid-cols-4 2xl:gap-7.5">
<ConnectorCard
knownConnector={connector}
<SyncConnectorModal
organization={organization}
workspace={workspace}
connector={connector}
/>
<UpdateConnectorModal
organization={organization}
connector={connector}
onUpdate={setConnector}
/>
<ApiKeyCard organization={organization} />
</div>
)}
<Statistics organization={organization} workspace={workspace} />
<div className="mt-4 grid grid-cols-12 gap-4 md:mt-6 md:gap-6 2xl:mt-7.5 2xl:gap-7.5">
<div className="col-span-12 xl:col-span-12">
<DocumentsList
knownConnector={connector}
organization={organization}
workspace={workspace}
workspaces={workspaces}
/>
</div>
</div>
<DocumentsList
knownConnector={connector}
organization={organization}
workspace={workspace}
workspaces={workspaces}
/>
<CloneWorkspaceModal workspace={workspace} />
</AppLayout>
);
}
const UpdateConnectorModal = ({
organization,
connector,
onUpdate,
}: {
organization: any;
connector: any;
onUpdate: (newConnector: any) => void;
}) => {
const [loading, setLoading] = useState(false);
const [type, setType] = useState(connector?.type);
const [error, setError] = useState<null | string>(null);
const [success, setSuccess] = useState<null | boolean>(false);
const settings = JSON.parse(connector?.settings);
const handleSubmit = async (e: any) => {
e.preventDefault();
setError(null);
setLoading(true);
const data = { type };
const form = new FormData(e.target);
for (var [_k, value] of form.entries()) {
if (_k.includes('::')) {
const [_key, __key] = _k.split('::');
if (!data.hasOwnProperty(_key)) data[_key] = {};
data[_key][__key] = value;
} else {
data[_k] = value;
}
}
const { connector, error } = await Organization.updateConnector(
organization.slug,
data
);
if (!connector) {
setLoading(false);
setError(error);
return false;
}
setLoading(false);
setSuccess(true);
setTimeout(() => {
onUpdate(connector);
setSuccess(false);
}, 1500);
};
return (
<dialog
id="edit-connector-modal"
className="w-1/2 rounded-lg"
onClick={(event) =>
event.target == event.currentTarget && event.currentTarget?.close()
}
>
<div className="rounded-sm bg-white p-[20px]">
<div className="px-6.5 py-4">
<h3 className="font-medium text-black dark:text-white">
Update Vector Database Connection
</h3>
<p className="text-sm text-gray-500">
{APP_NAME} is currently connected to a {connector?.type} vector
database instance. You can update your configuration settings here
if they have changed.
</p>
</div>
{loading ? (
<div className="px-6.5">
<div className="mb-4.5 flex w-full justify-center">
<PreLoader />
</div>
</div>
) : (
<form onSubmit={handleSubmit}>
<ul className="mx-6 flex w-full flex-wrap gap-6">
<li onClick={() => setType('chroma')} className="w-[250px]">
<input
name="type"
type="checkbox"
value="chroma"
className="peer hidden"
checked={type === 'chroma'}
formNoValidate={true}
/>
<label className="inline-flex h-full w-full cursor-pointer items-center justify-between rounded-lg border-2 border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 dark:peer-checked:text-gray-300">
<div className="block">
<img
src={ChromaLogo}
className="mb-2 h-10 w-10 rounded-full"
/>
<div className="w-full text-lg font-semibold">Chroma</div>
<div className="flex w-full flex-col gap-y-1 text-sm">
<p className="text-xs text-slate-400">trychroma.com</p>
Open source vector database you can host yourself.
</div>
</div>
</label>
</li>
<li onClick={() => setType('pinecone')} className="w-[250px]">
<input
name="type"
type="checkbox"
value="pinecone"
className="peer hidden"
checked={type === 'pinecone'}
formNoValidate={true}
/>
<label className="inline-flex h-full w-full cursor-pointer items-center justify-between rounded-lg border-2 border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 dark:peer-checked:text-gray-300">
<div className="block">
<img
src={PineconeLogo}
className="mb-2 h-10 w-10 rounded-full"
/>
<div className="w-full text-lg font-semibold">Pinecone</div>
<div className="flex w-full flex-col gap-y-1 text-sm">
<p className="text-xs text-slate-400">pinecone.io</p>
Cloud-hosted vector database.
</div>
</div>
</label>
</li>
<li onClick={() => setType('qdrant')} className="w-[250px]">
<input
name="type"
type="checkbox"
value="qdrant"
className="peer hidden"
checked={type === 'qdrant'}
formNoValidate={true}
/>
<label className="inline-flex h-full w-full cursor-pointer items-center justify-between rounded-lg border-2 border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 dark:peer-checked:text-gray-300">
<div className="block">
<img
src={qDrantLogo}
className="mb-2 h-10 w-10 rounded-full"
/>
<div className="w-full text-lg font-semibold">qDrant</div>
<div className="flex w-full flex-col gap-y-1 text-sm">
<p className="text-xs text-slate-400">qdrant.tech</p>
Open-source & hosted vector database.
</div>
</div>
</label>
</li>
<li onClick={() => setType('weaviate')} className="w-[250px]">
<input
name="type"
type="checkbox"
value="weaviate"
className="peer hidden"
checked={type === 'weaviate'}
formNoValidate={true}
/>
<label className="inline-flex h-full w-full cursor-pointer items-center justify-between rounded-lg border-2 border-gray-200 bg-white p-5 text-gray-500 hover:bg-gray-50 hover:text-gray-600 peer-checked:border-blue-600 peer-checked:bg-blue-50 peer-checked:text-gray-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-gray-300 dark:peer-checked:text-gray-300">
<div className="block">
<img
src={WeaviateLogo}
className="mb-2 h-10 w-10 rounded-full"
/>
<div className="w-full text-lg font-semibold">Weaviate</div>
<div className="flex w-full flex-col gap-y-1 text-sm">
<p className="text-xs text-slate-400">weaviate.io</p>
Open-source & hosted vector database.
</div>
</div>
</label>
</li>
</ul>
{type === 'chroma' && (
<div className="mx-6 my-4 flex flex-col gap-y-6">
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::instanceURL"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
Instance URL
</label>
<p className="text-xs text-gray-500">
This is the URL your chroma instance is reachable at.
</p>
</div>
<input
name="settings::instanceURL"
autoComplete="off"
type="url"
defaultValue={settings.instanceURL}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="https://my-domain.com:8000"
required={true}
/>
</div>
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::authToken"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
API Header & Key
</label>
<p className="text-xs text-gray-500">
If your hosted Chroma instance is protected by an API key
- enter the header and api key here.
</p>
</div>
<div className="flex w-full items-center gap-x-4">
<input
name="settings::authTokenHeader"
autoComplete="off"
type="text"
defaultValue={settings.authTokenHeader}
className="block w-[20%] rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="X-Api-Key"
/>
<input
name="settings::authToken"
autoComplete="off"
type="password"
defaultValue={settings.authToken}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="sk-myApiKeyToAccessMyChromaInstance"
/>
</div>
</div>
</div>
)}
{type === 'pinecone' && (
<div className="mx-6 my-4 flex flex-col gap-y-6">
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::environment"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
Pinecone Environment
</label>
<p className="text-xs text-gray-500">
You can find this on your Pinecone index.
</p>
</div>
<input
name="settings::environment"
autoComplete="off"
type="text"
defaultValue={settings.environment}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="us-west4-gcp-free"
required={true}
/>
</div>
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::index"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
Pinecone Index
</label>
<p className="text-xs text-gray-500">
You can find this on your Pinecone index.
</p>
</div>
<input
name="settings::index"
autoComplete="off"
type="text"
defaultValue={settings.index}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="my-index"
required={true}
/>
</div>
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::apiKey"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
API Key
</label>
<p className="text-xs text-gray-500">
If your hosted Chroma instance is protected by an API key
- enter it here.
</p>
</div>
<input
name="settings::apiKey"
autoComplete="off"
type="password"
defaultValue={settings.apiKey}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="ee1051-xxxx-xxxx-xxxx"
/>
</div>
</div>
)}
{type === 'qdrant' && (
<div className="mx-6 my-4 flex flex-col gap-y-6">
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::clusterUrl"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
qDrant Cluster URL
</label>
<p className="text-xs text-gray-500">
You can find this in your cloud hosted qDrant cluster or
just using the URL to your local docker container.
</p>
</div>
<input
name="settings::clusterUrl"
autoComplete="off"
type="url"
defaultValue={settings.clusterUrl}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="https://6b3a2d01-3b3f-4339-84e9-ead94f28a844.us-east-1-0.aws.cloud.qdrant.io"
required={true}
/>
</div>
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::apiKey"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
API Key
</label>
<p className="text-xs text-gray-500">
(optional) If you are using qDrant cloud you will need an
API key.
</p>
</div>
<input
name="settings::apiKey"
autoComplete="off"
type="password"
defaultValue={settings.apiKey}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="ee1051-xxxx-xxxx-xxxx"
/>
</div>
</div>
)}
{type === 'weaviate' && (
<div className="mx-6 my-4 flex flex-col gap-y-6">
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::clusterUrl"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
Weaviate Cluster URL
</label>
<p className="text-xs text-gray-500">
You can find this in your cloud hosted Weaviate cluster or
just using the URL to your local docker container.
</p>
</div>
<input
name="settings::clusterUrl"
autoComplete="off"
type="url"
defaultValue={settings.clusterUrl}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="https://my-sandbox-b5vipdmw.weaviate.network"
required={true}
/>
</div>
<div className="">
<div className="mb-2 flex flex-col gap-y-1">
<label
htmlFor="settings::apiKey"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
API Key
</label>
<p className="text-xs text-gray-500">
(optional) If you are using Weaviate cloud you will need
an API key.
</p>
</div>
<input
name="settings::apiKey"
autoComplete="off"
type="password"
defaultValue={settings.apiKey}
className="block w-full rounded-lg border border-gray-300 bg-gray-50 p-2.5 text-sm text-gray-900 focus:border-blue-500 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400 dark:focus:border-blue-500 dark:focus:ring-blue-500"
placeholder="ee1051-xxxx-xxxx-xxxx"
/>
</div>
</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-red-800">
{error}
</p>
)}
{success && (
<p className="my-2 w-full rounded-lg border-green-800 bg-green-50 px-4 py-2 text-green-800">
Connector changes saved
</p>
)}
<button
type="submit"
className="w-full rounded-lg border border-blue-600 py-2 text-center text-blue-600 hover:bg-blue-600 hover:text-white"
>
Connect to Vector Database
</button>
</div>
</form>
)}
</div>
</dialog>
);
};
const SyncConnectorModal = ({
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-lg"
onClick={(event) =>
event.target == event.currentTarget && event.currentTarget?.close()
}
>
<div className="overflow-y-scroll rounded-sm bg-white p-[20px]">
<div className="px-6.5 py-4">
<h3 className="font-medium text-black dark:text-white">
Sync Vector Database Connection
</h3>
<p className="text-sm text-gray-500">
{APP_NAME} can 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-blue-500"
>
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-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="w-full rounded-lg bg-blue-600 py-2 text-center text-white"
>
{loading ? 'Synchronizing...' : 'Synchronize embeddings'}
</button>
)}
</div>
</div>
</dialog>
);
};
const CloneWorkspaceModal = memo(({ workspace }: { workspace: any }) => {
const { slug } = useParams();
const [loading, setLoading] = useState(false);
@@ -265,3 +776,85 @@ const CloneWorkspaceModal = memo(({ workspace }: { workspace: any }) => {
</dialog>
);
});
function WorkspaceViewHeader({
organization,
workspace,
connector,
deleteWorkspace,
}: any) {
let logo;
switch (connector?.type) {
case 'chroma':
logo = ChromaLogo;
break;
case 'qdrant':
logo = qDrantLogo;
break;
case 'weaviate':
logo = WeaviateLogo;
break;
default:
logo = PineconeLogo;
}
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">
<a
href={paths.organization(organization)}
className="text-sky-400 hover:cursor-pointer hover:underline"
>
{truncate(organization?.name, 20)}
</a>
<div className="text-sky-400" style={{ transform: 'rotate(270deg)' }}>
<CaretDown weight="bold" />
</div>
<span className="text-white">{truncate(workspace?.name, 20)}</span>
</div>
</div>
<div className="flex gap-x-3">
<button
onClick={() =>
window.document?.getElementById('edit-connector-modal')?.showModal()
}
className="flex h-11 w-11 items-center justify-center rounded-lg border-2 border-white border-opacity-20 transition-all duration-300 hover:bg-opacity-5"
>
<img src={logo} alt="Connector logo" className="h-full p-1" />
</button>
<button
onClick={() =>
document?.getElementById('sync-connector-modal')?.showModal()
}
className="inline-flex h-11 w-[74px] flex-col items-center justify-center gap-2.5 rounded-lg bg-white bg-opacity-10 px-5 py-2.5 transition-all duration-300 hover:bg-opacity-5"
>
<div className="font-satoshi h-[25.53px] w-11 text-center text-base font-bold text-white">
Sync
</div>
</button>
<button
onClick={() =>
window.document
?.getElementById(`clone-workspace-${workspace.id}-modal`)
?.showModal()
}
className="inline-flex h-11 w-[74px] flex-col items-center justify-center gap-2.5 rounded-lg bg-white bg-opacity-10 px-5 py-2.5 transition-all duration-300 hover:bg-opacity-5"
>
<div className="font-satoshi h-[25.53px] w-11 text-center text-base font-bold text-white">
Clone
</div>
</button>
<button
onClick={deleteWorkspace}
className="inline-flex h-11 w-[74px] flex-col items-center justify-center gap-2.5 rounded-lg border-2 border-white border-opacity-20 px-3.5 py-2.5 transition-all duration-300 hover:bg-red-500"
>
<div className="font-satoshi h-[25.53px] w-[59px] text-center text-base font-bold text-white">
Delete
</div>
</button>
</div>
</>
);
}
-121
View File
@@ -1,121 +0,0 @@
/**
* @license
*
* Font Family: Satoshi
* Designed by: Deni Anggara
* URL: https://www.fontshare.com/fonts/satoshi
* © 2023 Indian Type Foundry
*
* Font Styles:
* Satoshi Light
* Satoshi Light Italic
* Satoshi Regular
* Satoshi Italic
* Satoshi Medium
* Satoshi Medium Italic
* Satoshi Bold
* Satoshi Bold Italic
* Satoshi Black
* Satoshi Black Italic
*
*/
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Light.woff2') format('woff2'),
url('./fonts/Satoshi-Light.woff') format('woff'),
url('./fonts/Satoshi-Light.ttf') format('truetype');
font-weight: 300;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-LightItalic.woff2') format('woff2'),
url('./fonts/Satoshi-LightItalic.woff') format('woff'),
url('./fonts/Satoshi-LightItalic.ttf') format('truetype');
font-weight: 300;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Regular.woff2') format('woff2'),
url('./fonts/Satoshi-Regular.woff') format('woff'),
url('./fonts/Satoshi-Regular.ttf') format('truetype');
font-weight: 400;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Italic.woff2') format('woff2'),
url('./fonts/Satoshi-Italic.woff') format('woff'),
url('./fonts/Satoshi-Italic.ttf') format('truetype');
font-weight: 400;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Medium.woff2') format('woff2'),
url('./fonts/Satoshi-Medium.woff') format('woff'),
url('./fonts/Satoshi-Medium.ttf') format('truetype');
font-weight: 500;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-MediumItalic.woff2') format('woff2'),
url('./fonts/Satoshi-MediumItalic.woff') format('woff'),
url('./fonts/Satoshi-MediumItalic.ttf') format('truetype');
font-weight: 500;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Bold.woff2') format('woff2'),
url('./fonts/Satoshi-Bold.woff') format('woff'),
url('./fonts/Satoshi-Bold.ttf') format('truetype');
font-weight: 700;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-BoldItalic.woff2') format('woff2'),
url('./fonts/Satoshi-BoldItalic.woff') format('woff'),
url('./fonts/Satoshi-BoldItalic.ttf') format('truetype');
font-weight: 700;
font-display: swap;
font-style: italic;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-Black.woff2') format('woff2'),
url('./fonts/Satoshi-Black.woff') format('woff'),
url('./fonts/Satoshi-Black.ttf') format('truetype');
font-weight: 900;
font-display: swap;
font-style: normal;
}
@font-face {
font-family: 'Satoshi';
src: url('./fonts/Satoshi-BlackItalic.woff2') format('woff2'),
url('./fonts/Satoshi-BlackItalic.woff') format('woff'),
url('./fonts/Satoshi-BlackItalic.ttf') format('truetype');
font-weight: 900;
font-display: swap;
font-style: italic;
}
-3
View File
@@ -6,9 +6,6 @@ module.exports = {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
darkMode: 'class',
theme: {
fontFamily: {
satoshi: ['Satoshi', 'sans-serif'],
},
screens: {
'2xsm': '375px',
xsm: '425px',