vidspotai-shared 1.0.81 → 1.0.82
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/lib/globals/aiModels/providers/google.d.ts.map +1 -1
- package/lib/globals/aiModels/providers/google.js +39 -7
- package/lib/schemas/project.schema.d.ts +3 -3
- package/lib/schemas/videoPlan.schema.d.ts +3 -3
- package/lib/services/agent/editClassifier.d.ts +2 -2
- package/lib/services/agent/eval/recorder.d.ts +13 -1
- package/lib/services/agent/eval/recorder.d.ts.map +1 -1
- package/lib/services/agent/eval/recorder.js +59 -0
- package/lib/services/agent/tools/composeScene.tool.d.ts +2 -2
- package/lib/services/agent/tools/estimateCost.tool.d.ts +1 -1
- package/lib/services/agent/tools/planVideo.tool.d.ts +1 -1
- package/lib/services/agent/tools/render.tool.d.ts +1 -1
- package/lib/services/aiGen/helpers.d.ts +8 -0
- package/lib/services/aiGen/helpers.d.ts.map +1 -1
- package/lib/services/aiGen/helpers.js +12 -0
- package/lib/services/aiGen/providers/google/google.service.d.ts +1 -0
- package/lib/services/aiGen/providers/google/google.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/google/google.service.js +100 -8
- package/lib/services/aiGen/providers/google/googleApiKeys.d.ts +71 -0
- package/lib/services/aiGen/providers/google/googleApiKeys.d.ts.map +1 -0
- package/lib/services/aiGen/providers/google/googleApiKeys.js +137 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts +52 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.d.ts.map +1 -0
- package/lib/services/aiGen/providers/google/googleKeyPool.js +129 -0
- package/lib/services/aiGen/providers/pixverse/pixverse.service.d.ts.map +1 -1
- package/lib/services/aiGen/providers/pixverse/pixverse.service.js +7 -1
- package/lib/services/bullmq.service.d.ts.map +1 -1
- package/lib/services/bullmq.service.js +23 -1
- package/lib/services/index.d.ts +1 -0
- package/lib/services/index.d.ts.map +1 -1
- package/lib/services/index.js +1 -0
- package/lib/services/rateLimiter/distributedRateLimiter.service.d.ts +60 -5
- package/lib/services/rateLimiter/distributedRateLimiter.service.d.ts.map +1 -1
- package/lib/services/rateLimiter/distributedRateLimiter.service.js +184 -16
- package/lib/services/translation/index.d.ts +2 -0
- package/lib/services/translation/index.d.ts.map +1 -0
- package/lib/services/translation/index.js +9 -0
- package/lib/services/translation/translation.service.d.ts +50 -0
- package/lib/services/translation/translation.service.d.ts.map +1 -0
- package/lib/services/translation/translation.service.js +211 -0
- package/lib/utils/helpers.d.ts +2 -4
- package/lib/utils/helpers.d.ts.map +1 -1
- package/lib/utils/helpers.js +9 -63
- package/package.json +6 -6
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure (SDK-free, Redis-free) helpers for the Google AI Studio API key pool.
|
|
4
|
+
*
|
|
5
|
+
* ── DECISION RECORD (2026-06-15) ────────────────────────────────────────────
|
|
6
|
+
* Problem: Veo on a single Gemini Developer API key is quota-starved — Tier 2
|
|
7
|
+
* is ~4 req/min and ~50/day per Veo model — which serialized bursts of jobs
|
|
8
|
+
* into a multi-hour queue (~370 min observed avg latency).
|
|
9
|
+
*
|
|
10
|
+
* Chosen fix (today): a multi-key pool over TWO Google AI Studio keys that live
|
|
11
|
+
* in SEPARATE GCP projects / billing accounts, so their quotas are independent
|
|
12
|
+
* and ADD UP. Keys are used in PRIORITY order:
|
|
13
|
+
* - key[0] = the NEW key (vidspotai project), currently **Tier 1** (2/min,
|
|
14
|
+
* 10/day per Veo model). Used FIRST, deliberately, to drive usage and
|
|
15
|
+
* promote its billing account up the tier ladder.
|
|
16
|
+
* - key[1] = the CURRENT key, **Tier 2** (4/min, 50/day). Used once key[0]
|
|
17
|
+
* is out of per-minute / per-day budget.
|
|
18
|
+
* Aggregate Veo budget = T1 + T2 = 6/min, 60/day. When BOTH are exhausted, the
|
|
19
|
+
* job-start capacity selector (videoJobProcessor) spills to another provider.
|
|
20
|
+
*
|
|
21
|
+
* When key[1]'s account is billed it moves to Tier 3; bump its tier in
|
|
22
|
+
* GOOGLE_API_KEY_TIERS then (no code change — the ladder below handles it).
|
|
23
|
+
*
|
|
24
|
+
* Vertex AI (DEFERRED, on record for the future): Veo is also available via
|
|
25
|
+
* Vertex, where quota is **per-project** and the current billing account is
|
|
26
|
+
* Tier-2-per-project — so we could create multiple projects under one account
|
|
27
|
+
* to scale further. We are NOT doing Vertex now: the two AI-Studio keys across
|
|
28
|
+
* two billing accounts already cover our needed headroom, and the Vertex path
|
|
29
|
+
* needs a different output flow (GCS `gs://` URIs rather than the Files API)
|
|
30
|
+
* that we'd rather build + test deliberately when the extra capacity is needed.
|
|
31
|
+
*
|
|
32
|
+
* ── CONFIG ──────────────────────────────────────────────────────────────────
|
|
33
|
+
* GOOGLE_API_KEYS — comma-separated keys in PRIORITY order. Falls back
|
|
34
|
+
* to the single legacy GOOGLE_API_KEY when unset.
|
|
35
|
+
* GOOGLE_API_KEY_TIERS — comma-separated tier numbers (1/2/3) aligned to
|
|
36
|
+
* GOOGLE_API_KEYS. Missing/extra entries default to 2.
|
|
37
|
+
* GOOGLE_API_KEY — legacy single key; also the client used to poll
|
|
38
|
+
* tasks submitted before the pool existed (un-tagged).
|
|
39
|
+
*
|
|
40
|
+
* Per-key budget is derived from the model config's Tier-2 baseline
|
|
41
|
+
* (requestPerMin / requestPerDay) scaled by each key's tier (see TIER_FACTORS);
|
|
42
|
+
* the pool gives us the SUM across keys.
|
|
43
|
+
*/
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.parseGoogleApiKeys = parseGoogleApiKeys;
|
|
46
|
+
exports.parseGoogleApiKeyTiers = parseGoogleApiKeyTiers;
|
|
47
|
+
exports.googleApiKeyCount = googleApiKeyCount;
|
|
48
|
+
exports.scaleLimitForTier = scaleLimitForTier;
|
|
49
|
+
exports.googleAggregateFactor = googleAggregateFactor;
|
|
50
|
+
exports.googleKeyId = googleKeyId;
|
|
51
|
+
exports.encodeVeoTask = encodeVeoTask;
|
|
52
|
+
exports.decodeVeoTask = decodeVeoTask;
|
|
53
|
+
/**
|
|
54
|
+
* Per-Veo-model rate ladder, expressed as a factor of the Tier-2 baseline that
|
|
55
|
+
* the model configs encode. Verified against Google's Veo quota
|
|
56
|
+
* (predict_long_running_requests_per_model): RPM 2/4/10 and RPD 10/50/500 for
|
|
57
|
+
* Tier 1/2/3. Only Veo configures per-key min/day caps today, so these factors
|
|
58
|
+
* only materially affect Veo; models with no caps (factor × 0 = 0) are
|
|
59
|
+
* unaffected.
|
|
60
|
+
*/
|
|
61
|
+
const TIER_FACTORS = {
|
|
62
|
+
1: { rpm: 2 / 4, rpd: 10 / 50 }, // 0.5×, 0.2×
|
|
63
|
+
2: { rpm: 1, rpd: 1 },
|
|
64
|
+
3: { rpm: 10 / 4, rpd: 500 / 50 }, // 2.5×, 10×
|
|
65
|
+
};
|
|
66
|
+
function tierFactor(tier) {
|
|
67
|
+
return TIER_FACTORS[tier] ?? TIER_FACTORS[2];
|
|
68
|
+
}
|
|
69
|
+
/** Parse the configured keys in priority order, de-duped, empties removed. */
|
|
70
|
+
function parseGoogleApiKeys() {
|
|
71
|
+
const multi = (process.env.GOOGLE_API_KEYS ?? "").trim();
|
|
72
|
+
const raw = multi || (process.env.GOOGLE_API_KEY ?? "").trim();
|
|
73
|
+
const seen = new Set();
|
|
74
|
+
const keys = [];
|
|
75
|
+
for (const part of raw.split(",")) {
|
|
76
|
+
const k = part.trim();
|
|
77
|
+
if (k && !seen.has(k)) {
|
|
78
|
+
seen.add(k);
|
|
79
|
+
keys.push(k);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return keys;
|
|
83
|
+
}
|
|
84
|
+
/** Tier per key (aligned to parseGoogleApiKeys order); defaults to 2. */
|
|
85
|
+
function parseGoogleApiKeyTiers() {
|
|
86
|
+
const keys = parseGoogleApiKeys();
|
|
87
|
+
const raw = (process.env.GOOGLE_API_KEY_TIERS ?? "").trim();
|
|
88
|
+
const parsed = raw
|
|
89
|
+
? raw.split(",").map((s) => Number(s.trim()))
|
|
90
|
+
: [];
|
|
91
|
+
return keys.map((_, i) => {
|
|
92
|
+
const t = parsed[i];
|
|
93
|
+
return t === 1 || t === 2 || t === 3 ? t : 2;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
/** Number of configured Google keys (>= 1 so callers can multiply safely). */
|
|
97
|
+
function googleApiKeyCount() {
|
|
98
|
+
return Math.max(1, parseGoogleApiKeys().length);
|
|
99
|
+
}
|
|
100
|
+
/** Per-key rate cap for a given Tier-2 baseline and tier. */
|
|
101
|
+
function scaleLimitForTier(baseline, tier, kind) {
|
|
102
|
+
if (!baseline)
|
|
103
|
+
return 0;
|
|
104
|
+
return Math.max(1, Math.round(baseline * tierFactor(tier)[kind]));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Aggregate multiplier across all configured keys, for scaling a Google
|
|
108
|
+
* model's Tier-2 baseline into the pool-wide budget. Returns 1 for a single
|
|
109
|
+
* default key (legacy behavior unchanged).
|
|
110
|
+
*/
|
|
111
|
+
function googleAggregateFactor(kind) {
|
|
112
|
+
const tiers = parseGoogleApiKeyTiers();
|
|
113
|
+
if (tiers.length <= 1)
|
|
114
|
+
return 1; // legacy single key → baseline unchanged
|
|
115
|
+
return tiers.reduce((sum, t) => sum + tierFactor(t)[kind], 0);
|
|
116
|
+
}
|
|
117
|
+
/** Stable id for the key at a given priority index. */
|
|
118
|
+
function googleKeyId(index) {
|
|
119
|
+
return `k${index}`;
|
|
120
|
+
}
|
|
121
|
+
const TASK_TAG_RE = /^gk:([a-z0-9]+)::([\s\S]+)$/;
|
|
122
|
+
/**
|
|
123
|
+
* Tag a Veo operation name with the id of the key that created it, so polling
|
|
124
|
+
* + download can re-select the same project-scoped client. With a single key
|
|
125
|
+
* we return the bare operation name (no tag) — byte-for-byte the legacy format,
|
|
126
|
+
* so nothing changes until a real pool is configured.
|
|
127
|
+
*/
|
|
128
|
+
function encodeVeoTask(keyId, operationName, poolSize) {
|
|
129
|
+
return poolSize > 1 ? `gk:${keyId}::${operationName}` : operationName;
|
|
130
|
+
}
|
|
131
|
+
/** Reverse of encodeVeoTask. `keyId` is undefined for legacy/un-tagged tasks. */
|
|
132
|
+
function decodeVeoTask(task) {
|
|
133
|
+
const m = TASK_TAG_RE.exec(task);
|
|
134
|
+
if (m)
|
|
135
|
+
return { keyId: m[1], operationName: m[2] };
|
|
136
|
+
return { operationName: task };
|
|
137
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { GoogleGenAI } from "@google/genai";
|
|
2
|
+
import { googleApiKeyCount } from "./googleApiKeys";
|
|
3
|
+
/**
|
|
4
|
+
* Google AI Studio multi-key pool.
|
|
5
|
+
*
|
|
6
|
+
* Each configured key is a separate GCP project / billing account with its own
|
|
7
|
+
* Tier-2 quota, so N keys give us N× the per-model daily + per-minute budget.
|
|
8
|
+
* The model-level rate limiter (DistributedRateLimiter) enforces the AGGREGATE
|
|
9
|
+
* (per-key config × key count — see getAiGenModelRateLimiter); this pool decides
|
|
10
|
+
* WHICH key serves each individual submit, in priority order, so we drain the
|
|
11
|
+
* first key's budget before leaning on the next.
|
|
12
|
+
*
|
|
13
|
+
* Veo operations are project-scoped — a task submitted with key K can only be
|
|
14
|
+
* polled / downloaded with key K — so submit tags the task with its key id
|
|
15
|
+
* (encodeVeoTask) and the poll path re-selects the client via clientById().
|
|
16
|
+
*
|
|
17
|
+
* Single-key / unconfigured pool: behaves exactly like the legacy single
|
|
18
|
+
* `GoogleGenAI({ apiKey })` client (no tagging, no Redis routing).
|
|
19
|
+
*/
|
|
20
|
+
interface KeyEntry {
|
|
21
|
+
id: string;
|
|
22
|
+
apiKey: string;
|
|
23
|
+
client: GoogleGenAI;
|
|
24
|
+
/** Gemini API tier (1/2/3) of this key's billing account. */
|
|
25
|
+
tier: number;
|
|
26
|
+
}
|
|
27
|
+
declare class GoogleKeyPool {
|
|
28
|
+
private readonly entries;
|
|
29
|
+
constructor();
|
|
30
|
+
get size(): number;
|
|
31
|
+
/** The highest-priority key's client (index 0). */
|
|
32
|
+
get primaryClient(): GoogleGenAI;
|
|
33
|
+
/** Resolve a tagged task's key id back to its client (undefined if unknown). */
|
|
34
|
+
clientById(id: string | undefined): GoogleGenAI | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Pick the key that should serve a new submission. Walks keys in priority
|
|
37
|
+
* order and returns the first whose per-key minute + day budgets still have
|
|
38
|
+
* headroom, consuming a slot on it. The caps are the model's Tier-2 baseline
|
|
39
|
+
* (baselineMin / baselineDay) scaled to each key's own tier — so a Tier-1
|
|
40
|
+
* key gets a smaller share than a Tier-2 one. Falls back to the primary key
|
|
41
|
+
* if Redis is down or all are at cap (the aggregate model-level gate should
|
|
42
|
+
* have prevented the latter).
|
|
43
|
+
*/
|
|
44
|
+
pickForSubmit(modelId: string, baselineMin: number, baselineDay: number): Promise<KeyEntry>;
|
|
45
|
+
/** Best-effort increment of a key's per-day + per-minute routing counters. */
|
|
46
|
+
private consume;
|
|
47
|
+
}
|
|
48
|
+
/** Lazily-built process-wide pool. */
|
|
49
|
+
export declare function getGoogleKeyPool(): GoogleKeyPool;
|
|
50
|
+
export { googleApiKeyCount };
|
|
51
|
+
export type { GoogleKeyPool };
|
|
52
|
+
//# sourceMappingURL=googleKeyPool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"googleKeyPool.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/google/googleKeyPool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EACL,iBAAiB,EAKlB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;GAgBG;AAEH,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,WAAW,CAAC;IACpB,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;CACd;AAcD,cAAM,aAAa;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAa;;IA4BrC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,mDAAmD;IACnD,IAAI,aAAa,IAAI,WAAW,CAE/B;IAED,gFAAgF;IAChF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,GAAG,SAAS;IAK3D;;;;;;;;OAQG;IACG,aAAa,CACjB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,QAAQ,CAAC;IA2CpB,8EAA8E;YAChE,OAAO;CAUtB;AAID,sCAAsC;AACtC,wBAAgB,gBAAgB,IAAI,aAAa,CAGhD;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;AAC7B,YAAY,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.googleApiKeyCount = void 0;
|
|
4
|
+
exports.getGoogleKeyPool = getGoogleKeyPool;
|
|
5
|
+
const genai_1 = require("@google/genai");
|
|
6
|
+
const logger_1 = require("../../../../utils/logger");
|
|
7
|
+
const redis_service_1 = require("../../../redis.service");
|
|
8
|
+
const googleApiKeys_1 = require("./googleApiKeys");
|
|
9
|
+
Object.defineProperty(exports, "googleApiKeyCount", { enumerable: true, get: function () { return googleApiKeys_1.googleApiKeyCount; } });
|
|
10
|
+
function utcDateKey() {
|
|
11
|
+
return new Date().toISOString().slice(0, 10);
|
|
12
|
+
}
|
|
13
|
+
function secsUntilMidnight() {
|
|
14
|
+
const now = new Date();
|
|
15
|
+
const midnight = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
|
|
16
|
+
return Math.ceil((midnight.getTime() - now.getTime()) / 1000);
|
|
17
|
+
}
|
|
18
|
+
class GoogleKeyPool {
|
|
19
|
+
constructor() {
|
|
20
|
+
const keys = (0, googleApiKeys_1.parseGoogleApiKeys)();
|
|
21
|
+
const tiers = (0, googleApiKeys_1.parseGoogleApiKeyTiers)();
|
|
22
|
+
this.entries = keys.map((apiKey, i) => ({
|
|
23
|
+
id: (0, googleApiKeys_1.googleKeyId)(i),
|
|
24
|
+
apiKey,
|
|
25
|
+
client: new genai_1.GoogleGenAI({ apiKey }),
|
|
26
|
+
tier: tiers[i] ?? 2,
|
|
27
|
+
}));
|
|
28
|
+
if (!this.entries.length) {
|
|
29
|
+
// Mirror the legacy constructor: a bang-asserted GOOGLE_API_KEY. Building
|
|
30
|
+
// a client with undefined defers the failure to first call, same as before.
|
|
31
|
+
this.entries.push({
|
|
32
|
+
id: (0, googleApiKeys_1.googleKeyId)(0),
|
|
33
|
+
apiKey: process.env.GOOGLE_API_KEY ?? "",
|
|
34
|
+
client: new genai_1.GoogleGenAI({ apiKey: process.env.GOOGLE_API_KEY }),
|
|
35
|
+
tier: 2,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
if (this.entries.length > 1) {
|
|
39
|
+
logger_1.logger.info("googleKeyPool: multi-key pool active", {
|
|
40
|
+
keys: this.entries.map((e) => ({ id: e.id, tier: e.tier })),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
get size() {
|
|
45
|
+
return this.entries.length;
|
|
46
|
+
}
|
|
47
|
+
/** The highest-priority key's client (index 0). */
|
|
48
|
+
get primaryClient() {
|
|
49
|
+
return this.entries[0].client;
|
|
50
|
+
}
|
|
51
|
+
/** Resolve a tagged task's key id back to its client (undefined if unknown). */
|
|
52
|
+
clientById(id) {
|
|
53
|
+
if (!id)
|
|
54
|
+
return undefined;
|
|
55
|
+
return this.entries.find((e) => e.id === id)?.client;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Pick the key that should serve a new submission. Walks keys in priority
|
|
59
|
+
* order and returns the first whose per-key minute + day budgets still have
|
|
60
|
+
* headroom, consuming a slot on it. The caps are the model's Tier-2 baseline
|
|
61
|
+
* (baselineMin / baselineDay) scaled to each key's own tier — so a Tier-1
|
|
62
|
+
* key gets a smaller share than a Tier-2 one. Falls back to the primary key
|
|
63
|
+
* if Redis is down or all are at cap (the aggregate model-level gate should
|
|
64
|
+
* have prevented the latter).
|
|
65
|
+
*/
|
|
66
|
+
async pickForSubmit(modelId, baselineMin, baselineDay) {
|
|
67
|
+
if (this.entries.length === 1)
|
|
68
|
+
return this.entries[0];
|
|
69
|
+
const client = redis_service_1.redis.getClient();
|
|
70
|
+
if (!client)
|
|
71
|
+
return this.entries[0];
|
|
72
|
+
const date = utcDateKey();
|
|
73
|
+
for (const entry of this.entries) {
|
|
74
|
+
try {
|
|
75
|
+
const perKeyMinLimit = (0, googleApiKeys_1.scaleLimitForTier)(baselineMin, entry.tier, "rpm");
|
|
76
|
+
const perKeyDayLimit = (0, googleApiKeys_1.scaleLimitForTier)(baselineDay, entry.tier, "rpd");
|
|
77
|
+
const dayKey = `gkpool:${entry.id}:${modelId}:day:${date}`;
|
|
78
|
+
const minKey = `gkpool:${entry.id}:${modelId}:min`;
|
|
79
|
+
const [dayRaw, minRaw] = await Promise.all([
|
|
80
|
+
client.get(dayKey),
|
|
81
|
+
client.get(minKey),
|
|
82
|
+
]);
|
|
83
|
+
const dayUsed = dayRaw ? Number(dayRaw) : 0;
|
|
84
|
+
const minUsed = minRaw ? Number(minRaw) : 0;
|
|
85
|
+
const dayOk = perKeyDayLimit <= 0 || dayUsed < perKeyDayLimit;
|
|
86
|
+
const minOk = perKeyMinLimit <= 0 || minUsed < perKeyMinLimit;
|
|
87
|
+
if (dayOk && minOk) {
|
|
88
|
+
await this.consume(entry, modelId, date);
|
|
89
|
+
return entry;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
logger_1.logger.warn("googleKeyPool: routing read failed, trying next key", {
|
|
94
|
+
keyId: entry.id,
|
|
95
|
+
err: err instanceof Error ? err.message : String(err),
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// All at cap / errored — use the primary and let Google's own 429 + the
|
|
100
|
+
// provider-fallback chain handle it.
|
|
101
|
+
logger_1.logger.warn("googleKeyPool: no key with headroom — using primary", {
|
|
102
|
+
modelId,
|
|
103
|
+
poolSize: this.entries.length,
|
|
104
|
+
});
|
|
105
|
+
await this.consume(this.entries[0], modelId, date).catch(() => undefined);
|
|
106
|
+
return this.entries[0];
|
|
107
|
+
}
|
|
108
|
+
/** Best-effort increment of a key's per-day + per-minute routing counters. */
|
|
109
|
+
async consume(entry, modelId, date) {
|
|
110
|
+
const client = redis_service_1.redis.getClient();
|
|
111
|
+
if (!client)
|
|
112
|
+
return;
|
|
113
|
+
const dayKey = `gkpool:${entry.id}:${modelId}:day:${date}`;
|
|
114
|
+
const minKey = `gkpool:${entry.id}:${modelId}:min`;
|
|
115
|
+
const newDay = await client.incr(dayKey);
|
|
116
|
+
if (newDay === 1)
|
|
117
|
+
await client.expire(dayKey, secsUntilMidnight());
|
|
118
|
+
const newMin = await client.incr(minKey);
|
|
119
|
+
if (newMin === 1)
|
|
120
|
+
await client.expire(minKey, 60);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let pool = null;
|
|
124
|
+
/** Lazily-built process-wide pool. */
|
|
125
|
+
function getGoogleKeyPool() {
|
|
126
|
+
if (!pool)
|
|
127
|
+
pool = new GoogleKeyPool();
|
|
128
|
+
return pool;
|
|
129
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pixverse.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/pixverse/pixverse.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA0FlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAKpE,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"pixverse.service.d.ts","sourceRoot":"","sources":["../../../../../src/services/aiGen/providers/pixverse/pixverse.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,8BAA8B,CAAC;AACxE,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,UAAU,CAAC;AA0FlB,qBAAa,eAAgB,SAAQ,wBAAwB;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkD;IAKpE,aAAa,CACjB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,qBAAqB,CAAC;IAyK3B,gBAAgB,CAAC,EACrB,IAAI,EACJ,cAAc,EACd,cAAyB,GAC1B,EAAE,iBAAiB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IA0FjD,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM;CAkDjD"}
|
|
@@ -211,7 +211,13 @@ class PixVerseService extends baseAiGenProvider_service_1.BaseAiGenProviderServi
|
|
|
211
211
|
if (isImageToVideo) {
|
|
212
212
|
body.image_url = params.inputImageUrl;
|
|
213
213
|
}
|
|
214
|
-
|
|
214
|
+
// I2V path is `/img/generate` — NOT `/image/generate`, which returns a bare
|
|
215
|
+
// "404 page not found" (verified 2026-06-15 via PixVerse OpenAPI smoke test
|
|
216
|
+
// + docs https://docs.platform.pixverse.ai/image-to-video-generation-13016633e0).
|
|
217
|
+
// This affected ALL PixVerse image-to-video models; it surfaced in prod as
|
|
218
|
+
// "pixverse-v6 404" only because v6 sits low in the I2V fallback chain and
|
|
219
|
+
// was the model selected when the rare fallback fired.
|
|
220
|
+
const endpoint = isImageToVideo ? `${this.baseUrl}/img/generate` : `${this.baseUrl}/text/generate`;
|
|
215
221
|
const data = await pixverseFetch(endpoint, {
|
|
216
222
|
method: "POST",
|
|
217
223
|
headers: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bullmq.service.d.ts","sourceRoot":"","sources":["../../src/services/bullmq.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAgB,EAAwB,YAAY,EAAE,MAAM,SAAS,CAAC;AAItE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAc1C,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAgB;IAIpC,OAAO,CAAC,UAAU,CAAC,CAAc;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO;WAQO,WAAW,CAAC,OAAO,EAAE,oBAAoB;IAOvD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,aAAa;IAOrB,sDAAsD;IACzC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBlC,gBAAgB;IACH,MAAM,CACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAUpC,YAAY,CACjB,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,eAAe,CAAC,EAAE,MAAM,CACtB,MAAM,EACN;QACE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CACF;
|
|
1
|
+
{"version":3,"file":"bullmq.service.d.ts","sourceRoot":"","sources":["../../src/services/bullmq.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,GAAG,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAgB,EAAwB,YAAY,EAAE,MAAM,SAAS,CAAC;AAItE,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAc1C,UAAU,oBAAoB;IAC5B,YAAY,EAAE,YAAY,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,cAAM,aAAa;IACjB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAgB;IACvC,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,YAAY,CAAgB;IAIpC,OAAO,CAAC,UAAU,CAAC,CAAc;IACjC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,YAAY,CAAS;IAE7B,OAAO;WAQO,WAAW,CAAC,OAAO,EAAE,oBAAoB;IAOvD;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,OAAO,CAAC,aAAa;IAOrB,sDAAsD;IACzC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBlC,gBAAgB;IACH,MAAM,CACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAUpC,YAAY,CACjB,UAAU,EAAE,MAAM,EAAE,EACpB,SAAS,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,EACtC,eAAe,CAAC,EAAE,MAAM,CACtB,MAAM,EACN;QACE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CACF;CAyIJ;AASD,eAAO,MAAM,MAAM,eAGjB,CAAC;AAEH,MAAM,MAAM,SAAS,GAAG,GAAG,CAAC"}
|
|
@@ -166,7 +166,28 @@ class BullMQService {
|
|
|
166
166
|
if (this.shuttingDown)
|
|
167
167
|
return;
|
|
168
168
|
this.shuttingDown = true;
|
|
169
|
-
|
|
169
|
+
// Hard backstop: worker.close() waits for the in-flight job's processor
|
|
170
|
+
// to finish, and a video job can legitimately run for the full poll
|
|
171
|
+
// window (~15 min) + finalization. If a job is wedged (e.g. an
|
|
172
|
+
// unforeseen hang — the rate-limiter pile-up that used to cause this is
|
|
173
|
+
// now bounded, see DistributedRateLimiter.waitUntilAvailable), an
|
|
174
|
+
// unbounded close() would hold the old Railway container alive
|
|
175
|
+
// indefinitely on a rolling deploy. Force-exit after a generous bound
|
|
176
|
+
// so a stuck process can never block a deploy. Default 20 min (> the
|
|
177
|
+
// 15-min legit job ceiling); override with SHUTDOWN_HARD_TIMEOUT_MS to
|
|
178
|
+
// match the Railway service's drain/grace window.
|
|
179
|
+
const hardTimeoutMs = Number(process.env.SHUTDOWN_HARD_TIMEOUT_MS) || 20 * 60000;
|
|
180
|
+
const hardExit = setTimeout(() => {
|
|
181
|
+
logger_1.logger.error("Graceful shutdown exceeded hard timeout — force-exiting", {
|
|
182
|
+
hardTimeoutMs,
|
|
183
|
+
activeWorkers: this.workers.length,
|
|
184
|
+
});
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}, hardTimeoutMs);
|
|
187
|
+
// Don't let this timer itself keep the event loop alive once everything
|
|
188
|
+
// else has settled.
|
|
189
|
+
hardExit.unref();
|
|
190
|
+
logger_1.logger.info("Shutting down BullMQ workers gracefully...", { hardTimeoutMs });
|
|
170
191
|
for (const worker of this.workers) {
|
|
171
192
|
await worker.close();
|
|
172
193
|
logger_1.logger.info(`Worker for queue closed`, { queueName: worker.name });
|
|
@@ -180,6 +201,7 @@ class BullMQService {
|
|
|
180
201
|
if (this.connection) {
|
|
181
202
|
await this.connection.quit();
|
|
182
203
|
}
|
|
204
|
+
clearTimeout(hardExit);
|
|
183
205
|
logger_1.logger.info("All workers closed. Exiting process.");
|
|
184
206
|
process.exit(0);
|
|
185
207
|
};
|
package/lib/services/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export * from "./notification.service";
|
|
|
10
10
|
export * from "./credits/pricing";
|
|
11
11
|
export * from "./analytics.service";
|
|
12
12
|
export * from "./tts";
|
|
13
|
+
export * from "./translation";
|
|
13
14
|
export * from "./musicGen";
|
|
14
15
|
export * from "./audioAnalysis";
|
|
15
16
|
export * from "./asr";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,OAAO,CAAC;AACtB,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,OAAO,CAAC;AACtB,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/services/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC;AACpC,cAAc,SAAS,CAAC;AACxB,cAAc,kBAAkB,CAAC;AACjC,cAAc,OAAO,CAAC;AACtB,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,OAAO,CAAC;AACtB,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,cAAc,OAAO,CAAC;AACtB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC"}
|
package/lib/services/index.js
CHANGED
|
@@ -26,6 +26,7 @@ __exportStar(require("./notification.service"), exports);
|
|
|
26
26
|
__exportStar(require("./credits/pricing"), exports);
|
|
27
27
|
__exportStar(require("./analytics.service"), exports);
|
|
28
28
|
__exportStar(require("./tts"), exports);
|
|
29
|
+
__exportStar(require("./translation"), exports);
|
|
29
30
|
__exportStar(require("./musicGen"), exports);
|
|
30
31
|
__exportStar(require("./audioAnalysis"), exports);
|
|
31
32
|
__exportStar(require("./asr"), exports);
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { TAiGenModel } from "../../globals/aiModels";
|
|
2
2
|
import { TtsProvider } from "../../globals/ttsModels/types";
|
|
3
|
+
/**
|
|
4
|
+
* Capacity-planning thresholds (used by previewCapacity + the bounded gate).
|
|
5
|
+
*
|
|
6
|
+
* MAX_ACCEPTABLE_WAIT_MS — a model whose projected wait for a submit slot
|
|
7
|
+
* exceeds this is considered "not available" by previewCapacity, so the
|
|
8
|
+
* job-start selector spills to a less-loaded model in the same tier chain.
|
|
9
|
+
* DAILY_NEAR_RESET_MS — when a model's per-day cap is exhausted, we only wait
|
|
10
|
+
* it out if the UTC-midnight reset is within this window; otherwise the
|
|
11
|
+
* bounded gate fast-fails (VIDEO_PROVIDER_RATE_LIMITED) so the caller can
|
|
12
|
+
* fall back rather than block for hours.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MAX_ACCEPTABLE_WAIT_MS = 180000;
|
|
15
|
+
export declare const DAILY_NEAR_RESET_MS = 120000;
|
|
3
16
|
/**
|
|
4
17
|
* DistributedRateLimiter — three-tier provider quota gate.
|
|
5
18
|
*
|
|
@@ -39,6 +52,29 @@ export interface LimiterSnapshot {
|
|
|
39
52
|
perMinLimit?: number;
|
|
40
53
|
perDayLimit?: number;
|
|
41
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* Read-only capacity estimate for a model — does NOT consume a slot. Used by
|
|
57
|
+
* the job-start model selector to decide whether to spill to another model in
|
|
58
|
+
* the tier chain before triggering. `available` folds the projected-wait and
|
|
59
|
+
* daily-reset thresholds into a single go/no-go.
|
|
60
|
+
*/
|
|
61
|
+
export interface CapacityPreview {
|
|
62
|
+
modelKey: string;
|
|
63
|
+
/** Rough estimate of how long until a NEW submit can proceed, in ms. */
|
|
64
|
+
projectedWaitMs: number;
|
|
65
|
+
/** Whether a new submission can proceed within the acceptable wait window. */
|
|
66
|
+
available: boolean;
|
|
67
|
+
/** Per-day cap is configured and currently exhausted. */
|
|
68
|
+
dailyExhausted: boolean;
|
|
69
|
+
secsUntilDayReset: number;
|
|
70
|
+
perMinUsed: number;
|
|
71
|
+
perMinLimit: number;
|
|
72
|
+
perDayUsed: number;
|
|
73
|
+
perDayLimit: number;
|
|
74
|
+
concurrentActive: number;
|
|
75
|
+
concurrentLimit: number;
|
|
76
|
+
concurrentQueueDepth: number;
|
|
77
|
+
}
|
|
42
78
|
export declare class DistributedRateLimiter {
|
|
43
79
|
private readonly modelKey;
|
|
44
80
|
private readonly concurrentLimit;
|
|
@@ -52,15 +88,34 @@ export declare class DistributedRateLimiter {
|
|
|
52
88
|
* a slot in each. Caller MUST call releaseSlot() when the in-flight task
|
|
53
89
|
* settles — per-min / per-day auto-expire but the concurrent counter does
|
|
54
90
|
* not. Mirrors the old AiGenModelRateLimiter API.
|
|
91
|
+
*
|
|
92
|
+
* `opts.maxWaitMs` bounds the rate-budget wait: if satisfying the per-day /
|
|
93
|
+
* per-minute gate would take longer than this, the call throws a
|
|
94
|
+
* `UserFacingError(VIDEO_PROVIDER_RATE_LIMITED)` instead of sleep-looping for
|
|
95
|
+
* hours. This is the backstop that stops jobs from piling up in-process when
|
|
96
|
+
* a daily cap is hit — the submit path passes it; legacy callers that omit
|
|
97
|
+
* it keep the original (unbounded) behavior.
|
|
55
98
|
*/
|
|
56
|
-
waitUntilAvailable(
|
|
99
|
+
waitUntilAvailable(opts?: {
|
|
100
|
+
maxWaitMs?: number;
|
|
101
|
+
}): Promise<void>;
|
|
57
102
|
/**
|
|
58
|
-
* Bumps per-
|
|
59
|
-
* Use for short-lived calls that share a
|
|
60
|
-
*
|
|
61
|
-
*
|
|
103
|
+
* Bumps per-MINUTE only — does NOT count toward the concurrent cap and does
|
|
104
|
+
* NOT consume/await the per-day gate. Use for short-lived calls that share a
|
|
105
|
+
* provider's per-minute budget but must not be blocked by the daily SUBMIT
|
|
106
|
+
* cap: status polls of already-running tasks (failing a poll on daily quota
|
|
107
|
+
* would abandon a video that's already generating), plus sync image/TTS gens
|
|
108
|
+
* (which have no per-day limit configured anyway). No release needed — Redis
|
|
109
|
+
* auto-expires the counters.
|
|
62
110
|
*/
|
|
63
111
|
waitUntilAvailableForOneShot(): Promise<void>;
|
|
112
|
+
/**
|
|
113
|
+
* Read-only capacity estimate — does NOT consume a slot. Reads current
|
|
114
|
+
* per-minute / per-day usage and concurrency pressure from Redis and
|
|
115
|
+
* projects how long a new submission would wait. Fails open (available:true,
|
|
116
|
+
* wait:0) on Redis errors / no client, matching the gate's fail-open policy.
|
|
117
|
+
*/
|
|
118
|
+
previewCapacity(maxAcceptableWaitMs?: number): Promise<CapacityPreview>;
|
|
64
119
|
/** Free a concurrent slot acquired via waitUntilAvailable(). */
|
|
65
120
|
releaseSlot(): void;
|
|
66
121
|
get activeConcurrentCount(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distributedRateLimiter.service.d.ts","sourceRoot":"","sources":["../../../src/services/rateLimiter/distributedRateLimiter.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"distributedRateLimiter.service.d.ts","sourceRoot":"","sources":["../../../src/services/rateLimiter/distributedRateLimiter.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAkB,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAM5D;;;;;;;;;;GAUG;AACH,eAAO,MAAM,sBAAsB,SAAU,CAAC;AAC9C,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAQ3C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,UAAU,4BAA4B;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,eAAe,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,yDAAyD;IACzD,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AA4DD,qBAAa,sBAAsB;IACjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,SAAS,CAAyB;gBAE9B,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,4BAA4B;IAOlE;;;;;;;;;;;;OAYG;IACG,kBAAkB,CAAC,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtE;;;;;;;;OAQG;IACG,4BAA4B,IAAI,OAAO,CAAC,IAAI,CAAC;IAInD;;;;;OAKG;IACG,eAAe,CACnB,mBAAmB,GAAE,MAA+B,GACnD,OAAO,CAAC,eAAe,CAAC;IAgF3B,gEAAgE;IAChE,WAAW,IAAI,IAAI;IAInB,IAAI,qBAAqB,IAAI,MAAM,CAElC;IAEK,QAAQ,IAAI,OAAO,CAAC,eAAe,CAAC;YAoC5B,iBAAiB;IAc/B,OAAO,CAAC,iBAAiB;YAQX,iBAAiB;CAoHhC;AAwBD,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,WAAW,GACpB,sBAAsB,CA0BxB;AAED,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,WAAW,GACpB,sBAAsB,CAexB;AAED;4DAC4D;AAC5D,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAM1E"}
|