docs-combiner 0.4.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/agentApi/agentControlClaim.js +26 -0
  2. package/dist/agentApi/approachMatrixView.js +19 -0
  3. package/dist/agentApi/capabilitiesMeta.js +78 -0
  4. package/dist/agentApi/catalogUrlPresets.js +26 -0
  5. package/dist/agentApi/constants.js +11 -0
  6. package/dist/agentApi/creativeSelections.js +25 -0
  7. package/dist/agentApi/escalation.js +221 -0
  8. package/dist/agentApi/generatedPairsGuard.js +85 -0
  9. package/dist/agentApi/httpHelpers.js +77 -0
  10. package/dist/agentApi/imagesReadiness.js +60 -0
  11. package/dist/agentApi/jobStatus.js +113 -0
  12. package/dist/agentApi/productImageDrive.js +39 -0
  13. package/dist/agentApi/regenerateImages.js +125 -0
  14. package/dist/agentApi/rendererTypes.js +3 -0
  15. package/dist/agentApi/routes.js +440 -0
  16. package/dist/agentApi/sessionSnapshot.js +82 -0
  17. package/dist/agentApi/sessionTypes.js +2 -0
  18. package/dist/agentApi/spec.js +1045 -0
  19. package/dist/agentApi/types.js +2 -0
  20. package/dist/agentApi/validation.js +32 -0
  21. package/dist/campaignsCsv.js +420 -0
  22. package/dist/contentPairs.js +62 -0
  23. package/dist/flexcardBalance.js +88 -0
  24. package/dist/integrations/driveApi.js +420 -0
  25. package/dist/integrations/httpTimeout.js +116 -0
  26. package/dist/integrations/openrouter.js +532 -0
  27. package/dist/main.js +316 -1214
  28. package/dist/models.js +17 -0
  29. package/dist/offerSettings.js +19 -0
  30. package/dist/preload.js +9 -0
  31. package/dist/promptOverrides.js +437 -0
  32. package/dist/prompts.js +1243 -0
  33. package/dist/renderer.js +19 -19
  34. package/dist/renderer.js.map +1 -1
  35. package/dist/server/app.js +378 -0
  36. package/dist/server/auth.js +74 -0
  37. package/dist/server/contentJobs.js +367 -0
  38. package/dist/server/driveCatalog.js +122 -0
  39. package/dist/server/driveProxy.js +220 -0
  40. package/dist/server/flexcardMeta.js +29 -0
  41. package/dist/server/googleAuth.js +84 -0
  42. package/dist/server/imageAssets.js +87 -0
  43. package/dist/server/imageJobs.js +776 -0
  44. package/dist/server/index.js +38 -0
  45. package/dist/server/jobEvents.js +116 -0
  46. package/dist/server/jobs.js +358 -0
  47. package/dist/server/openRouterMeta.js +58 -0
  48. package/dist/server/spec.js +544 -0
  49. package/dist/server/storage.js +133 -0
  50. package/dist/server/telegram.js +53 -0
  51. package/dist/server/workspaceSnapshot.js +273 -0
  52. package/package.json +15 -3
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildAgentValidation = buildAgentValidation;
4
+ const escalation_1 = require("./escalation");
5
+ const generatedPairsGuard_1 = require("./generatedPairsGuard");
6
+ function buildAgentValidation(input) {
7
+ var _a;
8
+ const checks = [
9
+ { id: 'googleAuth', ok: input.hasGoogleAuth, message: 'Google Drive auth is required.' },
10
+ { id: 'openRouterKey', ok: input.hasOpenRouterKey, message: 'OpenRouter API key is required.' },
11
+ { id: 'driveFolder', ok: input.hasDriveFolder, message: 'Working Drive folder is required.' },
12
+ { id: 'product', ok: Boolean(input.product.trim()), message: 'Product is required.' },
13
+ { id: 'geoBlock', ok: input.hasGeo, message: 'At least one geo row is required.' },
14
+ {
15
+ id: 'creativeSelections',
16
+ ok: input.textApproachCount >= 2 && input.imageApproachCounts.some(count => count > 0),
17
+ message: 'Text and image creative selections are required.',
18
+ },
19
+ ];
20
+ if ((_a = input.incompletePairs) === null || _a === void 0 ? void 0 : _a.length) {
21
+ checks.push({
22
+ id: 'generatedPairsComplete',
23
+ ok: false,
24
+ message: `Generated title/text pairs are incomplete — ${(0, generatedPairsGuard_1.buildIncompletePairsValidationMessage)(input.incompletePairs)}. Regenerate pairs before catalog export.`,
25
+ });
26
+ }
27
+ const hints = input.mappedOfferFromCsv
28
+ ? []
29
+ : ['Offer is not bound in campaigns.csv yet — bind via setup-campaign or UI selector.'];
30
+ const escalation = (0, escalation_1.buildValidationEscalation)({ blocking: checks.filter(check => !check.ok), hints });
31
+ return Object.assign({ ok: checks.every(check => check.ok), checks, blocking: checks.filter(check => !check.ok), hints }, (escalation ? { escalation } : {}));
32
+ }
@@ -0,0 +1,420 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CAMPAIGN_CSV_ADDITIONAL_INFO_OVERRIDE_HEADER = exports.CAMPAIGN_CSV_PRODUCT_OVERRIDE_HEADER = exports.CAMPAIGNS_CSV_FILENAME = void 0;
37
+ exports.parseCampaignsCsvText = parseCampaignsCsvText;
38
+ exports.findCampaignRowForFolder = findCampaignRowForFolder;
39
+ exports.findOfferForCampaign = findOfferForCampaign;
40
+ exports.computeCampaignDescriptionOverrides = computeCampaignDescriptionOverrides;
41
+ exports.campaignDescriptionOverridesEqual = campaignDescriptionOverridesEqual;
42
+ exports.campaignDescriptionOverridesFromRow = campaignDescriptionOverridesFromRow;
43
+ exports.uniqueOffersFromRows = uniqueOffersFromRows;
44
+ exports.upsertCampaignOfferInCsvText = upsertCampaignOfferInCsvText;
45
+ exports.upsertCampaignDescriptionOverridesInCsvText = upsertCampaignDescriptionOverridesInCsvText;
46
+ exports.removeCampaignFromCsvText = removeCampaignFromCsvText;
47
+ const XLSX = __importStar(require("xlsx"));
48
+ exports.CAMPAIGNS_CSV_FILENAME = 'campaigns.csv';
49
+ const normalizeCell = (value) => String(value !== null && value !== void 0 ? value : '').trim();
50
+ const normalizeKey = (value) => value.trim().toLowerCase();
51
+ const CAMPAIGN_HEADER_ALIASES = new Set([
52
+ 'campaign',
53
+ 'campaign_folder_id',
54
+ 'campaign_id',
55
+ 'campaign_path',
56
+ 'folder_id',
57
+ 'drive_folder_id',
58
+ 'кампания',
59
+ 'папка',
60
+ 'папка_кампании',
61
+ ]);
62
+ const OFFER_HEADER_ALIASES = new Set([
63
+ 'offer',
64
+ 'offer_folder',
65
+ 'offer_name',
66
+ 'offer_id',
67
+ 'оффер',
68
+ 'оффер_папка',
69
+ ]);
70
+ const PRODUCT_OVERRIDE_HEADER_ALIASES = new Set([
71
+ 'offer_product_override',
72
+ 'product_override',
73
+ 'offer_product',
74
+ 'product_description_override',
75
+ 'товар',
76
+ 'описание_товара',
77
+ ]);
78
+ const ADDITIONAL_INFO_OVERRIDE_HEADER_ALIASES = new Set([
79
+ 'offer_additional_info_override',
80
+ 'additional_info_override',
81
+ 'offer_additional_info',
82
+ 'additional_info',
83
+ 'доп_информация',
84
+ 'дополнительная_информация',
85
+ ]);
86
+ exports.CAMPAIGN_CSV_PRODUCT_OVERRIDE_HEADER = 'offer_product_override';
87
+ exports.CAMPAIGN_CSV_ADDITIONAL_INFO_OVERRIDE_HEADER = 'offer_additional_info_override';
88
+ const normalizeHeader = (header) => normalizeKey(String(header !== null && header !== void 0 ? header : ''))
89
+ .replace(/\s+/g, '_')
90
+ .replace(/[^\wа-яё]+/gi, '_')
91
+ .replace(/_+/g, '_')
92
+ .replace(/^_|_$/g, '');
93
+ const isCampaignHeader = (header) => CAMPAIGN_HEADER_ALIASES.has(header);
94
+ const isOfferHeader = (header) => OFFER_HEADER_ALIASES.has(header);
95
+ const isProductOverrideHeader = (header) => PRODUCT_OVERRIDE_HEADER_ALIASES.has(header);
96
+ const isAdditionalInfoOverrideHeader = (header) => ADDITIONAL_INFO_OVERRIDE_HEADER_ALIASES.has(header);
97
+ /** Парсит campaigns.csv (текст файла). Первая строка — заголовки. */
98
+ function parseCampaignsCsvText(text) {
99
+ const errors = [];
100
+ const trimmed = text.trim();
101
+ if (!trimmed) {
102
+ return { rows: [], errors: ['Файл campaigns.csv пустой'] };
103
+ }
104
+ try {
105
+ const workbook = XLSX.read(trimmed, { type: 'string' });
106
+ const sheetName = workbook.SheetNames[0];
107
+ if (!sheetName) {
108
+ return { rows: [], errors: ['В campaigns.csv нет листа'] };
109
+ }
110
+ const sheet = workbook.Sheets[sheetName];
111
+ const matrix = XLSX.utils.sheet_to_json(sheet, {
112
+ header: 1,
113
+ defval: '',
114
+ raw: false,
115
+ });
116
+ if (!matrix.length) {
117
+ return { rows: [], errors: ['В campaigns.csv нет строк'] };
118
+ }
119
+ const headerRow = matrix[0].map(normalizeHeader);
120
+ const campaignCols = [];
121
+ let offerCol = -1;
122
+ let productOverrideCol = -1;
123
+ let additionalInfoOverrideCol = -1;
124
+ headerRow.forEach((header, index) => {
125
+ if (!header)
126
+ return;
127
+ if (isOfferHeader(header))
128
+ offerCol = index;
129
+ else if (isProductOverrideHeader(header))
130
+ productOverrideCol = index;
131
+ else if (isAdditionalInfoOverrideHeader(header))
132
+ additionalInfoOverrideCol = index;
133
+ else if (isCampaignHeader(header))
134
+ campaignCols.push(index);
135
+ });
136
+ if (offerCol < 0) {
137
+ return {
138
+ rows: [],
139
+ errors: [
140
+ 'Не найдена колонка оффера (offer / offer_folder / оффер). Добавьте заголовок в первую строку.',
141
+ ],
142
+ };
143
+ }
144
+ if (campaignCols.length === 0) {
145
+ return {
146
+ rows: [],
147
+ errors: [
148
+ 'Не найдена колонка кампании (campaign / campaign_folder_id / кампания). Добавьте заголовок в первую строку.',
149
+ ],
150
+ };
151
+ }
152
+ const rows = [];
153
+ for (let i = 1; i < matrix.length; i++) {
154
+ const row = matrix[i] || [];
155
+ const offer = normalizeCell(row[offerCol]);
156
+ if (!offer)
157
+ continue;
158
+ const matchValues = campaignCols
159
+ .map(colIndex => normalizeCell(row[colIndex]))
160
+ .filter(Boolean);
161
+ if (matchValues.length === 0)
162
+ continue;
163
+ rows.push({
164
+ offer,
165
+ matchValues,
166
+ productOverride: productOverrideCol >= 0 ? normalizeCell(row[productOverrideCol]) || undefined : undefined,
167
+ additionalInfoOverride: additionalInfoOverrideCol >= 0
168
+ ? normalizeCell(row[additionalInfoOverrideCol]) || undefined
169
+ : undefined,
170
+ });
171
+ }
172
+ if (rows.length === 0) {
173
+ errors.push('Нет данных: заполните строки с кампанией и оффером');
174
+ }
175
+ return { rows, errors };
176
+ }
177
+ catch (err) {
178
+ return { rows: [], errors: [`Ошибка разбора campaigns.csv: ${String(err)}`] };
179
+ }
180
+ }
181
+ const matchesCampaignValue = (needle, input) => {
182
+ const key = normalizeKey(needle);
183
+ if (!key)
184
+ return false;
185
+ const folderId = normalizeKey(input.folderId);
186
+ const folderName = normalizeKey(input.folderName);
187
+ const displayPath = normalizeKey(input.displayPath);
188
+ if (folderId && key === folderId)
189
+ return true;
190
+ if (folderName && key === folderName)
191
+ return true;
192
+ if (displayPath && key === displayPath)
193
+ return true;
194
+ if (displayPath && folderName && key === folderName && displayPath.endsWith(`/${folderName}`)) {
195
+ return true;
196
+ }
197
+ if (displayPath.includes('/')) {
198
+ const suffix = displayPath.split('/').pop() || '';
199
+ if (suffix && key === suffix)
200
+ return true;
201
+ }
202
+ return false;
203
+ };
204
+ const findCampaignRowIndexInRows = (rows, input) => {
205
+ if (!input.folderId.trim() && !input.folderName.trim() && !input.displayPath.trim()) {
206
+ return -1;
207
+ }
208
+ return rows.findIndex(row => row.matchValues.some(value => matchesCampaignValue(value, input)));
209
+ };
210
+ /** Находит строку campaigns.csv для рабочей папки кампании. */
211
+ function findCampaignRowForFolder(rows, input) {
212
+ const index = findCampaignRowIndexInRows(rows, input);
213
+ return index >= 0 ? rows[index] : null;
214
+ }
215
+ /** Находит оффер для рабочей папки кампании по таблице campaigns.csv. */
216
+ function findOfferForCampaign(rows, input) {
217
+ var _a, _b;
218
+ return (_b = (_a = findCampaignRowForFolder(rows, input)) === null || _a === void 0 ? void 0 : _a.offer) !== null && _b !== void 0 ? _b : null;
219
+ }
220
+ /** Вычисляет, что писать в campaigns.csv как оверрайды относительно offer-settings. */
221
+ function computeCampaignDescriptionOverrides(product, additionalInfo, baseline) {
222
+ const productTrimmed = product.trim();
223
+ const additionalInfoTrimmed = additionalInfo.trim();
224
+ const baselineProduct = baseline.generateProduct.trim();
225
+ const baselineAdditionalInfo = baseline.generateAdditionalInfo.trim();
226
+ return {
227
+ productOverride: productTrimmed === baselineProduct ? '' : productTrimmed,
228
+ additionalInfoOverride: additionalInfoTrimmed === baselineAdditionalInfo ? '' : additionalInfoTrimmed,
229
+ };
230
+ }
231
+ function campaignDescriptionOverridesEqual(a, b) {
232
+ return (a.productOverride === b.productOverride &&
233
+ a.additionalInfoOverride === b.additionalInfoOverride);
234
+ }
235
+ function campaignDescriptionOverridesFromRow(row) {
236
+ var _a, _b, _c, _d;
237
+ return {
238
+ productOverride: (_b = (_a = row === null || row === void 0 ? void 0 : row.productOverride) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : '',
239
+ additionalInfoOverride: (_d = (_c = row === null || row === void 0 ? void 0 : row.additionalInfoOverride) === null || _c === void 0 ? void 0 : _c.trim()) !== null && _d !== void 0 ? _d : '',
240
+ };
241
+ }
242
+ /** Уникальные имена офферов из таблицы (порядок сохраняется). */
243
+ function uniqueOffersFromRows(rows) {
244
+ const seen = new Set();
245
+ const result = [];
246
+ for (const row of rows) {
247
+ const key = normalizeKey(row.offer);
248
+ if (!key || seen.has(key))
249
+ continue;
250
+ seen.add(key);
251
+ result.push(row.offer);
252
+ }
253
+ return result;
254
+ }
255
+ const DEFAULT_CAMPAIGNS_CSV_HEADERS = ['campaign_folder_id', 'offer'];
256
+ const parseCsvMatrix = (text) => {
257
+ const trimmed = text.trim();
258
+ if (!trimmed) {
259
+ return {
260
+ parsed: {
261
+ matrix: [DEFAULT_CAMPAIGNS_CSV_HEADERS],
262
+ headerRow: DEFAULT_CAMPAIGNS_CSV_HEADERS.map(normalizeHeader),
263
+ campaignCols: [0],
264
+ offerCol: 1,
265
+ campaignFolderIdCol: 0,
266
+ productOverrideCol: -1,
267
+ additionalInfoOverrideCol: -1,
268
+ },
269
+ };
270
+ }
271
+ try {
272
+ const workbook = XLSX.read(trimmed, { type: 'string' });
273
+ const sheetName = workbook.SheetNames[0];
274
+ if (!sheetName)
275
+ return { errors: ['В campaigns.csv нет листа'] };
276
+ const sheet = workbook.Sheets[sheetName];
277
+ const matrix = XLSX.utils.sheet_to_json(sheet, {
278
+ header: 1,
279
+ defval: '',
280
+ raw: false,
281
+ });
282
+ if (!matrix.length)
283
+ return { errors: ['В campaigns.csv нет строк'] };
284
+ const headerRow = matrix[0].map(normalizeHeader);
285
+ const campaignCols = [];
286
+ let offerCol = -1;
287
+ let productOverrideCol = -1;
288
+ let additionalInfoOverrideCol = -1;
289
+ headerRow.forEach((header, index) => {
290
+ if (!header)
291
+ return;
292
+ if (isOfferHeader(header))
293
+ offerCol = index;
294
+ else if (isProductOverrideHeader(header))
295
+ productOverrideCol = index;
296
+ else if (isAdditionalInfoOverrideHeader(header))
297
+ additionalInfoOverrideCol = index;
298
+ else if (isCampaignHeader(header))
299
+ campaignCols.push(index);
300
+ });
301
+ if (offerCol < 0 || campaignCols.length === 0) {
302
+ return { errors: ['Некорректные заголовки campaigns.csv'] };
303
+ }
304
+ const campaignFolderIdCol = headerRow.findIndex(h => h === 'campaign_folder_id') >= 0
305
+ ? headerRow.findIndex(h => h === 'campaign_folder_id')
306
+ : campaignCols[0];
307
+ return {
308
+ parsed: {
309
+ matrix,
310
+ headerRow,
311
+ campaignCols,
312
+ offerCol,
313
+ campaignFolderIdCol,
314
+ productOverrideCol,
315
+ additionalInfoOverrideCol,
316
+ },
317
+ };
318
+ }
319
+ catch (err) {
320
+ return { errors: [`Ошибка разбора campaigns.csv: ${String(err)}`] };
321
+ }
322
+ };
323
+ const serializeCsvMatrix = (matrix) => {
324
+ const sheet = XLSX.utils.aoa_to_sheet(matrix);
325
+ const workbook = XLSX.utils.book_new();
326
+ XLSX.utils.book_append_sheet(workbook, sheet, 'campaigns');
327
+ return XLSX.write(workbook, { type: 'string', bookType: 'csv' });
328
+ };
329
+ const ensureMatrixColumn = (matrix, headerRow, headerLabel, normalizedHeader) => {
330
+ let colIndex = headerRow.findIndex(header => header === normalizedHeader);
331
+ if (colIndex >= 0)
332
+ return colIndex;
333
+ colIndex = matrix[0].length;
334
+ matrix[0].push(headerLabel);
335
+ headerRow.push(normalizedHeader);
336
+ for (let i = 1; i < matrix.length; i++) {
337
+ const row = matrix[i] || [];
338
+ while (row.length < matrix[0].length)
339
+ row.push('');
340
+ matrix[i] = row;
341
+ }
342
+ return colIndex;
343
+ };
344
+ const findCampaignRowIndex = (matrix, campaignCols, campaignFolderId) => {
345
+ const folderKey = normalizeKey(campaignFolderId);
346
+ for (let i = 1; i < matrix.length; i++) {
347
+ const row = matrix[i] || [];
348
+ const matched = campaignCols.some(colIndex => normalizeKey(normalizeCell(row[colIndex])) === folderKey);
349
+ if (matched)
350
+ return i;
351
+ }
352
+ return -1;
353
+ };
354
+ /** Добавить или обновить привязку кампании к офферу в тексте campaigns.csv. */
355
+ function upsertCampaignOfferInCsvText(existingText, campaignFolderId, offer) {
356
+ const campaignId = normalizeCell(campaignFolderId);
357
+ const offerName = normalizeCell(offer);
358
+ if (!campaignId)
359
+ return { csvText: existingText || '', errors: ['Пустой ID рабочей папки'] };
360
+ if (!offerName)
361
+ return { csvText: existingText || '', errors: ['Пустое имя оффера'] };
362
+ const parsedResult = parseCsvMatrix(existingText || '');
363
+ if ('errors' in parsedResult) {
364
+ return { csvText: existingText || '', errors: parsedResult.errors };
365
+ }
366
+ const { matrix, campaignCols, offerCol, campaignFolderIdCol } = parsedResult.parsed;
367
+ const rowIndex = findCampaignRowIndex(matrix, campaignCols, campaignId);
368
+ if (rowIndex >= 0) {
369
+ matrix[rowIndex][offerCol] = offerName;
370
+ matrix[rowIndex][campaignFolderIdCol] = campaignId;
371
+ }
372
+ else {
373
+ const newRow = new Array(matrix[0].length).fill('');
374
+ newRow[campaignFolderIdCol] = campaignId;
375
+ newRow[offerCol] = offerName;
376
+ matrix.push(newRow);
377
+ }
378
+ return { csvText: serializeCsvMatrix(matrix), errors: [] };
379
+ }
380
+ /** Обновить оверрайды описания оффера для кампании в campaigns.csv. */
381
+ function upsertCampaignDescriptionOverridesInCsvText(existingText, campaignFolderId, overrides) {
382
+ const campaignId = normalizeCell(campaignFolderId);
383
+ if (!campaignId)
384
+ return { csvText: existingText || '', errors: ['Пустой ID рабочей папки'] };
385
+ const parsedResult = parseCsvMatrix(existingText || '');
386
+ if ('errors' in parsedResult) {
387
+ return { csvText: existingText || '', errors: parsedResult.errors };
388
+ }
389
+ const { matrix, headerRow, campaignCols } = parsedResult.parsed;
390
+ const rowIndex = findCampaignRowIndex(matrix, campaignCols, campaignId);
391
+ if (rowIndex < 0) {
392
+ return {
393
+ csvText: existingText || '',
394
+ errors: ['В campaigns.csv нет строки для этой рабочей папки — сначала выберите оффер'],
395
+ };
396
+ }
397
+ const productOverrideCol = ensureMatrixColumn(matrix, headerRow, exports.CAMPAIGN_CSV_PRODUCT_OVERRIDE_HEADER, normalizeHeader(exports.CAMPAIGN_CSV_PRODUCT_OVERRIDE_HEADER));
398
+ const additionalInfoOverrideCol = ensureMatrixColumn(matrix, headerRow, exports.CAMPAIGN_CSV_ADDITIONAL_INFO_OVERRIDE_HEADER, normalizeHeader(exports.CAMPAIGN_CSV_ADDITIONAL_INFO_OVERRIDE_HEADER));
399
+ matrix[rowIndex][productOverrideCol] = overrides.productOverride;
400
+ matrix[rowIndex][additionalInfoOverrideCol] = overrides.additionalInfoOverride;
401
+ return { csvText: serializeCsvMatrix(matrix), errors: [] };
402
+ }
403
+ /** Удалить привязку кампании из campaigns.csv. */
404
+ function removeCampaignFromCsvText(existingText, campaignFolderId) {
405
+ const campaignId = normalizeCell(campaignFolderId);
406
+ if (!campaignId)
407
+ return { csvText: existingText, errors: ['Пустой ID рабочей папки'] };
408
+ const parsedResult = parseCsvMatrix(existingText);
409
+ if ('errors' in parsedResult) {
410
+ return { csvText: existingText, errors: parsedResult.errors };
411
+ }
412
+ const { matrix, campaignCols } = parsedResult.parsed;
413
+ const rowIndex = findCampaignRowIndex(matrix, campaignCols, campaignId);
414
+ if (rowIndex < 0) {
415
+ return { csvText: existingText, errors: [] };
416
+ }
417
+ matrix.splice(rowIndex, 1);
418
+ return { csvText: serializeCsvMatrix(matrix), errors: [] };
419
+ }
420
+ ;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parsePairsFromModelContent = parsePairsFromModelContent;
4
+ exports.summarizePairParseResult = summarizePairParseResult;
5
+ /**
6
+ * Разбор строк «ЗАГОЛОВОК n:» / «ТЕКСТ n:» из ответа модели.
7
+ * Иногда модель пишет ZAGОЛОВОК (латиница ZAG + кириллица ОЛОВОК) или ZAGOLOVOK — без этого парсер не видит заголовки.
8
+ */
9
+ const PAIRS_TITLE_LINE_RE = /^[\s\uFEFF]*(?:\*{1,2})?\s*(?:ЗАГОЛОВОК|ZAG[ОOо][ЛLl][ОOо][ВBb][ОOо][КKk]|ZAGOLOVOK)\s+(\d+)\s*:\s*(.+)$/iu;
10
+ const PAIRS_TEXT_LINE_RE = /^[\s\uFEFF]*(?:\*{1,2})?\s*(?:ТЕКСТ|TEKST)\s+(\d+)\s*:\s*(.*)$/iu;
11
+ function parsePairsFromModelContent(content, count) {
12
+ const titleMap = {};
13
+ const textMap = {};
14
+ let currentTextNum = -1;
15
+ const currentTextLines = [];
16
+ const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n');
17
+ for (const rawLine of lines) {
18
+ const trimmed = rawLine.trim();
19
+ const titleMatch = trimmed.match(PAIRS_TITLE_LINE_RE);
20
+ if (titleMatch) {
21
+ if (currentTextNum > 0) {
22
+ textMap[currentTextNum] = currentTextLines.join('\n').trim();
23
+ currentTextLines.length = 0;
24
+ currentTextNum = -1;
25
+ }
26
+ titleMap[parseInt(titleMatch[1], 10)] = titleMatch[2].trim();
27
+ continue;
28
+ }
29
+ const textMatch = trimmed.match(PAIRS_TEXT_LINE_RE);
30
+ if (textMatch) {
31
+ if (currentTextNum > 0) {
32
+ textMap[currentTextNum] = currentTextLines.join('\n').trim();
33
+ currentTextLines.length = 0;
34
+ }
35
+ currentTextNum = parseInt(textMatch[1], 10);
36
+ if (textMatch[2].trim())
37
+ currentTextLines.push(textMatch[2].trim());
38
+ continue;
39
+ }
40
+ if (currentTextNum > 0 && trimmed.length > 0) {
41
+ currentTextLines.push(trimmed);
42
+ }
43
+ }
44
+ if (currentTextNum > 0) {
45
+ textMap[currentTextNum] = currentTextLines.join('\n').trim();
46
+ }
47
+ const pairs = Array.from({ length: count }, (_, i) => ({
48
+ title: titleMap[i + 1] || '',
49
+ text: textMap[i + 1] || '',
50
+ }));
51
+ return { pairs, titleMap, textMap };
52
+ }
53
+ function summarizePairParseResult(parsed, count) {
54
+ var _a, _b;
55
+ const lines = [];
56
+ for (let i = 1; i <= count; i++) {
57
+ const title = ((_a = parsed.titleMap[i]) === null || _a === void 0 ? void 0 : _a.trim()) || '';
58
+ const text = ((_b = parsed.textMap[i]) === null || _b === void 0 ? void 0 : _b.trim()) || '';
59
+ lines.push(`Пара ${i}: заголовок=${title ? `OK (${title.length} симв.)` : 'НЕ НАЙДЕН'}, текст=${text ? `OK (${text.length} симв.)` : 'НЕ НАЙДЕН'}`);
60
+ }
61
+ return lines.join('\n');
62
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ /**
3
+ * FlexCard — `GET /api/v1/finance/accounts/` с `X-API-Key`.
4
+ * Суммируем только поле `balance` (строка или число) по строкам с `currency` === USD.
5
+ */
6
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8
+ return new (P || (P = Promise))(function (resolve, reject) {
9
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
13
+ });
14
+ };
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.FLEXCARD_FINANCE_ACCOUNTS_URL = void 0;
17
+ exports.fetchFlexCardFinanceAccountsUsdTotal = fetchFlexCardFinanceAccountsUsdTotal;
18
+ const httpTimeout_1 = require("./integrations/httpTimeout");
19
+ exports.FLEXCARD_FINANCE_ACCOUNTS_URL = 'https://flexcard.cards/api/v1/finance/accounts/';
20
+ function num(v) {
21
+ if (typeof v === 'number' && !Number.isNaN(v))
22
+ return v;
23
+ const n = parseFloat(String(v));
24
+ return Number.isFinite(n) ? n : 0;
25
+ }
26
+ function fetchPaginatedResults(apiKey, initialUrl) {
27
+ return __awaiter(this, void 0, void 0, function* () {
28
+ var _a;
29
+ const trimmed = apiKey.trim();
30
+ const out = [];
31
+ let url = initialUrl;
32
+ for (let page = 0; page < 50 && url; page++) {
33
+ const res = yield (0, httpTimeout_1.fetchWithTimeout)(url, {
34
+ method: 'GET',
35
+ headers: {
36
+ 'X-API-Key': trimmed,
37
+ Accept: 'application/json',
38
+ },
39
+ });
40
+ if (!res.ok)
41
+ return null;
42
+ const data = (yield res.json());
43
+ let batch;
44
+ let next;
45
+ if (Array.isArray(data)) {
46
+ batch = data;
47
+ next = null;
48
+ }
49
+ else if (data && typeof data === 'object') {
50
+ const o = data;
51
+ const r = o.results;
52
+ batch = Array.isArray(r) ? r : [];
53
+ next = (_a = o.next) !== null && _a !== void 0 ? _a : null;
54
+ if (!Array.isArray(r))
55
+ return null;
56
+ }
57
+ else {
58
+ return null;
59
+ }
60
+ out.push(...batch);
61
+ url = next !== null && next !== void 0 ? next : null;
62
+ }
63
+ return out;
64
+ });
65
+ }
66
+ /** Сумма `balance` по всем счетам с валютой USD. */
67
+ function fetchFlexCardFinanceAccountsUsdTotal(apiKey) {
68
+ return __awaiter(this, void 0, void 0, function* () {
69
+ var _a;
70
+ const trimmed = apiKey.trim();
71
+ if (!trimmed)
72
+ return null;
73
+ const rows = yield fetchPaginatedResults(trimmed, exports.FLEXCARD_FINANCE_ACCOUNTS_URL);
74
+ if (rows === null)
75
+ return null;
76
+ let sum = 0;
77
+ for (const raw of rows) {
78
+ if (!raw || typeof raw !== 'object')
79
+ continue;
80
+ const r = raw;
81
+ const cur = String((_a = r.currency) !== null && _a !== void 0 ? _a : '').toUpperCase();
82
+ if (cur !== 'USD' && cur !== 'US')
83
+ continue;
84
+ sum += num(r.balance);
85
+ }
86
+ return sum;
87
+ });
88
+ }