docs-combiner 0.2.2 → 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 (53) 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 +830 -165
  28. package/dist/models.js +17 -0
  29. package/dist/offerSettings.js +19 -0
  30. package/dist/preload.js +28 -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.LICENSE.txt +4 -0
  35. package/dist/renderer.js.map +1 -1
  36. package/dist/server/app.js +378 -0
  37. package/dist/server/auth.js +74 -0
  38. package/dist/server/contentJobs.js +367 -0
  39. package/dist/server/driveCatalog.js +122 -0
  40. package/dist/server/driveProxy.js +220 -0
  41. package/dist/server/flexcardMeta.js +29 -0
  42. package/dist/server/googleAuth.js +84 -0
  43. package/dist/server/imageAssets.js +87 -0
  44. package/dist/server/imageJobs.js +776 -0
  45. package/dist/server/index.js +38 -0
  46. package/dist/server/jobEvents.js +116 -0
  47. package/dist/server/jobs.js +358 -0
  48. package/dist/server/openRouterMeta.js +58 -0
  49. package/dist/server/spec.js +544 -0
  50. package/dist/server/storage.js +133 -0
  51. package/dist/server/telegram.js +53 -0
  52. package/dist/server/workspaceSnapshot.js +273 -0
  53. package/package.json +18 -4
@@ -0,0 +1,367 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.runServerContentJob = runServerContentJob;
13
+ exports.createServerContentJob = createServerContentJob;
14
+ const openrouter_1 = require("../integrations/openrouter");
15
+ const models_1 = require("../models");
16
+ const prompts_1 = require("../prompts");
17
+ const contentPairs_1 = require("../contentPairs");
18
+ const jobs_1 = require("./jobs");
19
+ const DEFAULT_MAX_ATTEMPTS = 3;
20
+ const DEFAULT_RETRY_DELAY_MS = 2000;
21
+ function logContentJob(jobId, message, meta) {
22
+ const suffix = meta ? ` ${JSON.stringify(meta)}` : '';
23
+ console.log(`[docs-combiner-server] content-job ${jobId} ${message}${suffix}`);
24
+ }
25
+ function isPlainObject(value) {
26
+ return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
27
+ }
28
+ function badRequest(message) {
29
+ const err = new Error(message);
30
+ err.status = 400;
31
+ return err;
32
+ }
33
+ function normalizeSelectedPairApproaches(value) {
34
+ if (!Array.isArray(value))
35
+ return [];
36
+ return value
37
+ .map(item => Number(item))
38
+ .filter(item => Number.isInteger(item) && item >= 0 && item < 100);
39
+ }
40
+ function normalizeContentJobPayload(payload) {
41
+ var _a;
42
+ if (!isPlainObject(payload))
43
+ throw badRequest('JSON object payload is required.');
44
+ const workspaceId = typeof payload.workspaceId === 'string' ? payload.workspaceId.trim() : '';
45
+ const task = payload.task === 'translate-ru' ? 'translate-ru' : 'generate-pairs';
46
+ const product = typeof payload.product === 'string' ? payload.product.trim() : '';
47
+ const selectedPairApproaches = normalizeSelectedPairApproaches(payload.selectedPairApproaches);
48
+ const count = Math.max(1, Math.min(Math.floor(Number((_a = payload.count) !== null && _a !== void 0 ? _a : (selectedPairApproaches.length || 1))), 50));
49
+ const markets = Array.isArray(payload.markets)
50
+ ? payload.markets.map((item, i) => {
51
+ const market = isPlainObject(item) ? item : {};
52
+ return {
53
+ index: Number.isInteger(market.index) && Number(market.index) > 0 ? Number(market.index) : i + 1,
54
+ marketId: typeof market.marketId === 'string' ? market.marketId.trim() : undefined,
55
+ marketKey: typeof market.marketKey === 'string' ? market.marketKey.trim() : undefined,
56
+ geo: typeof market.geo === 'string' ? market.geo.trim() : '',
57
+ priceWithCurrency: typeof market.priceWithCurrency === 'string' ? market.priceWithCurrency.trim() : undefined,
58
+ };
59
+ }).filter(market => Boolean(market.geo))
60
+ : [];
61
+ const pairs = Array.isArray(payload.pairs)
62
+ ? payload.pairs.map(item => {
63
+ const pair = isPlainObject(item) ? item : {};
64
+ return {
65
+ title: typeof pair.title === 'string' ? pair.title : '',
66
+ text: typeof pair.text === 'string' ? pair.text : '',
67
+ };
68
+ }).filter(pair => pair.title.trim() || pair.text.trim())
69
+ : [];
70
+ return {
71
+ workspaceId,
72
+ task,
73
+ product,
74
+ additionalInfo: typeof payload.additionalInfo === 'string' ? payload.additionalInfo : undefined,
75
+ markets: task === 'generate-pairs' ? markets : [],
76
+ pairs: task === 'translate-ru' ? pairs : undefined,
77
+ selectedPairApproaches,
78
+ count,
79
+ model: typeof payload.model === 'string' && payload.model.trim() ? payload.model.trim() : undefined,
80
+ metadata: isPlainObject(payload.metadata) ? payload.metadata : undefined,
81
+ };
82
+ }
83
+ function stripMarkdownJsonFence(raw) {
84
+ let s = raw.trim();
85
+ if (!s.startsWith('```'))
86
+ return s;
87
+ s = s.replace(/^```(?:json)?\s*/i, '');
88
+ const end = s.lastIndexOf('```');
89
+ if (end >= 0)
90
+ s = s.slice(0, end).trim();
91
+ return s;
92
+ }
93
+ function parsePairTranslationsJson(raw) {
94
+ const cleaned = stripMarkdownJsonFence(raw);
95
+ const parse = (value) => {
96
+ const parsed = JSON.parse(value);
97
+ if (!Array.isArray(parsed))
98
+ return null;
99
+ return parsed.map(item => ({
100
+ titleRu: typeof (item === null || item === void 0 ? void 0 : item.titleRu) === 'string' ? item.titleRu : '',
101
+ textRu: typeof (item === null || item === void 0 ? void 0 : item.textRu) === 'string' ? item.textRu : '',
102
+ }));
103
+ };
104
+ try {
105
+ return parse(cleaned);
106
+ }
107
+ catch (_a) {
108
+ const jsonMatch = cleaned.match(/\[[\s\S]*\]/);
109
+ if (!jsonMatch)
110
+ return null;
111
+ try {
112
+ return parse(jsonMatch[0]);
113
+ }
114
+ catch (_b) {
115
+ return null;
116
+ }
117
+ }
118
+ }
119
+ function waitMs(ms) {
120
+ return new Promise(resolve => setTimeout(resolve, ms));
121
+ }
122
+ function generateContentSlot(args) {
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ var _a, _b, _c, _d;
125
+ const model = ((_a = args.slot.model) === null || _a === void 0 ? void 0 : _a.trim()) || args.fallbackModel;
126
+ const systemPrompt = (0, prompts_1.getPairsSystemPrompt)(args.slot.geo, false, args.count, args.selectedPairApproaches);
127
+ const userPrompt = (0, prompts_1.getPairsUserPrompt)(args.product, args.slot.geo, args.additionalInfo, false, args.count, args.selectedPairApproaches);
128
+ const completion = yield (0, openrouter_1.requestOpenRouterChatCompletion)({
129
+ model,
130
+ messages: [
131
+ { role: 'system', content: systemPrompt },
132
+ { role: 'user', content: userPrompt },
133
+ ],
134
+ temperature: 0.7,
135
+ max_tokens: 32000,
136
+ }, {
137
+ apiKey: args.apiKey,
138
+ preferStream: false,
139
+ readTimeoutMs: args.readTimeoutMs,
140
+ signal: args.signal,
141
+ });
142
+ const choice = (_c = (_b = completion.data) === null || _b === void 0 ? void 0 : _b.choices) === null || _c === void 0 ? void 0 : _c[0];
143
+ const content = (0, openrouter_1.extractChatCompletionText)(choice) ||
144
+ (choice === null || choice === void 0 ? void 0 : choice.text) ||
145
+ ((_d = choice === null || choice === void 0 ? void 0 : choice.delta) === null || _d === void 0 ? void 0 : _d.content) ||
146
+ '';
147
+ if (!content.trim())
148
+ throw new Error('Empty content in API response');
149
+ const parsed = (0, contentPairs_1.parsePairsFromModelContent)(content, args.count);
150
+ return {
151
+ pairs: parsed.pairs,
152
+ rawContent: content,
153
+ parseSummary: (0, contentPairs_1.summarizePairParseResult)(parsed, args.count),
154
+ };
155
+ });
156
+ }
157
+ function translatePairsToRussianSlot(args) {
158
+ return __awaiter(this, void 0, void 0, function* () {
159
+ var _a, _b, _c;
160
+ const userContent = args.pairs
161
+ .map((p, i) => `Пара ${i + 1}:\nЗаголовок: ${p.title || '—'}\nТекст: ${(p.text || '').replace(/\n/g, ' ') || '—'}`)
162
+ .join('\n\n');
163
+ const completion = yield (0, openrouter_1.requestOpenRouterChatCompletion)({
164
+ model: args.model,
165
+ messages: [
166
+ {
167
+ role: 'system',
168
+ content: 'Переведи на русский язык следующие рекламные пары «заголовок + текст». Верни ТОЛЬКО JSON-массив без пояснений: [{"titleRu": "...", "textRu": "..."}, ...]. Количество элементов должно совпадать с количеством пар.',
169
+ },
170
+ { role: 'user', content: userContent },
171
+ ],
172
+ temperature: 0.1,
173
+ max_tokens: 4000,
174
+ }, {
175
+ apiKey: args.apiKey,
176
+ preferStream: false,
177
+ readTimeoutMs: args.readTimeoutMs,
178
+ signal: args.signal,
179
+ });
180
+ const choice = (_b = (_a = completion.data) === null || _a === void 0 ? void 0 : _a.choices) === null || _b === void 0 ? void 0 : _b[0];
181
+ const raw = (0, openrouter_1.extractChatCompletionText)(choice) ||
182
+ (choice === null || choice === void 0 ? void 0 : choice.text) ||
183
+ ((_c = choice === null || choice === void 0 ? void 0 : choice.delta) === null || _c === void 0 ? void 0 : _c.content) ||
184
+ '';
185
+ if (!raw.trim())
186
+ throw new Error('Empty translation response');
187
+ const translations = parsePairTranslationsJson(raw);
188
+ if (!translations || translations.length !== args.pairs.length) {
189
+ throw new Error(`Invalid translation JSON: expected ${args.pairs.length} item(s).`);
190
+ }
191
+ return { translations, rawContent: raw };
192
+ });
193
+ }
194
+ function runServerContentJob(store_1, registry_1, jobId_1) {
195
+ return __awaiter(this, arguments, void 0, function* (store, registry, jobId, options = {}) {
196
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
197
+ const config = yield store.readConfig();
198
+ const apiKey = (_a = config.openaiApiKey) === null || _a === void 0 ? void 0 : _a.trim();
199
+ if (!apiKey) {
200
+ return registry.markJobFailed(jobId, 'OpenRouter API key is not configured on the server.');
201
+ }
202
+ const settings = yield store.readSettings();
203
+ const fallbackModel = ((_c = (_b = settings.models) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.trim()) ||
204
+ ((_e = (_d = settings.models) === null || _d === void 0 ? void 0 : _d.contentGeneration) === null || _e === void 0 ? void 0 : _e.trim()) ||
205
+ models_1.MODELS.contentGeneration;
206
+ const signal = registry.getAbortSignal(jobId);
207
+ if (!signal)
208
+ return null;
209
+ const job = registry.getJob(jobId);
210
+ if (!job || job.kind !== 'content')
211
+ return null;
212
+ logContentJob(jobId, 'started', {
213
+ workspaceId: job.workspaceId,
214
+ task: job.task,
215
+ markets: job.slots.length,
216
+ model: job.model || fallbackModel,
217
+ });
218
+ let failed = 0;
219
+ const maxAttempts = (_f = options.maxAttempts) !== null && _f !== void 0 ? _f : DEFAULT_MAX_ATTEMPTS;
220
+ const retryDelayMs = (_g = options.retryDelayMs) !== null && _g !== void 0 ? _g : DEFAULT_RETRY_DELAY_MS;
221
+ const readTimeoutMs = (_h = options.requestTimeoutMs) !== null && _h !== void 0 ? _h : 180000;
222
+ if (job.task === 'translate-ru') {
223
+ const slot = job.slots[0];
224
+ if (!slot)
225
+ return registry.markJobFailed(jobId, 'Translation slot is missing.');
226
+ registry.updateContentSlot(jobId, slot.index, { status: 'generating' });
227
+ registry.appendEvent(jobId, 'slot.generating', { index: slot.index, task: job.task });
228
+ try {
229
+ const result = yield translatePairsToRussianSlot({
230
+ apiKey,
231
+ pairs: job.pairs || [],
232
+ model: job.model || fallbackModel,
233
+ signal,
234
+ readTimeoutMs,
235
+ });
236
+ if (registry.isCancelled(jobId))
237
+ return registry.getJob(jobId);
238
+ registry.updateContentSlot(jobId, slot.index, {
239
+ status: 'generated',
240
+ attempts: 1,
241
+ pairs: job.pairs,
242
+ translations: result.translations,
243
+ rawContent: result.rawContent,
244
+ model: job.model || fallbackModel,
245
+ });
246
+ registry.appendEvent(jobId, 'slot.content.generated', {
247
+ index: slot.index,
248
+ task: job.task,
249
+ translations: result.translations.length,
250
+ });
251
+ logContentJob(jobId, 'translation generated', { translations: result.translations.length });
252
+ return registry.markJobSucceeded(jobId);
253
+ }
254
+ catch (err) {
255
+ const message = err instanceof Error ? err.message : String(err);
256
+ registry.updateContentSlot(jobId, slot.index, { status: 'failed', attempts: 1, error: message });
257
+ logContentJob(jobId, 'translation failed', { error: message });
258
+ (_j = options.onError) === null || _j === void 0 ? void 0 : _j.call(options, err);
259
+ return registry.markJobFailed(jobId, message);
260
+ }
261
+ }
262
+ for (const slot of job.slots) {
263
+ if (registry.isCancelled(jobId))
264
+ return registry.getJob(jobId);
265
+ let lastError = null;
266
+ let lastRawContent = '';
267
+ let lastParseSummary = '';
268
+ let lastPairs = null;
269
+ let attemptsUsed = 0;
270
+ registry.updateContentSlot(jobId, slot.index, { status: 'generating' });
271
+ registry.appendEvent(jobId, 'slot.generating', { index: slot.index, geo: slot.geo });
272
+ logContentJob(jobId, 'slot generating', { index: slot.index, geo: slot.geo });
273
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
274
+ attemptsUsed = attempt;
275
+ if (attempt > 1)
276
+ yield waitMs(retryDelayMs);
277
+ try {
278
+ const result = yield generateContentSlot({
279
+ apiKey,
280
+ slot,
281
+ product: job.product,
282
+ additionalInfo: job.additionalInfo || '',
283
+ count: job.count,
284
+ selectedPairApproaches: job.selectedPairApproaches,
285
+ fallbackModel: job.model || fallbackModel,
286
+ signal,
287
+ readTimeoutMs,
288
+ });
289
+ lastPairs = result.pairs;
290
+ lastRawContent = result.rawContent;
291
+ lastParseSummary = result.parseSummary;
292
+ lastError = null;
293
+ const allComplete = result.pairs.length > 0 &&
294
+ result.pairs.every(pair => pair.title.trim().length > 0 && pair.text.trim().length > 0);
295
+ if (allComplete || attempt === maxAttempts)
296
+ break;
297
+ }
298
+ catch (err) {
299
+ lastError = err instanceof Error ? err : new Error(String(err));
300
+ logContentJob(jobId, 'slot attempt failed', { index: slot.index, attempt, error: lastError.message });
301
+ (_k = options.onError) === null || _k === void 0 ? void 0 : _k.call(options, err);
302
+ }
303
+ }
304
+ if (registry.isCancelled(jobId))
305
+ return registry.getJob(jobId);
306
+ if (!lastPairs) {
307
+ failed += 1;
308
+ const message = (lastError === null || lastError === void 0 ? void 0 : lastError.message) || 'Unknown content generation error';
309
+ registry.updateContentSlot(jobId, slot.index, {
310
+ status: 'failed',
311
+ attempts: attemptsUsed,
312
+ rawContent: lastRawContent,
313
+ parseSummary: lastParseSummary,
314
+ error: message,
315
+ });
316
+ registry.appendEvent(jobId, 'job.failed', { index: slot.index, error: message });
317
+ logContentJob(jobId, 'slot failed', { index: slot.index, error: message });
318
+ continue;
319
+ }
320
+ registry.updateContentSlot(jobId, slot.index, {
321
+ status: 'generated',
322
+ attempts: attemptsUsed,
323
+ pairs: lastPairs,
324
+ rawContent: lastRawContent,
325
+ parseSummary: lastParseSummary,
326
+ model: job.model || fallbackModel,
327
+ });
328
+ registry.appendEvent(jobId, 'slot.content.generated', {
329
+ index: slot.index,
330
+ geo: slot.geo,
331
+ titles: lastPairs.filter(pair => pair.title.trim()).length,
332
+ texts: lastPairs.filter(pair => pair.text.trim()).length,
333
+ });
334
+ logContentJob(jobId, 'slot generated', {
335
+ index: slot.index,
336
+ geo: slot.geo,
337
+ attempts: attemptsUsed,
338
+ titles: lastPairs.filter(pair => pair.title.trim()).length,
339
+ texts: lastPairs.filter(pair => pair.text.trim()).length,
340
+ });
341
+ }
342
+ if (registry.isCancelled(jobId))
343
+ return registry.getJob(jobId);
344
+ if (failed === job.slots.length) {
345
+ return registry.markJobFailed(jobId, `${failed} content slot(s) failed.`);
346
+ }
347
+ return registry.markJobSucceeded(jobId);
348
+ });
349
+ }
350
+ function createServerContentJob(payload, registry = jobs_1.defaultServerJobRegistry, store, options = {}) {
351
+ const job = registry.createContentJob(normalizeContentJobPayload(payload));
352
+ logContentJob(job.id, 'created', {
353
+ workspaceId: job.workspaceId,
354
+ slots: job.kind === 'content' ? job.slots.length : 0,
355
+ start: Boolean(store),
356
+ });
357
+ if (store) {
358
+ queueMicrotask(() => {
359
+ void runServerContentJob(store, registry, job.id, options).catch(err => {
360
+ var _a;
361
+ registry.markJobFailed(job.id, err instanceof Error ? err.message : String(err));
362
+ (_a = options.onError) === null || _a === void 0 ? void 0 : _a.call(options, err);
363
+ });
364
+ });
365
+ }
366
+ return job;
367
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.getDriveCatalog = getDriveCatalog;
13
+ const campaignsCsv_1 = require("../campaignsCsv");
14
+ const driveApi_1 = require("../integrations/driveApi");
15
+ const googleAuth_1 = require("./googleAuth");
16
+ class DriveApiError extends Error {
17
+ constructor(status, message) {
18
+ super(message);
19
+ this.status = status;
20
+ }
21
+ }
22
+ class BadRequestError extends Error {
23
+ constructor() {
24
+ super(...arguments);
25
+ this.status = 400;
26
+ }
27
+ }
28
+ function normalizeDriveFiles(files) {
29
+ return (Array.isArray(files) ? files : [])
30
+ .map((file) => ({
31
+ id: String((file === null || file === void 0 ? void 0 : file.id) || '').trim(),
32
+ name: String((file === null || file === void 0 ? void 0 : file.name) || '').trim(),
33
+ }))
34
+ .filter(file => file.id && file.name)
35
+ .sort((a, b) => a.name.localeCompare(b.name, 'ru'));
36
+ }
37
+ function listChildFolders(token, parentFolderId) {
38
+ return __awaiter(this, void 0, void 0, function* () {
39
+ const q = `'${parentFolderId}' in parents and trashed = false and mimeType = 'application/vnd.google-apps.folder'`;
40
+ const response = yield (0, driveApi_1.driveListFiles)(token, q, 'files(id,name)', {
41
+ supportsAllDrives: true,
42
+ pageSize: 200,
43
+ });
44
+ if (!response.ok) {
45
+ const text = yield response.text().catch(() => '');
46
+ throw new DriveApiError(response.status, text || `Drive list folders failed (${response.status})`);
47
+ }
48
+ const data = yield response.json();
49
+ return normalizeDriveFiles(data.files);
50
+ });
51
+ }
52
+ function findCampaignsCsv(token, offersRootFolderId) {
53
+ return __awaiter(this, void 0, void 0, function* () {
54
+ const q = `'${offersRootFolderId}' in parents and trashed = false and name = '${campaignsCsv_1.CAMPAIGNS_CSV_FILENAME}'`;
55
+ const response = yield (0, driveApi_1.driveListFiles)(token, q, 'files(id,name)', { supportsAllDrives: true });
56
+ if (!response.ok) {
57
+ const text = yield response.text().catch(() => '');
58
+ throw new DriveApiError(response.status, text || `Drive search campaigns.csv failed (${response.status})`);
59
+ }
60
+ const data = yield response.json();
61
+ const file = normalizeDriveFiles(data.files)[0];
62
+ return { fileId: (file === null || file === void 0 ? void 0 : file.id) || null };
63
+ });
64
+ }
65
+ function loadCampaignsCsv(token, offersRootFolderId) {
66
+ return __awaiter(this, void 0, void 0, function* () {
67
+ const found = yield findCampaignsCsv(token, offersRootFolderId);
68
+ if (!found.fileId) {
69
+ return { hasFile: false, fileId: null, rows: [], errors: [] };
70
+ }
71
+ const response = yield (0, driveApi_1.driveDownloadFile)(token, found.fileId);
72
+ if (!response.ok) {
73
+ const text = yield response.text().catch(() => '');
74
+ throw new DriveApiError(response.status, text || `Drive download campaigns.csv failed (${response.status})`);
75
+ }
76
+ const parsed = (0, campaignsCsv_1.parseCampaignsCsvText)(yield response.text());
77
+ return {
78
+ hasFile: true,
79
+ fileId: found.fileId,
80
+ rows: parsed.rows,
81
+ errors: parsed.errors,
82
+ };
83
+ });
84
+ }
85
+ function getDriveCatalog(store, input) {
86
+ return __awaiter(this, void 0, void 0, function* () {
87
+ var _a, _b;
88
+ const offersRootFolderId = (_a = input.offersRootFolderId) === null || _a === void 0 ? void 0 : _a.trim();
89
+ const campaignsRootFolderId = (_b = input.campaignsRootFolderId) === null || _b === void 0 ? void 0 : _b.trim();
90
+ if (!offersRootFolderId && !campaignsRootFolderId) {
91
+ throw new BadRequestError('offersRootFolderId or campaignsRootFolderId is required.');
92
+ }
93
+ return (yield (0, googleAuth_1.withGoogleAccessTokenRetry)(store, (token) => __awaiter(this, void 0, void 0, function* () {
94
+ const offers = offersRootFolderId ? yield listChildFolders(token, offersRootFolderId) : [];
95
+ const campaignsCsv = offersRootFolderId
96
+ ? yield loadCampaignsCsv(token, offersRootFolderId)
97
+ : { hasFile: false, fileId: null, rows: [], errors: [] };
98
+ const campaignGroupFolders = campaignsRootFolderId
99
+ ? yield listChildFolders(token, campaignsRootFolderId)
100
+ : [];
101
+ const campaignGroups = [];
102
+ const workspaces = [];
103
+ for (const group of campaignGroupFolders) {
104
+ const groupWorkspaces = yield listChildFolders(token, group.id);
105
+ campaignGroups.push(Object.assign(Object.assign({}, group), { workspaces: groupWorkspaces }));
106
+ groupWorkspaces.forEach(workspace => {
107
+ workspaces.push(Object.assign(Object.assign({}, workspace), { groupId: group.id, groupName: group.name }));
108
+ });
109
+ }
110
+ return {
111
+ status: 200,
112
+ value: {
113
+ ok: true,
114
+ offers,
115
+ campaignGroups,
116
+ workspaces,
117
+ campaignsCsv,
118
+ },
119
+ };
120
+ }))).value;
121
+ });
122
+ }