@@ -305,6 +305,7 @@ function workspaceEndpoints(app) {
|
||||
documents: "countForEntity",
|
||||
vectors: "calcVectors",
|
||||
"cache-size": "calcVectorCache",
|
||||
dimensions: "calcDimensions",
|
||||
};
|
||||
|
||||
if (!Object.keys(methods).includes(statistic)) {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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',
|
||||
|
||||