@yrpri/api 9.0.231 → 9.0.233

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 (69) hide show
  1. package/agents/managers/subscriptionManager.js +2 -2
  2. package/controllers/allOurIdeas.js +1 -1
  3. package/controllers/communities.cjs +282 -91
  4. package/controllers/domains.cjs +54 -8
  5. package/controllers/groups.cjs +51 -6
  6. package/controllers/points.cjs +4 -9
  7. package/controllers/posts.cjs +7 -9
  8. package/controllers/ratings.cjs +3 -6
  9. package/models/point.cjs +31 -3
  10. package/models/post.cjs +2 -3
  11. package/package.json +32 -67
  12. package/services/engine/allOurIdeas/aiHelper.d.ts +7 -4
  13. package/services/engine/allOurIdeas/aiHelper.js +34 -19
  14. package/services/engine/allOurIdeas/explainAnswersAssistant.d.ts +1 -1
  15. package/services/engine/allOurIdeas/explainAnswersAssistant.js +3 -9
  16. package/services/engine/moderation/fraud/CreateFraudAuditReport.cjs +35 -11
  17. package/services/engine/moderation/fraud/CreateFraudAuditReport.d.cts +21 -0
  18. package/services/engine/moderation/fraud/FraudBase.cjs +38 -18
  19. package/services/engine/moderation/fraud/FraudBase.d.cts +2 -0
  20. package/services/engine/moderation/fraud/FraudDeleteBase.cjs +48 -29
  21. package/services/engine/moderation/fraud/FraudDeleteBase.d.cts +8 -6
  22. package/services/engine/moderation/fraud/FraudDeleteEndorsements.cjs +5 -4
  23. package/services/engine/moderation/fraud/FraudDeleteEndorsements.d.cts +2 -2
  24. package/services/engine/moderation/fraud/FraudDeletePointQualities.cjs +3 -2
  25. package/services/engine/moderation/fraud/FraudDeletePointQualities.d.cts +1 -1
  26. package/services/engine/moderation/fraud/FraudDeletePoints.cjs +3 -2
  27. package/services/engine/moderation/fraud/FraudDeletePoints.d.cts +1 -1
  28. package/services/engine/moderation/fraud/FraudDeletePosts.cjs +3 -2
  29. package/services/engine/moderation/fraud/FraudDeleteRatings.cjs +61 -4
  30. package/services/engine/moderation/fraud/FraudGetBase.cjs +44 -20
  31. package/services/engine/moderation/fraud/FraudGetBase.d.cts +5 -0
  32. package/services/engine/moderation/fraud/FraudGetEndorsements.cjs +4 -13
  33. package/services/engine/moderation/fraud/FraudGetEndorsements.d.cts +1 -1
  34. package/services/engine/moderation/fraud/FraudGetPointQualities.cjs +3 -0
  35. package/services/engine/moderation/fraud/FraudGetPointQualities.d.cts +1 -1
  36. package/services/engine/moderation/fraud/FraudGetPoints.cjs +3 -0
  37. package/services/engine/moderation/fraud/FraudGetPoints.d.cts +1 -1
  38. package/services/engine/moderation/fraud/FraudGetPosts.cjs +17 -16
  39. package/services/engine/moderation/fraud/FraudGetPosts.d.cts +3 -3
  40. package/services/engine/moderation/fraud/FraudGetRatings.cjs +62 -30
  41. package/services/engine/moderation/fraud/FraudGetRatings.d.cts +4 -1
  42. package/services/engine/moderation/fraud/FraudRequestValidation.cjs +143 -0
  43. package/services/engine/moderation/fraud/FraudRequestValidation.d.cts +21 -0
  44. package/services/engine/moderation/fraud/FraudScannerNotifier.cjs +59 -35
  45. package/services/engine/moderation/fraud/FraudScannerNotifier.d.cts +20 -1
  46. package/services/llms/baseChatBot.d.ts +2 -0
  47. package/services/llms/baseChatBot.js +25 -9
  48. package/services/llms/imageGeneration/chatGptImageGenerator.d.ts +2 -2
  49. package/services/llms/imageGeneration/chatGptImageGenerator.js +13 -10
  50. package/services/llms/imageGeneration/collectionImageGenerator.js +31 -13
  51. package/services/llms/imageGeneration/dalleImageGenerator.d.ts +2 -2
  52. package/services/llms/imageGeneration/dalleImageGenerator.js +28 -16
  53. package/services/llms/imageGeneration/fluxImageGenerator.d.ts +2 -2
  54. package/services/llms/imageGeneration/fluxImageGenerator.js +9 -3
  55. package/services/llms/imageGeneration/iImageGenerator.d.ts +8 -1
  56. package/services/llms/imageGeneration/imageModelConfig.cjs +319 -0
  57. package/services/llms/imageGeneration/imageModelConfig.d.cts +79 -0
  58. package/services/llms/imageGeneration/imagenImageGenerator.d.ts +2 -3
  59. package/services/llms/imageGeneration/imagenImageGenerator.js +10 -10
  60. package/tests/fraudManagement.test.cjs +470 -0
  61. package/tests/fraudManagement.test.d.cts +1 -0
  62. package/tests/imageModelConfig.test.cjs +144 -0
  63. package/tests/imageModelConfig.test.d.cts +1 -0
  64. package/utils/ai_image_generation_guard.cjs +268 -0
  65. package/utils/ai_image_generation_guard.d.cts +34 -0
  66. package/utils/fingerprint_data.cjs +32 -0
  67. package/utils/fingerprint_data.d.cts +6 -0
  68. package/utils/recount_utils.cjs +53 -37
  69. package/utils/recount_utils.d.cts +7 -7
@@ -24,6 +24,8 @@ const performSingleModerationAction = require("../services/engine/moderation/pro
24
24
  const request = require("../utils/requestCompat.cjs");
25
25
  const { updateAnswerTranslation, } = require("../services/utils/translation_helpers.cjs");
26
26
  const { updateSurveyTranslation, } = require("../services/utils/translation_helpers.cjs");
27
+ const { normalizeImageGenerationProfileOptions, } = require("../services/llms/imageGeneration/imageModelConfig.cjs");
28
+ const { validateImageGenerationStartRequest, canPollImageGenerationJob, publicJobFields, } = require("../utils/ai_image_generation_guard.cjs");
27
29
  const { plausibleStatsProxy, getPlausibleStats, } = require("../services/engine/analytics/plausible/manager.cjs");
28
30
  const { countAllModeratedItemsByGroup, } = require("../services/engine/moderation/get_moderation_items.cjs");
29
31
  const { isValidDbId } = require("../utils/is_valid_db_id.cjs");
@@ -1647,8 +1649,37 @@ router.get("/:groupId/:jobId/report_creation_progress", auth.can("edit group"),
1647
1649
  });
1648
1650
  });
1649
1651
  //TODO: Fix this permission back to edit
1650
- router.post("/:groupId/:start_generating/ai_image", auth.can("view group"), function (req, res) {
1651
- models.AcBackgroundJob.createJob({}, {}, (error, jobId) => {
1652
+ router.post("/:groupId/:start_generating/ai_image", auth.can("view group"), async function (req, res) {
1653
+ const imageOptions = normalizeImageGenerationProfileOptions(req.body.imageGenerationProfile, req.body.generationContext, req.body.imageType);
1654
+ if (imageOptions.error) {
1655
+ res.status(400).send({ error: imageOptions.error });
1656
+ return;
1657
+ }
1658
+ let guard;
1659
+ try {
1660
+ guard = await validateImageGenerationStartRequest(req, {
1661
+ collectionType: "group",
1662
+ collectionId: req.params.groupId,
1663
+ });
1664
+ }
1665
+ catch (error) {
1666
+ log.error("Could not validate image generation request", {
1667
+ err: error,
1668
+ context: "start_generating_ai_image",
1669
+ user: req.user ? toJson(req.user.simple()) : null,
1670
+ });
1671
+ res.sendStatus(500);
1672
+ return;
1673
+ }
1674
+ if (!guard.allowed) {
1675
+ res.status(guard.status).send(guard.body);
1676
+ return;
1677
+ }
1678
+ const internalData = {
1679
+ ...guard.internalData,
1680
+ imageGenerationProfile: imageOptions.imageGenerationProfile,
1681
+ };
1682
+ models.AcBackgroundJob.createJob({}, internalData, (error, jobId) => {
1652
1683
  if (error) {
1653
1684
  log.error("Could not create backgroundJob", {
1654
1685
  err: error,
@@ -1660,13 +1691,18 @@ router.post("/:groupId/:start_generating/ai_image", auth.can("view group"), func
1660
1691
  else {
1661
1692
  queue.add("process-generative-ai", {
1662
1693
  type: "collection-image",
1663
- //TODO: Look into this
1664
- userId: req.user ? req.user.id : 1,
1694
+ userId: guard.userId,
1665
1695
  jobId: jobId,
1666
1696
  collectionId: req.params.groupId,
1667
1697
  collectionType: "group",
1668
1698
  prompt: req.body.prompt,
1669
1699
  imageType: req.body.imageType,
1700
+ generationContext: guard.generationContext,
1701
+ imageGenerationProfile: imageOptions.imageGenerationProfile,
1702
+ imageProvider: imageOptions.imageProvider,
1703
+ imageModel: imageOptions.imageModel,
1704
+ imageSize: imageOptions.imageSize,
1705
+ imageQuality: imageOptions.imageQuality,
1670
1706
  }, "critical");
1671
1707
  res.send({ jobId });
1672
1708
  }
@@ -1674,14 +1710,23 @@ router.post("/:groupId/:start_generating/ai_image", auth.can("view group"), func
1674
1710
  });
1675
1711
  //TODO: Fix this permission back to edit
1676
1712
  router.get("/:groupId/:jobId/poll_for_generating_ai_image", auth.can("view group"), function (req, res) {
1713
+ if (!req.user) {
1714
+ res.sendStatus(401);
1715
+ return;
1716
+ }
1677
1717
  models.AcBackgroundJob.findOne({
1678
1718
  where: {
1679
1719
  id: req.params.jobId,
1680
1720
  },
1681
- attributes: ["id", "progress", "error", "data"],
1721
+ attributes: ["id", "progress", "error", "data", "internal_data"],
1682
1722
  })
1683
1723
  .then((job) => {
1684
- res.send(job);
1724
+ if (!canPollImageGenerationJob(req, job)) {
1725
+ res.sendStatus(404);
1726
+ }
1727
+ else {
1728
+ res.send(publicJobFields(job));
1729
+ }
1685
1730
  })
1686
1731
  .catch((error) => {
1687
1732
  log.error("Could not get backgroundJob", {
@@ -9,6 +9,7 @@ var async = require("async");
9
9
  const ogs = require("open-graph-scraper");
10
10
  var _ = require("lodash");
11
11
  var queue = require("../services/workers/queue.cjs");
12
+ const { getFingerprintDataFromBody, } = require("../utils/fingerprint_data.cjs");
12
13
  var changePointCounter = function (pointId, column, upDown, next) {
13
14
  models.Point.findOne({
14
15
  where: { id: pointId },
@@ -685,9 +686,7 @@ router.post("/:groupId", auth.can("create point"), function (req, res) {
685
686
  user_agent: req.useragent.source,
686
687
  ip_address: req.clientIp,
687
688
  data: {
688
- browserId: req.body.pointBaseId,
689
- browserFingerprint: req.body.pointValCode,
690
- browserFingerprintConfidence: req.body.pointConf,
689
+ ...getFingerprintDataFromBody(req.body, "point"),
691
690
  originalQueryString: req.body.originalQueryString,
692
691
  userLocale: req.body.userLocale,
693
692
  userAutoTranslate: req.body.userAutoTranslate,
@@ -930,9 +929,7 @@ router.post("/:id/pointQuality", auth.can("vote on point"), function (req, res)
930
929
  pointQuality.value = req.body.value;
931
930
  pointQuality.status = "active";
932
931
  pointQuality.set("data", {
933
- browserId: req.body.qualityBaseId,
934
- browserFingerprint: req.body.qualityValCode,
935
- browserFingerprintConfidence: req.body.qualityConf,
932
+ ...getFingerprintDataFromBody(req.body, "quality"),
936
933
  });
937
934
  }
938
935
  else {
@@ -941,9 +938,7 @@ router.post("/:id/pointQuality", auth.can("vote on point"), function (req, res)
941
938
  value: req.body.value,
942
939
  user_id: req.user.id,
943
940
  data: {
944
- browserId: req.body.qualityBaseId,
945
- browserFingerprint: req.body.qualityValCode,
946
- browserFingerprintConfidence: req.body.qualityConf,
941
+ ...getFingerprintDataFromBody(req.body, "quality"),
947
942
  },
948
943
  status: "active",
949
944
  user_agent: req.useragent.source,
@@ -12,6 +12,7 @@ const getAnonymousUser = require("../services/utils/get_anonymous_system_user.cj
12
12
  const moment = require("moment");
13
13
  const { plausibleStatsProxy, } = require("../services/engine/analytics/plausible/manager.cjs");
14
14
  const { isValidDbId } = require("../utils/is_valid_db_id.cjs");
15
+ const { getFingerprintDataFromBody, } = require("../utils/fingerprint_data.cjs");
15
16
  var changePostCounter = function (req, postId, column, upDown, next) {
16
17
  models.Post.findOne({
17
18
  where: { id: postId },
@@ -963,9 +964,7 @@ var truthValueFromBody = function (bodyParameter) {
963
964
  var updatePostData = function (req, post) {
964
965
  if (!post.data) {
965
966
  post.set("data", {
966
- browserId: req.body.postBaseId,
967
- browserFingerprint: req.body.postValCode,
968
- browserFingerprintConfidence: req.body.postConf,
967
+ ...getFingerprintDataFromBody(req.body, "post"),
969
968
  originalQueryString: req.body.originalQueryString,
970
969
  userLocale: req.body.userLocale,
971
970
  userAutoTranslate: req.body.userAutoTranslate,
@@ -1135,6 +1134,9 @@ router.post("/:groupId", auth.can("create post"), async function (req, res) {
1135
1134
  status: "active",
1136
1135
  user_agent: req.useragent.source,
1137
1136
  ip_address: req.clientIp,
1137
+ data: {
1138
+ ...getFingerprintDataFromBody(req.body, "post"),
1139
+ },
1138
1140
  })
1139
1141
  .save()
1140
1142
  .then(function (endorsement) {
@@ -1683,9 +1685,7 @@ router.post("/:id/endorse", auth.can("vote on post"), async function (req, res)
1683
1685
  endorsement.value = req.body.value;
1684
1686
  endorsement.status = "active";
1685
1687
  endorsement.set("data", {
1686
- browserId: req.body.endorsementBaseId,
1687
- browserFingerprint: req.body.endorsementValCode,
1688
- browserFingerprintConfidence: req.body.endorsementConf,
1688
+ ...getFingerprintDataFromBody(req.body, "endorsement"),
1689
1689
  });
1690
1690
  }
1691
1691
  else {
@@ -1694,9 +1694,7 @@ router.post("/:id/endorse", auth.can("vote on post"), async function (req, res)
1694
1694
  value: req.body.value,
1695
1695
  user_id: req.user.id,
1696
1696
  data: {
1697
- browserId: req.body.endorsementBaseId,
1698
- browserFingerprint: req.body.endorsementValCode,
1699
- browserFingerprintConfidence: req.body.endorsementConf,
1697
+ ...getFingerprintDataFromBody(req.body, "endorsement"),
1700
1698
  },
1701
1699
  status: "active",
1702
1700
  user_agent: req.useragent.source,
@@ -9,6 +9,7 @@ var async = require('async');
9
9
  var _ = require('lodash');
10
10
  var queue = require('../services/workers/queue.cjs');
11
11
  const moment = require('moment');
12
+ const { getFingerprintDataFromBody, } = require("../utils/fingerprint_data.cjs");
12
13
  router.post('/:post_id/:type_index', auth.can('rate post'), function (req, res) {
13
14
  var post;
14
15
  models.Rating.findOne({
@@ -30,9 +31,7 @@ router.post('/:post_id/:type_index', auth.can('rate post'), function (req, res)
30
31
  rating.value = req.body.value;
31
32
  post = rating.Post;
32
33
  rating.set('data', {
33
- browserId: req.body.ratingBaseId,
34
- browserFingerprint: req.body.ratingValCode,
35
- browserFingerprintConfidence: req.body.ratingConf
34
+ ...getFingerprintDataFromBody(req.body, "rating"),
36
35
  });
37
36
  }
38
37
  else {
@@ -42,9 +41,7 @@ router.post('/:post_id/:type_index', auth.can('rate post'), function (req, res)
42
41
  value: req.body.value,
43
42
  user_id: req.user.id,
44
43
  data: {
45
- browserId: req.body.ratingBaseId,
46
- browserFingerprint: req.body.ratingValCode,
47
- browserFingerprintConfidence: req.body.ratingConf
44
+ ...getFingerprintDataFromBody(req.body, "rating"),
48
45
  },
49
46
  user_agent: req.useragent.source,
50
47
  ip_address: req.clientIp
package/models/point.cjs CHANGED
@@ -3,6 +3,7 @@ const async = require('async');
3
3
  const queue = require('../services/workers/queue.cjs');
4
4
  const log = require('../utils/logger.cjs');
5
5
  const _ = require("lodash");
6
+ const { getFingerprintDataFromBody, } = require("../utils/fingerprint_data.cjs");
6
7
  const findCommunityAndDomainForPointFromGroup = (sequelize, options, callback) => {
7
8
  sequelize.models.Group.findOne({
8
9
  where: {
@@ -349,9 +350,7 @@ module.exports = (sequelize, DataTypes) => {
349
350
  },
350
351
  (seriesCallback) => {
351
352
  options.data = {
352
- browserId: req.body.pointBaseId,
353
- browserFingerprint: req.body.pointValCode,
354
- browserFingerprintConfidence: req.body.pointConf,
353
+ ...getFingerprintDataFromBody(req.body, "point"),
355
354
  originalQueryString: req.body.originalQueryString,
356
355
  userLocale: req.body.userLocale,
357
356
  userAutoTranslate: req.body.userAutoTranslate,
@@ -541,6 +540,35 @@ module.exports = (sequelize, DataTypes) => {
541
540
  options.status = 'published';
542
541
  options.user_agent = req.useragent.source;
543
542
  options.ip_address = req.clientIp;
543
+ const body = req.body || {};
544
+ const data = options.data ? { ...options.data } : {};
545
+ const fingerprintData = getFingerprintDataFromBody(body, "point");
546
+ const optionalDataKeys = [
547
+ "originalQueryString",
548
+ "userLocale",
549
+ "userAutoTranslate",
550
+ "referrer",
551
+ "url",
552
+ "screen_width"
553
+ ];
554
+ if (fingerprintData.browserId) {
555
+ data.browserId = fingerprintData.browserId;
556
+ }
557
+ if (fingerprintData.browserFingerprint) {
558
+ data.browserFingerprint = fingerprintData.browserFingerprint;
559
+ }
560
+ if (fingerprintData.browserId ||
561
+ fingerprintData.browserFingerprint ||
562
+ body.pointConf !== undefined) {
563
+ data.browserFingerprintConfidence =
564
+ fingerprintData.browserFingerprintConfidence;
565
+ }
566
+ optionalDataKeys.forEach(key => {
567
+ if (body[key] !== undefined) {
568
+ data[key] = body[key];
569
+ }
570
+ });
571
+ options.data = data;
544
572
  sequelize.models.Point.build(options).save().then((point) => {
545
573
  options.point_id = point.id;
546
574
  const pointRevision = sequelize.models.PointRevision.build(options);
package/models/post.cjs CHANGED
@@ -3,6 +3,7 @@ const async = require("async");
3
3
  const queue = require('../services/workers/queue.cjs');
4
4
  const log = require('../utils/logger.cjs');
5
5
  const _ = require('lodash');
6
+ const { getFingerprintDataFromBody, } = require("../utils/fingerprint_data.cjs");
6
7
  module.exports = (sequelize, DataTypes) => {
7
8
  const Post = sequelize.define("Post", {
8
9
  content_type: { type: DataTypes.INTEGER, allowNull: false },
@@ -490,9 +491,7 @@ module.exports = (sequelize, DataTypes) => {
490
491
  user_agent: req.useragent.source,
491
492
  ip_address: req.clientIp,
492
493
  data: {
493
- browserId: req.body.pointBaseId,
494
- browserFingerprint: req.body.pointValCode,
495
- browserFingerprintConfidence: req.body.pointConf,
494
+ ...getFingerprintDataFromBody(req.body, "point"),
496
495
  originalQueryString: req.body.originalQueryString,
497
496
  userLocale: req.body.userLocale,
498
497
  userAutoTranslate: req.body.userAutoTranslate,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yrpri/api",
3
- "version": "9.0.231",
3
+ "version": "9.0.233",
4
4
  "license": "MIT",
5
5
  "author": "Robert Bjarnason & Citizens Foundation",
6
6
  "repository": {
@@ -18,18 +18,18 @@
18
18
  "dependencies": {
19
19
  "@airbrake/node": "^2.1.9",
20
20
  "@aws-sdk/client-s3": "^3.1029.0",
21
- "@google-cloud/aiplatform": "^6.5.0",
22
- "@google-cloud/speech": "^7.3.0",
21
+ "@google-cloud/aiplatform": "^6.8.0",
22
+ "@google-cloud/speech": "^7.3.1",
23
23
  "@google-cloud/storage": "^7.19.0",
24
- "@google-cloud/translate": "^9.3.0",
24
+ "@google-cloud/translate": "^9.4.1",
25
25
  "@google-cloud/vertexai": "^1.10.0",
26
- "@google-cloud/vision": "^5.3.5",
26
+ "@google-cloud/vision": "^5.3.6",
27
27
  "@node-saml/passport-saml": "^5.1.0",
28
- "@policysynth/agents": "^1.3.180",
28
+ "@policysynth/agents": "^1.3.182",
29
29
  "async": "^3.2.6",
30
30
  "authorized": "^1.0.0",
31
31
  "aws-sdk": "^2.1693.0",
32
- "axios": "^1.15.2",
32
+ "axios": "^1.16.1",
33
33
  "bcrypt": "^6.0.0",
34
34
  "body-parser": "^2.2.2",
35
35
  "bullmq": "^5.31.0",
@@ -44,32 +44,32 @@
44
44
  "deep-equal": "^2.2.3",
45
45
  "docx": "^9.6.1",
46
46
  "download-file": "^0.1.5",
47
- "ejs": "^5.0.1",
47
+ "ejs": "^5.0.2",
48
48
  "exceljs": "^4.4.0",
49
49
  "express": "^5.2.1",
50
- "express-rate-limit": "^8.3.2",
50
+ "express-rate-limit": "^8.5.1",
51
51
  "express-session": "git+https://github.com/rbjarnason/session.git#upgrade-21",
52
- "express-useragent": "^2.1.0",
52
+ "express-useragent": "^2.1.1",
53
53
  "farmhash": "4.0.1",
54
54
  "form-data": "^4.0.4",
55
55
  "html-to-docx": "^1.8.0",
56
- "i18next": "^26.0.4",
56
+ "i18next": "^26.1.0",
57
57
  "i18next-fs-backend": "^2.6.5",
58
58
  "image-size": "^2.0.2",
59
- "isbot": "^5.1.37",
59
+ "isbot": "^5.1.40",
60
60
  "iso-639-1": "^3.1.5",
61
61
  "jsonrepair": "^3.12.0",
62
62
  "knuth-shuffle-seeded": "^1.0.6",
63
63
  "lodash": "^4.18.1",
64
- "marked": "^18.0.0",
64
+ "marked": "^18.0.3",
65
65
  "moment": "^2.30.1",
66
66
  "morgan": "^1.10.1",
67
67
  "multer": "^2.1.1",
68
68
  "multer-s3": "^3.0.1",
69
- "newrelic": "^13.18.0",
70
- "nodemailer": "^8.0.5",
69
+ "newrelic": "^13.20.0",
70
+ "nodemailer": "^8.0.7",
71
71
  "open-graph-scraper": "^6.11.0",
72
- "openai": "^6.34.0",
72
+ "openai": "^6.36.0",
73
73
  "passport": "^0.7.0",
74
74
  "passport-facebook": "^3.0.0",
75
75
  "passport-github": "^1.1.0",
@@ -80,16 +80,16 @@
80
80
  "pug": "^3.0.4",
81
81
  "randomstring": "^1.3.1",
82
82
  "rate-limit-redis": "^4.3.1",
83
- "redis": "^5.11.0",
83
+ "redis": "^5.12.1",
84
84
  "replicate": "^1.4.0",
85
85
  "request-ip": "^3.3.0",
86
86
  "sanitize-filename": "^1.6.4",
87
87
  "sequelize": "^6.37.7",
88
88
  "sharp": "^0.34.5",
89
89
  "sitemap": "^9.0.1",
90
- "stripe": "^22.0.1",
90
+ "stripe": "^22.1.1",
91
91
  "striptags": "^3.2.0",
92
- "uuid": "^13.0.0",
92
+ "uuid": "^13.0.2",
93
93
  "ws": "^8.18.2"
94
94
  },
95
95
  "devDependencies": {
@@ -100,17 +100,18 @@
100
100
  "@types/bunyan-prettystream": "^0.1.35",
101
101
  "@types/compression": "^1.8.1",
102
102
  "@types/cors": "^2.8.19",
103
- "@types/express": "^5.0.6",
104
- "@types/express-session": "^1.18.2",
103
+ "@types/express": "5.0.6",
104
+ "@types/express-serve-static-core": "5.0.7",
105
+ "@types/express-session": "^1.19.0",
105
106
  "@types/express-useragent": "^1.0.5",
106
107
  "@types/lodash": "^4.17.24",
107
108
  "@types/morgan": "^1.9.10",
108
- "@types/node": "^22.19.17",
109
+ "@types/node": "^22.19.19",
109
110
  "@types/passport": "^1.0.17",
110
111
  "@types/request-ip": "^0.0.41",
111
112
  "@types/uuid": "^10.0.0",
112
113
  "@types/ws": "^8.18.1",
113
- "axios": "^1.15.2",
114
+ "axios": "^1.16.1",
114
115
  "copyfiles": "^2.4.1",
115
116
  "csv-parse": "^6.2.1",
116
117
  "diacritics": "^1.3.0",
@@ -127,52 +128,16 @@
127
128
  "yamljs": "^0.3.0"
128
129
  },
129
130
  "overrides": {
130
- "basic-ftp": "^5.3.0",
131
- "@xmldom/xmldom": "0.8.13",
132
- "follow-redirects": "1.16.0",
133
- "protobufjs": "7.5.5",
134
- "validator": "13.15.35",
135
- "path-to-regexp": "8.4.2",
136
- "picomatch": "2.3.2",
137
- "dottie": "2.0.7",
138
- "glob@6.0.4": {
139
- "minimatch": {
140
- "brace-expansion": "1.1.14"
141
- }
131
+ "@types/express@5.0.6": {
132
+ "@types/express-serve-static-core": "5.0.7"
142
133
  },
143
- "glob@7.1.7": {
144
- "minimatch": {
145
- "brace-expansion": "1.1.14"
146
- }
134
+ "@google-cloud/storage": {
135
+ "retry-request": "8.0.2",
136
+ "teeny-request": "10.1.2"
147
137
  },
148
- "glob@7.2.3": {
149
- "minimatch": {
150
- "brace-expansion": "1.1.14"
151
- }
152
- },
153
- "copyfiles": {
154
- "minimatch": {
155
- ".": "3.1.4",
156
- "brace-expansion": "1.1.14"
157
- }
158
- },
159
- "readdir-glob": {
160
- "minimatch": {
161
- ".": "5.1.9",
162
- "brace-expansion": "2.1.0"
163
- }
164
- },
165
- "@types/request": {
166
- "form-data": "2.5.4"
167
- },
168
- "graphql-request": {
169
- "form-data": "3.0.4"
170
- },
171
- "prebuild-install": {
172
- "tar-fs": "2.1.4"
173
- },
174
- "prebuildify": {
175
- "tar-fs": "2.1.4"
138
+ "express-session": {
139
+ "cookie": "0.7.2",
140
+ "on-headers": "1.1.0"
176
141
  }
177
142
  },
178
143
  "scripts": {
@@ -4,18 +4,21 @@ export declare class AiHelper {
4
4
  openaiClient: OpenAI;
5
5
  wsClientSocket: WebSocket | undefined;
6
6
  modelName: string;
7
+ answerIdeasModelName: string;
8
+ analysisModelName: string;
7
9
  maxTokens: number;
8
10
  temperature: number;
9
11
  cacheExpireTime: number;
10
12
  redisClient?: any;
11
13
  cacheKeyForFullResponse?: string;
14
+ usesMaxCompletionTokens(modelName: string): boolean;
12
15
  constructor(wsClientSocket?: WebSocket | undefined);
13
16
  moderationSystemPrompt: (instructions: string) => string;
14
17
  moderationUserPrompt: (question: string, answer: string) => string;
15
18
  getModerationResponse: (instructions: string, question: string, answerToModerate: string) => Promise<boolean>;
16
- streamChatCompletions(messages: any[]): Promise<void>;
17
- sendToClient(sender: string, message: string, type?: string): void;
18
- streamWebSocketResponses(stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>): Promise<void>;
19
+ streamChatCompletions(messages: any[], modelName?: string, uniqueToken?: string): Promise<void>;
20
+ sendToClient(sender: string, message: string, type?: string, uniqueToken?: string): void;
21
+ streamWebSocketResponses(stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>, uniqueToken?: string): Promise<void>;
19
22
  getAnswerIdeas(question: string, previousIdeas: string[] | null, firstMessage: string | null): Promise<string | null | undefined>;
20
- getAiAnalysis(questionId: number, contextPrompt: string, answers: AoiChoiceData[], cacheKeyForFullResponse: string, redisClient: any, locale: string, topOrBottomIdeasText: string, typeOfAnalysisText: string): Promise<string | null | undefined>;
23
+ getAiAnalysis(questionId: number, contextPrompt: string, answers: AoiChoiceData[], cacheKeyForFullResponse: string, redisClient: any, locale: string, topOrBottomIdeasText: string, typeOfAnalysisText: string, uniqueToken?: string): Promise<string | null | undefined>;
21
24
  }
@@ -1,8 +1,13 @@
1
1
  import { OpenAI } from "openai";
2
2
  import log from "../../../utils/loggerTs.js";
3
3
  export class AiHelper {
4
+ usesMaxCompletionTokens(modelName) {
5
+ return modelName.startsWith("gpt-5");
6
+ }
4
7
  constructor(wsClientSocket = undefined) {
5
8
  this.modelName = "gpt-4o";
9
+ this.answerIdeasModelName = "gpt-5.3-chat-latest";
10
+ this.analysisModelName = "gpt-5.3-chat-latest";
6
11
  this.maxTokens = 2048;
7
12
  this.temperature = 0.7;
8
13
  this.cacheExpireTime = 60 * 60;
@@ -53,31 +58,41 @@ Only output: PASSES or FAILS`;
53
58
  });
54
59
  this.wsClientSocket = wsClientSocket;
55
60
  }
56
- async streamChatCompletions(messages) {
57
- const stream = await this.openaiClient.chat.completions.create({
58
- model: this.modelName,
61
+ async streamChatCompletions(messages, modelName = this.modelName, uniqueToken) {
62
+ const requestParams = {
63
+ model: modelName,
59
64
  messages,
60
- max_tokens: this.maxTokens,
61
- temperature: this.temperature,
62
65
  stream: true,
63
- });
64
- await this.streamWebSocketResponses(stream);
66
+ };
67
+ if (this.usesMaxCompletionTokens(modelName)) {
68
+ requestParams.max_completion_tokens = this.maxTokens;
69
+ }
70
+ else {
71
+ requestParams.max_tokens = this.maxTokens;
72
+ requestParams.temperature = this.temperature;
73
+ }
74
+ const stream = await this.openaiClient.chat.completions.create(requestParams);
75
+ await this.streamWebSocketResponses(stream, uniqueToken);
65
76
  }
66
- sendToClient(sender, message, type = "stream") {
77
+ sendToClient(sender, message, type = "stream", uniqueToken) {
67
78
  this.wsClientSocket?.send(JSON.stringify({
68
79
  sender,
69
80
  type: type,
70
81
  message,
82
+ uniqueToken,
71
83
  }));
72
84
  }
73
- async streamWebSocketResponses(stream) {
85
+ async streamWebSocketResponses(stream, uniqueToken) {
74
86
  return new Promise(async (resolve, reject) => {
75
- this.sendToClient("bot", "", "start");
87
+ this.sendToClient("assistant", "", "start", uniqueToken);
76
88
  try {
77
89
  let botMessage = "";
78
90
  for await (const part of stream) {
79
- this.sendToClient("bot", part.choices[0].delta.content);
80
- botMessage += part.choices[0].delta.content;
91
+ const content = part.choices[0].delta.content;
92
+ if (content) {
93
+ this.sendToClient("assistant", content, "stream", uniqueToken);
94
+ botMessage += content;
95
+ }
81
96
  }
82
97
  if (this.redisClient && this.cacheKeyForFullResponse) {
83
98
  this.redisClient.set(this.cacheKeyForFullResponse, botMessage, "EX", this.cacheExpireTime);
@@ -85,11 +100,11 @@ Only output: PASSES or FAILS`;
85
100
  }
86
101
  catch (error) {
87
102
  log.error(error);
88
- this.sendToClient("bot", "There has been an error, please retry", "error");
103
+ this.sendToClient("assistant", "There has been an error, please retry", "error", uniqueToken);
89
104
  reject();
90
105
  }
91
106
  finally {
92
- this.sendToClient("bot", "", "end");
107
+ this.sendToClient("assistant", "", "end", uniqueToken);
93
108
  }
94
109
  resolve();
95
110
  });
@@ -132,16 +147,16 @@ Only output: PASSES or FAILS`;
132
147
  content: `What are some possible answers to the question: ${question}\n\n${previewIdeasText}Answers:\n`,
133
148
  },
134
149
  ];
135
- await this.streamChatCompletions(messages);
150
+ await this.streamChatCompletions(messages, this.answerIdeasModelName);
136
151
  }
137
152
  }
138
153
  catch (error) {
139
154
  log.error("Error in getAnswerIdeas:", error);
140
- this.sendToClient("bot", "There has been an error, please retry", "error");
155
+ this.sendToClient("assistant", "There has been an error, please retry", "error");
141
156
  return null;
142
157
  }
143
158
  }
144
- async getAiAnalysis(questionId, contextPrompt, answers, cacheKeyForFullResponse, redisClient, locale, topOrBottomIdeasText, typeOfAnalysisText) {
159
+ async getAiAnalysis(questionId, contextPrompt, answers, cacheKeyForFullResponse, redisClient, locale, topOrBottomIdeasText, typeOfAnalysisText, uniqueToken) {
145
160
  this.redisClient = redisClient;
146
161
  this.cacheKeyForFullResponse = cacheKeyForFullResponse;
147
162
  const basePrePrompt = `You are a highly competent text and ideas analysis AI.
@@ -194,12 +209,12 @@ Only output: PASSES or FAILS`;
194
209
  Answers to analyse:\n${answersText}`,
195
210
  },
196
211
  ];
197
- this.streamChatCompletions(messages);
212
+ this.streamChatCompletions(messages, this.analysisModelName, uniqueToken);
198
213
  }
199
214
  }
200
215
  catch (error) {
201
216
  log.error("Error in getAiAnalysis:", error);
202
- this.sendToClient("bot", "There has been an error, please retry", "error");
217
+ this.sendToClient("assistant", "There has been an error, please retry", "error", uniqueToken);
203
218
  }
204
219
  }
205
220
  }
@@ -3,7 +3,7 @@ import { WebSocket } from "ws";
3
3
  import { YpBaseChatBot } from "../../llms/baseChatBot.js";
4
4
  export declare class ExplainAnswersAssistant extends YpBaseChatBot {
5
5
  openaiClient: OpenAI;
6
- modelName: string;
6
+ llmModel: string;
7
7
  maxTokens: number;
8
8
  temperature: number;
9
9
  languageName: string;
@@ -13,11 +13,11 @@ export class ExplainAnswersAssistant extends YpBaseChatBot {
13
13
  tls: tlsOptions,
14
14
  });
15
15
  super(wsClientId, wsClients, redisConnection, `${YpBaseChatBot.redisMemoryKeyPrefix}-${uuidv4()}-explain-answers-assistant`);
16
- this.modelName = "gpt-4o";
16
+ this.llmModel = "gpt-5.3-chat-latest";
17
17
  this.maxTokens = 4000;
18
18
  this.temperature = 0.8;
19
19
  this.explainConversation = async (chatLog) => {
20
- this.setChatLog(chatLog);
20
+ await this.setChatLog(chatLog);
21
21
  let messages = chatLog.map((message) => {
22
22
  return {
23
23
  role: message.sender,
@@ -29,13 +29,7 @@ export class ExplainAnswersAssistant extends YpBaseChatBot {
29
29
  content: this.renderSystemPrompt(),
30
30
  };
31
31
  messages.unshift(systemMessage);
32
- const stream = await this.openaiClient.chat.completions.create({
33
- model: this.llmModel,
34
- messages,
35
- max_tokens: this.maxTokens,
36
- temperature: this.temperature,
37
- stream: true,
38
- });
32
+ const stream = await this.openaiClient.chat.completions.create(this.getStreamingChatCompletionParams(messages));
39
33
  this.streamWebSocketResponses(stream);
40
34
  };
41
35
  this.languageName = languageName;