@intlayer/backend 5.4.2 → 5.5.0-canary.0

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 (105) hide show
  1. package/dist/cjs/controllers/ai.controller.cjs +60 -52
  2. package/dist/cjs/controllers/ai.controller.cjs.map +1 -1
  3. package/dist/cjs/controllers/dictionary.controller.cjs +5 -0
  4. package/dist/cjs/controllers/dictionary.controller.cjs.map +1 -1
  5. package/dist/cjs/export.cjs +4 -2
  6. package/dist/cjs/export.cjs.map +1 -1
  7. package/dist/cjs/routes/ai.routes.cjs +6 -0
  8. package/dist/cjs/routes/ai.routes.cjs.map +1 -1
  9. package/dist/cjs/services/dictionary.service.cjs +6 -1
  10. package/dist/cjs/services/dictionary.service.cjs.map +1 -1
  11. package/dist/cjs/utils/AI/aiSdk.cjs +140 -0
  12. package/dist/cjs/utils/AI/aiSdk.cjs.map +1 -0
  13. package/dist/cjs/utils/AI/askDocQuestion/PROMPT.md +2 -1
  14. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs +32 -27
  15. package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs.map +1 -1
  16. package/dist/cjs/utils/AI/askDocQuestion/embeddings.json +7374 -0
  17. package/dist/cjs/utils/AI/auditDictionary/PROMPT.md +4 -0
  18. package/dist/cjs/utils/AI/auditDictionary/index.cjs +36 -43
  19. package/dist/cjs/utils/AI/auditDictionary/index.cjs.map +1 -1
  20. package/dist/cjs/utils/AI/auditDictionaryField/PROMPT.md +4 -0
  21. package/dist/cjs/utils/AI/auditDictionaryField/index.cjs +34 -28
  22. package/dist/cjs/utils/AI/auditDictionaryField/index.cjs.map +1 -1
  23. package/dist/cjs/utils/AI/auditDictionaryMetadata/PROMPT.md +4 -0
  24. package/dist/cjs/utils/AI/auditDictionaryMetadata/index.cjs +23 -23
  25. package/dist/cjs/utils/AI/auditDictionaryMetadata/index.cjs.map +1 -1
  26. package/dist/cjs/utils/{auditTag → AI/auditTag}/PROMPT.md +4 -0
  27. package/dist/cjs/utils/{auditTag → AI/auditTag}/index.cjs +27 -27
  28. package/dist/cjs/utils/AI/auditTag/index.cjs.map +1 -0
  29. package/dist/cjs/utils/AI/autocomplete/PROMPT.md +4 -0
  30. package/dist/cjs/utils/AI/autocomplete/index.cjs +25 -22
  31. package/dist/cjs/utils/AI/autocomplete/index.cjs.map +1 -1
  32. package/dist/cjs/utils/AI/translateJSON/PROMPT.md +53 -0
  33. package/dist/cjs/utils/AI/translateJSON/index.cjs +106 -0
  34. package/dist/cjs/utils/AI/translateJSON/index.cjs.map +1 -0
  35. package/dist/cjs/utils/extractJSON.cjs +52 -0
  36. package/dist/cjs/utils/extractJSON.cjs.map +1 -0
  37. package/dist/esm/controllers/ai.controller.mjs +58 -51
  38. package/dist/esm/controllers/ai.controller.mjs.map +1 -1
  39. package/dist/esm/controllers/dictionary.controller.mjs +5 -0
  40. package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
  41. package/dist/esm/export.mjs +3 -2
  42. package/dist/esm/export.mjs.map +1 -1
  43. package/dist/esm/routes/ai.routes.mjs +8 -1
  44. package/dist/esm/routes/ai.routes.mjs.map +1 -1
  45. package/dist/esm/services/dictionary.service.mjs +6 -1
  46. package/dist/esm/services/dictionary.service.mjs.map +1 -1
  47. package/dist/esm/utils/AI/aiSdk.mjs +115 -0
  48. package/dist/esm/utils/AI/aiSdk.mjs.map +1 -0
  49. package/dist/esm/utils/AI/askDocQuestion/PROMPT.md +2 -1
  50. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs +32 -27
  51. package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
  52. package/dist/esm/utils/AI/askDocQuestion/embeddings.json +7374 -0
  53. package/dist/esm/utils/AI/auditDictionary/PROMPT.md +4 -0
  54. package/dist/esm/utils/AI/auditDictionary/index.mjs +36 -43
  55. package/dist/esm/utils/AI/auditDictionary/index.mjs.map +1 -1
  56. package/dist/esm/utils/AI/auditDictionaryField/PROMPT.md +4 -0
  57. package/dist/esm/utils/AI/auditDictionaryField/index.mjs +34 -28
  58. package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
  59. package/dist/esm/utils/AI/auditDictionaryMetadata/PROMPT.md +4 -0
  60. package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs +23 -23
  61. package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs.map +1 -1
  62. package/dist/esm/utils/{auditTag → AI/auditTag}/PROMPT.md +4 -0
  63. package/dist/esm/utils/AI/auditTag/index.mjs +49 -0
  64. package/dist/esm/utils/AI/auditTag/index.mjs.map +1 -0
  65. package/dist/esm/utils/AI/autocomplete/PROMPT.md +4 -0
  66. package/dist/esm/utils/AI/autocomplete/index.mjs +25 -22
  67. package/dist/esm/utils/AI/autocomplete/index.mjs.map +1 -1
  68. package/dist/esm/utils/AI/translateJSON/PROMPT.md +53 -0
  69. package/dist/esm/utils/AI/translateJSON/index.mjs +81 -0
  70. package/dist/esm/utils/AI/translateJSON/index.mjs.map +1 -0
  71. package/dist/esm/utils/extractJSON.mjs +28 -0
  72. package/dist/esm/utils/extractJSON.mjs.map +1 -0
  73. package/dist/types/controllers/ai.controller.d.ts +12 -21
  74. package/dist/types/controllers/ai.controller.d.ts.map +1 -1
  75. package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
  76. package/dist/types/export.d.ts +12 -11
  77. package/dist/types/export.d.ts.map +1 -1
  78. package/dist/types/routes/ai.routes.d.ts +5 -0
  79. package/dist/types/routes/ai.routes.d.ts.map +1 -1
  80. package/dist/types/services/dictionary.service.d.ts +2 -2
  81. package/dist/types/services/dictionary.service.d.ts.map +1 -1
  82. package/dist/types/utils/AI/aiSdk.d.ts +41 -0
  83. package/dist/types/utils/AI/aiSdk.d.ts.map +1 -0
  84. package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts +1 -1
  85. package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -1
  86. package/dist/types/utils/AI/auditDictionary/index.d.ts +10 -15
  87. package/dist/types/utils/AI/auditDictionary/index.d.ts.map +1 -1
  88. package/dist/types/utils/AI/auditDictionaryField/index.d.ts +9 -14
  89. package/dist/types/utils/AI/auditDictionaryField/index.d.ts.map +1 -1
  90. package/dist/types/utils/AI/auditDictionaryMetadata/index.d.ts +7 -13
  91. package/dist/types/utils/AI/auditDictionaryMetadata/index.d.ts.map +1 -1
  92. package/dist/types/utils/AI/auditTag/index.d.ts +18 -0
  93. package/dist/types/utils/AI/auditTag/index.d.ts.map +1 -0
  94. package/dist/types/utils/AI/autocomplete/index.d.ts +6 -12
  95. package/dist/types/utils/AI/autocomplete/index.d.ts.map +1 -1
  96. package/dist/types/utils/AI/translateJSON/index.d.ts +24 -0
  97. package/dist/types/utils/AI/translateJSON/index.d.ts.map +1 -0
  98. package/dist/types/utils/extractJSON.d.ts +6 -0
  99. package/dist/types/utils/extractJSON.d.ts.map +1 -0
  100. package/package.json +13 -7
  101. package/dist/cjs/utils/auditTag/index.cjs.map +0 -1
  102. package/dist/esm/utils/auditTag/index.mjs +0 -49
  103. package/dist/esm/utils/auditTag/index.mjs.map +0 -1
  104. package/dist/types/utils/auditTag/index.d.ts +0 -30
  105. package/dist/types/utils/auditTag/index.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/utils/AI/autocomplete/index.ts"],"sourcesContent":["import { readFileSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { logger } from '@logger';\nimport { OpenAI } from 'openai';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport type AIOptions = {\n model?: string;\n temperature?: number;\n openAiApiKey?: string;\n};\n\nexport type AutocompleteOptions = {\n text: string;\n customPrompt?: string;\n} & AIOptions;\nexport type AutocompleteFileResultData = {\n autocompletion: string;\n tokenUsed: number;\n};\n\n/**\n * Reads the content of a file synchronously.\n *\n * @function\n * @param relativeFilePath - The relative or absolute path to the target file.\n * @returns The entire contents of the specified file as a UTF-8 encoded string.\n */\nconst getFileContent = (relativeFilePath: string): string => {\n const absolutePath = join(__dirname, relativeFilePath);\n const fileContent = readFileSync(absolutePath, 'utf-8');\n return fileContent;\n};\n\n// The prompt template to send to ChatGPT, requesting an audit of content declaration files.\nconst CHAT_GPT_PROMPT = getFileContent('./PROMPT.md');\n\n/**\n * Autocompletes a content declaration file by constructing a prompt for ChatGPT.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies. It prints the prompt for each file,\n * and could be adapted to send requests to the ChatGPT model.\n *\n */\nexport const autocomplete = async ({\n text,\n model,\n openAiApiKey,\n temperature,\n customPrompt,\n}: AutocompleteOptions): Promise<AutocompleteFileResultData | undefined> => {\n try {\n // Optionally, you could initialize and configure the OpenAI client here, if you intend to make API calls.\n // Uncomment and configure the following lines if you have `openai` installed and want to call the API:\n\n const openai = new OpenAI({\n apiKey: openAiApiKey ?? process.env.OPENAI_API_KEY,\n });\n\n // Prepare the prompt for ChatGPT by replacing placeholders with actual values.\n const prompt = customPrompt ?? CHAT_GPT_PROMPT;\n\n // Example of how you might request a completion from ChatGPT:\n const chatCompletion = await openai.chat.completions.create({\n model: openAiApiKey ? (model ?? 'gpt-4o-mini') : 'gpt-4o-mini',\n temperature: openAiApiKey ? (temperature ?? 0.1) : 0.1,\n messages: [\n { role: 'system', content: prompt },\n { role: 'user', content: text },\n ],\n });\n\n const newContent = chatCompletion.choices[0].message?.content;\n\n logger.info(\n `${chatCompletion.usage?.total_tokens} tokens used in the request`\n );\n\n return {\n autocompletion: newContent ?? '',\n tokenUsed: chatCompletion.usage?.total_tokens ?? 0,\n };\n } catch (error) {\n console.error(error);\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAA6B;AAC7B,kBAA8B;AAC9B,iBAA8B;AAC9B,oBAAuB;AACvB,oBAAuB;AAJvB;AAMA,MAAM,gBAAY,yBAAQ,0BAAc,YAAY,GAAG,CAAC;AAwBxD,MAAM,iBAAiB,CAAC,qBAAqC;AAC3D,QAAM,mBAAe,kBAAK,WAAW,gBAAgB;AACrD,QAAM,kBAAc,wBAAa,cAAc,OAAO;AACtD,SAAO;AACT;AAGA,MAAM,kBAAkB,eAAe,aAAa;AAS7C,MAAM,eAAe,OAAO;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA4E;AAC1E,MAAI;AAIF,UAAM,SAAS,IAAI,qBAAO;AAAA,MACxB,QAAQ,gBAAgB,QAAQ,IAAI;AAAA,IACtC,CAAC;AAGD,UAAM,SAAS,gBAAgB;AAG/B,UAAM,iBAAiB,MAAM,OAAO,KAAK,YAAY,OAAO;AAAA,MAC1D,OAAO,eAAgB,SAAS,gBAAiB;AAAA,MACjD,aAAa,eAAgB,eAAe,MAAO;AAAA,MACnD,UAAU;AAAA,QACR,EAAE,MAAM,UAAU,SAAS,OAAO;AAAA,QAClC,EAAE,MAAM,QAAQ,SAAS,KAAK;AAAA,MAChC;AAAA,IACF,CAAC;AAED,UAAM,aAAa,eAAe,QAAQ,CAAC,EAAE,SAAS;AAEtD,yBAAO;AAAA,MACL,GAAG,eAAe,OAAO,YAAY;AAAA,IACvC;AAEA,WAAO;AAAA,MACL,gBAAgB,cAAc;AAAA,MAC9B,WAAW,eAAe,OAAO,gBAAgB;AAAA,IACnD;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,KAAK;AAAA,EACrB;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/utils/AI/autocomplete/index.ts"],"sourcesContent":["import { logger } from '@logger';\nimport { generateText } from 'ai';\nimport { readFileSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { AIOptions, AIProvider, getAIConfig } from '../aiSdk';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// Get the content of a file at the specified path\nconst getFileContent = (filePath: string) => {\n return readFileSync(join(__dirname, filePath), { encoding: 'utf-8' });\n};\n\nexport type AutocompleteOptions = {\n text: string;\n aiOptions?: AIOptions;\n};\n\nexport type AutocompleteFileResultData = {\n autocompletion: string;\n tokenUsed: number;\n};\n\n// The prompt template to send to the AI model\nconst CHAT_GPT_PROMPT = getFileContent('./PROMPT.md');\n\n/**\n * Autocompletes a content declaration file by constructing a prompt for AI models.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies.\n */\nexport const autocomplete = async ({\n text,\n aiOptions,\n}: AutocompleteOptions): Promise<AutocompleteFileResultData | undefined> => {\n try {\n // Prepare the prompt for AI by replacing placeholders with actual values.\n const prompt = CHAT_GPT_PROMPT.replace(\n '{{applicationContext}}',\n aiOptions?.applicationContext ?? ''\n );\n\n // Get the appropriate AI model configuration\n const aiConfig = await getAIConfig({\n model: 'gpt-4o-mini',\n provider: AIProvider.OPENAI,\n apiKey: process.env.OPENAI_API_KEY,\n ...aiOptions,\n });\n\n if (!aiConfig) {\n logger.error('Failed to configure AI model');\n return undefined;\n }\n\n // Use the AI SDK to generate the completion\n const { text: newContent, usage } = await generateText({\n model: aiConfig.model,\n temperature: aiConfig.temperature,\n messages: [\n { role: 'system', content: prompt },\n { role: 'user', content: text },\n ],\n });\n\n logger.info(`${usage?.totalTokens ?? 0} tokens used in the request`);\n\n return {\n autocompletion: newContent,\n tokenUsed: usage?.totalTokens ?? 0,\n };\n } catch (error) {\n console.error(error);\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAuB;AACvB,gBAA6B;AAC7B,gBAA6B;AAC7B,kBAA8B;AAC9B,iBAA8B;AAC9B,mBAAmD;AALnD;AAOA,MAAM,gBAAY,yBAAQ,0BAAc,YAAY,GAAG,CAAC;AAGxD,MAAM,iBAAiB,CAAC,aAAqB;AAC3C,aAAO,4BAAa,kBAAK,WAAW,QAAQ,GAAG,EAAE,UAAU,QAAQ,CAAC;AACtE;AAaA,MAAM,kBAAkB,eAAe,aAAa;AAO7C,MAAM,eAAe,OAAO;AAAA,EACjC;AAAA,EACA;AACF,MAA4E;AAC1E,MAAI;AAEF,UAAM,SAAS,gBAAgB;AAAA,MAC7B;AAAA,MACA,WAAW,sBAAsB;AAAA,IACnC;AAGA,UAAM,WAAW,UAAM,0BAAY;AAAA,MACjC,OAAO;AAAA,MACP,UAAU,wBAAW;AAAA,MACrB,QAAQ,QAAQ,IAAI;AAAA,MACpB,GAAG;AAAA,IACL,CAAC;AAED,QAAI,CAAC,UAAU;AACb,2BAAO,MAAM,8BAA8B;AAC3C,aAAO;AAAA,IACT;AAGA,UAAM,EAAE,MAAM,YAAY,MAAM,IAAI,UAAM,wBAAa;AAAA,MACrD,OAAO,SAAS;AAAA,MAChB,aAAa,SAAS;AAAA,MACtB,UAAU;AAAA,QACR,EAAE,MAAM,UAAU,SAAS,OAAO;AAAA,QAClC,EAAE,MAAM,QAAQ,SAAS,KAAK;AAAA,MAChC;AAAA,IACF,CAAC;AAED,yBAAO,KAAK,GAAG,OAAO,eAAe,CAAC,6BAA6B;AAEnE,WAAO;AAAA,MACL,gBAAgB;AAAA,MAChB,WAAW,OAAO,eAAe;AAAA,IACnC;AAAA,EACF,SAAS,OAAO;AACd,YAAQ,MAAM,KAAK;AAAA,EACrB;AACF;","names":[]}
@@ -0,0 +1,53 @@
1
+ You are an expert in internationalization, copy writing and content management. Your task is to audit the content declaration files in the project and identify any potential issues or inconsistencies. Provide a detailed report of any issues found, including the file path, line number, and a brief explanation of the issue.
2
+
3
+ **Instructions:**
4
+
5
+ 2. **Audit Requirements:**
6
+
7
+ - **Consistency:** The dictionary format should be the same as the one provided in entry. You should not rename or translate the entry keys.
8
+ - **Missing Content:** Identify any missing translations and specify the expected content.
9
+ - **Misplaced Content:** Detect if any translations are placed under incorrect keys.
10
+ - **Type Compliance:** Verify that the content types match the declarations (e.g., strings, string arrays).
11
+
12
+ 3. **Modification Guidelines:**
13
+
14
+ - **Do Not Alter Structure:** If the file structure is correct, do not modify it. Only add, update, or remove content declarations as necessary.
15
+ - **Return Only Final File Content:** Provide the updated file content without any additional comments or explanations.
16
+ - **Manage Localizations:** If the output languages targeted is a variant contains similar languages, as `en` and `en-GB`, consider `en` as English US, and adapt it into `en-GB` as English UK.
17
+ - **Conflict between language and content:** If you detect a conflict between the language and content, translate it if the Mode Instruction is in 'review' mode
18
+ - **Escape Special Characters:** If the translations contain special characters, escape them using the appropriate escape sequence.
19
+ - **Respect the tags and description instructions:** If the tags and description instructions are provided, ensure that the audited file adheres to them.
20
+ - **Consider the Preset Output Content** If Preset Output Content is provided, and coherent with the entry, you can consider reuse it to fill the output file content.
21
+ - **TypeNode field should not be translated** A value as `{ 'nodeType': 'XXX', ...}` should not be translated.
22
+
23
+ **Application Context**
24
+
25
+ {{applicationContext}}
26
+
27
+ **Mode Instruction:**
28
+
29
+ {{modeInstructions}}
30
+
31
+ **Tags Instructions:**
32
+
33
+ {{tagsInstructions}}
34
+
35
+ **Dictionary Description:**
36
+
37
+ {{dictionaryDescription}}
38
+
39
+ **Entry Content to Translate:**
40
+
41
+ - Given Language: {{entryLocale}}
42
+
43
+ {{entryFileContent}}
44
+
45
+ **Preset Output Content:**
46
+
47
+ - Target Language: {{outputLocale}}
48
+
49
+ {{presetOutputContent}}
50
+
51
+ **Expected Response:**
52
+
53
+ After auditing, provide only the final content of the file as plain text without any Markdown or code block formatting. If no changes are needed, return the file content exactly as it is.
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var translateJSON_exports = {};
20
+ __export(translateJSON_exports, {
21
+ translateJSON: () => translateJSON
22
+ });
23
+ module.exports = __toCommonJS(translateJSON_exports);
24
+ var import_config = require("@intlayer/config");
25
+ var import_core = require("@intlayer/core");
26
+ var import_logger = require('./../../../logger/index.cjs');
27
+ var import_extractJSON = require('./../../../utils/extractJSON.cjs');
28
+ var import_ai = require("ai");
29
+ var import_fs = require("fs");
30
+ var import_path = require("path");
31
+ var import_url = require("url");
32
+ var import_aiSdk = require('../aiSdk.cjs');
33
+ const import_meta = {};
34
+ const __dirname = (0, import_path.dirname)((0, import_url.fileURLToPath)(import_meta.url));
35
+ const getFileContent = (filePath) => {
36
+ return (0, import_fs.readFileSync)((0, import_path.join)(__dirname, filePath), { encoding: "utf-8" });
37
+ };
38
+ const CHAT_GPT_PROMPT = getFileContent("./PROMPT.md");
39
+ const formatLocaleWithName = (locale) => {
40
+ return `${locale}: ${(0, import_core.getLocaleName)(locale)}`;
41
+ };
42
+ const formatTagInstructions = (tags) => {
43
+ if (!tags || tags.length === 0) {
44
+ return "";
45
+ }
46
+ return `Based on the dictionary content, identify specific tags from the list below that would be relevant:
47
+
48
+ ${tags.map(({ key, description }) => `- ${key}: ${description}`).join("\n\n")}`;
49
+ };
50
+ const getModeInstructions = (mode) => {
51
+ if (mode === "complete") {
52
+ return 'Mode: "Complete" - Enrich the preset content with the missing keys and values. Do not update existing keys. Everything should be returned in the output.';
53
+ }
54
+ return 'Mode: "Review" - Fill missing content and review existing keys from the preset content. If you detect misspelled content, or content should be reformulated, correct it.';
55
+ };
56
+ const translateJSON = async ({
57
+ entryFileContent,
58
+ presetOutputContent,
59
+ dictionaryDescription,
60
+ aiOptions,
61
+ entryLocale,
62
+ outputLocale,
63
+ tags,
64
+ mode
65
+ }) => {
66
+ try {
67
+ const aiConfig = await (0, import_aiSdk.getAIConfig)({
68
+ provider: import_aiSdk.AIProvider.OPENAI,
69
+ model: "gpt-4o-mini-2024-07-18",
70
+ ...aiOptions
71
+ });
72
+ const prompt = CHAT_GPT_PROMPT.replace(
73
+ "{{entryLocale}}",
74
+ formatLocaleWithName(entryLocale)
75
+ ).replace("{{outputLocale}}", formatLocaleWithName(outputLocale)).replace("{{entryFileContent}}", JSON.stringify(entryFileContent)).replace("{{presetOutputContent}}", JSON.stringify(presetOutputContent)).replace("{{dictionaryDescription}}", dictionaryDescription).replace("{{applicationContext}}", aiOptions?.applicationContext ?? "").replace("{{tagsInstructions}}", formatTagInstructions(tags)).replace("{{modeInstructions}}", getModeInstructions(mode));
76
+ if (!aiConfig) {
77
+ import_logger.logger.error("Failed to configure AI model");
78
+ return void 0;
79
+ }
80
+ const result = await (0, import_config.retryManager)(async () => {
81
+ const { text: newContent, usage } = await (0, import_ai.generateText)({
82
+ model: aiConfig.model,
83
+ temperature: aiConfig.temperature,
84
+ messages: [{ role: "system", content: prompt }]
85
+ });
86
+ import_logger.logger.info(`${usage?.totalTokens ?? 0} tokens used in the request`);
87
+ return {
88
+ fileContent: (0, import_extractJSON.extractJson)(newContent),
89
+ tokenUsed: usage?.totalTokens ?? 0
90
+ };
91
+ })();
92
+ return {
93
+ fileContent: result.fileContent,
94
+ tokenUsed: result.tokenUsed
95
+ };
96
+ } catch (error) {
97
+ import_logger.logger.error("Error translating JSON:" + error, {
98
+ level: "error"
99
+ });
100
+ }
101
+ };
102
+ // Annotate the CommonJS export names for ESM import in node:
103
+ 0 && (module.exports = {
104
+ translateJSON
105
+ });
106
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/utils/AI/translateJSON/index.ts"],"sourcesContent":["import type { Tag } from '@/types/tag.types';\nimport { retryManager } from '@intlayer/config';\nimport { getLocaleName } from '@intlayer/core';\nimport { logger } from '@logger';\nimport { extractJson } from '@utils/extractJSON';\nimport { generateText } from 'ai';\nimport { readFileSync } from 'fs';\nimport type { Locales } from 'intlayer';\nimport { dirname, join } from 'path';\nimport { fileURLToPath } from 'url';\nimport { AIOptions, AIProvider, getAIConfig } from '../aiSdk';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// Get the content of a file at the specified path\nconst getFileContent = (filePath: string) => {\n return readFileSync(join(__dirname, filePath), { encoding: 'utf-8' });\n};\n\nexport type TranslateJSONOptions = {\n entryFileContent: JSON;\n presetOutputContent: JSON;\n dictionaryDescription: string;\n entryLocale: Locales;\n outputLocale: Locales;\n tags: Tag[];\n aiOptions?: AIOptions;\n mode: 'complete' | 'review';\n};\n\nexport type TranslateJSONResultData = {\n fileContent: string;\n tokenUsed: number;\n};\n\n// The prompt template to send to the AI model\nconst CHAT_GPT_PROMPT = getFileContent('./PROMPT.md');\n\n/**\n * Format a locale with its name.\n *\n * @param locale - The locale to format.\n * @returns A string in the format \"locale: name\", e.g. \"en: English\".\n */\nconst formatLocaleWithName = (locale: Locales): string => {\n return `${locale}: ${getLocaleName(locale)}`;\n};\n\n/**\n * Formats tag instructions for the AI prompt.\n * Creates a string with all available tags and their descriptions.\n *\n * @param tags - The list of tags to format.\n * @returns A formatted string with tag instructions.\n */\nconst formatTagInstructions = (tags: Tag[]): string => {\n if (!tags || tags.length === 0) {\n return '';\n }\n\n // Prepare the tag instructions.\n return `Based on the dictionary content, identify specific tags from the list below that would be relevant:\n \n${tags.map(({ key, description }) => `- ${key}: ${description}`).join('\\n\\n')}`;\n};\n\nconst getModeInstructions = (mode: 'complete' | 'review'): string => {\n if (mode === 'complete') {\n return 'Mode: \"Complete\" - Enrich the preset content with the missing keys and values. Do not update existing keys. Everything should be returned in the output.';\n }\n\n return 'Mode: \"Review\" - Fill missing content and review existing keys from the preset content. If you detect misspelled content, or content should be reformulated, correct it.';\n};\n\n/**\n * TranslateJSONs a content declaration file by constructing a prompt for AI models.\n * The prompt includes details about the project's locales, file paths of content declarations,\n * and requests for identifying issues or inconsistencies.\n */\nexport const translateJSON = async ({\n entryFileContent,\n presetOutputContent,\n dictionaryDescription,\n aiOptions,\n entryLocale,\n outputLocale,\n tags,\n mode,\n}: TranslateJSONOptions): Promise<TranslateJSONResultData | undefined> => {\n try {\n // Get the appropriate AI model configuration\n const aiConfig = await getAIConfig({\n provider: AIProvider.OPENAI,\n model: 'gpt-4o-mini-2024-07-18',\n ...aiOptions,\n });\n\n // Prepare the prompt for AI by replacing placeholders with actual values.\n const prompt = CHAT_GPT_PROMPT.replace(\n '{{entryLocale}}',\n formatLocaleWithName(entryLocale)\n )\n .replace('{{outputLocale}}', formatLocaleWithName(outputLocale))\n .replace('{{entryFileContent}}', JSON.stringify(entryFileContent))\n .replace('{{presetOutputContent}}', JSON.stringify(presetOutputContent))\n .replace('{{dictionaryDescription}}', dictionaryDescription)\n .replace('{{applicationContext}}', aiOptions?.applicationContext ?? '')\n .replace('{{tagsInstructions}}', formatTagInstructions(tags))\n .replace('{{modeInstructions}}', getModeInstructions(mode));\n\n if (!aiConfig) {\n logger.error('Failed to configure AI model');\n return undefined;\n }\n\n const result = await retryManager(async () => {\n // Use the AI SDK to generate the completion\n const { text: newContent, usage } = await generateText({\n model: aiConfig.model,\n temperature: aiConfig.temperature,\n messages: [{ role: 'system', content: prompt }],\n });\n\n logger.info(`${usage?.totalTokens ?? 0} tokens used in the request`);\n\n return {\n fileContent: extractJson(newContent),\n tokenUsed: usage?.totalTokens ?? 0,\n };\n })();\n\n return {\n fileContent: result.fileContent,\n tokenUsed: result.tokenUsed,\n };\n } catch (error) {\n logger.error('Error translating JSON:' + error, {\n level: 'error',\n });\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AACA,oBAA6B;AAC7B,kBAA8B;AAC9B,oBAAuB;AACvB,yBAA4B;AAC5B,gBAA6B;AAC7B,gBAA6B;AAE7B,kBAA8B;AAC9B,iBAA8B;AAC9B,mBAAmD;AAVnD;AAYA,MAAM,gBAAY,yBAAQ,0BAAc,YAAY,GAAG,CAAC;AAGxD,MAAM,iBAAiB,CAAC,aAAqB;AAC3C,aAAO,4BAAa,kBAAK,WAAW,QAAQ,GAAG,EAAE,UAAU,QAAQ,CAAC;AACtE;AAmBA,MAAM,kBAAkB,eAAe,aAAa;AAQpD,MAAM,uBAAuB,CAAC,WAA4B;AACxD,SAAO,GAAG,MAAM,SAAK,2BAAc,MAAM,CAAC;AAC5C;AASA,MAAM,wBAAwB,CAAC,SAAwB;AACrD,MAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,SAAO;AAAA;AAAA,EAEP,KAAK,IAAI,CAAC,EAAE,KAAK,YAAY,MAAM,KAAK,GAAG,KAAK,WAAW,EAAE,EAAE,KAAK,MAAM,CAAC;AAC7E;AAEA,MAAM,sBAAsB,CAAC,SAAwC;AACnE,MAAI,SAAS,YAAY;AACvB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAOO,MAAM,gBAAgB,OAAO;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAA0E;AACxE,MAAI;AAEF,UAAM,WAAW,UAAM,0BAAY;AAAA,MACjC,UAAU,wBAAW;AAAA,MACrB,OAAO;AAAA,MACP,GAAG;AAAA,IACL,CAAC;AAGD,UAAM,SAAS,gBAAgB;AAAA,MAC7B;AAAA,MACA,qBAAqB,WAAW;AAAA,IAClC,EACG,QAAQ,oBAAoB,qBAAqB,YAAY,CAAC,EAC9D,QAAQ,wBAAwB,KAAK,UAAU,gBAAgB,CAAC,EAChE,QAAQ,2BAA2B,KAAK,UAAU,mBAAmB,CAAC,EACtE,QAAQ,6BAA6B,qBAAqB,EAC1D,QAAQ,0BAA0B,WAAW,sBAAsB,EAAE,EACrE,QAAQ,wBAAwB,sBAAsB,IAAI,CAAC,EAC3D,QAAQ,wBAAwB,oBAAoB,IAAI,CAAC;AAE5D,QAAI,CAAC,UAAU;AACb,2BAAO,MAAM,8BAA8B;AAC3C,aAAO;AAAA,IACT;AAEA,UAAM,SAAS,UAAM,4BAAa,YAAY;AAE5C,YAAM,EAAE,MAAM,YAAY,MAAM,IAAI,UAAM,wBAAa;AAAA,QACrD,OAAO,SAAS;AAAA,QAChB,aAAa,SAAS;AAAA,QACtB,UAAU,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,CAAC;AAAA,MAChD,CAAC;AAED,2BAAO,KAAK,GAAG,OAAO,eAAe,CAAC,6BAA6B;AAEnE,aAAO;AAAA,QACL,iBAAa,gCAAY,UAAU;AAAA,QACnC,WAAW,OAAO,eAAe;AAAA,MACnC;AAAA,IACF,CAAC,EAAE;AAEH,WAAO;AAAA,MACL,aAAa,OAAO;AAAA,MACpB,WAAW,OAAO;AAAA,IACpB;AAAA,EACF,SAAS,OAAO;AACd,yBAAO,MAAM,4BAA4B,OAAO;AAAA,MAC9C,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF;","names":[]}
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var extractJSON_exports = {};
20
+ __export(extractJSON_exports, {
21
+ extractJson: () => extractJson
22
+ });
23
+ module.exports = __toCommonJS(extractJSON_exports);
24
+ const extractJson = (input) => {
25
+ const opening = input.match(/[{[]/);
26
+ if (!opening) throw new Error("No JSON start character ({ or [) found.");
27
+ const startIdx = opening.index;
28
+ const openChar = input[startIdx];
29
+ const closeChar = openChar === "{" ? "}" : "]";
30
+ let depth = 0;
31
+ for (let i = startIdx; i < input.length; i++) {
32
+ const char = input[i];
33
+ if (char === openChar) depth++;
34
+ else if (char === closeChar) {
35
+ depth--;
36
+ if (depth === 0) {
37
+ const jsonSubstring = input.slice(startIdx, i + 1);
38
+ try {
39
+ return JSON.parse(jsonSubstring);
40
+ } catch (err) {
41
+ throw new Error(`Failed to parse JSON: ${err.message}`);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ throw new Error("Reached end of input without closing JSON bracket.");
47
+ };
48
+ // Annotate the CommonJS export names for ESM import in node:
49
+ 0 && (module.exports = {
50
+ extractJson
51
+ });
52
+ //# sourceMappingURL=extractJSON.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/utils/extractJSON.ts"],"sourcesContent":["/**\n * Extracts the first JSON value (object or array) from a string.\n * Returns the parsed value, or throws if no valid JSON is found.\n */\nexport const extractJson = <T = any>(input: string): T => {\n const opening = input.match(/[{[]/);\n if (!opening) throw new Error('No JSON start character ({ or [) found.');\n\n const startIdx = opening.index!;\n const openChar = input[startIdx];\n const closeChar = openChar === '{' ? '}' : ']';\n let depth = 0;\n\n for (let i = startIdx; i < input.length; i++) {\n const char = input[i];\n if (char === openChar) depth++;\n else if (char === closeChar) {\n depth--;\n if (depth === 0) {\n const jsonSubstring = input.slice(startIdx, i + 1);\n try {\n return JSON.parse(jsonSubstring) as T;\n } catch (err) {\n throw new Error(`Failed to parse JSON: ${(err as Error).message}`);\n }\n }\n }\n }\n\n throw new Error('Reached end of input without closing JSON bracket.');\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIO,MAAM,cAAc,CAAU,UAAqB;AACxD,QAAM,UAAU,MAAM,MAAM,MAAM;AAClC,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,yCAAyC;AAEvE,QAAM,WAAW,QAAQ;AACzB,QAAM,WAAW,MAAM,QAAQ;AAC/B,QAAM,YAAY,aAAa,MAAM,MAAM;AAC3C,MAAI,QAAQ;AAEZ,WAAS,IAAI,UAAU,IAAI,MAAM,QAAQ,KAAK;AAC5C,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,SAAS,SAAU;AAAA,aACd,SAAS,WAAW;AAC3B;AACA,UAAI,UAAU,GAAG;AACf,cAAM,gBAAgB,MAAM,MAAM,UAAU,IAAI,CAAC;AACjD,YAAI;AACF,iBAAO,KAAK,MAAM,aAAa;AAAA,QACjC,SAAS,KAAK;AACZ,gBAAM,IAAI,MAAM,yBAA0B,IAAc,OAAO,EAAE;AAAA,QACnE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,oDAAoD;AACtE;","names":[]}
@@ -6,24 +6,50 @@ import * as askDocQuestionUtil from "./../utils/AI/askDocQuestion/askDocQuestion
6
6
  import * as auditContentDeclarationUtil from "./../utils/AI/auditDictionary/index.mjs";
7
7
  import * as auditContentDeclarationFieldUtil from "./../utils/AI/auditDictionaryField/index.mjs";
8
8
  import * as auditContentDeclarationMetadataUtil from "./../utils/AI/auditDictionaryMetadata/index.mjs";
9
+ import * as auditTagUtil from "./../utils/AI/auditTag/index.mjs";
9
10
  import * as autocompleteUtil from "./../utils/AI/autocomplete/index.mjs";
10
- import * as auditTagUtil from "./../utils/auditTag/index.mjs";
11
+ import * as translateJSONUtil from "./../utils/AI/translateJSON/index.mjs";
11
12
  import { ErrorHandler } from "./../utils/errors/index.mjs";
12
13
  import { formatResponse } from "./../utils/responseData.mjs";
14
+ const translateJSON = async (req, res, _next) => {
15
+ const { user, project, organization } = res.locals;
16
+ const { aiOptions, tagsKeys, ...rest } = req.body;
17
+ const hasApiKey = Boolean(aiOptions?.apiKey);
18
+ if (!hasApiKey) {
19
+ if (!user || !project || !organization) {
20
+ ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
21
+ return;
22
+ }
23
+ }
24
+ try {
25
+ let tags = [];
26
+ if (project?.organizationId) {
27
+ tags = await getTagsByKeys(tagsKeys, project.organizationId);
28
+ }
29
+ const auditResponse = await translateJSONUtil.translateJSON({
30
+ ...rest,
31
+ aiOptions,
32
+ tags
33
+ });
34
+ if (!auditResponse) {
35
+ ErrorHandler.handleGenericErrorResponse(res, "AUDIT_FAILED");
36
+ return;
37
+ }
38
+ const responseData = formatResponse({
39
+ data: auditResponse
40
+ });
41
+ res.json(responseData);
42
+ return;
43
+ } catch (error) {
44
+ ErrorHandler.handleAppErrorResponse(res, error);
45
+ return;
46
+ }
47
+ };
13
48
  const auditContentDeclaration = async (req, res, _next) => {
14
49
  const { user, project, organization } = res.locals;
15
- const {
16
- fileContent,
17
- filePath,
18
- openAiApiKey,
19
- customPrompt,
20
- model,
21
- temperature,
22
- locales,
23
- defaultLocale,
24
- tagsKeys
25
- } = req.body;
26
- if (!openAiApiKey) {
50
+ const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } = req.body;
51
+ const hasApiKey = Boolean(aiOptions?.apiKey);
52
+ if (!hasApiKey) {
27
53
  if (!user || !project || !organization) {
28
54
  ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
29
55
  return;
@@ -37,10 +63,7 @@ const auditContentDeclaration = async (req, res, _next) => {
37
63
  const auditResponse = await auditContentDeclarationUtil.auditDictionary({
38
64
  fileContent,
39
65
  filePath,
40
- model,
41
- temperature,
42
- openAiApiKey,
43
- customPrompt,
66
+ aiOptions,
44
67
  locales,
45
68
  defaultLocale,
46
69
  tags
@@ -61,17 +84,9 @@ const auditContentDeclaration = async (req, res, _next) => {
61
84
  };
62
85
  const auditContentDeclarationField = async (req, res, _next) => {
63
86
  const { user, project, organization } = res.locals;
64
- const {
65
- fileContent,
66
- openAiApiKey,
67
- customPrompt,
68
- model,
69
- temperature,
70
- locales,
71
- tagsKeys,
72
- keyPath
73
- } = req.body;
74
- if (!openAiApiKey) {
87
+ const { fileContent, aiOptions, locales, tagsKeys, keyPath } = req.body;
88
+ const hasApiKey = Boolean(aiOptions?.apiKey);
89
+ if (!hasApiKey) {
75
90
  if (!user || !project || !organization) {
76
91
  ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
77
92
  return;
@@ -84,10 +99,7 @@ const auditContentDeclarationField = async (req, res, _next) => {
84
99
  }
85
100
  const auditResponse = await auditContentDeclarationFieldUtil.auditDictionaryField({
86
101
  fileContent,
87
- model,
88
- temperature,
89
- openAiApiKey,
90
- customPrompt,
102
+ aiOptions,
91
103
  locales,
92
104
  tags,
93
105
  keyPath
@@ -108,8 +120,9 @@ const auditContentDeclarationField = async (req, res, _next) => {
108
120
  };
109
121
  const auditContentDeclarationMetadata = async (req, res, _next) => {
110
122
  const { user, organization, project } = res.locals;
111
- const { fileContent, openAiApiKey, customPrompt, model, temperature } = req.body;
112
- if (!openAiApiKey) {
123
+ const { fileContent, aiOptions } = req.body;
124
+ const hasApiKey = Boolean(aiOptions?.apiKey);
125
+ if (!hasApiKey) {
113
126
  if (!user || !project || !organization) {
114
127
  ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
115
128
  return;
@@ -125,10 +138,7 @@ const auditContentDeclarationMetadata = async (req, res, _next) => {
125
138
  );
126
139
  const auditResponse = await auditContentDeclarationMetadataUtil.auditDictionaryMetadata({
127
140
  fileContent,
128
- model,
129
- openAiApiKey,
130
- temperature,
131
- customPrompt,
141
+ aiOptions,
132
142
  tags
133
143
  });
134
144
  if (!auditResponse) {
@@ -147,8 +157,9 @@ const auditContentDeclarationMetadata = async (req, res, _next) => {
147
157
  };
148
158
  const auditTag = async (req, res, _next) => {
149
159
  const { user, project, organization } = res.locals;
150
- const { openAiApiKey, customPrompt, model, temperature, tag } = req.body;
151
- if (!openAiApiKey) {
160
+ const { aiOptions, tag } = req.body;
161
+ const hasApiKey = Boolean(aiOptions?.apiKey);
162
+ if (!hasApiKey) {
152
163
  if (!user || !project || !organization) {
153
164
  ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
154
165
  return;
@@ -160,10 +171,7 @@ const auditTag = async (req, res, _next) => {
160
171
  dictionaries = await getDictionariesByTags([tag.key], project._id);
161
172
  }
162
173
  const auditResponse = await auditTagUtil.auditTag({
163
- model,
164
- openAiApiKey,
165
- temperature,
166
- customPrompt,
174
+ aiOptions,
167
175
  dictionaries,
168
176
  tag
169
177
  });
@@ -234,9 +242,10 @@ data: ${JSON.stringify({ message: err.message })}
234
242
  };
235
243
  const autocomplete = async (req, res) => {
236
244
  try {
237
- const { text, model, openAiApiKey, customPrompt, temperature } = req.body;
245
+ const { text, aiOptions } = req.body;
238
246
  const { user, project, organization } = res.locals;
239
- if (!openAiApiKey) {
247
+ const hasApiKey = Boolean(aiOptions?.apiKey);
248
+ if (!hasApiKey) {
240
249
  if (!user || !project || !organization) {
241
250
  ErrorHandler.handleGenericErrorResponse(res, "AI_ACCESS_DENIED");
242
251
  return;
@@ -244,10 +253,7 @@ const autocomplete = async (req, res) => {
244
253
  }
245
254
  const response = await autocompleteUtil.autocomplete({
246
255
  text,
247
- model,
248
- openAiApiKey,
249
- temperature,
250
- customPrompt
256
+ aiOptions
251
257
  }) ?? {
252
258
  autocompletion: "",
253
259
  tokenUsed: 0
@@ -267,6 +273,7 @@ export {
267
273
  auditContentDeclarationField,
268
274
  auditContentDeclarationMetadata,
269
275
  auditTag,
270
- autocomplete
276
+ autocomplete,
277
+ translateJSON
271
278
  };
272
279
  //# sourceMappingURL=ai.controller.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { Tag } from '@/types/tag.types';\nimport { type KeyPath } from '@intlayer/core';\nimport type { ResponseWithInformation } 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 autocompleteUtil from '@utils/AI/autocomplete';\nimport * as auditTagUtil from '@utils/auditTag';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport type { Locales } from 'intlayer';\n\nexport type AuditContentDeclarationBody = {\n openAiApiKey?: string;\n customPrompt?: string;\n locales: Locales[];\n defaultLocale: Locales;\n fileContent: string;\n filePath?: string;\n model?: string;\n temperature?: number;\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: ResponseWithInformation<AuditContentDeclarationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization } = res.locals;\n const {\n fileContent,\n filePath,\n openAiApiKey,\n customPrompt,\n model,\n temperature,\n locales,\n defaultLocale,\n tagsKeys,\n } = req.body;\n\n if (!openAiApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 model,\n temperature,\n openAiApiKey,\n customPrompt,\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 openAiApiKey?: string;\n customPrompt?: string;\n locales: Locales[];\n fileContent: string;\n filePath?: string;\n model?: string;\n temperature?: number;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n req: Request<AuditContentDeclarationFieldBody>,\n res: ResponseWithInformation<AuditContentDeclarationFieldResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization } = res.locals;\n const {\n fileContent,\n openAiApiKey,\n customPrompt,\n model,\n temperature,\n locales,\n tagsKeys,\n keyPath,\n } = req.body;\n\n if (!openAiApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 model,\n temperature,\n openAiApiKey,\n customPrompt,\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<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 AuditContentDeclarationMetadataBody = {\n openAiApiKey?: string;\n customPrompt?: string;\n fileContent: string;\n model?: string;\n temperature?: number;\n};\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationUtil.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: ResponseWithInformation<AuditContentDeclarationMetadataResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, project } = res.locals;\n const { fileContent, openAiApiKey, customPrompt, model, temperature } =\n req.body;\n\n if (!openAiApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 model,\n openAiApiKey,\n temperature,\n customPrompt,\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 AuditTagBody = {\n openAiApiKey?: string;\n customPrompt?: string;\n filePath?: string;\n model?: string;\n temperature?: number;\n tag: Tag;\n};\nexport type AuditTagResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\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: ResponseWithInformation<AuditTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization } = res.locals;\n const { openAiApiKey, customPrompt, model, temperature, tag } = req.body;\n\n if (!openAiApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 model,\n openAiApiKey,\n temperature,\n customPrompt,\n dictionaries,\n tag,\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 AskDocQuestionBody = {\n messages: askDocQuestionUtil.ChatCompletionRequestMessage[];\n discutionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n req: Request<undefined, undefined, AskDocQuestionBody>,\n res: ResponseWithInformation<AskDocQuestionResult>\n) => {\n const { messages, discutionId } = req.body;\n const { user, project, organization } = res.locals;\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, {\n onMessage: (chunk) => {\n res.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n res.flush?.();\n },\n })\n .then(async (fullResponse) => {\n // 3. Persist discussion while the client already has all chunks\n await DiscussionModel.findOneAndUpdate(\n { discutionId },\n {\n $set: {\n discutionId,\n userId: user?._id,\n projectId: project?._id,\n organizationId: organization?._id,\n messages: messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: new Date(),\n })),\n },\n },\n { upsert: true, new: true }\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 model?: string;\n temperature?: number;\n openAiApiKey?: string;\n customPrompt?: string;\n};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n req: Request<undefined, undefined, AutocompleteBody>,\n res: ResponseWithInformation<AutocompleteResponse>\n) => {\n try {\n const { text, model, openAiApiKey, customPrompt, temperature } = req.body;\n const { user, project, organization } = res.locals;\n\n if (!openAiApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n model,\n openAiApiKey,\n temperature,\n customPrompt,\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/controllers/ai.controller.ts"],"sourcesContent":["import { DiscussionModel } from '@/models/discussion.model';\nimport type { Dictionary } from '@/types/dictionary.types';\nimport type { Tag } from '@/types/tag.types';\nimport { type KeyPath } from '@intlayer/core';\nimport type { ResponseWithInformation } from '@middlewares/sessionAuth.middleware';\nimport { getDictionariesByTags } from '@services/dictionary.service';\nimport * as tagService from '@services/tag.service';\nimport { getTagsByKeys } from '@services/tag.service';\nimport { AIOptions } from '@utils/AI/aiSdk';\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 translateJSONUtil from '@utils/AI/translateJSON';\nimport { type AppError, ErrorHandler } from '@utils/errors';\nimport { formatResponse, type ResponseData } from '@utils/responseData';\nimport type { NextFunction, Request } from 'express';\nimport type { Locales } from 'intlayer';\n\nexport type TranslateJSONBody = Omit<\n translateJSONUtil.TranslateJSONOptions,\n 'tags'\n> & {\n tagsKeys?: string[];\n};\nexport type TranslateJSONResult =\n ResponseData<translateJSONUtil.TranslateJSONResultData>;\n\nexport const translateJSON = async (\n req: Request<AuditContentDeclarationBody>,\n res: ResponseWithInformation<TranslateJSONResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization } = res.locals;\n const { aiOptions, tagsKeys, ...rest } = req.body;\n\n // Check if any API key is present\n const hasApiKey = Boolean(aiOptions?.apiKey);\n\n if (!hasApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 translateJSONUtil.translateJSON({\n ...rest,\n aiOptions,\n tags,\n });\n\n if (!auditResponse) {\n ErrorHandler.handleGenericErrorResponse(res, 'AUDIT_FAILED');\n return;\n }\n\n const responseData =\n formatResponse<translateJSONUtil.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 AuditContentDeclarationBody = {\n aiOptions?: AIOptions;\n locales: Locales[];\n defaultLocale: Locales;\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: ResponseWithInformation<AuditContentDeclarationResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization } = res.locals;\n const { fileContent, filePath, aiOptions, locales, defaultLocale, tagsKeys } =\n req.body;\n\n // Check if any API key is present\n const hasApiKey = Boolean(aiOptions?.apiKey);\n\n if (!hasApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 aiOptions,\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: Locales[];\n fileContent: string;\n filePath?: string;\n tagsKeys?: string[];\n keyPath: KeyPath[];\n};\nexport type AuditContentDeclarationFieldResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\n\n/**\n * Retrieves a list of dictionaries based on filters and pagination.\n */\nexport const auditContentDeclarationField = async (\n req: Request<AuditContentDeclarationFieldBody>,\n res: ResponseWithInformation<AuditContentDeclarationFieldResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization } = res.locals;\n const { fileContent, aiOptions, locales, tagsKeys, keyPath } = req.body;\n\n // Check if any API key is present\n const hasApiKey = Boolean(aiOptions?.apiKey);\n\n if (!hasApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 aiOptions,\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<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 AuditContentDeclarationMetadataBody = {\n aiOptions?: AIOptions;\n fileContent: string;\n};\nexport type AuditContentDeclarationMetadataResult =\n ResponseData<auditContentDeclarationUtil.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: ResponseWithInformation<AuditContentDeclarationMetadataResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, organization, project } = res.locals;\n const { fileContent, aiOptions } = req.body;\n\n // Check if any API key is present\n const hasApiKey = Boolean(aiOptions?.apiKey);\n\n if (!hasApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 aiOptions,\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 AuditTagBody = {\n aiOptions?: AIOptions;\n tag: Tag;\n};\nexport type AuditTagResult =\n ResponseData<auditContentDeclarationUtil.AuditFileResultData>;\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: ResponseWithInformation<AuditTagResult>,\n _next: NextFunction\n): Promise<void> => {\n const { user, project, organization } = res.locals;\n const { aiOptions, tag } = req.body;\n\n // Check if any API key is present\n const hasApiKey = Boolean(aiOptions?.apiKey);\n\n if (!hasApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\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 aiOptions,\n dictionaries,\n tag,\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 AskDocQuestionBody = {\n messages: askDocQuestionUtil.ChatCompletionRequestMessage[];\n discutionId: string;\n};\nexport type AskDocQuestionResult =\n ResponseData<askDocQuestionUtil.AskDocQuestionResult>;\n\nexport const askDocQuestion = async (\n req: Request<undefined, undefined, AskDocQuestionBody>,\n res: ResponseWithInformation<AskDocQuestionResult>\n) => {\n const { messages, discutionId } = req.body;\n const { user, project, organization } = res.locals;\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, {\n onMessage: (chunk) => {\n res.write(`data: ${JSON.stringify({ chunk })}\\n\\n`);\n res.flush?.();\n },\n })\n .then(async (fullResponse) => {\n // 3. Persist discussion while the client already has all chunks\n await DiscussionModel.findOneAndUpdate(\n { discutionId },\n {\n $set: {\n discutionId,\n userId: user?._id,\n projectId: project?._id,\n organizationId: organization?._id,\n messages: messages.map((msg) => ({\n role: msg.role,\n content: msg.content,\n timestamp: new Date(),\n })),\n },\n },\n { upsert: true, new: true }\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};\n\nexport type AutocompleteResponse = ResponseData<{\n autocompletion: string;\n}>;\n\nexport const autocomplete = async (\n req: Request<undefined, undefined, AutocompleteBody>,\n res: ResponseWithInformation<AutocompleteResponse>\n) => {\n try {\n const { text, aiOptions } = req.body;\n const { user, project, organization } = res.locals;\n\n // Check if any API key is present\n const hasApiKey = Boolean(aiOptions?.apiKey);\n\n if (!hasApiKey) {\n if (!user || !project || !organization) {\n ErrorHandler.handleGenericErrorResponse(res, 'AI_ACCESS_DENIED');\n return;\n }\n }\n\n const response = (await autocompleteUtil.autocomplete({\n text,\n aiOptions,\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"],"mappings":"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","names":[]}
@@ -396,20 +396,25 @@ const updateDictionary = async (req, res, _next) => {
396
396
  const deleteDictionary = async (req, res, _next) => {
397
397
  const { project, dictionaryRights } = res.locals;
398
398
  const { dictionaryId } = req.params;
399
+ console.log("dictionaryId1", dictionaryId);
399
400
  if (!dictionaryId) {
400
401
  ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_ID_NOT_FOUND");
401
402
  return;
402
403
  }
404
+ console.log("dictionaryId2", dictionaryId);
403
405
  if (!project) {
404
406
  ErrorHandler.handleGenericErrorResponse(res, "PROJECT_NOT_DEFINED");
405
407
  return;
406
408
  }
409
+ console.log("dictionaryId3", dictionaryId);
407
410
  if (!dictionaryRights?.admin) {
408
411
  ErrorHandler.handleGenericErrorResponse(res, "DICTIONARY_RIGHTS_NOT_ADMIN");
409
412
  return;
410
413
  }
414
+ console.log("dictionaryId4", dictionaryId);
411
415
  try {
412
416
  const dictionaryToDelete = await dictionaryService.getDictionaryById(dictionaryId);
417
+ console.log("dictionaryToDelete", dictionaryToDelete);
413
418
  if (!dictionaryToDelete.projectIds.includes(project._id)) {
414
419
  ErrorHandler.handleGenericErrorResponse(
415
420
  res,