workspace overhaul styling complete/header functionalities implemented

This commit is contained in:
shatfield4
2023-12-14 15:03:16 -08:00
parent 677d2fb0b4
commit f2fb81b35c
7 changed files with 1564 additions and 231 deletions
@@ -77,6 +77,8 @@ async function validatePinecone({ environment, index, apiKey }) {
indexName: index,
});
// TODO: CHECK INDEX SIZE (client.indexDimensions()) and store in return so we can store it in the database
if (!status.ready) throw new Error("Pinecone::Index not ready or found.");
return { valid: true, message: null };
} catch (e) {
@@ -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}
@@ -175,11 +175,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>
</>
File diff suppressed because it is too large Load Diff
@@ -1,19 +1,11 @@
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,
@@ -21,25 +13,17 @@ export default function SearchView({
}: {
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();
};
@@ -50,24 +34,20 @@ export default function SearchView({
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="flex items-center">
<form ref={formEl} onSubmit={handleSearch} className="w-full">
<div className="relative flex">
<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"
@@ -118,7 +98,7 @@ export default function SearchView({
<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"
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 ? (
@@ -157,7 +137,7 @@ export default function SearchView({
?.getElementById('upload-document-modal')
?.showModal();
}}
className="flex w-18 items-center justify-center rounded-[100px] bg-sky-400 px-2.5 text-xs"
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>
@@ -1,7 +1,6 @@
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';
@@ -15,13 +14,7 @@ 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 {
CaretDown,
File,
MagnifyingGlass,
Trash,
X,
} from '@phosphor-icons/react';
import { File, Trash } from '@phosphor-icons/react';
import PreLoader from '../../../components/Preloader';
export default function DocumentsList({
@@ -41,13 +34,10 @@ export default function DocumentsList({
const [documents, setDocuments] = useState([]);
const [totalDocuments, setTotalDocuments] = useState(0);
const [canUpload, setCanUpload] = useState(false);
const [searchBy, setSearchBy] = useState<ISearchTypes>('exactText');
const [searchResults, setSearchResults] = useState<any[]>([]);
const [currentPage, setCurrentPage] = useState(
Number(query.get('docPage')) || 1
);
const [showSearchMethods, setShowSearchMethods] = useState(false);
const formEl = useRef<HTMLFormElement>(null);
function updatePage(pgNum: number) {
const setTo = pgNum <= 0 ? 1 : pgNum;
@@ -172,10 +162,10 @@ export default function DocumentsList({
return (
<>
<div
className="flex h-screen flex-col overflow-hidden bg-main py-6 transition-all duration-300"
style={{ height: `calc(100vh - ${searchMode ? '130px' : '130px'})` }}
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 justify-between px-4">
<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">
@@ -188,18 +178,16 @@ export default function DocumentsList({
({workspace?.documentCount})
</span>
</div>
<SearchView
searchMode={searchMode}
organization={organization}
workspace={workspace}
workspaces={workspaces}
stopSearching={() => setSearchMode(false)}
deleteDocument={deleteDocument}
setSearchMode={setSearchMode}
handleSearchResults={handleSearchResults}
setLoading={setLoading}
/>
</div>
<SearchView
searchMode={searchMode}
organization={organization}
workspace={workspace}
stopSearching={() => setSearchMode(false)}
setSearchMode={setSearchMode}
handleSearchResults={handleSearchResults}
setLoading={setLoading}
/>
</div>
<div className="flex-grow overflow-y-auto rounded-xl border-2 border-white/20 bg-main">
@@ -486,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>
// );
// };
+546 -21
View File
@@ -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 ConnectorCard from './Connector';
import ApiKeyCard from './ApiKey';
import Statistics from './Statistics';
import DocumentsList from './DocumentsList';
@@ -127,30 +126,24 @@ export default function WorkspaceDashboard() {
organization={organization}
workspace={workspace}
connector={connector}
deleteWorkspace={deleteWorkspace}
/>
}
>
<Statistics organization={organization} workspace={workspace} />
{/* {!!organization && (
{!!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>
)}
<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}
@@ -163,6 +156,525 @@ export default function WorkspaceDashboard() {
);
}
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,7 +777,12 @@ const CloneWorkspaceModal = memo(({ workspace }: { workspace: any }) => {
);
});
function WorkspaceViewHeader({ organization, workspace, connector }: any) {
function WorkspaceViewHeader({
organization,
workspace,
connector,
deleteWorkspace,
}: any) {
let logo;
switch (connector?.type) {
case 'chroma':
@@ -299,7 +816,9 @@ function WorkspaceViewHeader({ organization, workspace, connector }: any) {
</div>
<div className="flex gap-x-3">
<button
// onClick={deleteDocument}
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
@@ -310,7 +829,9 @@ function WorkspaceViewHeader({ organization, workspace, connector }: any) {
</button>
<button
// onClick={cloneDocument}
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="h-[25.53px] w-11 text-center font-['Satoshi'] text-base font-bold text-white">
@@ -318,7 +839,11 @@ function WorkspaceViewHeader({ organization, workspace, connector }: any) {
</div>
</button>
<button
// onClick={cloneDocument}
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="h-[25.53px] w-11 text-center font-['Satoshi'] text-base font-bold text-white">
@@ -326,7 +851,7 @@ function WorkspaceViewHeader({ organization, workspace, connector }: any) {
</div>
</button>
<button
// onClick={deleteDocument}
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="h-[25.53px] w-[59px] text-center font-['Satoshi'] text-base font-bold text-white">