locai 2.1.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/README.md +928 -0
- package/dist/cli.js +718 -0
- package/dist/commands/review.js +357 -0
- package/dist/commands/translator.js +29 -0
- package/dist/config/index.js +52 -0
- package/dist/core/adapters/base-adapter.js +1 -0
- package/dist/core/adapters/factory.js +44 -0
- package/dist/core/adapters/json-adapter.js +20 -0
- package/dist/core/adapters/po-adapter.js +50 -0
- package/dist/core/adapters/properties-adapter.js +31 -0
- package/dist/core/adapters/yaml-adapter.js +24 -0
- package/dist/core/context-processor.js +247 -0
- package/dist/core/detection/framework-detector.js +56 -0
- package/dist/core/detection/structure-detector.js +30 -0
- package/dist/core/fallback-provider.js +442 -0
- package/dist/core/orchestrator.js +438 -0
- package/dist/core/pipeline/Pipeline.js +48 -0
- package/dist/core/pipeline/PipelineStep.js +13 -0
- package/dist/core/pipeline/context.js +21 -0
- package/dist/core/pipeline/steps/CacheReadStep.js +80 -0
- package/dist/core/pipeline/steps/CacheWriteStep.js +32 -0
- package/dist/core/pipeline/steps/ConfidenceCheckStep.js +67 -0
- package/dist/core/pipeline/steps/GlossaryPostStep.js +17 -0
- package/dist/core/pipeline/steps/GlossaryPreStep.js +20 -0
- package/dist/core/pipeline/steps/InputValidationStep.js +26 -0
- package/dist/core/pipeline/steps/QualityCheckStep.js +48 -0
- package/dist/core/pipeline/steps/ToneCheckStep.js +58 -0
- package/dist/core/pipeline/steps/TranslationStep.js +65 -0
- package/dist/core/pipeline/steps/VectorCacheReadStep.js +84 -0
- package/dist/core/pipeline/steps/VectorCacheWriteStep.js +42 -0
- package/dist/core/pipeline/steps/__tests__/ToneCheckStep.test.js +100 -0
- package/dist/core/provider-factory.js +139 -0
- package/dist/providers/base-provider.js +230 -0
- package/dist/providers/dashscope.js +128 -0
- package/dist/providers/deepseek.js +111 -0
- package/dist/providers/gemini.js +135 -0
- package/dist/providers/openai.js +203 -0
- package/dist/providers/xai.js +111 -0
- package/dist/services/embedding-provider.js +50 -0
- package/dist/services/translation-service.js +609 -0
- package/dist/services/vector-store.js +79 -0
- package/dist/utils/ai-context-analyzer.js +460 -0
- package/dist/utils/confidence-scorer.js +277 -0
- package/dist/utils/error-helper.js +386 -0
- package/dist/utils/file-manager.js +357 -0
- package/dist/utils/glossary-manager.js +194 -0
- package/dist/utils/graceful-shutdown.js +118 -0
- package/dist/utils/input-validator.js +663 -0
- package/dist/utils/logger.js +374 -0
- package/dist/utils/object-transformer.js +177 -0
- package/dist/utils/progress-tracker.js +277 -0
- package/dist/utils/prompt-templates.js +415 -0
- package/dist/utils/quality/__tests__/tone-verifier.test.js +91 -0
- package/dist/utils/quality/base-checker.js +73 -0
- package/dist/utils/quality/code-block-checker.js +214 -0
- package/dist/utils/quality/html-tag-checker.js +64 -0
- package/dist/utils/quality/index.js +220 -0
- package/dist/utils/quality/length-checker.js +140 -0
- package/dist/utils/quality/markdown-checker.js +220 -0
- package/dist/utils/quality/placeholder-checker.js +142 -0
- package/dist/utils/quality/punctuation-checker.js +50 -0
- package/dist/utils/quality/quote-balance-checker.js +143 -0
- package/dist/utils/quality/special-characters-checker.js +229 -0
- package/dist/utils/quality/style-guide-checker.js +192 -0
- package/dist/utils/quality/text-sanitizer.js +120 -0
- package/dist/utils/quality/tone-verifier.js +91 -0
- package/dist/utils/rate-limiter.js +398 -0
- package/dist/utils/retry-helper.js +166 -0
- package/dist/utils/state-manager.js +162 -0
- package/dist/utils/statistics-manager.js +51 -0
- package/dist/utils/ui-manager.js +98 -0
- package/package.json +80 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import BaseProvider from "./base-provider.js";
|
|
3
|
+
import { getPrompt, getAnalysisPrompt } from "../utils/prompt-templates.js";
|
|
4
|
+
import RetryHelper from "../utils/retry-helper.js";
|
|
5
|
+
/**
|
|
6
|
+
* Provider implementation for OpenAI (GPT) models.
|
|
7
|
+
*/
|
|
8
|
+
class OpenAIProvider extends BaseProvider {
|
|
9
|
+
client;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new OpenAIProvider instance.
|
|
12
|
+
* @param {ProviderConfig} config - Provider configuration.
|
|
13
|
+
*/
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
super("openai", config);
|
|
16
|
+
this.client = axios.create({
|
|
17
|
+
baseURL: "https://api.openai.com/v1",
|
|
18
|
+
headers: {
|
|
19
|
+
...this.commonHeaders,
|
|
20
|
+
Authorization: `Bearer ${this.getApiKey()}`,
|
|
21
|
+
},
|
|
22
|
+
timeout: 30000,
|
|
23
|
+
maxRedirects: 0,
|
|
24
|
+
validateStatus: (status) => status < 500,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
getApiKey() {
|
|
28
|
+
return process.env.OPENAI_API_KEY;
|
|
29
|
+
}
|
|
30
|
+
getEndpoint() {
|
|
31
|
+
return "/chat/completions";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Translate text using OpenAI.
|
|
35
|
+
*/
|
|
36
|
+
async translate(text, sourceLang, targetLang, options = {}) {
|
|
37
|
+
this.validateRequest(text, sourceLang, targetLang);
|
|
38
|
+
const config = this.getConfig(options.apiConfig?.openai);
|
|
39
|
+
const promptData = getPrompt("openai", sourceLang, targetLang, text, options);
|
|
40
|
+
return RetryHelper.withRetry(async () => {
|
|
41
|
+
try {
|
|
42
|
+
const response = await this.client.post(this.getEndpoint(), {
|
|
43
|
+
model: config.model || "gpt-4o",
|
|
44
|
+
...promptData,
|
|
45
|
+
temperature: config.temperature,
|
|
46
|
+
max_completion_tokens: config.maxTokens || config.max_tokens,
|
|
47
|
+
});
|
|
48
|
+
this.validateResponse(response, this.name);
|
|
49
|
+
const translation = this.extractTranslation(response.data, this.name);
|
|
50
|
+
return this.sanitizeTranslation(translation);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.handleApiError(error, this.name);
|
|
54
|
+
throw error; // handleApiError throws, but TS might need this
|
|
55
|
+
}
|
|
56
|
+
}, {
|
|
57
|
+
maxRetries: options.retryOptions?.maxRetries || 2,
|
|
58
|
+
initialDelay: options.retryOptions?.initialDelay || 1000,
|
|
59
|
+
context: "OpenAI Provider",
|
|
60
|
+
logContext: {
|
|
61
|
+
source: sourceLang,
|
|
62
|
+
target: targetLang,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async analyze(prompt, options = {}) {
|
|
67
|
+
const config = this.getConfig({
|
|
68
|
+
model: options.model || "gpt-4o",
|
|
69
|
+
temperature: options.temperature || 0.2,
|
|
70
|
+
maxTokens: options.maxTokens || 1000,
|
|
71
|
+
});
|
|
72
|
+
const promptData = getAnalysisPrompt("openai", prompt, options);
|
|
73
|
+
return RetryHelper.withRetry(async () => {
|
|
74
|
+
try {
|
|
75
|
+
const response = await this.client.post(this.getEndpoint(), {
|
|
76
|
+
model: config.model,
|
|
77
|
+
...promptData,
|
|
78
|
+
temperature: config.temperature,
|
|
79
|
+
max_completion_tokens: config.maxTokens || config.max_tokens,
|
|
80
|
+
});
|
|
81
|
+
this.validateResponse(response, this.name);
|
|
82
|
+
const result = this.extractTranslation(response.data, this.name);
|
|
83
|
+
return this.sanitizeTranslation(result);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.handleApiError(error, this.name);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}, {
|
|
90
|
+
maxRetries: options.maxRetries || 2,
|
|
91
|
+
initialDelay: options.initialDelay || 1000,
|
|
92
|
+
context: "OpenAI Provider Analysis",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async chat(messages, options = {}) {
|
|
96
|
+
const config = this.getConfig({
|
|
97
|
+
model: options.model || "gpt-4o",
|
|
98
|
+
temperature: options.temperature || 0.3,
|
|
99
|
+
maxTokens: options.maxTokens || 2000,
|
|
100
|
+
});
|
|
101
|
+
return RetryHelper.withRetry(async () => {
|
|
102
|
+
try {
|
|
103
|
+
const response = await this.client.post(this.getEndpoint(), {
|
|
104
|
+
model: config.model,
|
|
105
|
+
messages: messages,
|
|
106
|
+
temperature: config.temperature,
|
|
107
|
+
max_completion_tokens: config.maxTokens || config.max_tokens,
|
|
108
|
+
response_format: options.json ? { type: "json_object" } : undefined,
|
|
109
|
+
});
|
|
110
|
+
this.validateResponse(response, this.name);
|
|
111
|
+
const result = this.extractTranslation(response.data, this.name);
|
|
112
|
+
return this.sanitizeTranslation(result);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
this.handleApiError(error, this.name);
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}, {
|
|
119
|
+
maxRetries: options.maxRetries || 2,
|
|
120
|
+
initialDelay: options.initialDelay || 1000,
|
|
121
|
+
context: "OpenAI Provider Chat",
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async embed(text, options = {}) {
|
|
125
|
+
const config = this.getConfig({
|
|
126
|
+
model: options.model || "text-embedding-3-small",
|
|
127
|
+
});
|
|
128
|
+
return RetryHelper.withRetry(async () => {
|
|
129
|
+
try {
|
|
130
|
+
const response = await this.client.post("/embeddings", {
|
|
131
|
+
model: config.model,
|
|
132
|
+
input: text,
|
|
133
|
+
});
|
|
134
|
+
if (response.data && response.data.data && response.data.data[0]) {
|
|
135
|
+
return response.data.data[0].embedding;
|
|
136
|
+
}
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
this.handleApiError(error, this.name);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
}, {
|
|
144
|
+
maxRetries: options.maxRetries || 2,
|
|
145
|
+
initialDelay: options.initialDelay || 1000,
|
|
146
|
+
context: "OpenAI Provider Embed",
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async embedBatch(texts, options = {}) {
|
|
150
|
+
const config = this.getConfig({
|
|
151
|
+
model: options.model || "text-embedding-3-small",
|
|
152
|
+
});
|
|
153
|
+
return RetryHelper.withRetry(async () => {
|
|
154
|
+
try {
|
|
155
|
+
// OpenAI suggests replacing newlines for best results
|
|
156
|
+
const cleanTexts = texts.map((t) => t.replace(/\n/g, " "));
|
|
157
|
+
const response = await this.client.post("/embeddings", {
|
|
158
|
+
model: config.model,
|
|
159
|
+
input: cleanTexts,
|
|
160
|
+
});
|
|
161
|
+
if (response.data && response.data.data) {
|
|
162
|
+
return response.data.data
|
|
163
|
+
.sort((a, b) => a.index - b.index)
|
|
164
|
+
.map((item) => item.embedding);
|
|
165
|
+
}
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
this.handleApiError(error, this.name);
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
}, {
|
|
173
|
+
maxRetries: options.maxRetries || 2,
|
|
174
|
+
initialDelay: options.initialDelay || 1000,
|
|
175
|
+
context: "OpenAI Provider Embed Batch",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// Lazy singleton - created on first use
|
|
180
|
+
let openaiProvider = null;
|
|
181
|
+
function getProvider() {
|
|
182
|
+
if (!openaiProvider) {
|
|
183
|
+
openaiProvider = new OpenAIProvider();
|
|
184
|
+
}
|
|
185
|
+
return openaiProvider;
|
|
186
|
+
}
|
|
187
|
+
// Export both class and legacy functions
|
|
188
|
+
async function translate(text, sourceLang, targetLang, options = {}) {
|
|
189
|
+
return getProvider().translate(text, sourceLang, targetLang, options);
|
|
190
|
+
}
|
|
191
|
+
async function analyze(prompt, options = {}) {
|
|
192
|
+
return getProvider().analyze(prompt, options);
|
|
193
|
+
}
|
|
194
|
+
async function chat(messages, options = {}) {
|
|
195
|
+
return getProvider().chat(messages, options);
|
|
196
|
+
}
|
|
197
|
+
async function embed(text, options = {}) {
|
|
198
|
+
return getProvider().embed(text, options);
|
|
199
|
+
}
|
|
200
|
+
async function embedBatch(texts, options = {}) {
|
|
201
|
+
return getProvider().embedBatch(texts, options);
|
|
202
|
+
}
|
|
203
|
+
export { translate, analyze, chat, embed, embedBatch, OpenAIProvider };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import BaseProvider from "./base-provider.js";
|
|
3
|
+
import { getPrompt, getAnalysisPrompt } from "../utils/prompt-templates.js";
|
|
4
|
+
import RetryHelper from "../utils/retry-helper.js";
|
|
5
|
+
/**
|
|
6
|
+
* Provider implementation for X.AI (Grok) models.
|
|
7
|
+
*/
|
|
8
|
+
class XAIProvider extends BaseProvider {
|
|
9
|
+
client;
|
|
10
|
+
/**
|
|
11
|
+
* Create a new XAIProvider instance.
|
|
12
|
+
* @param {ProviderConfig} config - Provider configuration.
|
|
13
|
+
*/
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
super("xai", config);
|
|
16
|
+
this.client = axios.create({
|
|
17
|
+
baseURL: "https://api.x.ai/v1",
|
|
18
|
+
headers: {
|
|
19
|
+
...this.commonHeaders,
|
|
20
|
+
Authorization: `Bearer ${this.getApiKey()}`,
|
|
21
|
+
},
|
|
22
|
+
timeout: 30000,
|
|
23
|
+
maxRedirects: 0,
|
|
24
|
+
validateStatus: (status) => status < 500,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
getApiKey() {
|
|
28
|
+
return process.env.XAI_API_KEY;
|
|
29
|
+
}
|
|
30
|
+
getEndpoint() {
|
|
31
|
+
return "/chat/completions";
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Translate text using X.AI.
|
|
35
|
+
*/
|
|
36
|
+
async translate(text, sourceLang, targetLang, options = {}) {
|
|
37
|
+
this.validateRequest(text, sourceLang, targetLang);
|
|
38
|
+
const config = this.getConfig(options.apiConfig?.xai);
|
|
39
|
+
const promptData = getPrompt("xai", sourceLang, targetLang, text, options);
|
|
40
|
+
return RetryHelper.withRetry(async () => {
|
|
41
|
+
try {
|
|
42
|
+
const response = await this.client.post(this.getEndpoint(), {
|
|
43
|
+
model: config.model || "grok-4",
|
|
44
|
+
...promptData,
|
|
45
|
+
temperature: config.temperature || 0.3,
|
|
46
|
+
max_tokens: config.maxTokens || 2000,
|
|
47
|
+
});
|
|
48
|
+
this.validateResponse(response, this.name);
|
|
49
|
+
const translation = this.extractTranslation(response.data, this.name);
|
|
50
|
+
return this.sanitizeTranslation(translation);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.handleApiError(error, this.name);
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}, {
|
|
57
|
+
maxRetries: options.retryOptions?.maxRetries || 2,
|
|
58
|
+
initialDelay: options.retryOptions?.initialDelay || 1000,
|
|
59
|
+
context: "X.AI Provider",
|
|
60
|
+
logContext: {
|
|
61
|
+
source: sourceLang,
|
|
62
|
+
target: targetLang,
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async analyze(prompt, options = {}) {
|
|
67
|
+
const config = this.getConfig({
|
|
68
|
+
model: options.model || "grok-4",
|
|
69
|
+
temperature: options.temperature || 0.2,
|
|
70
|
+
maxTokens: options.maxTokens || 1000,
|
|
71
|
+
});
|
|
72
|
+
const promptData = getAnalysisPrompt("xai", prompt, options);
|
|
73
|
+
return RetryHelper.withRetry(async () => {
|
|
74
|
+
try {
|
|
75
|
+
const response = await this.client.post(this.getEndpoint(), {
|
|
76
|
+
model: config.model,
|
|
77
|
+
...promptData,
|
|
78
|
+
temperature: config.temperature,
|
|
79
|
+
max_tokens: config.maxTokens,
|
|
80
|
+
});
|
|
81
|
+
this.validateResponse(response, this.name);
|
|
82
|
+
const result = this.extractTranslation(response.data, this.name);
|
|
83
|
+
return this.sanitizeTranslation(result);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.handleApiError(error, this.name);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}, {
|
|
90
|
+
maxRetries: options.maxRetries || 2,
|
|
91
|
+
initialDelay: options.initialDelay || 1000,
|
|
92
|
+
context: "X.AI Provider Analysis",
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Lazy singleton - created on first use
|
|
97
|
+
let xaiProvider = null;
|
|
98
|
+
function getProvider() {
|
|
99
|
+
if (!xaiProvider) {
|
|
100
|
+
xaiProvider = new XAIProvider();
|
|
101
|
+
}
|
|
102
|
+
return xaiProvider;
|
|
103
|
+
}
|
|
104
|
+
// Export both class and legacy functions
|
|
105
|
+
async function translate(text, sourceLang, targetLang, options = {}) {
|
|
106
|
+
return getProvider().translate(text, sourceLang, targetLang, options);
|
|
107
|
+
}
|
|
108
|
+
async function analyze(prompt, options = {}) {
|
|
109
|
+
return getProvider().analyze(prompt, options);
|
|
110
|
+
}
|
|
111
|
+
export { translate, analyze, XAIProvider };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as openaiProvider from "../providers/openai.js";
|
|
2
|
+
/**
|
|
3
|
+
* Service to generate embeddings for text
|
|
4
|
+
*/
|
|
5
|
+
export class EmbeddingProvider {
|
|
6
|
+
provider;
|
|
7
|
+
model;
|
|
8
|
+
constructor(config = {}) {
|
|
9
|
+
this.provider = config.provider || "openai";
|
|
10
|
+
this.model = config.model || "text-embedding-3-small";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get embedding for a single text
|
|
14
|
+
*/
|
|
15
|
+
async getEmbedding(text) {
|
|
16
|
+
if (!text)
|
|
17
|
+
return [];
|
|
18
|
+
try {
|
|
19
|
+
if (this.provider === "openai") {
|
|
20
|
+
// Use the shared provider implementation
|
|
21
|
+
// Note: OpenAIProvider will handle auth, retries, and error logging
|
|
22
|
+
return await openaiProvider.embed(text, { model: this.model });
|
|
23
|
+
}
|
|
24
|
+
// Fallback or other providers would go here
|
|
25
|
+
console.warn(`Provider ${this.provider} not supported for embeddings yet.`);
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error(`Failed to generate embedding: ${error.message}`);
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get embeddings for multiple texts (batch)
|
|
35
|
+
*/
|
|
36
|
+
async getEmbeddings(texts) {
|
|
37
|
+
if (!texts || texts.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
try {
|
|
40
|
+
if (this.provider === "openai") {
|
|
41
|
+
return await openaiProvider.embedBatch(texts, { model: this.model });
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.error(`Failed to generate batch embeddings: ${error.message}`);
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|