You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
7.3 KiB

11 months ago
  1. const {
  2. multiUserMode,
  3. userFromSession,
  4. reqBody,
  5. safeJsonParse,
  6. } = require("../utils/http");
  7. const { validatedRequest } = require("../utils/middleware/validatedRequest");
  8. const { Telemetry } = require("../models/telemetry");
  9. const {
  10. flexUserRoleValid,
  11. ROLES,
  12. } = require("../utils/middleware/multiUserProtected");
  13. const { EventLogs } = require("../models/eventLogs");
  14. const { WorkspaceThread } = require("../models/workspaceThread");
  15. const {
  16. validWorkspaceSlug,
  17. validWorkspaceAndThreadSlug,
  18. } = require("../utils/middleware/validWorkspace");
  19. const { WorkspaceChats } = require("../models/workspaceChats");
  20. const { convertToChatHistory } = require("../utils/helpers/chat/responses");
  21. function workspaceThreadEndpoints(app) {
  22. if (!app) return;
  23. app.post(
  24. "/workspace/:slug/thread/new",
  25. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  26. async (request, response) => {
  27. try {
  28. const user = await userFromSession(request, response);
  29. const workspace = response.locals.workspace;
  30. const { thread, message } = await WorkspaceThread.new(
  31. workspace,
  32. user?.id
  33. );
  34. await Telemetry.sendTelemetry(
  35. "workspace_thread_created",
  36. {
  37. multiUserMode: multiUserMode(response),
  38. LLMSelection: process.env.LLM_PROVIDER || "openai",
  39. Embedder: process.env.EMBEDDING_ENGINE || "inherit",
  40. VectorDbSelection: process.env.VECTOR_DB || "lancedb",
  41. TTSSelection: process.env.TTS_PROVIDER || "native",
  42. },
  43. user?.id
  44. );
  45. await EventLogs.logEvent(
  46. "workspace_thread_created",
  47. {
  48. workspaceName: workspace?.name || "Unknown Workspace",
  49. },
  50. user?.id
  51. );
  52. response.status(200).json({ thread, message });
  53. } catch (e) {
  54. console.error(e.message, e);
  55. response.sendStatus(500).end();
  56. }
  57. }
  58. );
  59. app.get(
  60. "/workspace/:slug/threads",
  61. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  62. async (request, response) => {
  63. try {
  64. const user = await userFromSession(request, response);
  65. const workspace = response.locals.workspace;
  66. const threads = await WorkspaceThread.where({
  67. workspace_id: workspace.id,
  68. user_id: user?.id || null,
  69. });
  70. response.status(200).json({ threads });
  71. } catch (e) {
  72. console.error(e.message, e);
  73. response.sendStatus(500).end();
  74. }
  75. }
  76. );
  77. app.delete(
  78. "/workspace/:slug/thread/:threadSlug",
  79. [
  80. validatedRequest,
  81. flexUserRoleValid([ROLES.all]),
  82. validWorkspaceAndThreadSlug,
  83. ],
  84. async (_, response) => {
  85. try {
  86. const thread = response.locals.thread;
  87. await WorkspaceThread.delete({ id: thread.id });
  88. response.sendStatus(200).end();
  89. } catch (e) {
  90. console.error(e.message, e);
  91. response.sendStatus(500).end();
  92. }
  93. }
  94. );
  95. app.delete(
  96. "/workspace/:slug/thread-bulk-delete",
  97. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  98. async (request, response) => {
  99. try {
  100. const { slugs = [] } = reqBody(request);
  101. if (slugs.length === 0) return response.sendStatus(200).end();
  102. const user = await userFromSession(request, response);
  103. const workspace = response.locals.workspace;
  104. await WorkspaceThread.delete({
  105. slug: { in: slugs },
  106. user_id: user?.id ?? null,
  107. workspace_id: workspace.id,
  108. });
  109. response.sendStatus(200).end();
  110. } catch (e) {
  111. console.error(e.message, e);
  112. response.sendStatus(500).end();
  113. }
  114. }
  115. );
  116. app.get(
  117. "/workspace/:slug/thread/:threadSlug/chats",
  118. [
  119. validatedRequest,
  120. flexUserRoleValid([ROLES.all]),
  121. validWorkspaceAndThreadSlug,
  122. ],
  123. async (request, response) => {
  124. try {
  125. const user = await userFromSession(request, response);
  126. const workspace = response.locals.workspace;
  127. const thread = response.locals.thread;
  128. const history = await WorkspaceChats.where(
  129. {
  130. workspaceId: workspace.id,
  131. user_id: user?.id || null,
  132. thread_id: thread.id,
  133. api_session_id: null, // Do not include API session chats.
  134. include: true,
  135. },
  136. null,
  137. { id: "asc" }
  138. );
  139. response.status(200).json({ history: convertToChatHistory(history) });
  140. } catch (e) {
  141. console.error(e.message, e);
  142. response.sendStatus(500).end();
  143. }
  144. }
  145. );
  146. app.post(
  147. "/workspace/:slug/thread/:threadSlug/update",
  148. [
  149. validatedRequest,
  150. flexUserRoleValid([ROLES.all]),
  151. validWorkspaceAndThreadSlug,
  152. ],
  153. async (request, response) => {
  154. try {
  155. const data = reqBody(request);
  156. const currentThread = response.locals.thread;
  157. const { thread, message } = await WorkspaceThread.update(
  158. currentThread,
  159. data
  160. );
  161. response.status(200).json({ thread, message });
  162. } catch (e) {
  163. console.error(e.message, e);
  164. response.sendStatus(500).end();
  165. }
  166. }
  167. );
  168. app.delete(
  169. "/workspace/:slug/thread/:threadSlug/delete-edited-chats",
  170. [
  171. validatedRequest,
  172. flexUserRoleValid([ROLES.all]),
  173. validWorkspaceAndThreadSlug,
  174. ],
  175. async (request, response) => {
  176. try {
  177. const { startingId } = reqBody(request);
  178. const user = await userFromSession(request, response);
  179. const workspace = response.locals.workspace;
  180. const thread = response.locals.thread;
  181. await WorkspaceChats.delete({
  182. workspaceId: Number(workspace.id),
  183. thread_id: Number(thread.id),
  184. user_id: user?.id,
  185. id: { gte: Number(startingId) },
  186. });
  187. response.sendStatus(200).end();
  188. } catch (e) {
  189. console.error(e.message, e);
  190. response.sendStatus(500).end();
  191. }
  192. }
  193. );
  194. app.post(
  195. "/workspace/:slug/thread/:threadSlug/update-chat",
  196. [
  197. validatedRequest,
  198. flexUserRoleValid([ROLES.all]),
  199. validWorkspaceAndThreadSlug,
  200. ],
  201. async (request, response) => {
  202. try {
  203. const { chatId, newText = null } = reqBody(request);
  204. if (!newText || !String(newText).trim())
  205. throw new Error("Cannot save empty response");
  206. const user = await userFromSession(request, response);
  207. const workspace = response.locals.workspace;
  208. const thread = response.locals.thread;
  209. const existingChat = await WorkspaceChats.get({
  210. workspaceId: workspace.id,
  211. thread_id: thread.id,
  212. user_id: user?.id,
  213. id: Number(chatId),
  214. });
  215. if (!existingChat) throw new Error("Invalid chat.");
  216. const chatResponse = safeJsonParse(existingChat.response, null);
  217. if (!chatResponse) throw new Error("Failed to parse chat response");
  218. await WorkspaceChats._update(existingChat.id, {
  219. response: JSON.stringify({
  220. ...chatResponse,
  221. text: String(newText),
  222. }),
  223. });
  224. response.sendStatus(200).end();
  225. } catch (e) {
  226. console.error(e.message, e);
  227. response.sendStatus(500).end();
  228. }
  229. }
  230. );
  231. }
  232. module.exports = { workspaceThreadEndpoints };