| const path = require("path"); |
| const fs = require("fs"); |
| const { |
| reqBody, |
| multiUserMode, |
| userFromSession, |
| safeJsonParse, |
| } = require("../utils/http"); |
| const { normalizePath, isWithin } = require("../utils/files"); |
| 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 { handleFileUpload, handlePfpUpload } = 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 { |
| determineWorkspacePfpFilepath, |
| fetchPfp, |
| } = require("../utils/files/pfp"); |
| const { getTTSProvider } = require("../utils/TextToSpeech"); |
| const { WorkspaceThread } = require("../models/workspaceThread"); |
|
|
| const truncate = require("truncate"); |
| const { purgeDocument } = require("../utils/files/purgeDocument"); |
| const { getModelTag } = require("./utils"); |
| const { searchWorkspaceAndThreads } = require("../utils/helpers/search"); |
| const { workspaceParsedFilesEndpoints } = require("./workspacesParsedFiles"); |
|
|
| 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 || "lancedb", |
| TTSSelection: process.env.TTS_PROVIDER || "native", |
| LLMModel: getModelTag(), |
| }, |
| 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.error(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; |
| } |
|
|
| await Workspace.trackChange(currWorkspace, data, user); |
| const { workspace, message } = await Workspace.update( |
| currWorkspace.id, |
| data |
| ); |
| response.status(200).json({ workspace, message }); |
| } catch (e) { |
| console.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.post( |
| "/workspace/:slug/upload", |
| [ |
| validatedRequest, |
| flexUserRoleValid([ROLES.admin, ROLES.manager]), |
| handleFileUpload, |
| ], |
| async function (request, response) { |
| try { |
| 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 }); |
| } catch (e) { |
| console.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.post( |
| "/workspace/:slug/upload-link", |
| [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])], |
| async (request, response) => { |
| try { |
| 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 }); |
| } catch (e) { |
| console.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| 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.error(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.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.delete( |
| "/workspace/:slug/reset-vector-db", |
| [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 DocumentVectors.deleteForWorkspace(workspace.id); |
| await Document.delete({ workspaceId: Number(workspace.id) }); |
|
|
| await EventLogs.logEvent( |
| "workspace_vectors_reset", |
| { |
| 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.error(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.error(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.error(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.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.delete( |
| "/workspace/:slug/delete-chats", |
| [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], |
| async (request, response) => { |
| try { |
| const { chatIds = [] } = reqBody(request); |
| const user = await userFromSession(request, response); |
| const workspace = response.locals.workspace; |
|
|
| if (!workspace || !Array.isArray(chatIds)) { |
| response.sendStatus(400).end(); |
| return; |
| } |
|
|
| |
| |
| |
| await WorkspaceChats.delete({ |
| id: { in: chatIds.map((id) => Number(id)) }, |
| user_id: user?.id ?? null, |
| workspaceId: workspace.id, |
| }); |
|
|
| response.sendStatus(200).end(); |
| } catch (e) { |
| console.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.delete( |
| "/workspace/:slug/delete-edited-chats", |
| [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], |
| async (request, response) => { |
| try { |
| const { startingId } = reqBody(request); |
| const user = await userFromSession(request, response); |
| const workspace = response.locals.workspace; |
|
|
| await WorkspaceChats.delete({ |
| workspaceId: workspace.id, |
| thread_id: null, |
| user_id: user?.id, |
| id: { gte: Number(startingId) }, |
| }); |
|
|
| response.sendStatus(200).end(); |
| } catch (e) { |
| console.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.post( |
| "/workspace/:slug/update-chat", |
| [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], |
| async (request, response) => { |
| try { |
| const { chatId, newText = null } = reqBody(request); |
| if (!newText || !String(newText).trim()) |
| throw new Error("Cannot save empty response"); |
|
|
| const user = await userFromSession(request, response); |
| const workspace = response.locals.workspace; |
| const existingChat = await WorkspaceChats.get({ |
| workspaceId: workspace.id, |
| thread_id: null, |
| user_id: user?.id, |
| id: Number(chatId), |
| }); |
| if (!existingChat) throw new Error("Invalid chat."); |
|
|
| const chatResponse = safeJsonParse(existingChat.response, null); |
| if (!chatResponse) throw new Error("Failed to parse chat response"); |
|
|
| await WorkspaceChats._update(existingChat.id, { |
| response: JSON.stringify({ |
| ...chatResponse, |
| text: String(newText), |
| }), |
| }); |
|
|
| response.sendStatus(200).end(); |
| } catch (e) { |
| console.error(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/tts/:chatId", |
| [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], |
| async function (request, response) { |
| try { |
| const { chatId } = request.params; |
| const workspace = response.locals.workspace; |
| const cacheKey = `${workspace.slug}:${chatId}`; |
| const wsChat = await WorkspaceChats.get({ |
| id: Number(chatId), |
| workspaceId: workspace.id, |
| }); |
|
|
| const cachedResponse = responseCache.get(cacheKey); |
| if (cachedResponse) { |
| response.writeHead(200, { |
| "Content-Type": cachedResponse.mime || "audio/mpeg", |
| }); |
| response.end(cachedResponse.buffer); |
| return; |
| } |
|
|
| const text = safeJsonParse(wsChat.response, null)?.text; |
| if (!text) return response.sendStatus(204).end(); |
|
|
| const TTSProvider = getTTSProvider(); |
| const buffer = await TTSProvider.ttsBuffer(text); |
| if (buffer === null) return response.sendStatus(204).end(); |
|
|
| responseCache.set(cacheKey, { buffer, mime: "audio/mpeg" }); |
| response.writeHead(200, { |
| "Content-Type": "audio/mpeg", |
| }); |
| response.end(buffer); |
| return; |
| } catch (error) { |
| console.error("Error processing the TTS request:", error); |
| response.status(500).json({ message: "TTS could not be completed" }); |
| } |
| } |
| ); |
|
|
| 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]), |
| handlePfpUpload, |
| ], |
| 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 storagePath = path.join(__dirname, "../storage/assets/pfp"); |
| const oldPfpPath = path.join( |
| storagePath, |
| normalizePath(workspaceRecord.pfpFilename) |
| ); |
| if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath))) |
| throw new Error("Invalid path name"); |
| 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 storagePath = path.join(__dirname, "../storage/assets/pfp"); |
| const oldPfpPath = path.join( |
| storagePath, |
| normalizePath(oldPfpFilename) |
| ); |
| if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath))) |
| throw new Error("Invalid path name"); |
| if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath); |
| } |
|
|
| const { workspace, message } = await Workspace._update( |
| workspaceRecord.id, |
| { |
| pfpFilename: null, |
| } |
| ); |
|
|
| |
| 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" }); |
| } |
| } |
| ); |
|
|
| app.post( |
| "/workspace/:slug/thread/fork", |
| [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], |
| async (request, response) => { |
| try { |
| const user = await userFromSession(request, response); |
| const workspace = response.locals.workspace; |
| const { chatId, threadSlug } = reqBody(request); |
| if (!chatId) |
| return response.status(400).json({ message: "chatId is required" }); |
|
|
| |
| |
| const threadId = !!threadSlug |
| ? ( |
| await WorkspaceThread.get({ |
| slug: String(threadSlug), |
| workspace_id: workspace.id, |
| }) |
| )?.id ?? null |
| : null; |
| const chatsToFork = await WorkspaceChats.where( |
| { |
| workspaceId: workspace.id, |
| user_id: user?.id, |
| include: true, |
| thread_id: threadId, |
| api_session_id: null, |
| id: { lte: Number(chatId) }, |
| }, |
| null, |
| { id: "asc" } |
| ); |
|
|
| const { thread: newThread, message: threadError } = |
| await WorkspaceThread.new(workspace, user?.id); |
| if (threadError) |
| return response.status(500).json({ error: threadError }); |
|
|
| let lastMessageText = ""; |
| const chatsData = chatsToFork.map((chat) => { |
| const chatResponse = safeJsonParse(chat.response, {}); |
| if (chatResponse?.text) lastMessageText = chatResponse.text; |
|
|
| return { |
| workspaceId: workspace.id, |
| prompt: chat.prompt, |
| response: JSON.stringify(chatResponse), |
| user_id: user?.id, |
| thread_id: newThread.id, |
| }; |
| }); |
| await WorkspaceChats.bulkCreate(chatsData); |
| await WorkspaceThread.update(newThread, { |
| name: !!lastMessageText |
| ? truncate(lastMessageText, 22) |
| : "Forked Thread", |
| }); |
|
|
| await EventLogs.logEvent( |
| "thread_forked", |
| { |
| workspaceName: workspace?.name || "Unknown Workspace", |
| threadName: newThread.name, |
| }, |
| user?.id |
| ); |
| response.status(200).json({ newThreadSlug: newThread.slug }); |
| } catch (e) { |
| console.error(e.message, e); |
| response.status(500).json({ message: "Internal server error" }); |
| } |
| } |
| ); |
|
|
| app.put( |
| "/workspace/workspace-chats/:id", |
| [validatedRequest, flexUserRoleValid([ROLES.all])], |
| async (request, response) => { |
| try { |
| const { id } = request.params; |
| const user = await userFromSession(request, response); |
| const validChat = await WorkspaceChats.get({ |
| id: Number(id), |
| user_id: user?.id ?? null, |
| }); |
| if (!validChat) |
| return response |
| .status(404) |
| .json({ success: false, error: "Chat not found." }); |
|
|
| await WorkspaceChats._update(validChat.id, { include: false }); |
| response.json({ success: true, error: null }); |
| } catch (e) { |
| console.error(e.message, e); |
| response.status(500).json({ success: false, error: "Server error" }); |
| } |
| } |
| ); |
|
|
| |
| app.post( |
| "/workspace/:slug/upload-and-embed", |
| [ |
| validatedRequest, |
| flexUserRoleValid([ROLES.admin, ROLES.manager]), |
| handleFileUpload, |
| ], |
| async function (request, response) { |
| try { |
| const { slug = null } = request.params; |
| const user = await userFromSession(request, response); |
| const currWorkspace = multiUserMode(response) |
| ? await Workspace.getWithUser(user, { slug }) |
| : await Workspace.get({ slug }); |
|
|
| if (!currWorkspace) { |
| response.sendStatus(400).end(); |
| return; |
| } |
|
|
| 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, documents } = |
| await Collector.processDocument(originalname); |
| if (!success || documents?.length === 0) { |
| 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 |
| ); |
|
|
| const document = documents[0]; |
| const { failedToEmbed = [], errors = [] } = await Document.addDocuments( |
| currWorkspace, |
| [document.location], |
| response.locals?.user?.id |
| ); |
|
|
| if (failedToEmbed.length > 0) |
| return response |
| .status(200) |
| .json({ success: false, error: errors?.[0], document: null }); |
|
|
| response.status(200).json({ |
| success: true, |
| error: null, |
| document: { id: document.id, location: document.location }, |
| }); |
| } catch (e) { |
| console.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.delete( |
| "/workspace/:slug/remove-and-unembed", |
| [ |
| validatedRequest, |
| flexUserRoleValid([ROLES.admin, ROLES.manager]), |
| handleFileUpload, |
| ], |
| async function (request, response) { |
| try { |
| const { slug = null } = request.params; |
| const body = reqBody(request); |
| const user = await userFromSession(request, response); |
| const currWorkspace = multiUserMode(response) |
| ? await Workspace.getWithUser(user, { slug }) |
| : await Workspace.get({ slug }); |
|
|
| if (!currWorkspace || !body.documentLocation) |
| return response.sendStatus(400).end(); |
|
|
| |
| await purgeDocument(body.documentLocation); |
| response.status(200).end(); |
| } catch (e) { |
| console.error(e.message, e); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.get( |
| "/workspace/:slug/prompt-history", |
| [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug], |
| async (_, response) => { |
| try { |
| response.status(200).json({ |
| history: await Workspace.promptHistory({ |
| workspaceId: response.locals.workspace.id, |
| }), |
| }); |
| } catch (error) { |
| console.error("Error fetching prompt history:", error); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.delete( |
| "/workspace/:slug/prompt-history", |
| [ |
| validatedRequest, |
| flexUserRoleValid([ROLES.admin, ROLES.manager]), |
| validWorkspaceSlug, |
| ], |
| async (_, response) => { |
| try { |
| response.status(200).json({ |
| success: await Workspace.deleteAllPromptHistory({ |
| workspaceId: response.locals.workspace.id, |
| }), |
| }); |
| } catch (error) { |
| console.error("Error clearing prompt history:", error); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| app.delete( |
| "/workspace/prompt-history/:id", |
| [ |
| validatedRequest, |
| flexUserRoleValid([ROLES.admin, ROLES.manager]), |
| validWorkspaceSlug, |
| ], |
| async (request, response) => { |
| try { |
| const { id } = request.params; |
| response.status(200).json({ |
| success: await Workspace.deletePromptHistory({ |
| workspaceId: response.locals.workspace.id, |
| id: Number(id), |
| }), |
| }); |
| } catch (error) { |
| console.error("Error deleting prompt history:", error); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| |
| |
| |
| |
| app.post( |
| "/workspace/search", |
| [validatedRequest, flexUserRoleValid([ROLES.all])], |
| async (request, response) => { |
| try { |
| const { searchTerm } = reqBody(request); |
| const searchResults = await searchWorkspaceAndThreads( |
| searchTerm, |
| response.locals?.user |
| ); |
| response.status(200).json(searchResults); |
| } catch (error) { |
| console.error("Error searching for workspaces:", error); |
| response.sendStatus(500).end(); |
| } |
| } |
| ); |
|
|
| |
| workspaceParsedFilesEndpoints(app); |
| } |
|
|
| module.exports = { workspaceEndpoints }; |
|
|