diff --git a/.gitignore b/.gitignore
index 3c3bb305..3f76e4a5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,4 +6,6 @@ node_modules
__pycache__
v-env
.DS_Store
-yarn-error.log
\ No newline at end of file
+yarn-error.log
+yarn.lock
+frontend/.env.development
\ No newline at end of file
diff --git a/backend/endpoints/v1/workspaces/index.js b/backend/endpoints/v1/workspaces/index.js
index 1df3e38d..31ed492e 100644
--- a/backend/endpoints/v1/workspaces/index.js
+++ b/backend/endpoints/v1/workspaces/index.js
@@ -305,6 +305,7 @@ function workspaceEndpoints(app) {
documents: "countForEntity",
vectors: "calcVectors",
"cache-size": "calcVectorCache",
+ dimensions: "calcDimensions",
};
if (!Object.keys(methods).includes(statistic)) {
@@ -314,7 +315,6 @@ function workspaceEndpoints(app) {
return;
}
- console.log(workspace);
const value = await WorkspaceDocument[methods[statistic]](
"workspace_id",
workspace.id
diff --git a/backend/models/workspaceDocument.js b/backend/models/workspaceDocument.js
index a6852346..6e511ee7 100644
--- a/backend/models/workspaceDocument.js
+++ b/backend/models/workspaceDocument.js
@@ -159,6 +159,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 {
diff --git a/backend/utils/vectordatabases/providers/chroma/index.js b/backend/utils/vectordatabases/providers/chroma/index.js
index f3ec4a87..4f77ff99 100644
--- a/backend/utils/vectordatabases/providers/chroma/index.js
+++ b/backend/utils/vectordatabases/providers/chroma/index.js
@@ -98,6 +98,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();
diff --git a/backend/utils/vectordatabases/providers/weaviate/index.js b/backend/utils/vectordatabases/providers/weaviate/index.js
index ebd85917..9e21a611 100644
--- a/backend/utils/vectordatabases/providers/weaviate/index.js
+++ b/backend/utils/vectordatabases/providers/weaviate/index.js
@@ -66,7 +66,6 @@ class Weaviate {
var totalVectors = 0;
for (const collection of collections) {
if (!collection || !collection.name) continue;
- console.log({ dim: await this.indexDimensions(collection.name) });
totalVectors +=
(await this.namespaceWithClient(client, collection.name))
?.vectorCount || 0;
diff --git a/frontend/package.json b/frontend/package.json
index b3769cce..bbcdfa6f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -17,6 +17,7 @@
"dependencies": {
"@dqbd/tiktoken": "^1.0.7",
"@metamask/jazzicon": "^2.0.0",
+ "@phosphor-icons/react": "^2.0.15",
"jsvectormap": "^1.5.1",
"lodash": "^4.17.21",
"lodash.debounce": "^4.0.8",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 41d81db9..e5859255 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -17,10 +17,13 @@ const OrganizationSettingsView = lazy(
const OrganizationDashboard = lazy(() => import('./pages/Dashboard'));
const WorkspaceDashboard = lazy(() => import('./pages/WorkspaceDashboard'));
const DocumentView = lazy(() => import('./pages/DocumentView'));
-const SystemSetup = lazy(() => import('./pages/Authentication/SystemSetup'));
const OnboardingSecuritySetup = lazy(
() => import('./pages/Onboarding/security')
);
+
+// Onboarding v2
+const OnboardingFlow = lazy(() => import('./pages/OnboardingFlow'));
+
const OrganizationJobsView = lazy(() => import('./pages/Jobs'));
const OrganizationToolsView = lazy(() => import('./pages/Tools'));
const SystemSettingsView = lazy(() => import('./pages/SystemSettings'));
@@ -75,6 +78,7 @@ function App() {
element={ }
/>
+ } />
}
@@ -103,7 +107,6 @@ function App() {
} />
} />
- } />
}
diff --git a/frontend/src/components/DocumentPaginator/index.tsx b/frontend/src/components/DocumentPaginator/index.tsx
index 3d728897..9c9266c1 100644
--- a/frontend/src/components/DocumentPaginator/index.tsx
+++ b/frontend/src/components/DocumentPaginator/index.tsx
@@ -1,3 +1,4 @@
+import { CaretDown } from '@phosphor-icons/react';
import { numberWithCommas } from '../../utils/numbers';
function generatePageItems(total: number, current: number) {
@@ -30,17 +31,39 @@ export default function DocumentListPagination({
gotoPage,
}: IPaginationProps) {
const pageItems = generatePageItems(pageCount, currentPage);
+
+ const hasPrevious = currentPage > 1;
+ const hasNext = currentPage < pageCount;
+
+ const goToPrevious = () => {
+ if (currentPage > 1) gotoPage(currentPage - 1);
+ };
+
+ const goToNext = () => {
+ if (currentPage < pageCount) gotoPage(currentPage + 1);
+ };
+
+ if (pageCount < 2) return
-
+
{truncate(file.name, 30)}
diff --git a/frontend/src/pages/Dashboard/DocumentsList/UploadModal/index.tsx b/frontend/src/components/Modals/UploadDocumentModal/index.tsx
similarity index 73%
rename from frontend/src/pages/Dashboard/DocumentsList/UploadModal/index.tsx
rename to frontend/src/components/Modals/UploadDocumentModal/index.tsx
index aa37d1df..a36d05af 100644
--- a/frontend/src/pages/Dashboard/DocumentsList/UploadModal/index.tsx
+++ b/frontend/src/components/Modals/UploadDocumentModal/index.tsx
@@ -1,19 +1,23 @@
import { useCallback, useState, useEffect, ReactNode } from 'react';
-import { APP_NAME } from '../../../../utils/constants';
+import { APP_NAME } from '../../../utils/constants';
import { useDropzone } from 'react-dropzone';
import { v4 } from 'uuid';
-import System from '../../../../models/system';
-import { Frown } from 'react-feather';
+import System from '../../../models/system';
import FileUploadProgress from './FileUploadProgress';
import { useParams } from 'react-router-dom';
+import { SmileySad } from '@phosphor-icons/react';
export default function UploadDocumentModal({
workspaces,
+ workspace,
}: {
- workspaces: any;
+ workspaces?: any[];
+ workspace?: any;
}) {
const { slug } = useParams();
- const [targetWorkspace, setTargetWorkspace] = useState(null);
+ const [targetWorkspace, setTargetWorkspace] = useState(
+ workspace ? { ...workspace } : null
+ );
const [ready, setReady] = useState(null);
const [files, setFiles] = useState([]);
const [fileTypes, setFileTypes] = useState({});
@@ -60,12 +64,12 @@ export default function UploadDocumentModal({
if (ready === null || !slug) {
return (
-
+
Checking document processor is online - please wait.
-
+
this should only take a few moments.
@@ -78,8 +82,8 @@ export default function UploadDocumentModal({
return (
-
-
+
+
Document processor is offline.
@@ -92,7 +96,9 @@ export default function UploadDocumentModal({
);
}
- if (ready === true && targetWorkspace === null) {
+ // When workspace was not given as a prop we already know the workspace to
+ // target so do not allow a re-selection.
+ if (ready === true && targetWorkspace === null && !workspace) {
const saveWorkspace = (e: any) => {
e.preventDefault();
const form = new FormData(e.target);
@@ -106,21 +112,21 @@ export default function UploadDocumentModal({
return (
-
+
-
+
Please select the workspace you wish to upload documents to.
-
@@ -135,7 +141,7 @@ export default function UploadDocumentModal({
{files.length === 0 ? (
@@ -143,7 +149,7 @@ export default function UploadDocumentModal({
-
- Click to upload or drag
+
+ Click to upload or drag
and drop
@@ -178,9 +184,9 @@ export default function UploadDocumentModal({
)}
-
- supported file extensions are{' '}
-
+
+ Supported file extensions are{' '}
+
{Object.values(fileTypes).flat().join(' ')}
@@ -193,16 +199,14 @@ const ModalWrapper = ({ children }: { children: ReactNode }) => {
return (
{
event.target == event.currentTarget && event.currentTarget?.close();
}}
>
-
-
- Upload new document
-
-
+
+
Upload new document
+
Select a workspace and document you wish to upload and {APP_NAME} will
process, embed and store the data for you automatically.
diff --git a/frontend/src/components/Modals/UploadModalNoKey.tsx b/frontend/src/components/Modals/UploadModalNoKey.tsx
new file mode 100644
index 00000000..7fb910b9
--- /dev/null
+++ b/frontend/src/components/Modals/UploadModalNoKey.tsx
@@ -0,0 +1,77 @@
+import { AlertTriangle } from 'react-feather';
+import { APP_NAME } from '../../utils/constants';
+import System from '../../models/system';
+import { ReactNode } from 'react';
+
+export default function UploadModalNoKey() {
+ const updateSystemSetting = async (e: any) => {
+ e.preventDefault();
+ const form = new FormData(e.target);
+ const open_ai_api_key = form.get('open_ai_api_key');
+ await System.updateSettings({ open_ai_api_key });
+ window.location.reload();
+ };
+
+ return (
+
+
+
+
You cannot upload and embed documents without an
+ OpenAI API Key.
+
+
+ {APP_NAME} will automatically upload and embed your documents for you,
+ but for this to happen we must have an OpenAI key set.
+
+
+
+
+ );
+}
+
+const ModalWrapper = ({ children }: { children: ReactNode }) => {
+ return (
+
{
+ event.target == event.currentTarget && event.currentTarget?.close();
+ }}
+ >
+
+
Upload new document
+
+ Select a workspace and document you wish to upload and {APP_NAME} will
+ process, embed and store the data for you automatically.
+
+
+ {children}
+
+ );
+};
diff --git a/frontend/src/components/Notifications/index.tsx b/frontend/src/components/Notifications/index.tsx
new file mode 100644
index 00000000..342627a7
--- /dev/null
+++ b/frontend/src/components/Notifications/index.tsx
@@ -0,0 +1,391 @@
+import { ReactNode, useEffect, useRef, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import Organization from '../../models/organization';
+import { databaseTimestampFromNow } from '../../utils/data';
+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';
+import { Bell, Info, Warning, WarningOctagon } from '@phosphor-icons/react';
+
+const POLLING_INTERVAL = 30_000;
+
+export type INotification = {
+ id: number;
+ organization_id: number;
+ seen: boolean;
+ textContent: string;
+ symbol?:
+ | 'info'
+ | 'warning'
+ | 'error'
+ | 'chroma'
+ | 'pinecone'
+ | 'weaviate'
+ | 'qdrant';
+ link?: string;
+ target?: '_blank' | 'self';
+ createdAt: string;
+ lastUpdatedAt: string;
+};
+
+export default function Notifications() {
+ const { slug } = useParams();
+ const [loading, setLoading] = useState(true);
+ const [notifications, setNotifications] = useState
([]);
+ const [hasUnseen, setHasUnseen] = useState(false);
+ const [showNotifs, setShowNotifs] = useState(false);
+ const notificationRef = useRef(null);
+ const bellButtonRef = useRef(null);
+
+ async function handleClick() {
+ if (!showNotifs) {
+ !!slug && Organization.markNotificationsSeen(slug);
+ setShowNotifs(true);
+ setHasUnseen(false);
+ } else {
+ setShowNotifs(false);
+ }
+ }
+ useEffect(() => {
+ function handleClickOutside(event: any) {
+ if (
+ bellButtonRef.current &&
+ bellButtonRef.current.contains(event.target)
+ ) {
+ return;
+ }
+ if (
+ notificationRef.current &&
+ !notificationRef.current.contains(event.target)
+ ) {
+ setShowNotifs(false);
+ }
+ }
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }, []);
+
+ async function fetchNotifications() {
+ if (!slug) {
+ setLoading(false);
+ return;
+ }
+
+ const { notifications: _notifications } = await Organization.notifications(
+ slug
+ );
+ setNotifications(_notifications);
+ setHasUnseen(_notifications.some((notif) => notif.seen === false));
+ setLoading(false);
+ }
+
+ useEffect(() => {
+ if (!slug) return;
+ fetchNotifications();
+ setInterval(() => {
+ fetchNotifications();
+ }, POLLING_INTERVAL);
+ }, [slug]);
+
+ if (loading) return null;
+
+ return (
+
+
+
+
+
+
+
+ Notifications
+
+
+ {notifications.length === 0 ? (
+
+ ) : (
+
+ {notifications.map((notification) => (
+
+ ))}
+
+ )}
+
+
+
+ );
+}
+
+function NotificationImage({ notification }: { notification: INotification }) {
+ switch (notification.symbol) {
+ case 'info':
+ return (
+
+
+
+ );
+ case 'warning':
+ return (
+
+
+
+ );
+ case 'error':
+ return (
+
+
+
+ );
+ case 'chroma':
+ return (
+
+
+
+ );
+ case 'pinecone':
+ return (
+
+
+
+ );
+ case 'qdrant':
+ return (
+
+
+
+ );
+ case 'weaviate':
+ return (
+
+
+
+ );
+ default:
+ return (
+
+
+
+ );
+ }
+}
+
+function NotificationWrapper({
+ notification,
+ children,
+}: {
+ notification: INotification;
+ children: ReactNode;
+}) {
+ if (!!notification.link) {
+ return (
+
+ {children}
+
+ );
+ }
+ return (
+
+ {children}
+
+ );
+}
+
+function Notification({ notification }: { notification: INotification }) {
+ return (
+
+
+
+
+
+
+ {notification.textContent}
+
+
+ {databaseTimestampFromNow(notification.createdAt)}
+
+
+
+ );
+}
+
+//
+//
+//
+//
+//
+//
+// {' '}
+// {' '}
+//
diff --git a/frontend/src/components/Preloader.tsx b/frontend/src/components/Preloader.tsx
index 6201d919..e03cc12c 100644
--- a/frontend/src/components/Preloader.tsx
+++ b/frontend/src/components/Preloader.tsx
@@ -1,6 +1,6 @@
export default function PreLoader() {
return (
-
+
);
}
@@ -8,7 +8,7 @@ export function FullScreenLoader() {
return (
diff --git a/frontend/src/components/Sidebar/CreateOrganizationModal/index.tsx b/frontend/src/components/Sidebar/CreateOrganizationModal/index.tsx
index 4ae4bdc3..76bda003 100644
--- a/frontend/src/components/Sidebar/CreateOrganizationModal/index.tsx
+++ b/frontend/src/components/Sidebar/CreateOrganizationModal/index.tsx
@@ -18,10 +18,13 @@ export default function CreateOrganizationModal() {
};
return (
-
-
+
+
-
+
Create a New Organization
@@ -35,7 +38,7 @@ export default function CreateOrganizationModal() {