const prisma = require("../utils/prisma"); const slugifyModule = require("slugify"); const { v4: uuidv4 } = require("uuid"); const WorkspaceThread = { defaultName: "Thread", writable: ["name"], /** * The default Slugify module requires some additional mapping to prevent downstream issues * if the user is able to define a slug externally. We have to block non-escapable URL chars * so that is the slug is rendered it doesn't break the URL or UI when visited. * @param {...any} args - slugify args for npm package. * @returns {string} */ slugify: function (...args) { slugifyModule.extend({ "+": " plus ", "!": " bang ", "@": " at ", "*": " splat ", ".": " dot ", ":": "", "~": "", "(": "", ")": "", "'": "", '"': "", "|": "", }); return slugifyModule(...args); }, new: async function (workspace, userId = null, data = {}) { try { const thread = await prisma.workspace_threads.create({ data: { name: data.name ? String(data.name) : this.defaultName, slug: data.slug ? this.slugify(data.slug, { lowercase: true }) : uuidv4(), user_id: userId ? Number(userId) : null, workspace_id: workspace.id, }, }); return { thread, message: null }; } catch (error) { console.error(error.message); return { thread: null, message: error.message }; } }, update: async function (prevThread = null, data = {}) { if (!prevThread) throw new Error("No thread id provided for update"); const validData = {}; Object.entries(data).forEach(([key, value]) => { if (!this.writable.includes(key)) return; validData[key] = value; }); if (Object.keys(validData).length === 0) return { thread: prevThread, message: "No valid fields to update!" }; try { const thread = await prisma.workspace_threads.update({ where: { id: prevThread.id }, data: validData, }); return { thread, message: null }; } catch (error) { console.error(error.message); return { thread: null, message: error.message }; } }, get: async function (clause = {}) { try { const thread = await prisma.workspace_threads.findFirst({ where: clause, }); return thread || null; } catch (error) { console.error(error.message); return null; } }, delete: async function (clause = {}) { try { await prisma.workspace_threads.deleteMany({ where: clause, }); return true; } catch (error) { console.error(error.message); return false; } }, where: async function (clause = {}, limit = null, orderBy = null) { try { const results = await prisma.workspace_threads.findMany({ where: clause, ...(limit !== null ? { take: limit } : {}), ...(orderBy !== null ? { orderBy } : {}), }); return results; } catch (error) { console.error(error.message); return []; } }, // Will fire on first message (included or not) for a thread and rename the thread with the newName prop. autoRenameThread: async function ({ workspace = null, thread = null, user = null, newName = null, onRename = null, }) { if (!workspace || !thread || !newName) return false; if (thread.name !== this.defaultName) return false; // don't rename if already named. const { WorkspaceChats } = require("./workspaceChats"); const chatCount = await WorkspaceChats.count({ workspaceId: workspace.id, user_id: user?.id || null, thread_id: thread.id, }); if (chatCount !== 1) return { renamed: false, thread }; const { thread: updatedThread } = await this.update(thread, { name: newName, }); onRename?.(updatedThread); return true; }, }; module.exports = { WorkspaceThread };