diff --git a/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx b/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx index 52b818ffa..ff7f0dd65 100644 --- a/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/Documents/index.jsx @@ -6,7 +6,9 @@ import Directory from "./Directory"; import showToast from "../../../../utils/toast"; import WorkspaceDirectory from "./WorkspaceDirectory"; -const COST_PER_TOKEN = 0.0004; +// OpenAI Cost per token for text-ada-embedding +// ref: https://openai.com/pricing#:~:text=%C2%A0/%201K%20tokens-,Embedding%20models,-Build%20advanced%20search +const COST_PER_TOKEN = 0.0000001; // $0.0001 / 1K tokens export default function DocumentSettings({ workspace, diff --git a/frontend/src/components/Modals/MangeWorkspace/Settings/index.jsx b/frontend/src/components/Modals/MangeWorkspace/Settings/index.jsx index 29d2bfa73..2fce91e1f 100644 --- a/frontend/src/components/Modals/MangeWorkspace/Settings/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/Settings/index.jsx @@ -26,24 +26,11 @@ function castToType(key, value) { return definitions[key].cast(value); } -export default function WorkspaceSettings({ workspace }) { +export default function WorkspaceSettings({ active, workspace }) { const { slug } = useParams(); const formEl = useRef(null); const [saving, setSaving] = useState(false); const [hasChanges, setHasChanges] = useState(false); - const [totalVectors, setTotalVectors] = useState(null); - const [canDelete, setCanDelete] = useState(false); - - useEffect(() => { - async function fetchKeys() { - const canDelete = await System.getCanDeleteWorkspaces(); - setCanDelete(canDelete); - - const totalVectors = await System.totalIndexes(); - setTotalVectors(totalVectors); - } - fetchKeys(); - }, []); const handleUpdate = async (e) => { setSaving(true); @@ -89,6 +76,9 @@ export default function WorkspaceSettings({ workspace }) { <h3 className="text-white text-sm font-semibold"> Vector database identifier </h3> + <p className="text-white text-opacity-60 text-xs font-medium py-1.5"> + {" "} + </p> <p className="text-white text-opacity-60 text-sm font-medium"> {workspace?.slug} </p> @@ -101,13 +91,7 @@ export default function WorkspaceSettings({ workspace }) { <p className="text-white text-opacity-60 text-xs font-medium my-[2px]"> Total number of vectors in your vector database. </p> - {totalVectors !== null ? ( - <p className="text-white text-opacity-60 text-sm font-medium"> - {totalVectors} - </p> - ) : ( - <PreLoader size="4" /> - )} + <VectorCount reload={active} workspace={workspace} /> </div> </div> </div> @@ -275,15 +259,7 @@ export default function WorkspaceSettings({ workspace }) { </div> </div> <div className="flex items-center justify-between p-2 md:p-6 space-x-2 border-t rounded-b border-gray-600"> - {canDelete && ( - <button - onClick={deleteWorkspace} - type="button" - className="transition-all duration-300 border border-transparent rounded-lg whitespace-nowrap text-sm px-5 py-2.5 focus:z-10 bg-transparent text-white hover:text-white hover:bg-red-600" - > - Delete Workspace - </button> - )} + <DeleteWorkspace workspace={workspace} onClick={deleteWorkspace} /> {hasChanges && ( <button type="submit" @@ -296,3 +272,43 @@ export default function WorkspaceSettings({ workspace }) { </form> ); } + +function DeleteWorkspace({ workspace, onClick }) { + const [canDelete, setCanDelete] = useState(false); + useEffect(() => { + async function fetchKeys() { + const canDelete = await System.getCanDeleteWorkspaces(); + setCanDelete(canDelete); + } + fetchKeys(); + }, [workspace?.slug]); + + if (!canDelete) return null; + return ( + <button + onClick={onClick} + type="button" + className="transition-all duration-300 border border-transparent rounded-lg whitespace-nowrap text-sm px-5 py-2.5 focus:z-10 bg-transparent text-white hover:text-white hover:bg-red-600" + > + Delete Workspace + </button> + ); +} + +function VectorCount({ reload, workspace }) { + const [totalVectors, setTotalVectors] = useState(null); + useEffect(() => { + async function fetchVectorCount() { + const totalVectors = await System.totalIndexes(workspace.slug); + setTotalVectors(totalVectors); + } + fetchVectorCount(); + }, [workspace?.slug, reload]); + + if (totalVectors === null) return <PreLoader size="4" />; + return ( + <p className="text-white text-opacity-60 text-sm font-medium"> + {totalVectors} + </p> + ); +} diff --git a/frontend/src/components/Modals/MangeWorkspace/index.jsx b/frontend/src/components/Modals/MangeWorkspace/index.jsx index 946e96d26..9092d0d51 100644 --- a/frontend/src/components/Modals/MangeWorkspace/index.jsx +++ b/frontend/src/components/Modals/MangeWorkspace/index.jsx @@ -114,7 +114,10 @@ const ManageWorkspace = ({ hideModal = noop, providedSlug = null }) => { /> </div> <div className={selectedTab === "settings" ? "" : "hidden"}> - <WorkspaceSettings workspace={workspace} fileTypes={fileTypes} /> + <WorkspaceSettings + active={selectedTab === "settings"} // To force reload live sub-components like VectorCount + workspace={workspace} + /> </div> </Suspense> </div> diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js index 9b71a6055..c64ac66a1 100644 --- a/frontend/src/models/system.js +++ b/frontend/src/models/system.js @@ -9,8 +9,10 @@ const System = { .then((res) => res?.online || false) .catch(() => false); }, - totalIndexes: async function () { - return await fetch(`${API_BASE}/system/system-vectors`, { + totalIndexes: async function (slug = null) { + const url = new URL(`${API_BASE}/system/system-vectors`); + if (!!slug) url.searchParams.append("slug", encodeURIComponent(slug)); + return await fetch(url.toString(), { headers: baseHeaders(), }) .then((res) => { diff --git a/server/endpoints/invite.js b/server/endpoints/invite.js index c5c344510..4fd8d1545 100644 --- a/server/endpoints/invite.js +++ b/server/endpoints/invite.js @@ -42,11 +42,11 @@ function inviteEndpoints(app) { return; } - const { user, error } = await User.create(({ + const { user, error } = await User.create({ username, password, role: "default", - })); + }); if (!user) { console.error("Accepting invite:", error); response diff --git a/server/endpoints/system.js b/server/endpoints/system.js index 982d5ecaa..a6acf47e6 100644 --- a/server/endpoints/system.js +++ b/server/endpoints/system.js @@ -15,6 +15,7 @@ const { makeJWT, userFromSession, multiUserMode, + queryParams, } = require("../utils/http"); const { setupDataImports, @@ -180,16 +181,23 @@ function systemEndpoints(app) { } }); - app.get("/system/system-vectors", [validatedRequest], async (_, response) => { - try { - const VectorDb = getVectorDbClass(); - const vectorCount = await VectorDb.totalVectors(); - response.status(200).json({ vectorCount }); - } catch (e) { - console.log(e.message, e); - response.sendStatus(500).end(); + app.get( + "/system/system-vectors", + [validatedRequest], + async (request, response) => { + try { + const query = queryParams(request); + const VectorDb = getVectorDbClass(); + const vectorCount = !!query.slug + ? await VectorDb.namespaceCount(query.slug) + : await VectorDb.totalVectors(); + response.status(200).json({ vectorCount }); + } catch (e) { + console.log(e.message, e); + response.sendStatus(500).end(); + } } - }); + ); app.delete( "/system/remove-document",