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.

1066 lines
34 KiB

11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
11 months ago
  1. const path = require("path");
  2. const fs = require("fs");
  3. const {
  4. reqBody,
  5. multiUserMode,
  6. userFromSession,
  7. safeJsonParse,
  8. } = require("../utils/http");
  9. const { normalizePath, isWithin } = require("../utils/files");
  10. const { Workspace } = require("../models/workspace");
  11. const { Document } = require("../models/documents");
  12. const { DocumentVectors } = require("../models/vectors");
  13. const { WorkspaceChats } = require("../models/workspaceChats");
  14. const { getVectorDbClass } = require("../utils/helpers");
  15. const { handleFileUpload, handlePfpUpload, handleCommUpload } = require("../utils/files/multer");
  16. const { validatedRequest } = require("../utils/middleware/validatedRequest");
  17. const { Telemetry } = require("../models/telemetry");
  18. const {
  19. flexUserRoleValid,
  20. ROLES,
  21. } = require("../utils/middleware/multiUserProtected");
  22. const { EventLogs } = require("../models/eventLogs");
  23. const {
  24. WorkspaceSuggestedMessages,
  25. } = require("../models/workspacesSuggestedMessages");
  26. const { validWorkspaceSlug } = require("../utils/middleware/validWorkspace");
  27. const { convertToChatHistory } = require("../utils/helpers/chat/responses");
  28. const { CollectorApi } = require("../utils/collectorApi");
  29. const {
  30. determineWorkspacePfpFilepath,
  31. fetchPfp,
  32. } = require("../utils/files/pfp");
  33. const { getTTSProvider } = require("../utils/TextToSpeech");
  34. const { WorkspaceThread } = require("../models/workspaceThread");
  35. const truncate = require("truncate");
  36. const { purgeDocument } = require("../utils/files/purgeDocument");
  37. const { User } = require("../models/user");
  38. const { DeptUsers } = require("../models/deptUsers");
  39. const { DeptDocument } = require("../models/deptDocument");
  40. const { v4: uuidv4 } = require("uuid");
  41. const { moveAndRenameFile } = require("../utils/files/index");
  42. function workspaceEndpoints(app) {
  43. if (!app) return;
  44. const responseCache = new Map();
  45. app.post(
  46. "/workspace/new",
  47. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  48. async (request, response) => {
  49. try {
  50. const user = await userFromSession(request, response);
  51. const { name = null, onboardingComplete = false } = reqBody(request);
  52. const { workspace, message } = await Workspace.new(name, user?.id);
  53. await Telemetry.sendTelemetry(
  54. "workspace_created",
  55. {
  56. multiUserMode: multiUserMode(response),
  57. LLMSelection: process.env.LLM_PROVIDER || "openai",
  58. Embedder: process.env.EMBEDDING_ENGINE || "inherit",
  59. VectorDbSelection: process.env.VECTOR_DB || "lancedb",
  60. TTSSelection: process.env.TTS_PROVIDER || "native",
  61. },
  62. user?.id
  63. );
  64. await EventLogs.logEvent(
  65. "workspace_created",
  66. {
  67. workspaceName: workspace?.name || "Unknown Workspace",
  68. },
  69. user?.id
  70. );
  71. if (onboardingComplete === true)
  72. await Telemetry.sendTelemetry("onboarding_complete");
  73. response.status(200).json({ workspace, message });
  74. } catch (e) {
  75. console.error(e.message, e);
  76. response.sendStatus(500).end();
  77. }
  78. }
  79. );
  80. app.post(
  81. "/workspace/:slug/update",
  82. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  83. async (request, response) => {
  84. try {
  85. const user = await userFromSession(request, response);
  86. const { slug = null } = request.params;
  87. const data = reqBody(request);
  88. const currWorkspace = multiUserMode(response)
  89. ? await Workspace.getWithUser(user, { slug })
  90. : await Workspace.get({ slug });
  91. if (!currWorkspace) {
  92. response.sendStatus(400).end();
  93. return;
  94. }
  95. await Workspace.trackChange(currWorkspace, data, user);
  96. const { workspace, message } = await Workspace.update(
  97. currWorkspace.id,
  98. data
  99. );
  100. response.status(200).json({ workspace, message });
  101. } catch (e) {
  102. console.error(e.message, e);
  103. response.sendStatus(500).end();
  104. }
  105. }
  106. );
  107. // app.post(
  108. // "/workspace/:slug/upload",
  109. // [
  110. // validatedRequest,
  111. // flexUserRoleValid([ROLES.admin, ROLES.manager]),
  112. // handleFileUpload,
  113. // ],
  114. // async function (request, response) {
  115. // try {
  116. // const Collector = new CollectorApi();
  117. // const { originalname } = request.file;
  118. // const processingOnline = await Collector.online();
  119. //
  120. // if (!processingOnline) {
  121. // response
  122. // .status(500)
  123. // .json({
  124. // success: false,
  125. // error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`,
  126. // })
  127. // .end();
  128. // return;
  129. // }
  130. //
  131. // const { success, reason } =
  132. // await Collector.processDocument(originalname);
  133. // if (!success) {
  134. // response.status(500).json({ success: false, error: reason }).end();
  135. // return;
  136. // }
  137. //
  138. // Collector.log(
  139. // `Document ${originalname} uploaded processed and successfully. It is now available in documents.`
  140. // );
  141. // await Telemetry.sendTelemetry("document_uploaded");
  142. // await EventLogs.logEvent(
  143. // "document_uploaded",
  144. // {
  145. // documentName: originalname,
  146. // },
  147. // response.locals?.user?.id
  148. // );
  149. // response.status(200).json({ success: true, error: null });
  150. // } catch (e) {
  151. // console.error(e.message, e);
  152. // response.sendStatus(500).end();
  153. // }
  154. // }
  155. // );
  156. app.post(
  157. "/workspace/:slug/upload",
  158. [
  159. validatedRequest,
  160. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  161. handleFileUpload,
  162. ],
  163. async function (request, response) {
  164. try {
  165. const user = await userFromSession(request, response);
  166. const deptUserRecord = await DeptUsers.get({ userId: user.id });
  167. if (!deptUserRecord.deptUser) {
  168. return response.status(500).json({ success: false, error: "没有发现用户部门" });
  169. }
  170. const Collector = new CollectorApi();
  171. const { originalname } = request.file;
  172. const processingOnline = await Collector.online();
  173. if (!processingOnline) {
  174. response
  175. .status(500)
  176. .json({
  177. success: false,
  178. error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`,
  179. })
  180. .end();
  181. return;
  182. }
  183. const { success, reason, documents } =
  184. await Collector.processDocument(originalname);
  185. if (!success) {
  186. response.status(500).json({ success: false, error: reason }).end();
  187. return;
  188. }
  189. // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  190. // 假设路径字符串
  191. const location = documents[0].location;
  192. // 将路径中的反斜杠替换为正斜杠(可选,但通常更通用)
  193. const unixStylePath = location.replace(/\\/g, '/');
  194. // 找到最后一个目录分隔符的位置
  195. const lastIndex = unixStylePath.lastIndexOf('/');
  196. // 提取文件名
  197. const parsedFileName = unixStylePath.substring(lastIndex + 1);
  198. const fileExtension = path.extname(request.file.path).toLowerCase();
  199. const sourceFile = path.resolve(__dirname, request.file.destination, request.file.originalname);
  200. const targetDir =
  201. process.env.NODE_ENV === "development"
  202. ? path.resolve(__dirname, `../../server/storage/localFile`)
  203. : path.resolve(process.env.STORAGE_DIR, `../../server/storage/localFile`);
  204. const newFileName = uuidv4() + fileExtension; // 新文件名
  205. moveAndRenameFile(sourceFile, targetDir, newFileName);
  206. const deptDocData = {
  207. deptId: deptUserRecord.deptUser.deptId,
  208. parsedFileName: parsedFileName,
  209. parsedFilePath: location,
  210. realFileName: originalname,
  211. realFileAlias: newFileName,
  212. realFilePath: targetDir,
  213. };
  214. await DeptDocument.create(deptDocData);
  215. // +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  216. Collector.log(
  217. `Document ${originalname} uploaded processed and successfully. It is now available in documents.`
  218. );
  219. await Telemetry.sendTelemetry("document_uploaded");
  220. await EventLogs.logEvent(
  221. "document_uploaded",
  222. {
  223. documentName: originalname,
  224. },
  225. response.locals?.user?.id
  226. );
  227. response.status(200).json({ success: true, error: null });
  228. } catch (e) {
  229. console.error(e.message, e);
  230. response.sendStatus(500).end();
  231. }
  232. }
  233. );
  234. app.post(
  235. "/workspace/:slug/upload-link",
  236. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  237. async (request, response) => {
  238. try {
  239. const Collector = new CollectorApi();
  240. const { link = "" } = reqBody(request);
  241. const processingOnline = await Collector.online();
  242. if (!processingOnline) {
  243. response
  244. .status(500)
  245. .json({
  246. success: false,
  247. error: `Document processing API is not online. Link ${link} will not be processed automatically.`,
  248. })
  249. .end();
  250. return;
  251. }
  252. const { success, reason } = await Collector.processLink(link);
  253. if (!success) {
  254. response.status(500).json({ success: false, error: reason }).end();
  255. return;
  256. }
  257. Collector.log(
  258. `Link ${link} uploaded processed and successfully. It is now available in documents.`
  259. );
  260. await Telemetry.sendTelemetry("link_uploaded");
  261. await EventLogs.logEvent(
  262. "link_uploaded",
  263. { link },
  264. response.locals?.user?.id
  265. );
  266. response.status(200).json({ success: true, error: null });
  267. } catch (e) {
  268. console.error(e.message, e);
  269. response.sendStatus(500).end();
  270. }
  271. }
  272. );
  273. app.post(
  274. "/workspace/:slug/update-embeddings",
  275. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  276. async (request, response) => {
  277. try {
  278. const user = await userFromSession(request, response);
  279. const { slug = null } = request.params;
  280. const { adds = [], deletes = [] } = reqBody(request);
  281. const currWorkspace = multiUserMode(response)
  282. ? await Workspace.getWithUser(user, { slug })
  283. : await Workspace.get({ slug });
  284. if (!currWorkspace) {
  285. response.sendStatus(400).end();
  286. return;
  287. }
  288. await Document.removeDocuments(
  289. currWorkspace,
  290. deletes,
  291. response.locals?.user?.id
  292. );
  293. const { failedToEmbed = [], errors = [] } = await Document.addDocuments(
  294. currWorkspace,
  295. adds,
  296. response.locals?.user?.id
  297. );
  298. const updatedWorkspace = await Workspace.get({ id: currWorkspace.id });
  299. response.status(200).json({
  300. workspace: updatedWorkspace,
  301. message:
  302. failedToEmbed.length > 0
  303. ? `${failedToEmbed.length} documents failed to add.\n\n${errors
  304. .map((msg) => `${msg}`)
  305. .join("\n\n")}`
  306. : null,
  307. });
  308. } catch (e) {
  309. console.error(e.message, e);
  310. response.sendStatus(500).end();
  311. }
  312. }
  313. );
  314. app.delete(
  315. "/workspace/:slug",
  316. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  317. async (request, response) => {
  318. try {
  319. const { slug = "" } = request.params;
  320. const user = await userFromSession(request, response);
  321. const VectorDb = getVectorDbClass();
  322. const workspace = multiUserMode(response)
  323. ? await Workspace.getWithUser(user, { slug })
  324. : await Workspace.get({ slug });
  325. if (!workspace) {
  326. response.sendStatus(400).end();
  327. return;
  328. }
  329. await WorkspaceChats.delete({ workspaceId: Number(workspace.id) });
  330. await DocumentVectors.deleteForWorkspace(workspace.id);
  331. await Document.delete({ workspaceId: Number(workspace.id) });
  332. await Workspace.delete({ id: Number(workspace.id) });
  333. await EventLogs.logEvent(
  334. "workspace_deleted",
  335. {
  336. workspaceName: workspace?.name || "Unknown Workspace",
  337. },
  338. response.locals?.user?.id
  339. );
  340. try {
  341. await VectorDb["delete-namespace"]({ namespace: slug });
  342. } catch (e) {
  343. console.error(e.message);
  344. }
  345. response.sendStatus(200).end();
  346. } catch (e) {
  347. console.error(e.message, e);
  348. response.sendStatus(500).end();
  349. }
  350. }
  351. );
  352. app.delete(
  353. "/workspace/:slug/reset-vector-db",
  354. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  355. async (request, response) => {
  356. try {
  357. const { slug = "" } = request.params;
  358. const user = await userFromSession(request, response);
  359. const VectorDb = getVectorDbClass();
  360. const workspace = multiUserMode(response)
  361. ? await Workspace.getWithUser(user, { slug })
  362. : await Workspace.get({ slug });
  363. if (!workspace) {
  364. response.sendStatus(400).end();
  365. return;
  366. }
  367. await DocumentVectors.deleteForWorkspace(workspace.id);
  368. await Document.delete({ workspaceId: Number(workspace.id) });
  369. await EventLogs.logEvent(
  370. "workspace_vectors_reset",
  371. {
  372. workspaceName: workspace?.name || "Unknown Workspace",
  373. },
  374. response.locals?.user?.id
  375. );
  376. try {
  377. await VectorDb["delete-namespace"]({ namespace: slug });
  378. } catch (e) {
  379. console.error(e.message);
  380. }
  381. response.sendStatus(200).end();
  382. } catch (e) {
  383. console.error(e.message, e);
  384. response.sendStatus(500).end();
  385. }
  386. }
  387. );
  388. app.get(
  389. "/workspaces",
  390. [validatedRequest, flexUserRoleValid([ROLES.all])],
  391. async (request, response) => {
  392. try {
  393. const user = await userFromSession(request, response);
  394. const workspaces = multiUserMode(response)
  395. ? await Workspace.whereWithUser(user)
  396. : await Workspace.where();
  397. response.status(200).json({ workspaces });
  398. } catch (e) {
  399. console.error(e.message, e);
  400. response.sendStatus(500).end();
  401. }
  402. }
  403. );
  404. app.get(
  405. "/workspace/:slug",
  406. [validatedRequest, flexUserRoleValid([ROLES.all])],
  407. async (request, response) => {
  408. try {
  409. const { slug } = request.params;
  410. const user = await userFromSession(request, response);
  411. const workspace = multiUserMode(response)
  412. ? await Workspace.getWithUser(user, { slug })
  413. : await Workspace.get({ slug });
  414. response.status(200).json({ workspace });
  415. } catch (e) {
  416. console.error(e.message, e);
  417. response.sendStatus(500).end();
  418. }
  419. }
  420. );
  421. app.get(
  422. "/workspace/:slug/chats",
  423. [validatedRequest, flexUserRoleValid([ROLES.all])],
  424. async (request, response) => {
  425. try {
  426. const { slug } = request.params;
  427. const user = await userFromSession(request, response);
  428. const workspace = multiUserMode(response)
  429. ? await Workspace.getWithUser(user, { slug })
  430. : await Workspace.get({ slug });
  431. if (!workspace) {
  432. response.sendStatus(400).end();
  433. return;
  434. }
  435. const history = multiUserMode(response)
  436. ? await WorkspaceChats.forWorkspaceByUser(workspace.id, user.id)
  437. : await WorkspaceChats.forWorkspace(workspace.id);
  438. response.status(200).json({ history: convertToChatHistory(history) });
  439. } catch (e) {
  440. console.error(e.message, e);
  441. response.sendStatus(500).end();
  442. }
  443. }
  444. );
  445. app.delete(
  446. "/workspace/:slug/delete-chats",
  447. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  448. async (request, response) => {
  449. try {
  450. const { chatIds = [] } = reqBody(request);
  451. const user = await userFromSession(request, response);
  452. const workspace = response.locals.workspace;
  453. if (!workspace || !Array.isArray(chatIds)) {
  454. response.sendStatus(400).end();
  455. return;
  456. }
  457. // This works for both workspace and threads.
  458. // we simplify this by just looking at workspace<>user overlap
  459. // since they are all on the same table.
  460. await WorkspaceChats.delete({
  461. id: { in: chatIds.map((id) => Number(id)) },
  462. user_id: user?.id ?? null,
  463. workspaceId: workspace.id,
  464. });
  465. response.sendStatus(200).end();
  466. } catch (e) {
  467. console.error(e.message, e);
  468. response.sendStatus(500).end();
  469. }
  470. }
  471. );
  472. app.delete(
  473. "/workspace/:slug/delete-edited-chats",
  474. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  475. async (request, response) => {
  476. try {
  477. const { startingId } = reqBody(request);
  478. const user = await userFromSession(request, response);
  479. const workspace = response.locals.workspace;
  480. await WorkspaceChats.delete({
  481. workspaceId: workspace.id,
  482. thread_id: null,
  483. user_id: user?.id,
  484. id: { gte: Number(startingId) },
  485. });
  486. response.sendStatus(200).end();
  487. } catch (e) {
  488. console.error(e.message, e);
  489. response.sendStatus(500).end();
  490. }
  491. }
  492. );
  493. app.post(
  494. "/workspace/:slug/update-chat",
  495. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  496. async (request, response) => {
  497. try {
  498. const { chatId, newText = null } = reqBody(request);
  499. if (!newText || !String(newText).trim())
  500. throw new Error("Cannot save empty response");
  501. const user = await userFromSession(request, response);
  502. const workspace = response.locals.workspace;
  503. const existingChat = await WorkspaceChats.get({
  504. workspaceId: workspace.id,
  505. thread_id: null,
  506. user_id: user?.id,
  507. id: Number(chatId),
  508. });
  509. if (!existingChat) throw new Error("Invalid chat.");
  510. const chatResponse = safeJsonParse(existingChat.response, null);
  511. if (!chatResponse) throw new Error("Failed to parse chat response");
  512. await WorkspaceChats._update(existingChat.id, {
  513. response: JSON.stringify({
  514. ...chatResponse,
  515. text: String(newText),
  516. }),
  517. });
  518. response.sendStatus(200).end();
  519. } catch (e) {
  520. console.error(e.message, e);
  521. response.sendStatus(500).end();
  522. }
  523. }
  524. );
  525. app.post(
  526. "/workspace/:slug/chat-feedback/:chatId",
  527. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  528. async (request, response) => {
  529. try {
  530. const { chatId } = request.params;
  531. const { feedback = null } = reqBody(request);
  532. const existingChat = await WorkspaceChats.get({
  533. id: Number(chatId),
  534. workspaceId: response.locals.workspace.id,
  535. });
  536. if (!existingChat) {
  537. response.status(404).end();
  538. return;
  539. }
  540. const result = await WorkspaceChats.updateFeedbackScore(
  541. chatId,
  542. feedback
  543. );
  544. response.status(200).json({ success: result });
  545. } catch (error) {
  546. console.error("Error updating chat feedback:", error);
  547. response.status(500).end();
  548. }
  549. }
  550. );
  551. app.get(
  552. "/workspace/:slug/suggested-messages",
  553. [validatedRequest, flexUserRoleValid([ROLES.all])],
  554. async function (request, response) {
  555. try {
  556. const { slug } = request.params;
  557. const suggestedMessages =
  558. await WorkspaceSuggestedMessages.getMessages(slug);
  559. response.status(200).json({ success: true, suggestedMessages });
  560. } catch (error) {
  561. console.error("Error fetching suggested messages:", error);
  562. response
  563. .status(500)
  564. .json({ success: false, message: "Internal server error" });
  565. }
  566. }
  567. );
  568. app.post(
  569. "/workspace/:slug/suggested-messages",
  570. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  571. async (request, response) => {
  572. try {
  573. const { messages = [] } = reqBody(request);
  574. const { slug } = request.params;
  575. if (!Array.isArray(messages)) {
  576. return response.status(400).json({
  577. success: false,
  578. message: "Invalid message format. Expected an array of messages.",
  579. });
  580. }
  581. await WorkspaceSuggestedMessages.saveAll(messages, slug);
  582. return response.status(200).json({
  583. success: true,
  584. message: "Suggested messages saved successfully.",
  585. });
  586. } catch (error) {
  587. console.error("Error processing the suggested messages:", error);
  588. response.status(500).json({
  589. success: true,
  590. message: "Error saving the suggested messages.",
  591. });
  592. }
  593. }
  594. );
  595. app.post(
  596. "/workspace/:slug/update-pin",
  597. [
  598. validatedRequest,
  599. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  600. validWorkspaceSlug,
  601. ],
  602. async (request, response) => {
  603. try {
  604. const { docPath, pinStatus = false } = reqBody(request);
  605. const workspace = response.locals.workspace;
  606. const document = await Document.get({
  607. workspaceId: workspace.id,
  608. docpath: docPath,
  609. });
  610. if (!document) return response.sendStatus(404).end();
  611. await Document.update(document.id, { pinned: pinStatus });
  612. return response.status(200).end();
  613. } catch (error) {
  614. console.error("Error processing the pin status update:", error);
  615. return response.status(500).end();
  616. }
  617. }
  618. );
  619. app.get(
  620. "/workspace/:slug/tts/:chatId",
  621. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  622. async function (request, response) {
  623. try {
  624. const { chatId } = request.params;
  625. const workspace = response.locals.workspace;
  626. const cacheKey = `${workspace.slug}:${chatId}`;
  627. const wsChat = await WorkspaceChats.get({
  628. id: Number(chatId),
  629. workspaceId: workspace.id,
  630. });
  631. const cachedResponse = responseCache.get(cacheKey);
  632. if (cachedResponse) {
  633. response.writeHead(200, {
  634. "Content-Type": cachedResponse.mime || "audio/mpeg",
  635. });
  636. response.end(cachedResponse.buffer);
  637. return;
  638. }
  639. const text = safeJsonParse(wsChat.response, null)?.text;
  640. if (!text) return response.sendStatus(204).end();
  641. const TTSProvider = getTTSProvider();
  642. const buffer = await TTSProvider.ttsBuffer(text);
  643. if (buffer === null) return response.sendStatus(204).end();
  644. responseCache.set(cacheKey, { buffer, mime: "audio/mpeg" });
  645. response.writeHead(200, {
  646. "Content-Type": "audio/mpeg",
  647. });
  648. response.end(buffer);
  649. return;
  650. } catch (error) {
  651. console.error("Error processing the TTS request:", error);
  652. response.status(500).json({ message: "TTS could not be completed" });
  653. }
  654. }
  655. );
  656. app.get(
  657. "/workspace/:slug/pfp",
  658. [validatedRequest, flexUserRoleValid([ROLES.all])],
  659. async function (request, response) {
  660. try {
  661. const { slug } = request.params;
  662. const cachedResponse = responseCache.get(slug);
  663. if (cachedResponse) {
  664. response.writeHead(200, {
  665. "Content-Type": cachedResponse.mime || "image/png",
  666. });
  667. response.end(cachedResponse.buffer);
  668. return;
  669. }
  670. const pfpPath = await determineWorkspacePfpFilepath(slug);
  671. if (!pfpPath) {
  672. response.sendStatus(204).end();
  673. return;
  674. }
  675. const { found, buffer, mime } = fetchPfp(pfpPath);
  676. if (!found) {
  677. response.sendStatus(204).end();
  678. return;
  679. }
  680. responseCache.set(slug, { buffer, mime });
  681. response.writeHead(200, {
  682. "Content-Type": mime || "image/png",
  683. });
  684. response.end(buffer);
  685. return;
  686. } catch (error) {
  687. console.error("Error processing the logo request:", error);
  688. response.status(500).json({ message: "Internal server error" });
  689. }
  690. }
  691. );
  692. app.post(
  693. "/workspace/:slug/upload-pfp",
  694. [
  695. validatedRequest,
  696. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  697. handlePfpUpload,
  698. ],
  699. async function (request, response) {
  700. try {
  701. const { slug } = request.params;
  702. const uploadedFileName = request.randomFileName;
  703. if (!uploadedFileName) {
  704. return response.status(400).json({ message: "File upload failed." });
  705. }
  706. const workspaceRecord = await Workspace.get({
  707. slug,
  708. });
  709. const oldPfpFilename = workspaceRecord.pfpFilename;
  710. if (oldPfpFilename) {
  711. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  712. const oldPfpPath = path.join(
  713. storagePath,
  714. normalizePath(workspaceRecord.pfpFilename)
  715. );
  716. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  717. throw new Error("Invalid path name");
  718. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  719. }
  720. const { workspace, message } = await Workspace._update(
  721. workspaceRecord.id,
  722. {
  723. pfpFilename: uploadedFileName,
  724. }
  725. );
  726. return response.status(workspace ? 200 : 500).json({
  727. message: workspace
  728. ? "Profile picture uploaded successfully."
  729. : message,
  730. });
  731. } catch (error) {
  732. console.error("Error processing the profile picture upload:", error);
  733. response.status(500).json({ message: "Internal server error" });
  734. }
  735. }
  736. );
  737. app.delete(
  738. "/workspace/:slug/remove-pfp",
  739. [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
  740. async function (request, response) {
  741. try {
  742. const { slug } = request.params;
  743. const workspaceRecord = await Workspace.get({
  744. slug,
  745. });
  746. const oldPfpFilename = workspaceRecord.pfpFilename;
  747. if (oldPfpFilename) {
  748. const storagePath = path.join(__dirname, "../storage/assets/pfp");
  749. const oldPfpPath = path.join(
  750. storagePath,
  751. normalizePath(oldPfpFilename)
  752. );
  753. if (!isWithin(path.resolve(storagePath), path.resolve(oldPfpPath)))
  754. throw new Error("Invalid path name");
  755. if (fs.existsSync(oldPfpPath)) fs.unlinkSync(oldPfpPath);
  756. }
  757. const { workspace, message } = await Workspace._update(
  758. workspaceRecord.id,
  759. {
  760. pfpFilename: null,
  761. }
  762. );
  763. // Clear the cache
  764. responseCache.delete(slug);
  765. return response.status(workspace ? 200 : 500).json({
  766. message: workspace
  767. ? "Profile picture removed successfully."
  768. : message,
  769. });
  770. } catch (error) {
  771. console.error("Error processing the profile picture removal:", error);
  772. response.status(500).json({ message: "Internal server error" });
  773. }
  774. }
  775. );
  776. app.post(
  777. "/workspace/:slug/thread/fork",
  778. [validatedRequest, flexUserRoleValid([ROLES.all]), validWorkspaceSlug],
  779. async (request, response) => {
  780. try {
  781. const user = await userFromSession(request, response);
  782. const workspace = response.locals.workspace;
  783. const { chatId, threadSlug } = reqBody(request);
  784. if (!chatId)
  785. return response.status(400).json({ message: "chatId is required" });
  786. // Get threadId we are branching from if that request body is sent
  787. // and is a valid thread slug.
  788. const threadId = !!threadSlug
  789. ? (
  790. await WorkspaceThread.get({
  791. slug: String(threadSlug),
  792. workspace_id: workspace.id,
  793. })
  794. )?.id ?? null
  795. : null;
  796. const chatsToFork = await WorkspaceChats.where(
  797. {
  798. workspaceId: workspace.id,
  799. user_id: user?.id,
  800. include: true, // only duplicate visible chats
  801. thread_id: threadId,
  802. api_session_id: null, // Do not include API session chats.
  803. id: { lte: Number(chatId) },
  804. },
  805. null,
  806. { id: "asc" }
  807. );
  808. const { thread: newThread, message: threadError } =
  809. await WorkspaceThread.new(workspace, user?.id);
  810. if (threadError)
  811. return response.status(500).json({ error: threadError });
  812. let lastMessageText = "";
  813. const chatsData = chatsToFork.map((chat) => {
  814. const chatResponse = safeJsonParse(chat.response, {});
  815. if (chatResponse?.text) lastMessageText = chatResponse.text;
  816. return {
  817. workspaceId: workspace.id,
  818. prompt: chat.prompt,
  819. response: JSON.stringify(chatResponse),
  820. user_id: user?.id,
  821. thread_id: newThread.id,
  822. };
  823. });
  824. await WorkspaceChats.bulkCreate(chatsData);
  825. await WorkspaceThread.update(newThread, {
  826. name: !!lastMessageText
  827. ? truncate(lastMessageText, 22)
  828. : "Forked Thread",
  829. });
  830. await Telemetry.sendTelemetry("thread_forked");
  831. await EventLogs.logEvent(
  832. "thread_forked",
  833. {
  834. workspaceName: workspace?.name || "Unknown Workspace",
  835. threadName: newThread.name,
  836. },
  837. user?.id
  838. );
  839. response.status(200).json({ newThreadSlug: newThread.slug });
  840. } catch (e) {
  841. console.error(e.message, e);
  842. response.status(500).json({ message: "Internal server error" });
  843. }
  844. }
  845. );
  846. app.put(
  847. "/workspace/workspace-chats/:id",
  848. [validatedRequest, flexUserRoleValid([ROLES.all])],
  849. async (request, response) => {
  850. try {
  851. const { id } = request.params;
  852. const user = await userFromSession(request, response);
  853. const validChat = await WorkspaceChats.get({
  854. id: Number(id),
  855. user_id: user?.id ?? null,
  856. });
  857. if (!validChat)
  858. return response
  859. .status(404)
  860. .json({ success: false, error: "Chat not found." });
  861. await WorkspaceChats._update(validChat.id, { include: false });
  862. response.json({ success: true, error: null });
  863. } catch (e) {
  864. console.error(e.message, e);
  865. response.status(500).json({ success: false, error: "Server error" });
  866. }
  867. }
  868. );
  869. /** Handles the uploading and embedding in one-call by uploading via drag-and-drop in chat container. */
  870. app.post(
  871. "/workspace/:slug/upload-and-embed",
  872. [
  873. validatedRequest,
  874. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  875. handleFileUpload,
  876. ],
  877. async function (request, response) {
  878. try {
  879. const { slug = null } = request.params;
  880. const user = await userFromSession(request, response);
  881. const currWorkspace = multiUserMode(response)
  882. ? await Workspace.getWithUser(user, { slug })
  883. : await Workspace.get({ slug });
  884. if (!currWorkspace) {
  885. response.sendStatus(400).end();
  886. return;
  887. }
  888. const Collector = new CollectorApi();
  889. const { originalname } = request.file;
  890. const processingOnline = await Collector.online();
  891. if (!processingOnline) {
  892. response
  893. .status(500)
  894. .json({
  895. success: false,
  896. error: `Document processing API is not online. Document ${originalname} will not be processed automatically.`,
  897. })
  898. .end();
  899. return;
  900. }
  901. const { success, reason, documents } =
  902. await Collector.processDocument(originalname);
  903. if (!success || documents?.length === 0) {
  904. response.status(500).json({ success: false, error: reason }).end();
  905. return;
  906. }
  907. Collector.log(
  908. `Document ${originalname} uploaded processed and successfully. It is now available in documents.`
  909. );
  910. await Telemetry.sendTelemetry("document_uploaded");
  911. await EventLogs.logEvent(
  912. "document_uploaded",
  913. {
  914. documentName: originalname,
  915. },
  916. response.locals?.user?.id
  917. );
  918. const document = documents[0];
  919. const { failedToEmbed = [], errors = [] } = await Document.addDocuments(
  920. currWorkspace,
  921. [document.location],
  922. response.locals?.user?.id
  923. );
  924. if (failedToEmbed.length > 0)
  925. return response
  926. .status(200)
  927. .json({ success: false, error: errors?.[0], document: null });
  928. response.status(200).json({
  929. success: true,
  930. error: null,
  931. document: { id: document.id, location: document.location },
  932. });
  933. } catch (e) {
  934. console.error(e.message, e);
  935. response.sendStatus(500).end();
  936. }
  937. }
  938. );
  939. app.delete(
  940. "/workspace/:slug/remove-and-unembed",
  941. [
  942. validatedRequest,
  943. flexUserRoleValid([ROLES.admin, ROLES.manager]),
  944. handleFileUpload,
  945. ],
  946. async function (request, response) {
  947. try {
  948. const { slug = null } = request.params;
  949. const body = reqBody(request);
  950. const user = await userFromSession(request, response);
  951. const currWorkspace = multiUserMode(response)
  952. ? await Workspace.getWithUser(user, { slug })
  953. : await Workspace.get({ slug });
  954. if (!currWorkspace || !body.documentLocation)
  955. return response.sendStatus(400).end();
  956. // Will delete the document from the entire system + wil unembed it.
  957. await purgeDocument(body.documentLocation);
  958. response.status(200).end();
  959. } catch (e) {
  960. console.error(e.message, e);
  961. response.sendStatus(500).end();
  962. }
  963. }
  964. );
  965. }
  966. module.exports = { workspaceEndpoints };