const { reqBody, multiUserMode, userFromSession } = require("../utils/http"); const { Workspace } = require("../models/workspace"); const { Document } = require("../models/documents"); const { DocumentVectors } = require("../models/vectors"); const { WorkspaceChats } = require("../models/workspaceChats"); const { getVectorDbClass } = require("../utils/helpers"); const { setupMulter } = require("../utils/files/multer"); const { validatedRequest } = require("../utils/middleware/validatedRequest"); const { Telemetry } = require("../models/telemetry"); const { flexUserRoleValid, ROLES, } = require("../utils/middleware/multiUserProtected"); const { EventLogs } = require("../models/eventLogs"); const { WorkspaceSuggestedMessages, } = require("../models/workspacesSuggestedMessages"); const { validWorkspaceSlug } = require("../utils/middleware/validWorkspace"); const { convertToChatHistory } = require("../utils/helpers/chat/responses"); const { CollectorApi } = require("../utils/collectorApi"); const { handleUploads } = setupMulter(); const { setupPfpUploads } = require("../utils/files/multer"); const { normalizePath } = require("../utils/files"); const { handlePfpUploads } = setupPfpUploads(); const path = require("path"); const fs = require("fs"); const { determineWorkspacePfpFilepath, fetchPfp, } = require("../utils/files/pfp"); function workspaceEndpoints(app) { if (!app) return; const responseCache = new Map(); app.post( "/workspace/new", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async (request, response) => { try { const user = await userFromSession(request, response); const { name = null, onboardingComplete = false } = reqBody(request); const { workspace, message } = await Workspace.new(name, user?.id); await Telemetry.sendTelemetry( "workspace_created", { multiUserMode: multiUserMode(response), LLMSelection: process.env.LLM_PROVIDER || "openai", Embedder: process.env.EMBEDDING_ENGINE || "inherit", VectorDbSelection: process.env.VECTOR_DB || "pinecone", }, user?.id ); await EventLogs.logEvent( "workspace_created", { workspaceName: workspace?.name || "Unknown Workspace", }, user?.id ); if (onboardingComplete === true) await Telemetry.sendTelemetry("onboarding_complete"); response.status(200).json({ workspace, message }); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); } } ); app.post( "/workspace/:slug/update", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async (request, response) => { try { const user = await userFromSession(request, response); const { slug = null } = request.params; const data = reqBody(request); const currWorkspace = multiUserMode(response) ? await Workspace.getWithUser(user, { slug }) : await Workspace.get({ slug }); if (!currWorkspace) { response.sendStatus(400).end(); return; } if ( !currWorkspace.metaResponse && !currWorkspace.metaResponseSettings ) { metaResponseDefaultSettings.inputs.config.systemPrompt.openAiPrompt = currWorkspace.openAiPrompt || ""; data.metaResponseSettings = JSON.stringify( metaResponseDefaultSettings ); await EventLogs.logEvent( "workspace_meta_response_enabled", { workspaceName: currWorkspace?.name || "Unknown Workspace", }, user?.id ); } const { workspace, message } = await Workspace.update( currWorkspace.id, data ); response.status(200).json({ workspace, message }); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); } } ); app.post( "/workspace/:slug/upload", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], handleUploads.single("file"), async function (request, response) { const Collector = new CollectorApi(); const { originalname } = request.file; const processingOnline = await Collector.online(); if (!processingOnline) { response .status(500) .json({ success: false, error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`, }) .end(); return; } const { success, reason } = await Collector.processDocument(originalname); if (!success) { response.status(500).json({ success: false, error: reason }).end(); return; } Collector.log( `Document ${originalname} uploaded processed and successfully. It is now available in documents.` ); await Telemetry.sendTelemetry("document_uploaded"); await EventLogs.logEvent( "document_uploaded", { documentName: originalname, }, response.locals?.user?.id ); response.status(200).json({ success: true, error: null }); } ); app.post( "/workspace/:slug/upload-link", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async (request, response) => { const Collector = new CollectorApi(); const { link = "" } = reqBody(request); const processingOnline = await Collector.online(); if (!processingOnline) { response .status(500) .json({ success: false, error: `Document processing API is not online. Link ${link} will not be processed automatically.`, }) .end(); return; } const { success, reason } = await Collector.processLink(link); if (!success) { response.status(500).json({ success: false, error: reason }).end(); return; } Collector.log( `Link ${link} uploaded processed and successfully. It is now available in documents.` ); await Telemetry.sendTelemetry("link_uploaded"); await EventLogs.logEvent( "link_uploaded", { link }, response.locals?.user?.id ); response.status(200).json({ success: true, error: null }); } ); app.post( "/workspace/:slug/update-embeddings", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async (request, response) => { try { const user = await userFromSession(request, response); const { slug = null } = request.params; const { adds = [], deletes = [] } = reqBody(request); const currWorkspace = multiUserMode(response) ? await Workspace.getWithUser(user, { slug }) : await Workspace.get({ slug }); if (!currWorkspace) { response.sendStatus(400).end(); return; } await Document.removeDocuments( currWorkspace, deletes, response.locals?.user?.id ); const { failedToEmbed = [], errors = [] } = await Document.addDocuments( currWorkspace, adds, response.locals?.user?.id ); const updatedWorkspace = await Workspace.get({ id: currWorkspace.id }); response.status(200).json({ workspace: updatedWorkspace, message: failedToEmbed.length > 0 ? `${failedToEmbed.length} documents failed to add.\n\n${errors .map((msg) => `${msg}`) .join("\n\n")}` : null, }); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); } } ); app.delete( "/workspace/:slug", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async (request, response) => { try { const { slug = "" } = request.params; const user = await userFromSession(request, response); const VectorDb = getVectorDbClass(); const workspace = multiUserMode(response) ? await Workspace.getWithUser(user, { slug }) : await Workspace.get({ slug }); if (!workspace) { response.sendStatus(400).end(); return; } await WorkspaceChats.delete({ workspaceId: Number(workspace.id) }); await DocumentVectors.deleteForWorkspace(workspace.id); await Document.delete({ workspaceId: Number(workspace.id) }); await Workspace.delete({ id: Number(workspace.id) }); await EventLogs.logEvent( "workspace_deleted", { workspaceName: workspace?.name || "Unknown Workspace", }, response.locals?.user?.id ); try { await VectorDb["delete-namespace"]({ namespace: slug }); } catch (e) { console.error(e.message); } response.sendStatus(200).end(); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); } } ); app.get( "/workspaces", [validatedRequest, flexUserRoleValid([ROLES.all])], async (request, response) => { try { const user = await userFromSession(request, response); const workspaces = multiUserMode(response) ? await Workspace.whereWithUser(user) : await Workspace.where(); response.status(200).json({ workspaces }); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); } } ); app.get( "/workspace/:slug", [validatedRequest, flexUserRoleValid([ROLES.all])], async (request, response) => { try { const { slug } = request.params; const user = await userFromSession(request, response); const workspace = multiUserMode(response) ? await Workspace.getWithUser(user, { slug }) : await Workspace.get({ slug }); response.status(200).json({ workspace }); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); } } ); app.get( "/workspace/:slug/chats", [validatedRequest, flexUserRoleValid([ROLES.all])], async (request, response) => { try { const { slug } = request.params; const user = await userFromSession(request, response); const workspace = multiUserMode(response) ? await Workspace.getWithUser(user, { slug }) : await Workspace.get({ slug }); if (!workspace) { response.sendStatus(400).end(); return; } const history = multiUserMode(response) ? await WorkspaceChats.forWorkspaceByUser(workspace.id, user.id) : await WorkspaceChats.forWorkspace(workspace.id); response.status(200).json({ history: convertToChatHistory(history) }); } catch (e) { console.log(e.message, e); response.sendStatus(500).end(); } } ); app.post( "/workspace/:slug/chat-feedback/:chatId", [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], async (request, response) => { try { const { chatId } = request.params; const { feedback = null } = reqBody(request); const existingChat = await WorkspaceChats.get({ id: Number(chatId), workspaceId: response.locals.workspace.id, }); if (!existingChat) { response.status(404).end(); return; } const result = await WorkspaceChats.updateFeedbackScore( chatId, feedback ); response.status(200).json({ success: result }); } catch (error) { console.error("Error updating chat feedback:", error); response.status(500).end(); } } ); app.get( "/workspace/:slug/suggested-messages", [validatedRequest, flexUserRoleValid([ROLES.all])], async function (request, response) { try { const { slug } = request.params; const suggestedMessages = await WorkspaceSuggestedMessages.getMessages(slug); response.status(200).json({ success: true, suggestedMessages }); } catch (error) { console.error("Error fetching suggested messages:", error); response .status(500) .json({ success: false, message: "Internal server error" }); } } ); app.post( "/workspace/:slug/suggested-messages", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async (request, response) => { try { const { messages = [] } = reqBody(request); const { slug } = request.params; if (!Array.isArray(messages)) { return response.status(400).json({ success: false, message: "Invalid message format. Expected an array of messages.", }); } await WorkspaceSuggestedMessages.saveAll(messages, slug); return response.status(200).json({ success: true, message: "Suggested messages saved successfully.", }); } catch (error) { console.error("Error processing the suggested messages:", error); response.status(500).json({ success: true, message: "Error saving the suggested messages.", }); } } ); app.post( "/workspace/:slug/update-pin", [ validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager]), validWorkspaceSlug, ], async (request, response) => { try { const { docPath, pinStatus = false } = reqBody(request); const workspace = response.locals.workspace; const document = await Document.get({ workspaceId: workspace.id, docpath: docPath, }); if (!document) return response.sendStatus(404).end(); await Document.update(document.id, { pinned: pinStatus }); return response.status(200).end(); } catch (error) { console.error("Error processing the pin status update:", error); return response.status(500).end(); } } ); app.get( "/workspace/:slug/pfp", [validatedRequest, flexUserRoleValid([ROLES.all])], async function (request, response) { try { const { slug } = request.params; const cachedResponse = responseCache.get(slug); if (cachedResponse) { response.writeHead(200, { "Content-Type": cachedResponse.mime || "image/png", }); response.end(cachedResponse.buffer); return; } const pfpPath = await determineWorkspacePfpFilepath(slug); if (!pfpPath) { response.sendStatus(204).end(); return; } const { found, buffer, mime } = fetchPfp(pfpPath); if (!found) { response.sendStatus(204).end(); return; } responseCache.set(slug, { buffer, mime }); response.writeHead(200, { "Content-Type": mime || "image/png", }); response.end(buffer); return; } catch (error) { console.error("Error processing the logo request:", error); response.status(500).json({ message: "Internal server error" }); } } ); app.post( "/workspace/:slug/upload-pfp", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], handlePfpUploads.single("file"), async function (request, response) { try { const { slug } = request.params; const uploadedFileName = request.randomFileName; if (!uploadedFileName) { return response.status(400).json({ message: "File upload failed." }); } const workspaceRecord = await Workspace.get({ slug, }); const oldPfpFilename = workspaceRecord.pfpFilename; if (oldPfpFilename) { const oldPfpPath = path.join( __dirname, `../storage/assets/pfp/${normalizePath( workspaceRecord.pfpFilename )}` ); if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); } const { workspace, message } = await Workspace.update( workspaceRecord.id, { pfpFilename: uploadedFileName, } ); return response.status(workspace ? 200 : 500).json({ message: workspace ? "Profile picture uploaded successfully." : message, }); } catch (error) { console.error("Error processing the profile picture upload:", error); response.status(500).json({ message: "Internal server error" }); } } ); app.delete( "/workspace/:slug/remove-pfp", [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], async function (request, response) { try { const { slug } = request.params; const workspaceRecord = await Workspace.get({ slug, }); const oldPfpFilename = workspaceRecord.pfpFilename; if (oldPfpFilename) { const oldPfpPath = path.join( __dirname, `../storage/assets/pfp/${normalizePath(oldPfpFilename)}` ); if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); } const { workspace, message } = await Workspace.update( workspaceRecord.id, { pfpFilename: null, } ); // Clear the cache responseCache.delete(slug); return response.status(workspace ? 200 : 500).json({ message: workspace ? "Profile picture removed successfully." : message, }); } catch (error) { console.error("Error processing the profile picture removal:", error); response.status(500).json({ message: "Internal server error" }); } } ); } const metaResponseDefaultSettings = { inputs: { isEnabled: false, config: { systemPrompt: { isEnabled: false, content: "", openAiPrompt: "", overrideSystemPrompt: false, suggestionsList: [ { title: "", content: "", }, ], canEdit: ["admin", "manager"], }, promptSchema: { content: "", suggestionsList: [ { title: "", content: "", }, ], overrideWorkspacePrompt: false, canEdit: ["admin", "manager"], }, components: { dropDownMenu: { isEnabled: false, options: [], }, optionsList: { isEnabled: false, options: [], }, optionsButtons: { isEnabled: false, options: [], }, multiSelectCheckboxes: { isEnabled: false, options: [], }, }, }, permissions: ["user"], description: "Traditionally, interaction with AnythingLLM occurs through a text area. Meta Inputs enhance this by offering alternative interaction methods, including option buttons, multi-select checkboxes, sliders, drop-down menus, and date/time selectors. To utilize these components, you'll need to guide the LLM on incorporating them into its responses with a specific schema", }, sentiments: { isEnabled: false, config: { systemPrompt: { isEnabled: false, content: "", openAiPrompt: "", overrideSystemPrompt: false, suggestionsList: [ { title: "", content: "", }, ], canEdit: ["admin", "manager"], }, promptSchema: { content: "", suggestionsList: [ { title: "", content: "", }, ], overrideWorkspacePrompt: false, canEdit: ["admin", "manager"], }, components: { dropDownMenu: { isEnabled: false, options: [], }, optionsList: { isEnabled: false, options: [], }, optionsButtons: { isEnabled: false, options: [], }, multiSelectCheckboxes: { isEnabled: false, options: [], }, }, }, permissions: ["user"], description: "Activate to enable the AI to analyze and adapt its responses based on the emotional tone of the conversation, enhancing interaction personalization", }, avatars: { isEnabled: false, config: { systemPrompt: { isEnabled: false, content: "", openAiPrompt: "", overrideSystemPrompt: false, suggestionsList: [ { title: "", content: "", }, ], canEdit: ["admin", "manager"], }, promptSchema: { content: "", suggestionsList: [ { title: "", content: "", }, ], overrideWorkspacePrompt: false, canEdit: ["admin", "manager"], }, components: { dropDownMenu: { isEnabled: false, options: [], }, optionsList: { isEnabled: false, options: [], }, optionsButtons: { isEnabled: false, options: [], }, multiSelectCheckboxes: { isEnabled: false, options: [], }, }, }, permissions: ["user"], description: "Enable avatars to reflect user sentiments, allowing the AI to visually empathize and convey understanding through changes in its profile image based on the meta object's sentiment data.", }, }; module.exports = { workspaceEndpoints };