@intlayer/backend 7.5.8 → 7.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +9 -2
  2. package/dist/assets/utils/AI/askDocQuestion/PROMPT.md +1 -1
  3. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/cli/init.json +2054 -0
  4. package/dist/assets/utils/AI/askDocQuestion/embeddings/docs/en/intlayer_with_fastify.json +9 -0
  5. package/dist/esm/controllers/ai.controller.mjs +95 -128
  6. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  7. package/dist/esm/controllers/dictionary.controller.mjs +86 -198
  8. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  9. package/dist/esm/controllers/eventListener.controller.mjs +13 -19
  10. package/dist/esm/controllers/eventListener.controller.mjs.map +1 -1
  11. package/dist/esm/controllers/github.controller.mjs +77 -0
  12. package/dist/esm/controllers/github.controller.mjs.map +1 -0
  13. package/dist/esm/controllers/newsletter.controller.mjs +30 -60
  14. package/dist/esm/controllers/newsletter.controller.mjs.map +1 -1
  15. package/dist/esm/controllers/oAuth2.controller.mjs +11 -8
  16. package/dist/esm/controllers/oAuth2.controller.mjs.map +1 -1
  17. package/dist/esm/controllers/organization.controller.mjs +100 -225
  18. package/dist/esm/controllers/organization.controller.mjs.map +1 -1
  19. package/dist/esm/controllers/project.controller.mjs +87 -204
  20. package/dist/esm/controllers/project.controller.mjs.map +1 -1
  21. package/dist/esm/controllers/projectAccessKey.controller.mjs +38 -71
  22. package/dist/esm/controllers/projectAccessKey.controller.mjs.map +1 -1
  23. package/dist/esm/controllers/search.controller.mjs +3 -3
  24. package/dist/esm/controllers/search.controller.mjs.map +1 -1
  25. package/dist/esm/controllers/stripe.controller.mjs +34 -67
  26. package/dist/esm/controllers/stripe.controller.mjs.map +1 -1
  27. package/dist/esm/controllers/tag.controller.mjs +51 -113
  28. package/dist/esm/controllers/tag.controller.mjs.map +1 -1
  29. package/dist/esm/controllers/user.controller.mjs +64 -113
  30. package/dist/esm/controllers/user.controller.mjs.map +1 -1
  31. package/dist/esm/export.mjs +2 -1
  32. package/dist/esm/index.mjs +101 -41
  33. package/dist/esm/index.mjs.map +1 -1
  34. package/dist/esm/middlewares/oAuth2.middleware.mjs +19 -14
  35. package/dist/esm/middlewares/oAuth2.middleware.mjs.map +1 -1
  36. package/dist/esm/middlewares/sessionAuth.middleware.mjs +6 -7
  37. package/dist/esm/middlewares/sessionAuth.middleware.mjs.map +1 -1
  38. package/dist/esm/routes/ai.routes.mjs +19 -15
  39. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  40. package/dist/esm/routes/dictionary.routes.mjs +10 -10
  41. package/dist/esm/routes/dictionary.routes.mjs.map +1 -1
  42. package/dist/esm/routes/eventListener.routes.mjs +3 -3
  43. package/dist/esm/routes/eventListener.routes.mjs.map +1 -1
  44. package/dist/esm/routes/github.routes.mjs +43 -0
  45. package/dist/esm/routes/github.routes.mjs.map +1 -0
  46. package/dist/esm/routes/newsletter.routes.mjs +5 -5
  47. package/dist/esm/routes/newsletter.routes.mjs.map +1 -1
  48. package/dist/esm/routes/organization.routes.mjs +11 -11
  49. package/dist/esm/routes/organization.routes.mjs.map +1 -1
  50. package/dist/esm/routes/project.routes.mjs +13 -13
  51. package/dist/esm/routes/project.routes.mjs.map +1 -1
  52. package/dist/esm/routes/search.routes.mjs +3 -3
  53. package/dist/esm/routes/search.routes.mjs.map +1 -1
  54. package/dist/esm/routes/stripe.routes.mjs +5 -5
  55. package/dist/esm/routes/stripe.routes.mjs.map +1 -1
  56. package/dist/esm/routes/tags.routes.mjs +6 -6
  57. package/dist/esm/routes/tags.routes.mjs.map +1 -1
  58. package/dist/esm/routes/user.routes.mjs +9 -9
  59. package/dist/esm/routes/user.routes.mjs.map +1 -1
  60. package/dist/esm/schemas/project.schema.mjs +35 -1
  61. package/dist/esm/schemas/project.schema.mjs.map +1 -1
  62. package/dist/esm/services/email.service.mjs +1 -1
  63. package/dist/esm/services/email.service.mjs.map +1 -1
  64. package/dist/esm/services/github.service.mjs +130 -0
  65. package/dist/esm/services/github.service.mjs.map +1 -0
  66. package/dist/esm/services/oAuth2.service.mjs +1 -1
  67. package/dist/esm/services/subscription.service.mjs +1 -1
  68. package/dist/esm/services/subscription.service.mjs.map +1 -1
  69. package/dist/esm/utils/auth/getAuth.mjs +14 -8
  70. package/dist/esm/utils/auth/getAuth.mjs.map +1 -1
  71. package/dist/esm/utils/cors.mjs +15 -5
  72. package/dist/esm/utils/cors.mjs.map +1 -1
  73. package/dist/esm/utils/errors/ErrorHandler.mjs +32 -4
  74. package/dist/esm/utils/errors/ErrorHandler.mjs.map +1 -1
  75. package/dist/esm/utils/errors/ErrorsClass.mjs +1 -1
  76. package/dist/esm/utils/errors/ErrorsClass.mjs.map +1 -1
  77. package/dist/esm/utils/errors/errorCodes.mjs +78 -0
  78. package/dist/esm/utils/errors/errorCodes.mjs.map +1 -1
  79. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs +3 -2
  80. package/dist/esm/utils/filtersAndPagination/getDictionaryFiltersAndPagination.mjs.map +1 -1
  81. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs +1 -1
  82. package/dist/esm/utils/filtersAndPagination/getDiscussionFiltersAndPagination.mjs.map +1 -1
  83. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs +1 -1
  84. package/dist/esm/utils/filtersAndPagination/getFiltersAndPaginationFromBody.mjs.map +1 -1
  85. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs +3 -2
  86. package/dist/esm/utils/filtersAndPagination/getOrganizationFiltersAndPagination.mjs.map +1 -1
  87. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs +3 -2
  88. package/dist/esm/utils/filtersAndPagination/getProjectFiltersAndPagination.mjs.map +1 -1
  89. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs +3 -2
  90. package/dist/esm/utils/filtersAndPagination/getTagFiltersAndPagination.mjs.map +1 -1
  91. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs +3 -2
  92. package/dist/esm/utils/filtersAndPagination/getUserFiltersAndPagination.mjs.map +1 -1
  93. package/dist/esm/utils/mapper/project.mjs +28 -1
  94. package/dist/esm/utils/mapper/project.mjs.map +1 -1
  95. package/dist/esm/utils/mongoDB/connectDB.mjs +1 -1
  96. package/dist/esm/utils/rateLimiter.mjs +40 -30
  97. package/dist/esm/utils/rateLimiter.mjs.map +1 -1
  98. package/dist/esm/webhooks/stripe.webhook.mjs +2 -2
  99. package/dist/esm/webhooks/stripe.webhook.mjs.map +1 -1
  100. package/dist/types/controllers/ai.controller.d.ts +29 -12
  101. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  102. package/dist/types/controllers/dictionary.controller.d.ts +23 -13
  103. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  104. package/dist/types/controllers/eventListener.controller.d.ts +4 -2
  105. package/dist/types/controllers/eventListener.controller.d.ts.map +1 -1
  106. package/dist/types/controllers/github.controller.d.ts +63 -0
  107. package/dist/types/controllers/github.controller.d.ts.map +1 -0
  108. package/dist/types/controllers/newsletter.controller.d.ts +8 -7
  109. package/dist/types/controllers/newsletter.controller.d.ts.map +1 -1
  110. package/dist/types/controllers/oAuth2.controller.d.ts +4 -2
  111. package/dist/types/controllers/oAuth2.controller.d.ts.map +1 -1
  112. package/dist/types/controllers/organization.controller.d.ts +28 -12
  113. package/dist/types/controllers/organization.controller.d.ts.map +1 -1
  114. package/dist/types/controllers/project.controller.d.ts +21 -16
  115. package/dist/types/controllers/project.controller.d.ts.map +1 -1
  116. package/dist/types/controllers/projectAccessKey.controller.d.ts +10 -5
  117. package/dist/types/controllers/projectAccessKey.controller.d.ts.map +1 -1
  118. package/dist/types/controllers/search.controller.d.ts +4 -2
  119. package/dist/types/controllers/search.controller.d.ts.map +1 -1
  120. package/dist/types/controllers/stripe.controller.d.ts +11 -12
  121. package/dist/types/controllers/stripe.controller.d.ts.map +1 -1
  122. package/dist/types/controllers/tag.controller.d.ts +14 -9
  123. package/dist/types/controllers/tag.controller.d.ts.map +1 -1
  124. package/dist/types/controllers/user.controller.d.ts +22 -9
  125. package/dist/types/controllers/user.controller.d.ts.map +1 -1
  126. package/dist/types/emails/InviteUserEmail.d.ts +4 -4
  127. package/dist/types/emails/InviteUserEmail.d.ts.map +1 -1
  128. package/dist/types/emails/MagicLinkEmail.d.ts +4 -4
  129. package/dist/types/emails/OAuthTokenCreatedEmail.d.ts +4 -4
  130. package/dist/types/emails/ResetUserPassword.d.ts +4 -4
  131. package/dist/types/emails/ResetUserPassword.d.ts.map +1 -1
  132. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts +4 -4
  133. package/dist/types/emails/SubscriptionPaymentCancellation.d.ts.map +1 -1
  134. package/dist/types/emails/SubscriptionPaymentError.d.ts +4 -4
  135. package/dist/types/emails/SubscriptionPaymentSuccess.d.ts +4 -4
  136. package/dist/types/emails/ValidateUserEmail.d.ts +4 -4
  137. package/dist/types/emails/ValidateUserEmail.d.ts.map +1 -1
  138. package/dist/types/emails/Welcome.d.ts +4 -4
  139. package/dist/types/emails/Welcome.d.ts.map +1 -1
  140. package/dist/types/export.d.ts +6 -4
  141. package/dist/types/middlewares/oAuth2.middleware.d.ts +9 -4
  142. package/dist/types/middlewares/oAuth2.middleware.d.ts.map +1 -1
  143. package/dist/types/middlewares/sessionAuth.middleware.d.ts +13 -3
  144. package/dist/types/middlewares/sessionAuth.middleware.d.ts.map +1 -1
  145. package/dist/types/models/dictionary.model.d.ts +4 -4
  146. package/dist/types/models/discussion.model.d.ts +2 -2
  147. package/dist/types/models/oAuth2.model.d.ts +3 -3
  148. package/dist/types/routes/ai.routes.d.ts +2 -2
  149. package/dist/types/routes/ai.routes.d.ts.map +1 -1
  150. package/dist/types/routes/dictionary.routes.d.ts +2 -2
  151. package/dist/types/routes/dictionary.routes.d.ts.map +1 -1
  152. package/dist/types/routes/eventListener.routes.d.ts +2 -2
  153. package/dist/types/routes/eventListener.routes.d.ts.map +1 -1
  154. package/dist/types/routes/github.routes.d.ts +35 -0
  155. package/dist/types/routes/github.routes.d.ts.map +1 -0
  156. package/dist/types/routes/newsletter.routes.d.ts +2 -2
  157. package/dist/types/routes/newsletter.routes.d.ts.map +1 -1
  158. package/dist/types/routes/organization.routes.d.ts +2 -2
  159. package/dist/types/routes/organization.routes.d.ts.map +1 -1
  160. package/dist/types/routes/project.routes.d.ts +2 -2
  161. package/dist/types/routes/project.routes.d.ts.map +1 -1
  162. package/dist/types/routes/search.routes.d.ts +2 -2
  163. package/dist/types/routes/search.routes.d.ts.map +1 -1
  164. package/dist/types/routes/stripe.routes.d.ts +2 -2
  165. package/dist/types/routes/stripe.routes.d.ts.map +1 -1
  166. package/dist/types/routes/tags.routes.d.ts +2 -2
  167. package/dist/types/routes/tags.routes.d.ts.map +1 -1
  168. package/dist/types/routes/user.routes.d.ts +2 -2
  169. package/dist/types/routes/user.routes.d.ts.map +1 -1
  170. package/dist/types/schemas/dictionary.schema.d.ts +6 -6
  171. package/dist/types/schemas/discussion.schema.d.ts +6 -6
  172. package/dist/types/schemas/oAuth2.schema.d.ts +5 -5
  173. package/dist/types/schemas/project.schema.d.ts +6 -6
  174. package/dist/types/schemas/project.schema.d.ts.map +1 -1
  175. package/dist/types/schemas/session.schema.d.ts +6 -6
  176. package/dist/types/schemas/tag.schema.d.ts +6 -6
  177. package/dist/types/schemas/user.schema.d.ts +6 -6
  178. package/dist/types/services/email.service.d.ts +11 -11
  179. package/dist/types/services/github.service.d.ts +21 -0
  180. package/dist/types/services/github.service.d.ts.map +1 -0
  181. package/dist/types/types/project.types.d.ts +18 -5
  182. package/dist/types/types/project.types.d.ts.map +1 -1
  183. package/dist/types/types/session.types.d.ts +1 -1
  184. package/dist/types/types/user.types.d.ts +1 -1
  185. package/dist/types/utils/AI/auditTag/index.d.ts +1 -1
  186. package/dist/types/utils/auth/getAuth.d.ts.map +1 -1
  187. package/dist/types/utils/cors.d.ts +2 -2
  188. package/dist/types/utils/errors/ErrorHandler.d.ts +31 -3
  189. package/dist/types/utils/errors/ErrorHandler.d.ts.map +1 -1
  190. package/dist/types/utils/errors/ErrorsClass.d.ts +1 -1
  191. package/dist/types/utils/errors/errorCodes.d.ts +78 -0
  192. package/dist/types/utils/errors/errorCodes.d.ts.map +1 -1
  193. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts +8 -4
  194. package/dist/types/utils/filtersAndPagination/getDictionaryFiltersAndPagination.d.ts.map +1 -1
  195. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts +6 -3
  196. package/dist/types/utils/filtersAndPagination/getDiscussionFiltersAndPagination.d.ts.map +1 -1
  197. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts +6 -2
  198. package/dist/types/utils/filtersAndPagination/getFiltersAndPaginationFromBody.d.ts.map +1 -1
  199. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts +8 -4
  200. package/dist/types/utils/filtersAndPagination/getOrganizationFiltersAndPagination.d.ts.map +1 -1
  201. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts +8 -4
  202. package/dist/types/utils/filtersAndPagination/getProjectFiltersAndPagination.d.ts.map +1 -1
  203. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts +8 -4
  204. package/dist/types/utils/filtersAndPagination/getTagFiltersAndPagination.d.ts.map +1 -1
  205. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts +6 -2
  206. package/dist/types/utils/filtersAndPagination/getUserFiltersAndPagination.d.ts.map +1 -1
  207. package/dist/types/utils/mapper/project.d.ts.map +1 -1
  208. package/dist/types/utils/mergeFunctionTypes.d.ts.map +1 -1
  209. package/dist/types/utils/permissions.d.ts +1 -1
  210. package/dist/types/utils/rateLimiter.d.ts +4 -2
  211. package/dist/types/utils/rateLimiter.d.ts.map +1 -1
  212. package/package.json +23 -27
  213. package/dist/esm/middlewares/request.middleware.mjs +0 -17
  214. package/dist/esm/middlewares/request.middleware.mjs.map +0 -1
  215. package/dist/types/middlewares/request.middleware.d.ts +0 -7
  216. package/dist/types/middlewares/request.middleware.d.ts.map +0 -1
@@ -0,0 +1,9 @@
1
+ {
2
+ "chunk_1": [],
3
+ "chunk_2": [],
4
+ "chunk_3": [],
5
+ "chunk_4": [],
6
+ "chunk_5": [],
7
+ "chunk_6": [],
8
+ "chunk_7": []
9
+ }
@@ -15,18 +15,20 @@ import { DiscussionModel } from "../models/discussion.model.mjs";
15
15
  import { getAIConfig } from "@intlayer/ai";
16
16
 
17
17
  //#region src/controllers/ai.controller.ts
18
- const customQuery = async (req, res, _next) => {
19
- const { aiOptions, tagsKeys, ...rest } = req.body;
18
+ const customQuery = async (request, reply) => {
19
+ const { aiOptions, tagsKeys, ...rest } = request.body;
20
+ const { user, project } = request.locals || {};
21
+ const projectAIOptions = project?.configuration?.ai ? project.configuration.ai : void 0;
20
22
  let aiConfig;
21
23
  try {
22
24
  aiConfig = await getAIConfig({
23
25
  userOptions: aiOptions,
26
+ projectOptions: projectAIOptions,
24
27
  defaultOptions: aiDefaultOptions$5,
25
28
  accessType: ["registered_user", "apiKey"]
26
- }, !!res.locals.user);
29
+ }, !!user);
27
30
  } catch (_error) {
28
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
29
- return;
31
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
30
32
  }
31
33
  try {
32
34
  const auditResponse = await customQuery$2({
@@ -34,31 +36,27 @@ const customQuery = async (req, res, _next) => {
34
36
  aiConfig,
35
37
  applicationContext: aiOptions?.applicationContext
36
38
  });
37
- if (!auditResponse) {
38
- ErrorHandler.handleGenericErrorResponse(res, "QUERY_FAILED");
39
- return;
40
- }
39
+ if (!auditResponse) return ErrorHandler.handleGenericErrorResponse(reply, "QUERY_FAILED");
41
40
  const responseData = formatResponse({ data: auditResponse });
42
- res.json(responseData);
43
- return;
41
+ return reply.send(responseData);
44
42
  } catch (error) {
45
- ErrorHandler.handleAppErrorResponse(res, error);
46
- return;
43
+ return ErrorHandler.handleAppErrorResponse(reply, error);
47
44
  }
48
45
  };
49
- const translateJSON = async (req, res, _next) => {
50
- const { project } = res.locals;
51
- const { aiOptions, tagsKeys, ...rest } = req.body;
46
+ const translateJSON = async (request, reply) => {
47
+ const { project, user } = request.locals || {};
48
+ const { aiOptions, tagsKeys, ...rest } = request.body;
49
+ const projectAIOptions = project?.configuration?.ai ? project.configuration.ai : void 0;
52
50
  let aiConfig;
53
51
  try {
54
52
  aiConfig = await getAIConfig({
55
53
  userOptions: aiOptions,
54
+ projectOptions: projectAIOptions,
56
55
  defaultOptions: aiDefaultOptions$6,
57
56
  accessType: ["registered_user", "apiKey"]
58
- }, !!res.locals.user);
57
+ }, !!user);
59
58
  } catch (_error) {
60
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
61
- return;
59
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
62
60
  }
63
61
  try {
64
62
  let tags = [];
@@ -69,38 +67,34 @@ const translateJSON = async (req, res, _next) => {
69
67
  applicationContext: aiOptions?.applicationContext,
70
68
  tags
71
69
  });
72
- if (!auditResponse) {
73
- ErrorHandler.handleGenericErrorResponse(res, "AUDIT_FAILED");
74
- return;
75
- }
70
+ if (!auditResponse) return ErrorHandler.handleGenericErrorResponse(reply, "AUDIT_FAILED");
76
71
  const responseData = formatResponse({ data: auditResponse });
77
- res.json(responseData);
78
- return;
72
+ return reply.send(responseData);
79
73
  } catch (error) {
80
- ErrorHandler.handleAppErrorResponse(res, error);
81
- return;
74
+ return ErrorHandler.handleAppErrorResponse(reply, error);
82
75
  }
83
76
  };
84
77
  /**
85
78
  * Retrieves a list of dictionaries based on filters and pagination.
86
79
  */
87
- const auditContentDeclaration = async (req, res, _next) => {
88
- const { project } = res.locals;
89
- const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } = req.body;
80
+ const auditContentDeclaration = async (request, reply) => {
81
+ const { project, user } = request.locals || {};
82
+ const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } = request.body;
83
+ const projectAIOptions = project?.configuration?.ai ? project.configuration.ai : void 0;
90
84
  let aiConfig;
91
85
  try {
92
86
  aiConfig = await getAIConfig({
93
87
  userOptions: aiOptions,
88
+ projectOptions: projectAIOptions,
94
89
  defaultOptions: aiDefaultOptions,
95
90
  accessType: ["registered_user", "apiKey"]
96
- }, !!res.locals.user);
91
+ }, !!user);
97
92
  } catch (_error) {
98
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
99
- return;
93
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
100
94
  }
101
95
  try {
102
96
  let tags = [];
103
- if (project?.organizationId) tags = await getTagsByKeys(tagsKeys, project.organizationId);
97
+ if (project?.organizationId) tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);
104
98
  const auditResponse = await auditDictionary({
105
99
  fileContent,
106
100
  filePath,
@@ -110,38 +104,34 @@ const auditContentDeclaration = async (req, res, _next) => {
110
104
  defaultLocale,
111
105
  tags
112
106
  });
113
- if (!auditResponse) {
114
- ErrorHandler.handleGenericErrorResponse(res, "AUDIT_FAILED");
115
- return;
116
- }
107
+ if (!auditResponse) return ErrorHandler.handleGenericErrorResponse(reply, "AUDIT_FAILED");
117
108
  const responseData = formatResponse({ data: auditResponse });
118
- res.json(responseData);
119
- return;
109
+ return reply.send(responseData);
120
110
  } catch (error) {
121
- ErrorHandler.handleAppErrorResponse(res, error);
122
- return;
111
+ return ErrorHandler.handleAppErrorResponse(reply, error);
123
112
  }
124
113
  };
125
114
  /**
126
115
  * Retrieves a list of dictionaries based on filters and pagination.
127
116
  */
128
- const auditContentDeclarationField = async (req, res, _next) => {
129
- const { project } = res.locals;
130
- const { fileContent, aiOptions, locales, tagsKeys, keyPath } = req.body;
117
+ const auditContentDeclarationField = async (request, reply) => {
118
+ const { project, user } = request.locals || {};
119
+ const { fileContent, aiOptions, locales, tagsKeys, keyPath } = request.body;
120
+ const projectAIOptions = project?.configuration?.ai ? project.configuration.ai : void 0;
131
121
  let aiConfig;
132
122
  try {
133
123
  aiConfig = await getAIConfig({
134
124
  userOptions: aiOptions,
125
+ projectOptions: projectAIOptions,
135
126
  defaultOptions: aiDefaultOptions$1,
136
127
  accessType: ["registered_user", "apiKey"]
137
- }, !!res.locals.user);
128
+ }, !!user);
138
129
  } catch (_error) {
139
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
140
- return;
130
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
141
131
  }
142
132
  try {
143
133
  let tags = [];
144
- if (project?.organizationId) tags = await getTagsByKeys(tagsKeys, project.organizationId);
134
+ if (project?.organizationId) tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);
145
135
  const auditResponse = await auditDictionaryField({
146
136
  fileContent,
147
137
  aiConfig,
@@ -150,34 +140,28 @@ const auditContentDeclarationField = async (req, res, _next) => {
150
140
  tags,
151
141
  keyPath
152
142
  });
153
- if (!auditResponse) {
154
- ErrorHandler.handleGenericErrorResponse(res, "AUDIT_FAILED");
155
- return;
156
- }
143
+ if (!auditResponse) return ErrorHandler.handleGenericErrorResponse(reply, "AUDIT_FAILED");
157
144
  const responseData = formatResponse({ data: auditResponse });
158
- res.json(responseData);
159
- return;
145
+ return reply.send(responseData);
160
146
  } catch (error) {
161
- ErrorHandler.handleAppErrorResponse(res, error);
162
- return;
147
+ return ErrorHandler.handleAppErrorResponse(reply, error);
163
148
  }
164
149
  };
165
150
  /**
166
151
  * Retrieves a list of dictionaries based on filters and pagination.
167
152
  */
168
- const auditContentDeclarationMetadata = async (req, res, _next) => {
169
- const { organization } = res.locals;
170
- const { fileContent, aiOptions } = req.body;
153
+ const auditContentDeclarationMetadata = async (request, reply) => {
154
+ const { organization, user } = request.locals || {};
155
+ const { fileContent, aiOptions } = request.body;
171
156
  let aiConfig;
172
157
  try {
173
158
  aiConfig = await getAIConfig({
174
159
  userOptions: aiOptions,
175
160
  defaultOptions: aiDefaultOptions$2,
176
161
  accessType: ["registered_user", "apiKey"]
177
- }, !!res.locals.user);
162
+ }, !!user);
178
163
  } catch (_error) {
179
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
180
- return;
164
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
181
165
  }
182
166
  try {
183
167
  const tags = await findTags({ organizationId: organization?.id }, 0, 1e3);
@@ -187,34 +171,30 @@ const auditContentDeclarationMetadata = async (req, res, _next) => {
187
171
  applicationContext: aiOptions?.applicationContext,
188
172
  tags
189
173
  });
190
- if (!auditResponse) {
191
- ErrorHandler.handleGenericErrorResponse(res, "AUDIT_FAILED");
192
- return;
193
- }
174
+ if (!auditResponse) return ErrorHandler.handleGenericErrorResponse(reply, "AUDIT_FAILED");
194
175
  const responseData = formatResponse({ data: auditResponse });
195
- res.json(responseData);
196
- return;
176
+ return reply.send(responseData);
197
177
  } catch (error) {
198
- ErrorHandler.handleAppErrorResponse(res, error);
199
- return;
178
+ return ErrorHandler.handleAppErrorResponse(reply, error);
200
179
  }
201
180
  };
202
181
  /**
203
182
  * Retrieves a list of dictionaries based on filters and pagination.
204
183
  */
205
- const auditTag = async (req, res, _next) => {
206
- const { project } = res.locals;
207
- const { aiOptions, tag } = req.body;
184
+ const auditTag = async (request, reply) => {
185
+ const { project, user } = request.locals || {};
186
+ const { aiOptions, tag } = request.body;
187
+ const projectAIOptions = project?.configuration?.ai ? project.configuration.ai : void 0;
208
188
  let aiConfig;
209
189
  try {
210
190
  aiConfig = await getAIConfig({
211
191
  userOptions: aiOptions,
192
+ projectOptions: projectAIOptions,
212
193
  defaultOptions: aiDefaultOptions$3,
213
194
  accessType: ["registered_user", "apiKey"]
214
- }, !!res.locals.user);
195
+ }, !!user);
215
196
  } catch (_error) {
216
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
217
- return;
197
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
218
198
  }
219
199
  try {
220
200
  let dictionaries = [];
@@ -225,41 +205,35 @@ const auditTag = async (req, res, _next) => {
225
205
  tag,
226
206
  applicationContext: aiOptions?.applicationContext
227
207
  });
228
- if (!auditResponse) {
229
- ErrorHandler.handleGenericErrorResponse(res, "AUDIT_FAILED");
230
- return;
231
- }
208
+ if (!auditResponse) return ErrorHandler.handleGenericErrorResponse(reply, "AUDIT_FAILED");
232
209
  const responseData = formatResponse({ data: auditResponse });
233
- res.json(responseData);
234
- return;
210
+ return reply.send(responseData);
235
211
  } catch (error) {
236
- ErrorHandler.handleAppErrorResponse(res, error);
237
- return;
212
+ return ErrorHandler.handleAppErrorResponse(reply, error);
238
213
  }
239
214
  };
240
- const askDocQuestion = async (req, res, _next) => {
241
- const { messages = [], discussionId } = req.body;
242
- const { user, project, organization } = res.locals;
215
+ const askDocQuestion = async (request, reply) => {
216
+ const { messages = [], discussionId } = request.body;
217
+ const { user, project, organization } = request.locals || {};
218
+ const projectAIOptions = project?.configuration?.ai ? project.configuration.ai : void 0;
243
219
  let aiConfig;
244
220
  try {
245
221
  aiConfig = await getAIConfig({
246
222
  userOptions: {},
223
+ projectOptions: projectAIOptions,
247
224
  accessType: ["public"]
248
- }, !!res.locals.user);
225
+ }, !!user);
249
226
  } catch (_error) {
250
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
251
- return;
227
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
252
228
  }
253
- res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
254
- res.setHeader("Cache-Control", "no-cache, no-transform");
255
- res.setHeader("Connection", "keep-alive");
256
- res.setHeader("X-Accel-Buffering", "no");
257
- res.flushHeaders?.();
258
- res.write(": connected\n\n");
259
- res.flush?.();
229
+ reply.raw.setHeader("Content-Type", "text/event-stream; charset=utf-8");
230
+ reply.raw.setHeader("Cache-Control", "no-cache, no-transform");
231
+ reply.raw.setHeader("Connection", "keep-alive");
232
+ reply.raw.setHeader("X-Accel-Buffering", "no");
233
+ reply.raw.flushHeaders?.();
234
+ reply.raw.write(": connected\n\n");
260
235
  askDocQuestion$1(messages, aiConfig, { onMessage: (chunk) => {
261
- res.write(`data: ${JSON.stringify({ chunk })}\n\n`);
262
- res.flush?.();
236
+ reply.raw.write(`data: ${JSON.stringify({ chunk })}\n\n`);
263
237
  } }).then(async (fullResponse) => {
264
238
  const lastUserMessageContent = messages.findLast((message) => message.role === "user")?.content;
265
239
  if ((lastUserMessageContent ? lastUserMessageContent.split(" ").length : 0) > 2) await DiscussionModel.findOneAndUpdate({ discussionId }, { $set: {
@@ -281,29 +255,31 @@ const askDocQuestion = async (req, res, _next) => {
281
255
  upsert: true,
282
256
  new: true
283
257
  });
284
- res.write(`data: ${JSON.stringify({
258
+ reply.raw.write(`data: ${JSON.stringify({
285
259
  done: true,
286
260
  response: fullResponse
287
261
  })}\n\n`);
288
- res.end();
262
+ reply.raw.end();
289
263
  }).catch((err) => {
290
- res.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`);
291
- res.end();
264
+ reply.raw.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`);
265
+ reply.raw.end();
292
266
  });
293
267
  };
294
- const autocomplete = async (req, res, _next) => {
268
+ const autocomplete = async (request, reply) => {
269
+ const { user, project } = request.locals || {};
270
+ const projectAIOptions = project?.configuration?.ai ? project.configuration.ai : void 0;
295
271
  try {
296
- const { text, aiOptions, contextBefore, currentLine, contextAfter } = req.body;
272
+ const { text, aiOptions, contextBefore, currentLine, contextAfter } = request.body;
297
273
  let aiConfig;
298
274
  try {
299
275
  aiConfig = await getAIConfig({
300
276
  userOptions: aiOptions,
277
+ projectOptions: projectAIOptions,
301
278
  defaultOptions: aiDefaultOptions$4,
302
279
  accessType: ["public"]
303
- }, !!res.locals.user);
280
+ }, !!user);
304
281
  } catch (_error) {
305
- ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
306
- return;
282
+ return ErrorHandler.handleGenericErrorResponse(reply, "AI_ACCESS_DENIED");
307
283
  }
308
284
  const responseData = formatResponse({ data: await autocomplete$1({
309
285
  text,
@@ -316,24 +292,20 @@ const autocomplete = async (req, res, _next) => {
316
292
  autocompletion: "",
317
293
  tokenUsed: 0
318
294
  } });
319
- res.json(responseData);
295
+ return reply.send(responseData);
320
296
  } catch (error) {
321
- ErrorHandler.handleAppErrorResponse(res, error);
322
- return;
297
+ return ErrorHandler.handleAppErrorResponse(reply, error);
323
298
  }
324
299
  };
325
300
  /**
326
301
  * Retrieves a list of discussions with filters and pagination.
327
302
  * Only the owner or admins can access. By default, users only see their own.
328
303
  */
329
- const getDiscussions = async (req, res, _next) => {
330
- const { user, roles } = res.locals;
331
- const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getDiscussionFiltersAndPagination(req, res);
332
- const includeMessages = req.query?.includeMessages !== "false";
333
- if (!user) {
334
- ErrorHandler.handleGenericErrorResponse(res, "USER_NOT_DEFINED");
335
- return;
336
- }
304
+ const getDiscussions = async (request, reply) => {
305
+ const { user, roles } = request.locals || {};
306
+ const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } = getDiscussionFiltersAndPagination(request);
307
+ const includeMessages = request.query?.includeMessages !== "false";
308
+ if (!user) return ErrorHandler.handleGenericErrorResponse(reply, "USER_NOT_DEFINED");
337
309
  try {
338
310
  const projection = includeMessages ? {} : { messages: 0 };
339
311
  const discussions = await DiscussionModel.find(filters, projection).sort(sortOptions).skip(skip).limit(pageSize).lean();
@@ -344,10 +316,7 @@ const getDiscussions = async (req, res, _next) => {
344
316
  for (const c of counts) numberOfMessagesById[String(c._id)] = c.numberOfMessages ?? 0;
345
317
  }
346
318
  const allOwnedByUser = discussions.every((d) => String(d.userId) === String(user.id));
347
- if (!(roles.includes("admin") || allOwnedByUser)) {
348
- ErrorHandler.handleGenericErrorResponse(res, "PERMISSION_DENIED");
349
- return;
350
- }
319
+ if (!(roles?.includes("admin") || allOwnedByUser)) return ErrorHandler.handleGenericErrorResponse(reply, "PERMISSION_DENIED");
351
320
  const totalItems = await DiscussionModel.countDocuments(filters);
352
321
  const responseData = formatPaginatedResponse({
353
322
  data: discussions.map((d) => ({
@@ -360,11 +329,9 @@ const getDiscussions = async (req, res, _next) => {
360
329
  totalPages: getNumberOfPages(totalItems),
361
330
  totalItems
362
331
  });
363
- res.json(responseData);
364
- return;
332
+ return reply.send(responseData);
365
333
  } catch (error) {
366
- ErrorHandler.handleAppErrorResponse(res, error);
367
- return;
334
+ return ErrorHandler.handleAppErrorResponse(reply, error);
368
335
  }
369
336
  };
370
337
 
@@ -1 +1 @@
1
- {"version":3,"file":"ai.controller.mjs","names":["aiConfig: AIConfig","customQueryUtil.aiDefaultOptions","customQueryUtil.customQuery","translateJSONUtil.aiDefaultOptions","tags: Tag[]","translateJSONUtil.translateJSON","auditContentDeclarationUtil.aiDefaultOptions","auditContentDeclarationUtil.auditDictionary","auditContentDeclarationFieldUtil.aiDefaultOptions","auditContentDeclarationFieldUtil.auditDictionaryField","auditContentDeclarationMetadataUtil.aiDefaultOptions","tagService.findTags","auditContentDeclarationMetadataUtil.auditDictionaryMetadata","auditTagUtil.aiDefaultOptions","dictionaries: Dictionary[]","auditTagUtil.auditTag","autocompleteUtil.aiDefaultOptions","autocompleteUtil.autocomplete","numberOfMessagesById: Record<string, number>"],"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import {\n type AIConfig,\n type AIOptions,\n type ChatCompletionRequestMessage,\n getAIConfig,\n} from '@intlayer/ai';\nimport type { KeyPath, Locale } from '@intlayer/types';\nimport type { ResponseWithSession } from '@middlewares/sessionAuth.middleware';\nimport { getDictionariesByTags } from '@services/dictionary.service';\nimport * as tagService from '@services/tag.service';\nimport { getTagsByKeys } from '@services/tag.service';\nimport * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport * as auditContentDeclarationUtil from '@utils/AI/auditDictionary';\nimport * as auditContentDeclarationFieldUtil from '@utils/AI/auditDictionaryField';\nimport * as auditContentDeclarationMetadataUtil from '@utils/AI/auditDictionaryMetadata';\nimport * as auditTagUtil from '@utils/AI/auditTag';\nimport * as autocompleteUtil from '@utils/AI/autocomplete';\nimport * as customQueryUtil from '@utils/AI/customQuery';\nimport * as translateJSONUtil from '@utils/AI/translateJSON';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DiscussionFiltersParams,\n getDiscussionFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDiscussionFiltersAndPagination';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { DiscussionAPI } from '@/types/discussion.types';\nimport type { Tag, TagAPI } from '@/types/tag.types';\n\ntype ReplaceAIConfigByOptions<T> = Omit<T, 'aiConfig'> & {\n aiOptions?: AIOptions;\n};\n\nexport type CustomQueryBody =\n ReplaceAIConfigByOptions<customQueryUtil.CustomQueryOptions> & {\n tagsKeys?: string[];\n applicationContext?: string;\n };\nexport type CustomQueryResult =\n ResponseData<customQueryUtil.CustomQueryResultData>;\n\nexport const customQuery = async (\n req: Request<CustomQueryBody>,\n res: ResponseWithSession<CustomQueryResult>,\n _next: NextFunction\n): Promise<void> => {\n // biome-ignore lint/correctness/noUnusedVariables: Just filter out tagsKeys\n const { aiOptions, tagsKeys, ...rest } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: customQueryUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n const auditResponse = await customQueryUtil.customQuery({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'QUERY_FAILED');\n return;\n }\n\n const responseData = formatResponse<customQueryUtil.CustomQueryResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type TranslateJSONBody = Omit<\n ReplaceAIConfigByOptions<translateJSONUtil.TranslateJSONOptions<JSON>>,\n 'tags'\n> & {\n tagsKeys?: string[];\n};\nexport type TranslateJSONResult = ResponseData<\n translateJSONUtil.TranslateJSONResultData<JSON>\n>;\n\nexport const translateJSON = async (\n req: Request<TranslateJSONBody>,\n res: ResponseWithSession<TranslateJSONResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { aiOptions, tagsKeys, ...rest } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: translateJSONUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId && tagsKeys) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await translateJSONUtil.translateJSON<any>({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData = formatResponse<\n translateJSONUtil.TranslateJSONResultData<any>\n >({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n defaultLocale: Locale;\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n};\nexport type AuditContentDeclarationResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclaration = async (\n req: Request<AuditContentDeclarationBody>,\n res: ResponseWithSession<AuditContentDeclarationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } =\n req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await auditContentDeclarationUtil.auditDictionary({\n fileContent,\n filePath,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n defaultLocale,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationFieldBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n req: Request<AuditContentDeclarationFieldBody>,\n res: ResponseWithSession<AuditContentDeclarationFieldResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { fileContent, aiOptions, locales, tagsKeys, keyPath } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationFieldUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse =\n await auditContentDeclarationFieldUtil.auditDictionaryField({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n tags,\n keyPath,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>(\n {\n data: auditResponse,\n }\n );\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditContentDeclarationMetadataBody = {\n aiOptions?: AIOptions;\n fileContent: string;\n};\n\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationMetadataUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationMetadata = async (\n req: Request<AuditContentDeclarationMetadataBody>,\n res: ResponseWithSession<AuditContentDeclarationMetadataResult>,\n _next: NextFunction\n): Promise<void> => {\n const { organization } = res.locals;\n const { fileContent, aiOptions } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationMetadataUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n const tags: Tag[] = await tagService.findTags(\n {\n organizationId: organization?.id,\n },\n 0,\n 1000\n );\n\n const auditResponse =\n await auditContentDeclarationMetadataUtil.auditDictionaryMetadata({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<auditContentDeclarationMetadataUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AuditTagBody = {\n aiOptions?: AIOptions;\n tag: TagAPI;\n};\nexport type AuditTagResult = ResponseData<auditTagUtil.TranslateJSONResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditTag = async (\n req: Request<undefined, undefined, AuditTagBody>,\n res: ResponseWithSession<AuditTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { project } = res.locals;\n const { aiOptions, tag } = req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: auditTagUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n try {\n let dictionaries: Dictionary[] = [];\n if (project?.organizationId) {\n dictionaries = await getDictionariesByTags([tag.key], project.id);\n }\n\n const auditResponse = await auditTagUtil.auditTag({\n aiConfig,\n dictionaries,\n tag,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData = formatResponse<auditTagUtil.TranslateJSONResultData>({\n data: auditResponse,\n });\n\n res.json(responseData);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type AskDocQuestionBody = {\n messages: ChatCompletionRequestMessage[];\n discussionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n req: Request<undefined, undefined, AskDocQuestionBody>,\n res: ResponseWithSession<AskDocQuestionResult>,\n _next: NextFunction\n): Promise<void> => {\n const { messages = [], discussionId } = req.body;\n const { user, project, organization } = res.locals;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: {},\n accessType: ['public'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n // 1. Prepare SSE headers and flush them NOW\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no'); // disable nginx buffering\n res.flushHeaders?.();\n res.write(': connected\\n\\n'); // initial comment keeps some browsers happy\n res.flush?.();\n\n // 2. Kick off the upstream stream WITHOUT awaiting it\n askDocQuestionUtil\n .askDocQuestion(messages, aiConfig, {\n onMessage: (chunk) => {\n res.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n res.flush?.();\n },\n })\n .then(async (fullResponse) => {\n const lastUserMessageContent = messages.findLast(\n (message) => message.role === 'user'\n )?.content;\n const lastUserMessageNbWords = lastUserMessageContent\n ? lastUserMessageContent.split(' ').length\n : 0;\n if (lastUserMessageNbWords > 2) {\n // If the last user message is less than 3 words, don't persist the discussion\n // Example: \"Hello\", \"Hi\", \"Hey\", \"test\", etc.\n\n // 3. Persist discussion while the client already has all chunks\n await DiscussionModel.findOneAndUpdate(\n { discussionId },\n {\n $set: {\n discussionId,\n userId: user?.id,\n projectId: project?.id,\n organizationId: organization?.id,\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp,\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n },\n },\n { upsert: true, new: true }\n );\n }\n\n // 4. Tell the client we're done and close the stream\n res.write(\n `data: ${JSON.stringify({ done: true, response: fullResponse })}\\n\\n`\n );\n res.end();\n })\n .catch((err) => {\n // propagate error as an SSE event so the client knows why it closed\n res.write(\n `event: error\\ndata: ${JSON.stringify({ message: err.message })}\\n\\n`\n );\n res.end();\n });\n};\n\nexport type AutocompleteBody = {\n text: string;\n aiOptions?: AIOptions;\n contextBefore?: string;\n currentLine?: string;\n contextAfter?: string;\n};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n req: Request<AutocompleteBody>,\n res: ResponseWithSession<AutocompleteResponse>,\n _next: NextFunction\n): Promise<void> => {\n try {\n const { text, aiOptions, contextBefore, currentLine, contextAfter } =\n req.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: autocompleteUtil.aiDefaultOptions,\n accessType: ['public'],\n },\n !!res.locals.user\n );\n } catch (_error) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n contextBefore,\n currentLine,\n contextAfter,\n })) ?? {\n autocompletion: '',\n tokenUsed: 0,\n };\n\n const responseData =\n formatResponse<autocompleteUtil.AutocompleteFileResultData>({\n data: response,\n });\n\n res.json(responseData);\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n\nexport type GetDiscussionsParams =\n | ({\n page?: string | number;\n pageSize?: string | number;\n includeMessages?: 'true' | 'false';\n } & DiscussionFiltersParams)\n | undefined;\n\nexport type GetDiscussionsResult = PaginatedResponse<DiscussionAPI>;\n\n/**\n * Retrieves a list of discussions with filters and pagination.\n * Only the owner or admins can access. By default, users only see their own.\n */\nexport const getDiscussions = async (\n req: Request<GetDiscussionsParams>,\n res: ResponseWithSession<GetDiscussionsResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, roles } = res.locals;\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDiscussionFiltersAndPagination(req, res);\n const includeMessagesParam = (req.query as any)?.includeMessages as\n | 'true'\n | 'false'\n | undefined;\n const includeMessages = includeMessagesParam !== 'false';\n\n if (!user) {\n ErrorHandler.handleGenericErrorResponse(res, 'USER_NOT_DEFINED');\n return;\n }\n\n try {\n const projection = includeMessages ? {} : { messages: 0 };\n const discussions = await DiscussionModel.find(filters, projection)\n .sort(sortOptions)\n .skip(skip)\n .limit(pageSize)\n .lean();\n\n // Compute number of messages for each discussion\n const numberOfMessagesById: Record<string, number> = {};\n if (!includeMessages && discussions.length > 0) {\n const ids = discussions.map((d: any) => d._id);\n const counts = await DiscussionModel.aggregate([\n { $match: { _id: { $in: ids } } },\n {\n $project: {\n numberOfMessages: { $size: { $ifNull: ['$messages', []] } },\n },\n },\n ]);\n for (const c of counts as any[]) {\n numberOfMessagesById[String(c._id)] = c.numberOfMessages ?? 0;\n }\n }\n\n // Permission: allow admin, or the owner for all returned entries\n const allOwnedByUser = discussions.every(\n (d) => String(d.userId) === String(user.id)\n );\n const isAllowed = roles.includes('admin') || allOwnedByUser;\n\n if (!isAllowed) {\n ErrorHandler.handleGenericErrorResponse(res, 'PERMISSION_DENIED');\n return;\n }\n\n const totalItems = await DiscussionModel.countDocuments(filters);\n\n const responseData = formatPaginatedResponse({\n data: discussions.map((d: any) => ({\n ...d,\n id: String(d._id ?? d.id),\n numberOfMessages: includeMessages\n ? Array.isArray(d.messages)\n ? d.messages.length\n : 0\n : (numberOfMessagesById[String(d._id ?? d.id)] ?? 0),\n })),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n res.json(responseData as any);\n return;\n } catch (error) {\n ErrorHandler.handleAppErrorResponse(res, error as AppError);\n return;\n }\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ai.controller.mjs","names":["aiConfig: AIConfig","customQueryUtil.aiDefaultOptions","customQueryUtil.customQuery","translateJSONUtil.aiDefaultOptions","tags: Tag[]","translateJSONUtil.translateJSON","auditContentDeclarationUtil.aiDefaultOptions","auditContentDeclarationUtil.auditDictionary","auditContentDeclarationFieldUtil.aiDefaultOptions","auditContentDeclarationFieldUtil.auditDictionaryField","auditContentDeclarationMetadataUtil.aiDefaultOptions","tagService.findTags","auditContentDeclarationMetadataUtil.auditDictionaryMetadata","auditTagUtil.aiDefaultOptions","dictionaries: Dictionary[]","auditTagUtil.auditTag","autocompleteUtil.aiDefaultOptions","autocompleteUtil.autocomplete","numberOfMessagesById: Record<string, number>"],"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import {\n type AIConfig,\n type AIOptions,\n type ChatCompletionRequestMessage,\n getAIConfig,\n} from '@intlayer/ai';\nimport type { KeyPath, Locale } from '@intlayer/types';\nimport { getDictionariesByTags } from '@services/dictionary.service';\nimport * as tagService from '@services/tag.service';\nimport { getTagsByKeys } from '@services/tag.service';\nimport * as askDocQuestionUtil from '@utils/AI/askDocQuestion/askDocQuestion';\nimport * as auditContentDeclarationUtil from '@utils/AI/auditDictionary';\nimport * as auditContentDeclarationFieldUtil from '@utils/AI/auditDictionaryField';\nimport * as auditContentDeclarationMetadataUtil from '@utils/AI/auditDictionaryMetadata';\nimport * as auditTagUtil from '@utils/AI/auditTag';\nimport * as autocompleteUtil from '@utils/AI/autocomplete';\nimport * as customQueryUtil from '@utils/AI/customQuery';\nimport * as translateJSONUtil from '@utils/AI/translateJSON';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport {\n type DiscussionFiltersParams,\n getDiscussionFiltersAndPagination,\n} from '@utils/filtersAndPagination/getDiscussionFiltersAndPagination';\nimport {\n formatPaginatedResponse,\n formatResponse,\n type PaginatedResponse,\n type ResponseData,\n} from '@utils/responseData';\nimport type { FastifyReply, FastifyRequest } from 'fastify';\nimport { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { DiscussionAPI } from '@/types/discussion.types';\nimport type { Tag, TagAPI } from '@/types/tag.types';\n\ntype ReplaceAIConfigByOptions<T> = Omit<T, 'aiConfig'> & {\n aiOptions?: AIOptions;\n};\n\nexport type CustomQueryBody =\n ReplaceAIConfigByOptions<customQueryUtil.CustomQueryOptions> & {\n tagsKeys?: string[];\n applicationContext?: string;\n };\nexport type CustomQueryResult =\n ResponseData<customQueryUtil.CustomQueryResultData>;\n\nexport const customQuery = async (\n request: FastifyRequest<{ Body: CustomQueryBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { aiOptions, tagsKeys, ...rest } = request.body;\n const { user, project } = request.locals || {};\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: customQueryUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n const auditResponse = await customQueryUtil.customQuery({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'QUERY_FAILED');\n }\n\n const responseData = formatResponse<customQueryUtil.CustomQueryResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type TranslateJSONBody = Omit<\n ReplaceAIConfigByOptions<translateJSONUtil.TranslateJSONOptions<JSON>>,\n 'tags'\n> & {\n tagsKeys?: string[];\n};\nexport type TranslateJSONResult = ResponseData<\n translateJSONUtil.TranslateJSONResultData<JSON>\n>;\n\nexport const translateJSON = async (\n request: FastifyRequest<{ Body: TranslateJSONBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { aiOptions, tagsKeys, ...rest } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: translateJSONUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId && tagsKeys) {\n tags = await getTagsByKeys(tagsKeys, project.organizationId);\n }\n\n const auditResponse = await translateJSONUtil.translateJSON<any>({\n ...rest,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData = formatResponse<\n translateJSONUtil.TranslateJSONResultData<any>\n >({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n defaultLocale: Locale;\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n};\nexport type AuditContentDeclarationResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclaration = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } =\n request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditContentDeclarationUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);\n }\n\n const auditResponse = await auditContentDeclarationUtil.auditDictionary({\n fileContent,\n filePath,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n defaultLocale,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationFieldBody = {\n aiOptions?: AIOptions;\n locales: Locale[];\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationFieldBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { fileContent, aiOptions, locales, tagsKeys, keyPath } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditContentDeclarationFieldUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let tags: Tag[] = [];\n\n if (project?.organizationId) {\n tags = await getTagsByKeys(tagsKeys ?? [], project.organizationId);\n }\n\n const auditResponse =\n await auditContentDeclarationFieldUtil.auditDictionaryField({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n locales,\n tags,\n keyPath,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationFieldUtil.AuditDictionaryFieldResultData>(\n {\n data: auditResponse,\n }\n );\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditContentDeclarationMetadataBody = {\n aiOptions?: AIOptions;\n fileContent: string;\n};\n\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationMetadataUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationMetadata = async (\n request: FastifyRequest<{ Body: AuditContentDeclarationMetadataBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { organization, user } = request.locals || {};\n const { fileContent, aiOptions } = request.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n defaultOptions: auditContentDeclarationMetadataUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n const tags: Tag[] = await tagService.findTags(\n {\n organizationId: organization?.id,\n },\n 0,\n 1000\n );\n\n const auditResponse =\n await auditContentDeclarationMetadataUtil.auditDictionaryMetadata({\n fileContent,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n tags,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData =\n formatResponse<auditContentDeclarationMetadataUtil.AuditFileResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AuditTagBody = {\n aiOptions?: AIOptions;\n tag: TagAPI;\n};\nexport type AuditTagResult = ResponseData<auditTagUtil.TranslateJSONResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditTag = async (\n request: FastifyRequest<{ Body: AuditTagBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { project, user } = request.locals || {};\n const { aiOptions, tag } = request.body;\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: auditTagUtil.aiDefaultOptions,\n accessType: ['registered_user', 'apiKey'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n try {\n let dictionaries: Dictionary[] = [];\n if (project?.organizationId) {\n dictionaries = await getDictionariesByTags([tag.key], project.id);\n }\n\n const auditResponse = await auditTagUtil.auditTag({\n aiConfig,\n dictionaries,\n tag,\n applicationContext: aiOptions?.applicationContext,\n });\n\n if (!auditResponse) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AUDIT_FAILED');\n }\n\n const responseData = formatResponse<auditTagUtil.TranslateJSONResultData>({\n data: auditResponse,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type AskDocQuestionBody = {\n messages: ChatCompletionRequestMessage[];\n discussionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n request: FastifyRequest<{ Body: AskDocQuestionBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { messages = [], discussionId } = request.body;\n const { user, project, organization } = request.locals || {};\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: {},\n projectOptions: projectAIOptions,\n accessType: ['public'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n // 1. Prepare SSE headers and flush them NOW\n reply.raw.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n reply.raw.setHeader('Cache-Control', 'no-cache, no-transform');\n reply.raw.setHeader('Connection', 'keep-alive');\n reply.raw.setHeader('X-Accel-Buffering', 'no'); // disable nginx buffering\n reply.raw.flushHeaders?.();\n reply.raw.write(': connected\\n\\n'); // initial comment keeps some browsers happy\n\n // 2. Kick off the upstream stream WITHOUT awaiting it\n askDocQuestionUtil\n .askDocQuestion(messages, aiConfig, {\n onMessage: (chunk) => {\n reply.raw.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n },\n })\n .then(async (fullResponse) => {\n const lastUserMessageContent = messages.findLast(\n (message) => message.role === 'user'\n )?.content;\n const lastUserMessageNbWords = lastUserMessageContent\n ? lastUserMessageContent.split(' ').length\n : 0;\n if (lastUserMessageNbWords > 2) {\n // If the last user message is less than 3 words, don't persist the discussion\n // Example: \"Hello\", \"Hi\", \"Hey\", \"test\", etc.\n\n // 3. Persist discussion while the client already has all chunks\n await DiscussionModel.findOneAndUpdate(\n { discussionId },\n {\n $set: {\n discussionId,\n userId: user?.id,\n projectId: project?.id,\n organizationId: organization?.id,\n messages: [\n ...messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: msg.timestamp,\n })),\n {\n role: 'assistant',\n content: fullResponse.response,\n relatedFiles: fullResponse.relatedFiles,\n timestamp: new Date(),\n },\n ],\n },\n },\n { upsert: true, new: true }\n );\n }\n\n // 4. Tell the client we're done and close the stream\n reply.raw.write(\n `data: ${JSON.stringify({ done: true, response: fullResponse })}\\n\\n`\n );\n reply.raw.end();\n })\n .catch((err) => {\n // propagate error as an SSE event so the client knows why it closed\n reply.raw.write(\n `event: error\\ndata: ${JSON.stringify({ message: err.message })}\\n\\n`\n );\n reply.raw.end();\n });\n};\n\nexport type AutocompleteBody = {\n text: string;\n aiOptions?: AIOptions;\n contextBefore?: string;\n currentLine?: string;\n contextAfter?: string;\n};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n request: FastifyRequest<{ Body: AutocompleteBody }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, project } = request.locals || {};\n\n const projectAIOptions = project?.configuration?.ai\n ? (project.configuration.ai as AIOptions)\n : undefined;\n\n try {\n const { text, aiOptions, contextBefore, currentLine, contextAfter } =\n request.body;\n\n let aiConfig: AIConfig;\n try {\n aiConfig = await getAIConfig(\n {\n userOptions: aiOptions,\n projectOptions: projectAIOptions,\n defaultOptions: autocompleteUtil.aiDefaultOptions,\n accessType: ['public'],\n },\n !!user\n );\n } catch (_error) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'AI_ACCESS_DENIED');\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n aiConfig,\n applicationContext: aiOptions?.applicationContext,\n contextBefore,\n currentLine,\n contextAfter,\n })) ?? {\n autocompletion: '',\n tokenUsed: 0,\n };\n\n const responseData =\n formatResponse<autocompleteUtil.AutocompleteFileResultData>({\n data: response,\n });\n\n return reply.send(responseData);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n\nexport type GetDiscussionsParams =\n | ({\n page?: string | number;\n pageSize?: string | number;\n includeMessages?: 'true' | 'false';\n } & DiscussionFiltersParams)\n | undefined;\n\nexport type GetDiscussionsResult = PaginatedResponse<DiscussionAPI>;\n\n/**\n * Retrieves a list of discussions with filters and pagination.\n * Only the owner or admins can access. By default, users only see their own.\n */\nexport const getDiscussions = async (\n request: FastifyRequest<{ Querystring: GetDiscussionsParams }>,\n reply: FastifyReply\n): Promise<void> => {\n const { user, roles } = request.locals || {};\n const { filters, sortOptions, pageSize, skip, page, getNumberOfPages } =\n getDiscussionFiltersAndPagination(request);\n const includeMessagesParam = (request.query as any)?.includeMessages as\n | 'true'\n | 'false'\n | undefined;\n const includeMessages = includeMessagesParam !== 'false';\n\n if (!user) {\n return ErrorHandler.handleGenericErrorResponse(reply, 'USER_NOT_DEFINED');\n }\n\n try {\n const projection = includeMessages ? {} : { messages: 0 };\n const discussions = await DiscussionModel.find(filters, projection)\n .sort(sortOptions)\n .skip(skip)\n .limit(pageSize)\n .lean();\n\n // Compute number of messages for each discussion\n const numberOfMessagesById: Record<string, number> = {};\n if (!includeMessages && discussions.length > 0) {\n const ids = discussions.map((d: any) => d._id);\n const counts = await DiscussionModel.aggregate([\n { $match: { _id: { $in: ids } } },\n {\n $project: {\n numberOfMessages: { $size: { $ifNull: ['$messages', []] } },\n },\n },\n ]);\n for (const c of counts as any[]) {\n numberOfMessagesById[String(c._id)] = c.numberOfMessages ?? 0;\n }\n }\n\n // Permission: allow admin, or the owner for all returned entries\n const allOwnedByUser = discussions.every(\n (d) => String(d.userId) === String(user.id)\n );\n const isAllowed = roles?.includes('admin') || allOwnedByUser;\n\n if (!isAllowed) {\n return ErrorHandler.handleGenericErrorResponse(\n reply,\n 'PERMISSION_DENIED'\n );\n }\n\n const totalItems = await DiscussionModel.countDocuments(filters);\n\n const responseData = formatPaginatedResponse({\n data: discussions.map((d: any) => ({\n ...d,\n id: String(d._id ?? d.id),\n numberOfMessages: includeMessages\n ? Array.isArray(d.messages)\n ? d.messages.length\n : 0\n : (numberOfMessagesById[String(d._id ?? d.id)] ?? 0),\n })),\n page,\n pageSize,\n totalPages: getNumberOfPages(totalItems),\n totalItems,\n });\n\n return reply.send(responseData as any);\n } catch (error) {\n return ErrorHandler.handleAppErrorResponse(reply, error as AppError);\n }\n};\n"],"mappings":"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"}