@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.
- package/dist/cjs/controllers/ai.controller.cjs +60 -52
- package/dist/cjs/controllers/ai.controller.cjs.map +1 -1
- package/dist/cjs/controllers/dictionary.controller.cjs +5 -0
- package/dist/cjs/controllers/dictionary.controller.cjs.map +1 -1
- package/dist/cjs/export.cjs +4 -2
- package/dist/cjs/export.cjs.map +1 -1
- package/dist/cjs/routes/ai.routes.cjs +6 -0
- package/dist/cjs/routes/ai.routes.cjs.map +1 -1
- package/dist/cjs/services/dictionary.service.cjs +6 -1
- package/dist/cjs/services/dictionary.service.cjs.map +1 -1
- package/dist/cjs/utils/AI/aiSdk.cjs +140 -0
- package/dist/cjs/utils/AI/aiSdk.cjs.map +1 -0
- package/dist/cjs/utils/AI/askDocQuestion/PROMPT.md +2 -1
- package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs +32 -27
- package/dist/cjs/utils/AI/askDocQuestion/askDocQuestion.cjs.map +1 -1
- package/dist/cjs/utils/AI/askDocQuestion/embeddings.json +7374 -0
- package/dist/cjs/utils/AI/auditDictionary/PROMPT.md +4 -0
- package/dist/cjs/utils/AI/auditDictionary/index.cjs +36 -43
- package/dist/cjs/utils/AI/auditDictionary/index.cjs.map +1 -1
- package/dist/cjs/utils/AI/auditDictionaryField/PROMPT.md +4 -0
- package/dist/cjs/utils/AI/auditDictionaryField/index.cjs +34 -28
- package/dist/cjs/utils/AI/auditDictionaryField/index.cjs.map +1 -1
- package/dist/cjs/utils/AI/auditDictionaryMetadata/PROMPT.md +4 -0
- package/dist/cjs/utils/AI/auditDictionaryMetadata/index.cjs +23 -23
- package/dist/cjs/utils/AI/auditDictionaryMetadata/index.cjs.map +1 -1
- package/dist/cjs/utils/{auditTag → AI/auditTag}/PROMPT.md +4 -0
- package/dist/cjs/utils/{auditTag → AI/auditTag}/index.cjs +27 -27
- package/dist/cjs/utils/AI/auditTag/index.cjs.map +1 -0
- package/dist/cjs/utils/AI/autocomplete/PROMPT.md +4 -0
- package/dist/cjs/utils/AI/autocomplete/index.cjs +25 -22
- package/dist/cjs/utils/AI/autocomplete/index.cjs.map +1 -1
- package/dist/cjs/utils/AI/translateJSON/PROMPT.md +53 -0
- package/dist/cjs/utils/AI/translateJSON/index.cjs +106 -0
- package/dist/cjs/utils/AI/translateJSON/index.cjs.map +1 -0
- package/dist/cjs/utils/extractJSON.cjs +52 -0
- package/dist/cjs/utils/extractJSON.cjs.map +1 -0
- package/dist/esm/controllers/ai.controller.mjs +58 -51
- package/dist/esm/controllers/ai.controller.mjs.map +1 -1
- package/dist/esm/controllers/dictionary.controller.mjs +5 -0
- package/dist/esm/controllers/dictionary.controller.mjs.map +1 -1
- package/dist/esm/export.mjs +3 -2
- package/dist/esm/export.mjs.map +1 -1
- package/dist/esm/routes/ai.routes.mjs +8 -1
- package/dist/esm/routes/ai.routes.mjs.map +1 -1
- package/dist/esm/services/dictionary.service.mjs +6 -1
- package/dist/esm/services/dictionary.service.mjs.map +1 -1
- package/dist/esm/utils/AI/aiSdk.mjs +115 -0
- package/dist/esm/utils/AI/aiSdk.mjs.map +1 -0
- package/dist/esm/utils/AI/askDocQuestion/PROMPT.md +2 -1
- package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs +32 -27
- package/dist/esm/utils/AI/askDocQuestion/askDocQuestion.mjs.map +1 -1
- package/dist/esm/utils/AI/askDocQuestion/embeddings.json +7374 -0
- package/dist/esm/utils/AI/auditDictionary/PROMPT.md +4 -0
- package/dist/esm/utils/AI/auditDictionary/index.mjs +36 -43
- package/dist/esm/utils/AI/auditDictionary/index.mjs.map +1 -1
- package/dist/esm/utils/AI/auditDictionaryField/PROMPT.md +4 -0
- package/dist/esm/utils/AI/auditDictionaryField/index.mjs +34 -28
- package/dist/esm/utils/AI/auditDictionaryField/index.mjs.map +1 -1
- package/dist/esm/utils/AI/auditDictionaryMetadata/PROMPT.md +4 -0
- package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs +23 -23
- package/dist/esm/utils/AI/auditDictionaryMetadata/index.mjs.map +1 -1
- package/dist/esm/utils/{auditTag → AI/auditTag}/PROMPT.md +4 -0
- package/dist/esm/utils/AI/auditTag/index.mjs +49 -0
- package/dist/esm/utils/AI/auditTag/index.mjs.map +1 -0
- package/dist/esm/utils/AI/autocomplete/PROMPT.md +4 -0
- package/dist/esm/utils/AI/autocomplete/index.mjs +25 -22
- package/dist/esm/utils/AI/autocomplete/index.mjs.map +1 -1
- package/dist/esm/utils/AI/translateJSON/PROMPT.md +53 -0
- package/dist/esm/utils/AI/translateJSON/index.mjs +81 -0
- package/dist/esm/utils/AI/translateJSON/index.mjs.map +1 -0
- package/dist/esm/utils/extractJSON.mjs +28 -0
- package/dist/esm/utils/extractJSON.mjs.map +1 -0
- package/dist/types/controllers/ai.controller.d.ts +12 -21
- package/dist/types/controllers/ai.controller.d.ts.map +1 -1
- package/dist/types/controllers/dictionary.controller.d.ts.map +1 -1
- package/dist/types/export.d.ts +12 -11
- package/dist/types/export.d.ts.map +1 -1
- package/dist/types/routes/ai.routes.d.ts +5 -0
- package/dist/types/routes/ai.routes.d.ts.map +1 -1
- package/dist/types/services/dictionary.service.d.ts +2 -2
- package/dist/types/services/dictionary.service.d.ts.map +1 -1
- package/dist/types/utils/AI/aiSdk.d.ts +41 -0
- package/dist/types/utils/AI/aiSdk.d.ts.map +1 -0
- package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts +1 -1
- package/dist/types/utils/AI/askDocQuestion/askDocQuestion.d.ts.map +1 -1
- package/dist/types/utils/AI/auditDictionary/index.d.ts +10 -15
- package/dist/types/utils/AI/auditDictionary/index.d.ts.map +1 -1
- package/dist/types/utils/AI/auditDictionaryField/index.d.ts +9 -14
- package/dist/types/utils/AI/auditDictionaryField/index.d.ts.map +1 -1
- package/dist/types/utils/AI/auditDictionaryMetadata/index.d.ts +7 -13
- package/dist/types/utils/AI/auditDictionaryMetadata/index.d.ts.map +1 -1
- package/dist/types/utils/AI/auditTag/index.d.ts +18 -0
- package/dist/types/utils/AI/auditTag/index.d.ts.map +1 -0
- package/dist/types/utils/AI/autocomplete/index.d.ts +6 -12
- package/dist/types/utils/AI/autocomplete/index.d.ts.map +1 -1
- package/dist/types/utils/AI/translateJSON/index.d.ts +24 -0
- package/dist/types/utils/AI/translateJSON/index.d.ts.map +1 -0
- package/dist/types/utils/extractJSON.d.ts +6 -0
- package/dist/types/utils/extractJSON.d.ts.map +1 -0
- package/package.json +13 -7
- package/dist/cjs/utils/auditTag/index.cjs.map +0 -1
- package/dist/esm/utils/auditTag/index.mjs +0 -49
- package/dist/esm/utils/auditTag/index.mjs.map +0 -1
- package/dist/types/utils/auditTag/index.d.ts +0 -30
- 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 {
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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,
|
|
112
|
-
|
|
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
|
-
|
|
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 {
|
|
151
|
-
|
|
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
|
-
|
|
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,
|
|
245
|
+
const { text, aiOptions } = req.body;
|
|
238
246
|
const { user, project, organization } = res.locals;
|
|
239
|
-
|
|
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
|
-
|
|
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":"AAAA,SAAS,uBAAuB;AAKhC,SAAS,6BAA6B;AACtC,YAAY,gBAAgB;AAC5B,SAAS,qBAAqB;AAE9B,YAAY,wBAAwB;AACpC,YAAY,iCAAiC;AAC7C,YAAY,sCAAsC;AAClD,YAAY,yCAAyC;AACrD,YAAY,kBAAkB;AAC9B,YAAY,sBAAsB;AAClC,YAAY,uBAAuB;AACnC,SAAwB,oBAAoB;AAC5C,SAAS,sBAAyC;AAa3C,MAAM,gBAAgB,OAC3B,KACA,KACA,UACkB;AAClB,QAAM,EAAE,MAAM,SAAS,aAAa,IAAI,IAAI;AAC5C,QAAM,EAAE,WAAW,UAAU,GAAG,KAAK,IAAI,IAAI;AAG7C,QAAM,YAAY,QAAQ,WAAW,MAAM;AAE3C,MAAI,CAAC,WAAW;AACd,QAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cAAc;AACtC,mBAAa,2BAA2B,KAAK,kBAAkB;AAC/D;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,QAAI,OAAc,CAAC;AAEnB,QAAI,SAAS,gBAAgB;AAC3B,aAAO,MAAM,cAAc,UAAU,QAAQ,cAAc;AAAA,IAC7D;AAEA,UAAM,gBAAgB,MAAM,kBAAkB,cAAc;AAAA,MAC1D,GAAG;AAAA,MACH;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,CAAC,eAAe;AAClB,mBAAa,2BAA2B,KAAK,cAAc;AAC3D;AAAA,IACF;AAEA,UAAM,eACJ,eAA0D;AAAA,MACxD,MAAM;AAAA,IACR,CAAC;AAEH,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;AAgBO,MAAM,0BAA0B,OACrC,KACA,KACA,UACkB;AAClB,QAAM,EAAE,MAAM,SAAS,aAAa,IAAI,IAAI;AAC5C,QAAM,EAAE,aAAa,UAAU,WAAW,SAAS,eAAe,SAAS,IACzE,IAAI;AAGN,QAAM,YAAY,QAAQ,WAAW,MAAM;AAE3C,MAAI,CAAC,WAAW;AACd,QAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cAAc;AACtC,mBAAa,2BAA2B,KAAK,kBAAkB;AAC/D;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,QAAI,OAAc,CAAC;AAEnB,QAAI,SAAS,gBAAgB;AAC3B,aAAO,MAAM,cAAc,UAAU,QAAQ,cAAc;AAAA,IAC7D;AAEA,UAAM,gBAAgB,MAAM,4BAA4B,gBAAgB;AAAA,MACtE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,CAAC,eAAe;AAClB,mBAAa,2BAA2B,KAAK,cAAc;AAC3D;AAAA,IACF;AAEA,UAAM,eACJ,eAAgE;AAAA,MAC9D,MAAM;AAAA,IACR,CAAC;AAEH,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;AAgBO,MAAM,+BAA+B,OAC1C,KACA,KACA,UACkB;AAClB,QAAM,EAAE,MAAM,SAAS,aAAa,IAAI,IAAI;AAC5C,QAAM,EAAE,aAAa,WAAW,SAAS,UAAU,QAAQ,IAAI,IAAI;AAGnE,QAAM,YAAY,QAAQ,WAAW,MAAM;AAE3C,MAAI,CAAC,WAAW;AACd,QAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cAAc;AACtC,mBAAa,2BAA2B,KAAK,kBAAkB;AAC/D;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,QAAI,OAAc,CAAC;AAEnB,QAAI,SAAS,gBAAgB;AAC3B,aAAO,MAAM,cAAc,UAAU,QAAQ,cAAc;AAAA,IAC7D;AAEA,UAAM,gBACJ,MAAM,iCAAiC,qBAAqB;AAAA,MAC1D;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAEH,QAAI,CAAC,eAAe;AAClB,mBAAa,2BAA2B,KAAK,cAAc;AAC3D;AAAA,IACF;AAEA,UAAM,eACJ,eAAgE;AAAA,MAC9D,MAAM;AAAA,IACR,CAAC;AAEH,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;AAYO,MAAM,kCAAkC,OAC7C,KACA,KACA,UACkB;AAClB,QAAM,EAAE,MAAM,cAAc,QAAQ,IAAI,IAAI;AAC5C,QAAM,EAAE,aAAa,UAAU,IAAI,IAAI;AAGvC,QAAM,YAAY,QAAQ,WAAW,MAAM;AAE3C,MAAI,CAAC,WAAW;AACd,QAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cAAc;AACtC,mBAAa,2BAA2B,KAAK,kBAAkB;AAC/D;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,UAAM,OAAc,MAAM,WAAW;AAAA,MACnC;AAAA,QACE,gBAAgB,cAAc;AAAA,MAChC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAEA,UAAM,gBACJ,MAAM,oCAAoC,wBAAwB;AAAA,MAChE;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAEH,QAAI,CAAC,eAAe;AAClB,mBAAa,2BAA2B,KAAK,cAAc;AAC3D;AAAA,IACF;AAEA,UAAM,eACJ,eAAgE;AAAA,MAC9D,MAAM;AAAA,IACR,CAAC;AAEH,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;AAYO,MAAM,WAAW,OACtB,KACA,KACA,UACkB;AAClB,QAAM,EAAE,MAAM,SAAS,aAAa,IAAI,IAAI;AAC5C,QAAM,EAAE,WAAW,IAAI,IAAI,IAAI;AAG/B,QAAM,YAAY,QAAQ,WAAW,MAAM;AAE3C,MAAI,CAAC,WAAW;AACd,QAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cAAc;AACtC,mBAAa,2BAA2B,KAAK,kBAAkB;AAC/D;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACF,QAAI,eAA6B,CAAC;AAClC,QAAI,SAAS,gBAAgB;AAC3B,qBAAe,MAAM,sBAAsB,CAAC,IAAI,GAAG,GAAG,QAAQ,GAAG;AAAA,IACnE;AAEA,UAAM,gBAAgB,MAAM,aAAa,SAAS;AAAA,MAChD;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,CAAC,eAAe;AAClB,mBAAa,2BAA2B,KAAK,cAAc;AAC3D;AAAA,IACF;AAEA,UAAM,eACJ,eAAgE;AAAA,MAC9D,MAAM;AAAA,IACR,CAAC;AAEH,QAAI,KAAK,YAAY;AACrB;AAAA,EACF,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;AASO,MAAM,iBAAiB,OAC5B,KACA,QACG;AACH,QAAM,EAAE,UAAU,YAAY,IAAI,IAAI;AACtC,QAAM,EAAE,MAAM,SAAS,aAAa,IAAI,IAAI;AAG5C,MAAI,UAAU,gBAAgB,kCAAkC;AAChE,MAAI,UAAU,iBAAiB,wBAAwB;AACvD,MAAI,UAAU,cAAc,YAAY;AACxC,MAAI,UAAU,qBAAqB,IAAI;AACvC,MAAI,eAAe;AACnB,MAAI,MAAM,iBAAiB;AAC3B,MAAI,QAAQ;AAGZ,qBACG,eAAe,UAAU;AAAA,IACxB,WAAW,CAAC,UAAU;AACpB,UAAI,MAAM,SAAS,KAAK,UAAU,EAAE,MAAM,CAAC,CAAC;AAAA;AAAA,CAAM;AAClD,UAAI,QAAQ;AAAA,IACd;AAAA,EACF,CAAC,EACA,KAAK,OAAO,iBAAiB;AAE5B,UAAM,gBAAgB;AAAA,MACpB,EAAE,YAAY;AAAA,MACd;AAAA,QACE,MAAM;AAAA,UACJ;AAAA,UACA,QAAQ,MAAM;AAAA,UACd,WAAW,SAAS;AAAA,UACpB,gBAAgB,cAAc;AAAA,UAC9B,UAAU,SAAS,IAAI,CAAC,SAAS;AAAA,YAC/B,MAAM,IAAI;AAAA,YACV,SAAS,IAAI;AAAA,YACb,WAAW,oBAAI,KAAK;AAAA,UACtB,EAAE;AAAA,QACJ;AAAA,MACF;AAAA,MACA,EAAE,QAAQ,MAAM,KAAK,KAAK;AAAA,IAC5B;AAGA,QAAI;AAAA,MACF,SAAS,KAAK,UAAU,EAAE,MAAM,MAAM,UAAU,aAAa,CAAC,CAAC;AAAA;AAAA;AAAA,IACjE;AACA,QAAI,IAAI;AAAA,EACV,CAAC,EACA,MAAM,CAAC,QAAQ;AAEd,QAAI;AAAA,MACF;AAAA,QAAuB,KAAK,UAAU,EAAE,SAAS,IAAI,QAAQ,CAAC,CAAC;AAAA;AAAA;AAAA,IACjE;AACA,QAAI,IAAI;AAAA,EACV,CAAC;AACL;AAWO,MAAM,eAAe,OAC1B,KACA,QACG;AACH,MAAI;AACF,UAAM,EAAE,MAAM,UAAU,IAAI,IAAI;AAChC,UAAM,EAAE,MAAM,SAAS,aAAa,IAAI,IAAI;AAG5C,UAAM,YAAY,QAAQ,WAAW,MAAM;AAE3C,QAAI,CAAC,WAAW;AACd,UAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,cAAc;AACtC,qBAAa,2BAA2B,KAAK,kBAAkB;AAC/D;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAY,MAAM,iBAAiB,aAAa;AAAA,MACpD;AAAA,MACA;AAAA,IACF,CAAC,KAAM;AAAA,MACL,gBAAgB;AAAA,MAChB,WAAW;AAAA,IACb;AAEA,UAAM,eACJ,eAA4D;AAAA,MAC1D,MAAM;AAAA,IACR,CAAC;AAEH,QAAI,KAAK,YAAY;AAAA,EACvB,SAAS,OAAO;AACd,iBAAa,uBAAuB,KAAK,KAAiB;AAC1D;AAAA,EACF;AACF;","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,
|