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.
- package/dist/agentApi/agentControlClaim.js +26 -0
- package/dist/agentApi/approachMatrixView.js +19 -0
- package/dist/agentApi/capabilitiesMeta.js +78 -0
- package/dist/agentApi/catalogUrlPresets.js +26 -0
- package/dist/agentApi/constants.js +11 -0
- package/dist/agentApi/creativeSelections.js +25 -0
- package/dist/agentApi/escalation.js +221 -0
- package/dist/agentApi/generatedPairsGuard.js +85 -0
- package/dist/agentApi/httpHelpers.js +77 -0
- package/dist/agentApi/imagesReadiness.js +60 -0
- package/dist/agentApi/jobStatus.js +113 -0
- package/dist/agentApi/productImageDrive.js +39 -0
- package/dist/agentApi/regenerateImages.js +125 -0
- package/dist/agentApi/rendererTypes.js +3 -0
- package/dist/agentApi/routes.js +440 -0
- package/dist/agentApi/sessionSnapshot.js +82 -0
- package/dist/agentApi/sessionTypes.js +2 -0
- package/dist/agentApi/spec.js +1045 -0
- package/dist/agentApi/types.js +2 -0
- package/dist/agentApi/validation.js +32 -0
- package/dist/campaignsCsv.js +420 -0
- package/dist/contentPairs.js +62 -0
- package/dist/flexcardBalance.js +88 -0
- package/dist/integrations/driveApi.js +420 -0
- package/dist/integrations/httpTimeout.js +116 -0
- package/dist/integrations/openrouter.js +532 -0
- package/dist/main.js +830 -165
- package/dist/models.js +17 -0
- package/dist/offerSettings.js +19 -0
- package/dist/preload.js +28 -0
- package/dist/promptOverrides.js +437 -0
- package/dist/prompts.js +1243 -0
- package/dist/renderer.js +19 -19
- package/dist/renderer.js.LICENSE.txt +4 -0
- package/dist/renderer.js.map +1 -1
- package/dist/server/app.js +378 -0
- package/dist/server/auth.js +74 -0
- package/dist/server/contentJobs.js +367 -0
- package/dist/server/driveCatalog.js +122 -0
- package/dist/server/driveProxy.js +220 -0
- package/dist/server/flexcardMeta.js +29 -0
- package/dist/server/googleAuth.js +84 -0
- package/dist/server/imageAssets.js +87 -0
- package/dist/server/imageJobs.js +776 -0
- package/dist/server/index.js +38 -0
- package/dist/server/jobEvents.js +116 -0
- package/dist/server/jobs.js +358 -0
- package/dist/server/openRouterMeta.js +58 -0
- package/dist/server/spec.js +544 -0
- package/dist/server/storage.js +133 -0
- package/dist/server/telegram.js +53 -0
- package/dist/server/workspaceSnapshot.js +273 -0
- package/package.json +18 -4
|
@@ -0,0 +1,38 @@
|
|
|
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
|
+
const http_1 = require("http");
|
|
13
|
+
const app_1 = require("./app");
|
|
14
|
+
const jobEvents_1 = require("./jobEvents");
|
|
15
|
+
const jobs_1 = require("./jobs");
|
|
16
|
+
const storage_1 = require("./storage");
|
|
17
|
+
const version = process.env.npm_package_version || '0.5.0';
|
|
18
|
+
const host = process.env.DOCS_COMBINER_SERVER_HOST || '127.0.0.1';
|
|
19
|
+
const port = Number(process.env.DOCS_COMBINER_SERVER_PORT || 17321);
|
|
20
|
+
const baseUrl = process.env.DOCS_COMBINER_PUBLIC_BASE_URL || 'https://combiner.site';
|
|
21
|
+
function main() {
|
|
22
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
23
|
+
const store = new storage_1.ServerStore((0, storage_1.resolveServerDataDir)());
|
|
24
|
+
yield store.init();
|
|
25
|
+
const app = (0, app_1.createServerApp)({ store, version, baseUrl, jobRegistry: jobs_1.defaultServerJobRegistry });
|
|
26
|
+
const server = (0, http_1.createServer)(app);
|
|
27
|
+
(0, jobEvents_1.attachServerJobWebSocket)(server, { store, registry: jobs_1.defaultServerJobRegistry });
|
|
28
|
+
server.listen(port, host, () => {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.log(`[docs-combiner-server] listening on http://${host}:${port} (baseUrl=${baseUrl}, dataDir=${store.dataDir})`);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
void main().catch(err => {
|
|
35
|
+
// eslint-disable-next-line no-console
|
|
36
|
+
console.error('[docs-combiner-server] failed to start:', err);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
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
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.attachServerJobWebSocket = attachServerJobWebSocket;
|
|
46
|
+
const ws_1 = __importStar(require("ws"));
|
|
47
|
+
const auth_1 = require("./auth");
|
|
48
|
+
const jobs_1 = require("./jobs");
|
|
49
|
+
function parseRequestUrl(req) {
|
|
50
|
+
try {
|
|
51
|
+
return new URL(req.url || '/', 'http://127.0.0.1');
|
|
52
|
+
}
|
|
53
|
+
catch (_a) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function jobIdFromPath(pathname, pathPrefix) {
|
|
58
|
+
const escapedPrefix = pathPrefix.replace(/\/+$/, '');
|
|
59
|
+
const match = pathname.match(new RegExp(`^${escapedPrefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/([^/]+)/events$`));
|
|
60
|
+
return (match === null || match === void 0 ? void 0 : match[1]) ? decodeURIComponent(match[1]) : null;
|
|
61
|
+
}
|
|
62
|
+
function authTokenFromRequest(req, url) {
|
|
63
|
+
var _a;
|
|
64
|
+
const headerToken = (0, auth_1.extractBearerToken)(req.headers.authorization);
|
|
65
|
+
if (headerToken)
|
|
66
|
+
return headerToken;
|
|
67
|
+
return ((_a = url.searchParams.get('token')) === null || _a === void 0 ? void 0 : _a.trim()) || null;
|
|
68
|
+
}
|
|
69
|
+
function sendJson(ws, payload) {
|
|
70
|
+
if (ws.readyState === ws_1.default.OPEN) {
|
|
71
|
+
ws.send(JSON.stringify(payload));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function attachServerJobWebSocket(server, options) {
|
|
75
|
+
var _a, _b;
|
|
76
|
+
const registry = (_a = options.registry) !== null && _a !== void 0 ? _a : jobs_1.defaultServerJobRegistry;
|
|
77
|
+
const pathPrefix = (_b = options.pathPrefix) !== null && _b !== void 0 ? _b : '/api/jobs';
|
|
78
|
+
const wss = new ws_1.WebSocketServer({ noServer: true });
|
|
79
|
+
server.on('upgrade', (req, socket, head) => __awaiter(this, void 0, void 0, function* () {
|
|
80
|
+
const url = parseRequestUrl(req);
|
|
81
|
+
const jobId = url ? jobIdFromPath(url.pathname, pathPrefix) : null;
|
|
82
|
+
if (!url || !jobId)
|
|
83
|
+
return;
|
|
84
|
+
try {
|
|
85
|
+
const auth = yield (0, auth_1.verifyBearerToken)(options.store, authTokenFromRequest(req, url));
|
|
86
|
+
if (!auth.ok) {
|
|
87
|
+
socket.write(`HTTP/1.1 ${auth.status} ${auth.error}\r\nConnection: close\r\n\r\n`);
|
|
88
|
+
socket.destroy();
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!registry.getJob(jobId)) {
|
|
92
|
+
socket.write('HTTP/1.1 404 Job not found\r\nConnection: close\r\n\r\n');
|
|
93
|
+
socket.destroy();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
wss.handleUpgrade(req, socket, head, ws => {
|
|
97
|
+
wss.emit('connection', ws, req, jobId, url);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (_a) {
|
|
101
|
+
socket.write('HTTP/1.1 500 Internal Server Error\r\nConnection: close\r\n\r\n');
|
|
102
|
+
socket.destroy();
|
|
103
|
+
}
|
|
104
|
+
}));
|
|
105
|
+
wss.on('connection', (ws, _req, jobId, url) => {
|
|
106
|
+
const lastEventId = Number(url.searchParams.get('lastEventId') || 0);
|
|
107
|
+
sendJson(ws, { type: 'connected', jobId });
|
|
108
|
+
for (const event of registry.getEventsSince(jobId, Number.isFinite(lastEventId) ? lastEventId : 0)) {
|
|
109
|
+
sendJson(ws, event);
|
|
110
|
+
}
|
|
111
|
+
const unsubscribe = registry.subscribe(jobId, event => sendJson(ws, event));
|
|
112
|
+
ws.on('close', unsubscribe);
|
|
113
|
+
ws.on('error', unsubscribe);
|
|
114
|
+
});
|
|
115
|
+
return wss;
|
|
116
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
3
|
+
var t = {};
|
|
4
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
5
|
+
t[p] = s[p];
|
|
6
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
7
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
8
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
9
|
+
t[p[i]] = s[p[i]];
|
|
10
|
+
}
|
|
11
|
+
return t;
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.defaultServerJobRegistry = exports.ServerJobRegistry = void 0;
|
|
15
|
+
const events_1 = require("events");
|
|
16
|
+
const DEFAULT_TTL_MS = 6 * 60 * 60 * 1000;
|
|
17
|
+
const DEFAULT_MAX_JOBS = 200;
|
|
18
|
+
const DEFAULT_MAX_EVENTS_PER_JOB = 250;
|
|
19
|
+
function randomJobId() {
|
|
20
|
+
return `job_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
21
|
+
}
|
|
22
|
+
function normalizeSlotCount(input) {
|
|
23
|
+
var _a;
|
|
24
|
+
if (Array.isArray(input.slots) && input.slots.length > 0)
|
|
25
|
+
return Math.min(input.slots.length, 100);
|
|
26
|
+
const count = Number((_a = input.count) !== null && _a !== void 0 ? _a : 1);
|
|
27
|
+
if (!Number.isFinite(count) || count < 1)
|
|
28
|
+
return 1;
|
|
29
|
+
return Math.min(Math.floor(count), 100);
|
|
30
|
+
}
|
|
31
|
+
function buildInitialSlots(input) {
|
|
32
|
+
const count = normalizeSlotCount(input);
|
|
33
|
+
return Array.from({ length: count }, (_, i) => {
|
|
34
|
+
var _a, _b;
|
|
35
|
+
const slot = (_b = (_a = input.slots) === null || _a === void 0 ? void 0 : _a[i]) !== null && _b !== void 0 ? _b : {};
|
|
36
|
+
return {
|
|
37
|
+
index: Number.isInteger(slot.index) && Number(slot.index) > 0 ? Number(slot.index) : i + 1,
|
|
38
|
+
status: 'queued',
|
|
39
|
+
prompt: typeof slot.prompt === 'string' ? slot.prompt : undefined,
|
|
40
|
+
approach: typeof slot.approach === 'string' ? slot.approach : undefined,
|
|
41
|
+
aspectRatio: slot.aspectRatio === '2:3' || slot.aspectRatio === '4:5' || slot.aspectRatio === '1:1'
|
|
42
|
+
? slot.aspectRatio
|
|
43
|
+
: undefined,
|
|
44
|
+
imageSize: slot.imageSize === '1K' || slot.imageSize === '2K' || slot.imageSize === '4K'
|
|
45
|
+
? slot.imageSize
|
|
46
|
+
: undefined,
|
|
47
|
+
model: typeof slot.model === 'string' ? slot.model : undefined,
|
|
48
|
+
sourceImageAssetId: typeof slot.sourceImageAssetId === 'string' ? slot.sourceImageAssetId : undefined,
|
|
49
|
+
sourceImageUrl: typeof slot.sourceImageUrl === 'string' ? slot.sourceImageUrl : undefined,
|
|
50
|
+
sourceImageJobSlot: slot.sourceImageJobSlot,
|
|
51
|
+
referenceImageAssetIds: Array.isArray(slot.referenceImageAssetIds)
|
|
52
|
+
? slot.referenceImageAssetIds.filter((assetId) => typeof assetId === 'string' && Boolean(assetId.trim()))
|
|
53
|
+
: undefined,
|
|
54
|
+
referenceImageUrls: Array.isArray(slot.referenceImageUrls)
|
|
55
|
+
? slot.referenceImageUrls.filter((url) => typeof url === 'string' && Boolean(url.trim()))
|
|
56
|
+
: undefined,
|
|
57
|
+
referenceImageJobSlots: slot.referenceImageJobSlots,
|
|
58
|
+
validationInput: slot.validationInput,
|
|
59
|
+
upload: slot.upload,
|
|
60
|
+
};
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
function buildInitialContentSlots(input) {
|
|
64
|
+
if (input.task === 'translate-ru') {
|
|
65
|
+
return [{
|
|
66
|
+
index: 1,
|
|
67
|
+
status: 'queued',
|
|
68
|
+
geo: 'RU',
|
|
69
|
+
model: input.model,
|
|
70
|
+
pairs: input.pairs,
|
|
71
|
+
}];
|
|
72
|
+
}
|
|
73
|
+
return input.markets.map((market, i) => ({
|
|
74
|
+
index: Number.isInteger(market.index) && Number(market.index) > 0 ? Number(market.index) : i + 1,
|
|
75
|
+
status: 'queued',
|
|
76
|
+
marketId: typeof market.marketId === 'string' ? market.marketId : undefined,
|
|
77
|
+
marketKey: typeof market.marketKey === 'string' ? market.marketKey : undefined,
|
|
78
|
+
geo: market.geo,
|
|
79
|
+
priceWithCurrency: typeof market.priceWithCurrency === 'string' ? market.priceWithCurrency : undefined,
|
|
80
|
+
approachIndices: input.selectedPairApproaches,
|
|
81
|
+
model: input.model,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
function estimateJobMemoryBytes(job) {
|
|
85
|
+
if (job.kind === 'images') {
|
|
86
|
+
return job.slots.reduce((sum, slot) => { var _a, _b; return sum + ((_b = (_a = slot.imageDataUrl) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); }, 0);
|
|
87
|
+
}
|
|
88
|
+
return job.slots.reduce((sum, slot) => { var _a, _b; return sum + ((_b = (_a = slot.rawContent) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0); }, 0);
|
|
89
|
+
}
|
|
90
|
+
class ServerJobRegistry {
|
|
91
|
+
constructor(options = {}) {
|
|
92
|
+
var _a, _b, _c, _d, _e;
|
|
93
|
+
this.jobs = new Map();
|
|
94
|
+
this.emitter = new events_1.EventEmitter();
|
|
95
|
+
this.nextEventId = 1;
|
|
96
|
+
this.ttlMs = (_a = options.ttlMs) !== null && _a !== void 0 ? _a : DEFAULT_TTL_MS;
|
|
97
|
+
this.maxJobs = (_b = options.maxJobs) !== null && _b !== void 0 ? _b : DEFAULT_MAX_JOBS;
|
|
98
|
+
this.maxEventsPerJob = (_c = options.maxEventsPerJob) !== null && _c !== void 0 ? _c : DEFAULT_MAX_EVENTS_PER_JOB;
|
|
99
|
+
this.now = (_d = options.now) !== null && _d !== void 0 ? _d : (() => new Date());
|
|
100
|
+
this.idFactory = (_e = options.idFactory) !== null && _e !== void 0 ? _e : randomJobId;
|
|
101
|
+
}
|
|
102
|
+
createImageJob(input) {
|
|
103
|
+
var _a;
|
|
104
|
+
const workspaceId = String((_a = input.workspaceId) !== null && _a !== void 0 ? _a : '').trim();
|
|
105
|
+
if (!workspaceId) {
|
|
106
|
+
const err = new Error('workspaceId is required.');
|
|
107
|
+
err.status = 400;
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
this.cleanupExpired();
|
|
111
|
+
this.enforceMaxJobs();
|
|
112
|
+
const now = this.now();
|
|
113
|
+
const job = {
|
|
114
|
+
id: this.idFactory(),
|
|
115
|
+
kind: 'images',
|
|
116
|
+
status: 'queued',
|
|
117
|
+
createdAt: now.toISOString(),
|
|
118
|
+
updatedAt: now.toISOString(),
|
|
119
|
+
expiresAt: new Date(now.getTime() + this.ttlMs).toISOString(),
|
|
120
|
+
workspaceId,
|
|
121
|
+
metadata: input.metadata && typeof input.metadata === 'object' ? input.metadata : {},
|
|
122
|
+
events: [],
|
|
123
|
+
slots: buildInitialSlots(input),
|
|
124
|
+
abortController: new AbortController(),
|
|
125
|
+
};
|
|
126
|
+
this.jobs.set(job.id, job);
|
|
127
|
+
this.appendEvent(job.id, 'job.started', { workspaceId, slotCount: job.slots.length });
|
|
128
|
+
job.status = 'running';
|
|
129
|
+
for (const slot of job.slots) {
|
|
130
|
+
this.appendEvent(job.id, 'slot.queued', { index: slot.index, prompt: slot.prompt, approach: slot.approach });
|
|
131
|
+
}
|
|
132
|
+
return this.snapshot(job);
|
|
133
|
+
}
|
|
134
|
+
createContentJob(input) {
|
|
135
|
+
var _a, _b, _c;
|
|
136
|
+
const workspaceId = String((_a = input.workspaceId) !== null && _a !== void 0 ? _a : '').trim();
|
|
137
|
+
const product = String((_b = input.product) !== null && _b !== void 0 ? _b : '').trim();
|
|
138
|
+
const task = input.task === 'translate-ru' ? 'translate-ru' : 'generate-pairs';
|
|
139
|
+
const markets = Array.isArray(input.markets)
|
|
140
|
+
? input.markets.filter(market => typeof market.geo === 'string' && Boolean(market.geo.trim()))
|
|
141
|
+
: [];
|
|
142
|
+
const pairs = Array.isArray(input.pairs)
|
|
143
|
+
? input.pairs
|
|
144
|
+
.map(pair => ({
|
|
145
|
+
title: typeof pair.title === 'string' ? pair.title : '',
|
|
146
|
+
text: typeof pair.text === 'string' ? pair.text : '',
|
|
147
|
+
}))
|
|
148
|
+
.filter(pair => pair.title.trim() || pair.text.trim())
|
|
149
|
+
: [];
|
|
150
|
+
if (!workspaceId) {
|
|
151
|
+
const err = new Error('workspaceId is required.');
|
|
152
|
+
err.status = 400;
|
|
153
|
+
throw err;
|
|
154
|
+
}
|
|
155
|
+
if (task === 'generate-pairs' && !product) {
|
|
156
|
+
const err = new Error('product is required.');
|
|
157
|
+
err.status = 400;
|
|
158
|
+
throw err;
|
|
159
|
+
}
|
|
160
|
+
if (task === 'generate-pairs' && markets.length === 0) {
|
|
161
|
+
const err = new Error('At least one market is required.');
|
|
162
|
+
err.status = 400;
|
|
163
|
+
throw err;
|
|
164
|
+
}
|
|
165
|
+
if (task === 'translate-ru' && pairs.length === 0) {
|
|
166
|
+
const err = new Error('At least one pair is required for translation.');
|
|
167
|
+
err.status = 400;
|
|
168
|
+
throw err;
|
|
169
|
+
}
|
|
170
|
+
this.cleanupExpired();
|
|
171
|
+
this.enforceMaxJobs();
|
|
172
|
+
const selectedPairApproaches = Array.isArray(input.selectedPairApproaches)
|
|
173
|
+
? input.selectedPairApproaches.filter(value => Number.isInteger(value) && value >= 0)
|
|
174
|
+
: [];
|
|
175
|
+
const count = Math.max(1, Math.min(Math.floor(Number((_c = input.count) !== null && _c !== void 0 ? _c : (selectedPairApproaches.length || 1))), 50));
|
|
176
|
+
const normalizedInput = Object.assign(Object.assign({}, input), { task,
|
|
177
|
+
product, markets: task === 'generate-pairs' ? markets.map((market, i) => ({
|
|
178
|
+
index: market.index,
|
|
179
|
+
marketId: typeof market.marketId === 'string' ? market.marketId : undefined,
|
|
180
|
+
marketKey: typeof market.marketKey === 'string' ? market.marketKey : undefined,
|
|
181
|
+
geo: market.geo.trim(),
|
|
182
|
+
priceWithCurrency: typeof market.priceWithCurrency === 'string' ? market.priceWithCurrency : undefined,
|
|
183
|
+
})) : [], pairs,
|
|
184
|
+
selectedPairApproaches,
|
|
185
|
+
count, model: typeof input.model === 'string' && input.model.trim() ? input.model.trim() : undefined, additionalInfo: typeof input.additionalInfo === 'string' ? input.additionalInfo : undefined });
|
|
186
|
+
const now = this.now();
|
|
187
|
+
const job = {
|
|
188
|
+
id: this.idFactory(),
|
|
189
|
+
kind: 'content',
|
|
190
|
+
status: 'queued',
|
|
191
|
+
createdAt: now.toISOString(),
|
|
192
|
+
updatedAt: now.toISOString(),
|
|
193
|
+
expiresAt: new Date(now.getTime() + this.ttlMs).toISOString(),
|
|
194
|
+
workspaceId,
|
|
195
|
+
task,
|
|
196
|
+
product,
|
|
197
|
+
additionalInfo: normalizedInput.additionalInfo,
|
|
198
|
+
pairs: task === 'translate-ru' ? pairs : undefined,
|
|
199
|
+
selectedPairApproaches,
|
|
200
|
+
count,
|
|
201
|
+
model: normalizedInput.model,
|
|
202
|
+
metadata: input.metadata && typeof input.metadata === 'object' ? input.metadata : {},
|
|
203
|
+
events: [],
|
|
204
|
+
slots: buildInitialContentSlots(normalizedInput),
|
|
205
|
+
abortController: new AbortController(),
|
|
206
|
+
};
|
|
207
|
+
this.jobs.set(job.id, job);
|
|
208
|
+
this.appendEvent(job.id, 'job.started', { workspaceId, slotCount: job.slots.length, kind: job.kind, task });
|
|
209
|
+
job.status = 'running';
|
|
210
|
+
for (const slot of job.slots) {
|
|
211
|
+
this.appendEvent(job.id, 'slot.queued', { index: slot.index, geo: slot.geo, marketKey: slot.marketKey });
|
|
212
|
+
}
|
|
213
|
+
return this.snapshot(job);
|
|
214
|
+
}
|
|
215
|
+
getJob(jobId) {
|
|
216
|
+
this.cleanupExpired();
|
|
217
|
+
const job = this.jobs.get(jobId);
|
|
218
|
+
return job ? this.snapshot(job) : null;
|
|
219
|
+
}
|
|
220
|
+
listJobs() {
|
|
221
|
+
this.cleanupExpired();
|
|
222
|
+
return [...this.jobs.values()].map(job => this.snapshot(job));
|
|
223
|
+
}
|
|
224
|
+
cancelJob(jobId) {
|
|
225
|
+
const job = this.jobs.get(jobId);
|
|
226
|
+
if (!job)
|
|
227
|
+
return null;
|
|
228
|
+
if (job.status === 'succeeded' || job.status === 'failed' || job.status === 'cancelled') {
|
|
229
|
+
return this.snapshot(job);
|
|
230
|
+
}
|
|
231
|
+
job.abortController.abort();
|
|
232
|
+
job.status = 'cancelled';
|
|
233
|
+
job.updatedAt = this.now().toISOString();
|
|
234
|
+
if (job.kind === 'images') {
|
|
235
|
+
job.slots = job.slots.map(slot => ['uploaded', 'failed'].includes(slot.status) ? slot : Object.assign(Object.assign({}, slot), { status: 'cancelled' }));
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
job.slots = job.slots.map(slot => ['generated', 'failed'].includes(slot.status) ? slot : Object.assign(Object.assign({}, slot), { status: 'cancelled' }));
|
|
239
|
+
}
|
|
240
|
+
this.appendEvent(job.id, 'job.cancelled', {});
|
|
241
|
+
return this.snapshot(job);
|
|
242
|
+
}
|
|
243
|
+
getAbortSignal(jobId) {
|
|
244
|
+
var _a;
|
|
245
|
+
const job = this.jobs.get(jobId);
|
|
246
|
+
return (_a = job === null || job === void 0 ? void 0 : job.abortController.signal) !== null && _a !== void 0 ? _a : null;
|
|
247
|
+
}
|
|
248
|
+
isCancelled(jobId) {
|
|
249
|
+
const job = this.jobs.get(jobId);
|
|
250
|
+
return !job || job.status === 'cancelled' || job.abortController.signal.aborted;
|
|
251
|
+
}
|
|
252
|
+
markJobSucceeded(jobId) {
|
|
253
|
+
const job = this.jobs.get(jobId);
|
|
254
|
+
if (!job) {
|
|
255
|
+
const err = new Error('Job not found.');
|
|
256
|
+
err.status = 404;
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
if (job.status !== 'cancelled') {
|
|
260
|
+
job.status = 'succeeded';
|
|
261
|
+
job.updatedAt = this.now().toISOString();
|
|
262
|
+
this.appendEvent(job.id, 'job.finished', {});
|
|
263
|
+
}
|
|
264
|
+
return this.snapshot(job);
|
|
265
|
+
}
|
|
266
|
+
markJobFailed(jobId, message) {
|
|
267
|
+
const job = this.jobs.get(jobId);
|
|
268
|
+
if (!job) {
|
|
269
|
+
const err = new Error('Job not found.');
|
|
270
|
+
err.status = 404;
|
|
271
|
+
throw err;
|
|
272
|
+
}
|
|
273
|
+
if (job.status !== 'cancelled') {
|
|
274
|
+
job.status = 'failed';
|
|
275
|
+
job.error = message;
|
|
276
|
+
job.updatedAt = this.now().toISOString();
|
|
277
|
+
this.appendEvent(job.id, 'job.failed', { error: message });
|
|
278
|
+
}
|
|
279
|
+
return this.snapshot(job);
|
|
280
|
+
}
|
|
281
|
+
appendEvent(jobId, type, data) {
|
|
282
|
+
const job = this.jobs.get(jobId);
|
|
283
|
+
if (!job) {
|
|
284
|
+
const err = new Error('Job not found.');
|
|
285
|
+
err.status = 404;
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
const event = Object.assign({ id: this.nextEventId, jobId,
|
|
289
|
+
type, timestamp: this.now().toISOString() }, (data ? { data } : {}));
|
|
290
|
+
this.nextEventId += 1;
|
|
291
|
+
job.updatedAt = event.timestamp;
|
|
292
|
+
job.events.push(event);
|
|
293
|
+
if (job.events.length > this.maxEventsPerJob) {
|
|
294
|
+
job.events.splice(0, job.events.length - this.maxEventsPerJob);
|
|
295
|
+
}
|
|
296
|
+
this.emitter.emit(jobId, event);
|
|
297
|
+
return event;
|
|
298
|
+
}
|
|
299
|
+
updateSlot(jobId, index, patch) {
|
|
300
|
+
const job = this.jobs.get(jobId);
|
|
301
|
+
if (!job) {
|
|
302
|
+
const err = new Error('Job not found.');
|
|
303
|
+
err.status = 404;
|
|
304
|
+
throw err;
|
|
305
|
+
}
|
|
306
|
+
job.slots = job.slots.map(slot => (slot.index === index ? Object.assign(Object.assign(Object.assign({}, slot), patch), { index }) : slot));
|
|
307
|
+
job.updatedAt = this.now().toISOString();
|
|
308
|
+
return this.snapshot(job);
|
|
309
|
+
}
|
|
310
|
+
updateContentSlot(jobId, index, patch) {
|
|
311
|
+
const job = this.jobs.get(jobId);
|
|
312
|
+
if (!job) {
|
|
313
|
+
const err = new Error('Job not found.');
|
|
314
|
+
err.status = 404;
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
if (job.kind !== 'content') {
|
|
318
|
+
const err = new Error('Job is not a content job.');
|
|
319
|
+
err.status = 400;
|
|
320
|
+
throw err;
|
|
321
|
+
}
|
|
322
|
+
job.slots = job.slots.map(slot => (slot.index === index ? Object.assign(Object.assign(Object.assign({}, slot), patch), { index }) : slot));
|
|
323
|
+
job.updatedAt = this.now().toISOString();
|
|
324
|
+
return this.snapshot(job);
|
|
325
|
+
}
|
|
326
|
+
subscribe(jobId, listener) {
|
|
327
|
+
this.emitter.on(jobId, listener);
|
|
328
|
+
return () => this.emitter.off(jobId, listener);
|
|
329
|
+
}
|
|
330
|
+
getEventsSince(jobId, lastEventId = 0) {
|
|
331
|
+
const job = this.jobs.get(jobId);
|
|
332
|
+
if (!job)
|
|
333
|
+
return [];
|
|
334
|
+
return job.events.filter(event => event.id > lastEventId);
|
|
335
|
+
}
|
|
336
|
+
snapshot(job) {
|
|
337
|
+
const { abortController: _abortController } = job, snapshot = __rest(job, ["abortController"]);
|
|
338
|
+
return Object.assign(Object.assign({}, snapshot), { events: job.events.map(event => (Object.assign(Object.assign({}, event), { data: event.data ? Object.assign({}, event.data) : undefined }))), slots: job.slots.map(slot => (Object.assign({}, slot))), memoryBytes: estimateJobMemoryBytes(job) });
|
|
339
|
+
}
|
|
340
|
+
cleanupExpired() {
|
|
341
|
+
const now = this.now().getTime();
|
|
342
|
+
for (const [jobId, job] of this.jobs.entries()) {
|
|
343
|
+
if (new Date(job.expiresAt).getTime() < now) {
|
|
344
|
+
this.jobs.delete(jobId);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
enforceMaxJobs() {
|
|
349
|
+
while (this.jobs.size >= this.maxJobs) {
|
|
350
|
+
const oldest = [...this.jobs.values()].sort((a, b) => a.createdAt.localeCompare(b.createdAt))[0];
|
|
351
|
+
if (!oldest)
|
|
352
|
+
return;
|
|
353
|
+
this.jobs.delete(oldest.id);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
exports.ServerJobRegistry = ServerJobRegistry;
|
|
358
|
+
exports.defaultServerJobRegistry = new ServerJobRegistry();
|
|
@@ -0,0 +1,58 @@
|
|
|
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.getOpenRouterModels = getOpenRouterModels;
|
|
13
|
+
exports.getOpenRouterBalance = getOpenRouterBalance;
|
|
14
|
+
const openrouter_1 = require("../integrations/openrouter");
|
|
15
|
+
function badRequest(message) {
|
|
16
|
+
const err = new Error(message);
|
|
17
|
+
err.status = 400;
|
|
18
|
+
return err;
|
|
19
|
+
}
|
|
20
|
+
function readOpenRouterApiKey(store) {
|
|
21
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
22
|
+
var _a;
|
|
23
|
+
const config = yield store.readConfig();
|
|
24
|
+
const apiKey = (_a = config.openaiApiKey) === null || _a === void 0 ? void 0 : _a.trim();
|
|
25
|
+
if (!apiKey)
|
|
26
|
+
throw badRequest('openaiApiKey is not configured on the server.');
|
|
27
|
+
return apiKey;
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function getOpenRouterModels(store) {
|
|
31
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
32
|
+
const apiKey = yield readOpenRouterApiKey(store);
|
|
33
|
+
const models = yield (0, openrouter_1.fetchOpenRouterModels)(apiKey, (level, ...args) => {
|
|
34
|
+
console[level]('[docs-combiner-server] openrouter models', ...args);
|
|
35
|
+
});
|
|
36
|
+
if (!models) {
|
|
37
|
+
const err = new Error('Failed to fetch OpenRouter models.');
|
|
38
|
+
err.status = 502;
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
models,
|
|
44
|
+
imageModels: (0, openrouter_1.filterImageGenerationModels)(models),
|
|
45
|
+
chatModels: (0, openrouter_1.filterChatCompletionModels)(models),
|
|
46
|
+
validationModels: (0, openrouter_1.filterVisionValidationModels)(models),
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function getOpenRouterBalance(store) {
|
|
51
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
52
|
+
const apiKey = yield readOpenRouterApiKey(store);
|
|
53
|
+
const balances = yield (0, openrouter_1.fetchOpenRouterBalances)(apiKey, (level, ...args) => {
|
|
54
|
+
console[level]('[docs-combiner-server] openrouter balance', ...args);
|
|
55
|
+
});
|
|
56
|
+
return Object.assign({ ok: true }, balances);
|
|
57
|
+
});
|
|
58
|
+
}
|