mirror of
https://github.com/Mintplex-Labs/vector-admin.git
synced 2026-07-19 21:23:38 -04:00
workspace overhaul styling complete/header functionalities implemented
This commit is contained in:
@@ -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>
|
||||
// );
|
||||
// };
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user