diff --git a/.vscode/settings.json b/.vscode/settings.json index 1409c10739..8405c5281d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -50,6 +50,7 @@ "textgenwebui", "togetherai", "Unembed", + "uuidv", "vectordbs", "Weaviate", "Zilliz" diff --git a/server/endpoints/api/workspaceThread/index.js b/server/endpoints/api/workspaceThread/index.js index e7f53698ad..0d6eb59c67 100644 --- a/server/endpoints/api/workspaceThread/index.js +++ b/server/endpoints/api/workspaceThread/index.js @@ -31,12 +31,14 @@ function apiWorkspaceThreadEndpoints(app) { type: 'string' } #swagger.requestBody = { - description: 'Optional userId associated with the thread', + description: 'Optional userId associated with the thread, thread slug and thread name', required: false, content: { "application/json": { example: { - userId: 1 + userId: 1, + name: 'Name', + slug: 'thread-slug' } } } @@ -67,9 +69,9 @@ function apiWorkspaceThreadEndpoints(app) { } */ try { - const { slug } = request.params; - let { userId = null } = reqBody(request); - const workspace = await Workspace.get({ slug }); + const wslug = request.params.slug; + let { userId = null, name = null, slug = null } = reqBody(request); + const workspace = await Workspace.get({ slug: wslug }); if (!workspace) { response.sendStatus(400).end(); @@ -83,7 +85,8 @@ function apiWorkspaceThreadEndpoints(app) { const { thread, message } = await WorkspaceThread.new( workspace, - userId ? Number(userId) : null + userId ? Number(userId) : null, + { name, slug } ); await Telemetry.sendTelemetry("workspace_thread_created", { diff --git a/server/models/workspaceThread.js b/server/models/workspaceThread.js index 32e9f89b68..1ac6040cdf 100644 --- a/server/models/workspaceThread.js +++ b/server/models/workspaceThread.js @@ -1,16 +1,44 @@ const prisma = require("../utils/prisma"); +const slugifyModule = require("slugify"); const { v4: uuidv4 } = require("uuid"); const WorkspaceThread = { defaultName: "Thread", writable: ["name"], - new: async function (workspace, userId = null) { + /** + * 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: this.defaultName, - slug: uuidv4(), + 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, }, diff --git a/server/swagger/openapi.json b/server/swagger/openapi.json index 07955bc35d..b12fbf5359 100644 --- a/server/swagger/openapi.json +++ b/server/swagger/openapi.json @@ -2391,12 +2391,14 @@ } }, "requestBody": { - "description": "Optional userId associated with the thread", + "description": "Optional userId associated with the thread, thread slug and thread name", "required": false, "content": { "application/json": { "example": { - "userId": 1 + "userId": 1, + "name": "Name", + "slug": "thread-slug" } } }