smart-review 1.0.1 → 1.0.2
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.en-US.md +580 -0
- package/README.md +93 -54
- package/bin/install.js +192 -55
- package/bin/review.js +42 -47
- package/index.js +0 -1
- package/lib/ai-client-pool.js +63 -25
- package/lib/ai-client.js +262 -415
- package/lib/config-loader.js +35 -7
- package/lib/default-config.js +33 -24
- package/lib/reviewer.js +267 -97
- package/lib/segmented-analyzer.js +102 -126
- package/lib/utils/git-diff-parser.js +9 -8
- package/lib/utils/i18n.js +986 -0
- package/package.json +2 -10
- package/templates/rules/en-US/best-practices.js +111 -0
- package/templates/rules/en-US/performance.js +123 -0
- package/templates/rules/en-US/security.js +311 -0
- package/templates/rules/zh-CN/best-practices.js +111 -0
- package/templates/rules/zh-CN/performance.js +123 -0
- package/templates/rules/zh-CN/security.js +311 -0
- package/templates/smart-review.json +2 -1
package/bin/review.js
CHANGED
|
@@ -6,6 +6,7 @@ import { CodeReviewer } from '../lib/reviewer.js';
|
|
|
6
6
|
import { ConfigLoader } from '../lib/config-loader.js';
|
|
7
7
|
import { logger } from '../lib/utils/logger.js';
|
|
8
8
|
import { BATCH_CONSTANTS } from '../lib/utils/constants.js';
|
|
9
|
+
import { t, displayRisk } from '../lib/utils/i18n.js';
|
|
9
10
|
|
|
10
11
|
class ReviewCLI {
|
|
11
12
|
constructor() {
|
|
@@ -33,30 +34,31 @@ class ReviewCLI {
|
|
|
33
34
|
const args = process.argv.slice(2);
|
|
34
35
|
|
|
35
36
|
try {
|
|
36
|
-
logger.progress('代码审查启动中,请等待...');
|
|
37
37
|
// 加载配置
|
|
38
38
|
const configLoader = new ConfigLoader(this.projectRoot);
|
|
39
39
|
const config = await configLoader.loadConfig();
|
|
40
|
+
this.config = config;
|
|
41
|
+
logger.progress(t(config, 'cli_start'));
|
|
40
42
|
|
|
41
43
|
// 调试日志开关(命令行)
|
|
42
44
|
if (args.includes('--debug')) {
|
|
43
45
|
logger.debugMode = true;
|
|
44
|
-
logger.info('
|
|
46
|
+
logger.info(t(config, 'debug_enabled'));
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// 处理AI相关的命令行参数
|
|
48
50
|
if (args.includes('--no-ai')) {
|
|
49
51
|
config.ai = { ...config.ai, enabled: false };
|
|
50
|
-
logger.info('
|
|
52
|
+
logger.info(t(config, 'ai_disabled'));
|
|
51
53
|
} else if (args.includes('--ai')) {
|
|
52
54
|
config.ai = { ...config.ai, enabled: true };
|
|
53
|
-
logger.info('
|
|
55
|
+
logger.info(t(config, 'ai_enabled'));
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
// 处理Git Diff审查相关参数
|
|
57
59
|
if (args.includes('--diff-only')) {
|
|
58
60
|
config.ai = { ...config.ai, reviewOnlyChanges: true };
|
|
59
|
-
logger.info(
|
|
61
|
+
logger.info(t(config, 'diff_only_enabled'));
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
const rules = await configLoader.loadRules(config);
|
|
@@ -71,33 +73,33 @@ class ReviewCLI {
|
|
|
71
73
|
const fileList = args[filesIndex + 1]?.split(',').map(f => f.trim()) || [];
|
|
72
74
|
result = await reviewer.reviewSpecificFiles(fileList);
|
|
73
75
|
} else {
|
|
74
|
-
logger.info('
|
|
75
|
-
logger.info(
|
|
76
|
-
logger.info(
|
|
77
|
-
logger.info(
|
|
76
|
+
logger.info(t(config, 'usage_header'));
|
|
77
|
+
logger.info(t(config, 'usage_staged'));
|
|
78
|
+
logger.info(t(config, 'usage_diffonly'));
|
|
79
|
+
logger.info(t(config, 'usage_files'));
|
|
78
80
|
process.exit(1);
|
|
79
81
|
}
|
|
80
82
|
|
|
81
|
-
this.printResults(result);
|
|
83
|
+
this.printResults(result, config);
|
|
82
84
|
process.exit(result.blockSubmission ? 1 : 0);
|
|
83
85
|
|
|
84
86
|
} catch (error) {
|
|
85
|
-
logger.error('
|
|
87
|
+
logger.error(t(this.config || process.env.SMART_REVIEW_LOCALE || 'zh-CN', 'review_error', { error: error.message }));
|
|
86
88
|
process.exit(1);
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
printResults(result) {
|
|
92
|
+
printResults(result, config) {
|
|
91
93
|
const staticIssues = result.issues.filter(i => i.source === 'static');
|
|
92
94
|
const aiIssues = result.issues.filter(i => i.source === 'ai');
|
|
93
95
|
// 本地规则审查结果
|
|
94
|
-
logger.info('\n
|
|
96
|
+
logger.info('\n' + t(config, 'local_analysis_header'));
|
|
95
97
|
const staticByFile = this.groupIssuesByFile(staticIssues);
|
|
96
98
|
if (staticIssues.length === 0) {
|
|
97
|
-
logger.info('
|
|
99
|
+
logger.info(t(config, 'no_issues'));
|
|
98
100
|
} else {
|
|
99
101
|
Object.entries(staticByFile).forEach(([file, issues]) => {
|
|
100
|
-
logger.info(
|
|
102
|
+
logger.info('\n' + t(config, 'file_label', { file }));
|
|
101
103
|
// 根据行号排序,保证位置从上到下
|
|
102
104
|
const getLineKey = (i) => {
|
|
103
105
|
const s = Number(i.lineStart);
|
|
@@ -110,36 +112,36 @@ class ReviewCLI {
|
|
|
110
112
|
};
|
|
111
113
|
const sorted = [...issues].sort((a, b) => getLineKey(a) - getLineKey(b));
|
|
112
114
|
sorted.forEach((issue, index) => {
|
|
113
|
-
logger.info(
|
|
114
|
-
const locationLabel = this.formatLocationLabel(issue);
|
|
115
|
+
logger.info('\n' + t(config, 'issue_label', { index: index + 1 }));
|
|
116
|
+
const locationLabel = this.formatLocationLabel(issue, config);
|
|
115
117
|
if (locationLabel) logger.info(locationLabel);
|
|
116
118
|
// 美化代码片段输出:去除行号前缀并统一缩进
|
|
117
119
|
if (issue.snippet && issue.snippet.trim().length > 0) {
|
|
118
|
-
logger.info('
|
|
120
|
+
logger.info(t(config, 'snippet_label'));
|
|
119
121
|
logger.info(this.formatSnippet(issue.snippet));
|
|
120
122
|
} else {
|
|
121
|
-
logger.info(
|
|
123
|
+
logger.info(t(config, 'snippet_global_label'));
|
|
122
124
|
}
|
|
123
|
-
logger.info(
|
|
124
|
-
logger.info(
|
|
125
|
-
if (issue.suggestion) logger.info(
|
|
125
|
+
logger.info(t(config, 'risk_level_label') + displayRisk(issue.risk, config));
|
|
126
|
+
logger.info(t(config, 'risk_reason_label') + issue.message);
|
|
127
|
+
if (issue.suggestion) logger.info(t(config, 'suggestions_label') + issue.suggestion);
|
|
126
128
|
});
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
131
|
|
|
130
132
|
// AI代码分析结果(若有)
|
|
131
133
|
if (result.aiRan) {
|
|
132
|
-
logger.info('\
|
|
134
|
+
logger.info('\n' + t(config, 'ai_analysis_header'));
|
|
133
135
|
// 说明:行号可能不连续是预处理所致(剥离注释/无需审查片段),请忽略行号跳跃
|
|
134
|
-
logger.info('
|
|
136
|
+
logger.info(t(config, 'tip_line_numbers'));
|
|
135
137
|
// 去重:按 file+line+message 进行去重,避免重复输出
|
|
136
138
|
// 打印时不再对 AI 结果做粗略去重(聚合逻辑已在 reviewer.generateResult 中完成),仅分文件展示
|
|
137
139
|
const aiByFile = this.groupIssuesByFile(aiIssues);
|
|
138
140
|
if (aiIssues.length === 0) {
|
|
139
|
-
logger.info('
|
|
141
|
+
logger.info(t(config, 'no_issues'));
|
|
140
142
|
} else {
|
|
141
143
|
Object.entries(aiByFile).forEach(([file, issues]) => {
|
|
142
|
-
logger.info(
|
|
144
|
+
logger.info('\n' + t(config, 'file_label', { file }));
|
|
143
145
|
// 根据行号排序:起始行号优先,其次单行号;无行号的排后
|
|
144
146
|
const getLineKey = (i) => {
|
|
145
147
|
const s = Number(i.lineStart);
|
|
@@ -152,19 +154,19 @@ class ReviewCLI {
|
|
|
152
154
|
};
|
|
153
155
|
const sorted = [...issues].sort((a, b) => getLineKey(a) - getLineKey(b));
|
|
154
156
|
sorted.forEach((issue, index) => {
|
|
155
|
-
logger.info(
|
|
156
|
-
const locationLabel = this.formatLocationLabel(issue);
|
|
157
|
+
logger.info('\n' + t(config, 'issue_label', { index: index + 1 }));
|
|
158
|
+
const locationLabel = this.formatLocationLabel(issue, config);
|
|
157
159
|
if (locationLabel) logger.info(locationLabel);
|
|
158
160
|
// 美化代码片段输出:去除行号前缀并统一缩进
|
|
159
161
|
if (issue.snippet && issue.snippet.trim().length > 0) {
|
|
160
|
-
logger.info('
|
|
162
|
+
logger.info(t(config, 'snippet_label'));
|
|
161
163
|
logger.info(this.formatSnippet(issue.snippet));
|
|
162
164
|
} else {
|
|
163
|
-
logger.info(
|
|
165
|
+
logger.info(t(config, 'snippet_global_label'));
|
|
164
166
|
}
|
|
165
|
-
logger.info(
|
|
166
|
-
logger.info(
|
|
167
|
-
if (issue.suggestion) logger.info(
|
|
167
|
+
logger.info(t(config, 'risk_level_label') + displayRisk(issue.risk, config));
|
|
168
|
+
logger.info(t(config, 'risk_reason_label') + issue.message);
|
|
169
|
+
if (issue.suggestion) logger.info(t(config, 'suggestions_label') + issue.suggestion);
|
|
168
170
|
});
|
|
169
171
|
});
|
|
170
172
|
}
|
|
@@ -188,28 +190,21 @@ class ReviewCLI {
|
|
|
188
190
|
}, {});
|
|
189
191
|
}
|
|
190
192
|
|
|
191
|
-
getRiskLevelText(risk) {
|
|
192
|
-
|
|
193
|
-
'critical': '致命',
|
|
194
|
-
'high': '高危',
|
|
195
|
-
'medium': '中危',
|
|
196
|
-
'low': '低危',
|
|
197
|
-
'suggestion': '建议'
|
|
198
|
-
};
|
|
199
|
-
return levels[risk] || risk;
|
|
193
|
+
getRiskLevelText(risk, config) {
|
|
194
|
+
return displayRisk(risk, config);
|
|
200
195
|
}
|
|
201
196
|
|
|
202
197
|
// 位置标签:范围为“行号范围:start-end”,单行为“行号:n”
|
|
203
|
-
formatLocationLabel(issue) {
|
|
198
|
+
formatLocationLabel(issue, config) {
|
|
204
199
|
const start = Number(issue.lineStart);
|
|
205
200
|
const end = Number(issue.lineEnd);
|
|
206
201
|
const single = Number(issue.line);
|
|
207
202
|
if (Number.isFinite(start) && Number.isFinite(end) && start > 0 && end >= start) {
|
|
208
|
-
if (start === end) return
|
|
209
|
-
return
|
|
203
|
+
if (start === end) return t(config, 'line_label', { line: start });
|
|
204
|
+
return t(config, 'line_range_label', { start, end });
|
|
210
205
|
}
|
|
211
206
|
if (Number.isFinite(single) && single > 0) {
|
|
212
|
-
return
|
|
207
|
+
return t(config, 'line_label', { line: single });
|
|
213
208
|
}
|
|
214
209
|
return '';
|
|
215
210
|
}
|
|
@@ -253,4 +248,4 @@ class ReviewCLI {
|
|
|
253
248
|
|
|
254
249
|
// 运行审查
|
|
255
250
|
const cli = new ReviewCLI();
|
|
256
|
-
cli.run().catch(error => logger.error('
|
|
251
|
+
cli.run().catch(error => logger.error(t(process.env.SMART_REVIEW_LOCALE || 'zh-CN', 'review_error', { error: error.message })));
|
package/index.js
CHANGED
|
@@ -2,4 +2,3 @@ export { CodeReviewer } from './lib/reviewer.js';
|
|
|
2
2
|
export { AIClient } from './lib/ai-client.js';
|
|
3
3
|
export { ConfigLoader } from './lib/config-loader.js';
|
|
4
4
|
export { defaultConfig, defaultRules } from './lib/default-config.js';
|
|
5
|
-
// https://ci.das-security.cn/repository/ah_npm
|
package/lib/ai-client-pool.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* 管理多个AI客户端实例,支持并发请求处理
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import path from 'path';
|
|
7
6
|
import { AIClient } from './ai-client.js';
|
|
8
7
|
import { logger } from './utils/logger.js';
|
|
8
|
+
import { t } from './utils/i18n.js';
|
|
9
9
|
|
|
10
10
|
export class AIClientPool {
|
|
11
11
|
constructor(config, rules, poolSize = 3, concurrencyLimiter = null) {
|
|
@@ -30,10 +30,15 @@ export class AIClientPool {
|
|
|
30
30
|
* 初始化客户端池
|
|
31
31
|
*/
|
|
32
32
|
initializePool() {
|
|
33
|
-
logger.debug(
|
|
33
|
+
logger.debug(t(this.config, 'init_pool_dbg', { size: this.poolSize }));
|
|
34
34
|
|
|
35
35
|
for (let i = 0; i < this.poolSize; i++) {
|
|
36
|
-
|
|
36
|
+
// 将 reviewDir 与 locale 一并传递,确保池中客户端的国际化与自定义提示词目录正确
|
|
37
|
+
const client = new AIClient({
|
|
38
|
+
...this.config.ai,
|
|
39
|
+
reviewDir: this.config.reviewDir,
|
|
40
|
+
locale: this.config.locale
|
|
41
|
+
});
|
|
37
42
|
client.poolId = i;
|
|
38
43
|
// 注入全局并发限速器,确保批次与分段共享并发资源
|
|
39
44
|
if (this.concurrencyLimiter) {
|
|
@@ -42,7 +47,7 @@ export class AIClientPool {
|
|
|
42
47
|
this.clients.push(client);
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
logger.debug(
|
|
50
|
+
logger.debug(t(this.config, 'pool_init_done_dbg', { count: this.clients.length }));
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
/**
|
|
@@ -142,7 +147,7 @@ export class AIClientPool {
|
|
|
142
147
|
successCount++;
|
|
143
148
|
} catch (error) {
|
|
144
149
|
failureCount++;
|
|
145
|
-
logger.error(
|
|
150
|
+
logger.error(t(this.config, 'batch_process_failed', { i: batch.originalIndex + 1, error: error.message }));
|
|
146
151
|
}
|
|
147
152
|
}
|
|
148
153
|
|
|
@@ -165,7 +170,7 @@ export class AIClientPool {
|
|
|
165
170
|
return [];
|
|
166
171
|
}
|
|
167
172
|
|
|
168
|
-
logger.debug(
|
|
173
|
+
logger.debug(t(this.config, 'start_concurrent_dbg', { count: batches.length }));
|
|
169
174
|
|
|
170
175
|
// 设置总请求数用于进度显示
|
|
171
176
|
this.stats.totalRequests = batches.length;
|
|
@@ -198,15 +203,15 @@ export class AIClientPool {
|
|
|
198
203
|
}
|
|
199
204
|
} else {
|
|
200
205
|
failureCount++;
|
|
201
|
-
logger.error(
|
|
206
|
+
logger.error(t(this.config, 'concurrent_group_failed', { index: index + 1, error: result.reason?.message || String(result.reason) }));
|
|
202
207
|
}
|
|
203
208
|
});
|
|
204
209
|
|
|
205
|
-
logger.debug(
|
|
210
|
+
logger.debug(t(this.config, 'concurrent_done_dbg', { succ: successCount, fail: failureCount }));
|
|
206
211
|
return { issues: allIssues, totalDurationMs };
|
|
207
212
|
|
|
208
213
|
} catch (error) {
|
|
209
|
-
logger.error(
|
|
214
|
+
logger.error(t(this.config, 'concurrent_processing_error', { error: error?.message || String(error) }));
|
|
210
215
|
throw error;
|
|
211
216
|
}
|
|
212
217
|
}
|
|
@@ -254,7 +259,13 @@ export class AIClientPool {
|
|
|
254
259
|
if (Array.isArray(parsed)) issueCount = parsed.length;
|
|
255
260
|
else if (parsed && Array.isArray(parsed.issues)) issueCount = parsed.issues.length;
|
|
256
261
|
} catch (e) {}
|
|
257
|
-
|
|
262
|
+
logger.success(t(this.config, 'batch_complete', {
|
|
263
|
+
i: batchIndex + 1,
|
|
264
|
+
total: this.stats.totalRequests,
|
|
265
|
+
context: fileNames,
|
|
266
|
+
issues: issueCount,
|
|
267
|
+
secs: (duration/1000).toFixed(1)
|
|
268
|
+
}));
|
|
258
269
|
if (typeof progressCallback === 'function') {
|
|
259
270
|
try { progressCallback(batchIndex, batch, 'completed', null); } catch (e) {}
|
|
260
271
|
}
|
|
@@ -268,13 +279,24 @@ export class AIClientPool {
|
|
|
268
279
|
const item = batch.items[0];
|
|
269
280
|
const fullPath = item.originalFilePath || item.filePath;
|
|
270
281
|
const totalSeg = item.totalChunks || batch.totalSegments || 1;
|
|
271
|
-
|
|
282
|
+
logger.info(t(this.config, 'batch_start_segmented', {
|
|
283
|
+
i: batchIndex + 1,
|
|
284
|
+
total: this.stats.totalRequests,
|
|
285
|
+
path: fullPath,
|
|
286
|
+
segments: totalSeg
|
|
287
|
+
}));
|
|
272
288
|
} else {
|
|
273
289
|
// 小文件批次:保持一致的“批次 i/x”格式
|
|
274
|
-
|
|
290
|
+
logger.info(t(this.config, 'batch_start_regular', {
|
|
291
|
+
i: batchIndex + 1,
|
|
292
|
+
total: this.stats.totalRequests,
|
|
293
|
+
files: fileNames,
|
|
294
|
+
tokens: batch.totalTokens,
|
|
295
|
+
count: batch.items.length
|
|
296
|
+
}));
|
|
275
297
|
}
|
|
276
298
|
const startTime = Date.now();
|
|
277
|
-
logger.debug(
|
|
299
|
+
logger.debug(t(this.config, 'request_batch_start_dbg', { index: batchIndex + 1, client: client.constructor.name }));
|
|
278
300
|
|
|
279
301
|
const result = await client.analyzeSmartBatch(formattedBatch, batch, requestMeta);
|
|
280
302
|
|
|
@@ -286,9 +308,21 @@ export class AIClientPool {
|
|
|
286
308
|
if (batch.isLargeFileSegment) {
|
|
287
309
|
const item = batch.items[0];
|
|
288
310
|
const fullPath = item.originalFilePath || item.filePath;
|
|
289
|
-
|
|
311
|
+
logger.success(t(this.config, 'batch_complete', {
|
|
312
|
+
i: batchIndex + 1,
|
|
313
|
+
total: this.stats.totalRequests,
|
|
314
|
+
context: fullPath,
|
|
315
|
+
issues: issueCount,
|
|
316
|
+
secs: (duration/1000).toFixed(1)
|
|
317
|
+
}));
|
|
290
318
|
} else {
|
|
291
|
-
|
|
319
|
+
logger.success(t(this.config, 'batch_complete', {
|
|
320
|
+
i: batchIndex + 1,
|
|
321
|
+
total: this.stats.totalRequests,
|
|
322
|
+
context: fileNames,
|
|
323
|
+
issues: issueCount,
|
|
324
|
+
secs: (duration/1000).toFixed(1)
|
|
325
|
+
}));
|
|
292
326
|
}
|
|
293
327
|
}
|
|
294
328
|
|
|
@@ -296,19 +330,19 @@ export class AIClientPool {
|
|
|
296
330
|
if (batch.isLargeFileSegment) {
|
|
297
331
|
const item = batch.items[0];
|
|
298
332
|
const fullPath = item.originalFilePath || item.filePath;
|
|
299
|
-
logger.debug(
|
|
333
|
+
logger.debug(t(this.config, 'seg_chunk_no_issues_dbg', { file: fullPath, chunk: item.chunkIndex + 1, total: item.totalChunks, preview: JSON.stringify(result).substring(0, 200) + '...' }));
|
|
300
334
|
} else {
|
|
301
335
|
const fileNamesAbs = batch.items.map(item => (item.originalFilePath || item.filePath)).join(', ');
|
|
302
|
-
logger.debug(
|
|
336
|
+
logger.debug(t(this.config, 'files_no_issues_dbg', { files: fileNamesAbs, preview: JSON.stringify(result).substring(0, 200) + '...' }));
|
|
303
337
|
}
|
|
304
338
|
} else {
|
|
305
339
|
if (batch.isLargeFileSegment) {
|
|
306
340
|
const item = batch.items[0];
|
|
307
341
|
const fullPath = item.originalFilePath || item.filePath;
|
|
308
|
-
logger.debug(
|
|
342
|
+
logger.debug(t(this.config, 'issues_risk_levels_dbg', { file: fullPath, levels: result.issues.map(i => i.risk || 'unknown').join(', ') }));
|
|
309
343
|
} else {
|
|
310
344
|
const fileNamesAbs = batch.items.map(item => (item.originalFilePath || item.filePath)).join(', ');
|
|
311
|
-
logger.debug(
|
|
345
|
+
logger.debug(t(this.config, 'issues_risk_levels_dbg', { file: fileNamesAbs, levels: result.issues.map(i => i.risk || 'unknown').join(', ') }));
|
|
312
346
|
}
|
|
313
347
|
}
|
|
314
348
|
|
|
@@ -317,13 +351,13 @@ export class AIClientPool {
|
|
|
317
351
|
if (batch.isLargeFileSegment) {
|
|
318
352
|
const item = batch.items[0];
|
|
319
353
|
const fullPath = item.originalFilePath || item.filePath;
|
|
320
|
-
logger.debug(
|
|
354
|
+
logger.debug(t(this.config, 'issues_details_dbg', { file: fullPath }));
|
|
321
355
|
} else {
|
|
322
356
|
const fileNamesAbs = batch.items.map(item => (item.originalFilePath || item.filePath)).join(', ');
|
|
323
|
-
logger.debug(
|
|
357
|
+
logger.debug(t(this.config, 'issues_details_dbg', { file: fileNamesAbs }));
|
|
324
358
|
}
|
|
325
359
|
result.issues.forEach((issue, idx) => {
|
|
326
|
-
logger.debug(
|
|
360
|
+
logger.debug(t(this.config, 'issue_item_dbg', { index: idx + 1, risk: issue.risk, message: (issue.message || '').slice(0, 100) + '...' }));
|
|
327
361
|
});
|
|
328
362
|
}
|
|
329
363
|
|
|
@@ -343,7 +377,11 @@ export class AIClientPool {
|
|
|
343
377
|
this.stats.retryCount++;
|
|
344
378
|
|
|
345
379
|
if (retryCount < maxRetries) {
|
|
346
|
-
|
|
380
|
+
logger.warn(t(this.config, 'batch_retry_warn', {
|
|
381
|
+
i: batchIndex + 1,
|
|
382
|
+
retry: retryCount + 1,
|
|
383
|
+
error: error.message
|
|
384
|
+
}));
|
|
347
385
|
|
|
348
386
|
// 指数退避延迟
|
|
349
387
|
const delay = Math.pow(2, retryCount) * 1000;
|
|
@@ -351,7 +389,7 @@ export class AIClientPool {
|
|
|
351
389
|
|
|
352
390
|
return this.processBatchWithRetry(batch, batchIndex, progressCallback, retryCount + 1);
|
|
353
391
|
} else {
|
|
354
|
-
logger.error(
|
|
392
|
+
logger.error(t(this.config, 'batch_retry_error', { i: batchIndex + 1, max: maxRetries, error: error.message }));
|
|
355
393
|
|
|
356
394
|
if (progressCallback) {
|
|
357
395
|
progressCallback(batchIndex, batch, 'failed', error);
|
|
@@ -405,7 +443,7 @@ export class AIClientPool {
|
|
|
405
443
|
* 清理资源
|
|
406
444
|
*/
|
|
407
445
|
cleanup() {
|
|
408
|
-
logger.debug('
|
|
446
|
+
logger.debug(t(this.config, 'cleanup_pool_dbg'));
|
|
409
447
|
|
|
410
448
|
// 清理等待队列
|
|
411
449
|
this.waitingQueue.forEach(resolve => {
|