@umituz/web-ai 1.0.1 → 1.0.3
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/cancellation-error-CKcZRhFg.d.ts +129 -0
- package/dist/cancellation-error-DwHrDKBz.d.cts +129 -0
- package/dist/content/index.cjs +914 -37
- package/dist/content/index.cjs.map +1 -1
- package/dist/content/index.d.cts +1 -1
- package/dist/content/index.d.ts +1 -1
- package/dist/content/index.js +914 -37
- package/dist/content/index.js.map +1 -1
- package/dist/core/index.cjs +10 -0
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +13 -3
- package/dist/core/index.d.ts +13 -3
- package/dist/core/index.js +9 -1
- package/dist/core/index.js.map +1 -1
- package/dist/{create-ai-DXEoke8-.d.ts → create-ai-9PW752z8.d.ts} +11 -1
- package/dist/{create-ai-DOI9jD3v.d.cts → create-ai-CXYKPK6G.d.cts} +11 -1
- package/dist/hooks/index.cjs +1192 -39
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +64 -2
- package/dist/hooks/index.d.ts +64 -2
- package/dist/hooks/index.js +1188 -40
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.cjs +1230 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +1201 -38
- package/dist/index.js.map +1 -1
- package/dist/providers/index.cjs +1505 -37
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.d.cts +115 -8
- package/dist/providers/index.d.ts +115 -8
- package/dist/providers/index.js +1478 -38
- package/dist/providers/index.js.map +1 -1
- package/package.json +11 -1
package/dist/hooks/index.cjs
CHANGED
|
@@ -117,6 +117,14 @@ var AIError = class _AIError extends Error {
|
|
|
117
117
|
}
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
+
// src/core/errors/groq-error.ts
|
|
121
|
+
var GroqError = class extends AIError {
|
|
122
|
+
constructor(type, message, cause, metadata = {}) {
|
|
123
|
+
super(type, message, cause, metadata);
|
|
124
|
+
this.name = "GroqError";
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
120
128
|
// src/core/errors/error-classifier.ts
|
|
121
129
|
var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
|
|
122
130
|
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
@@ -1341,6 +1349,27 @@ var RequestDeduplicator = class {
|
|
|
1341
1349
|
}
|
|
1342
1350
|
};
|
|
1343
1351
|
|
|
1352
|
+
// src/core/utils/api-key-validator.ts
|
|
1353
|
+
var DEFAULT_API_KEY_RULE = {
|
|
1354
|
+
minLength: 10
|
|
1355
|
+
};
|
|
1356
|
+
function validateApiKey(apiKey, rule = DEFAULT_API_KEY_RULE) {
|
|
1357
|
+
if (!apiKey) {
|
|
1358
|
+
return { valid: false, reason: "API key is required" };
|
|
1359
|
+
}
|
|
1360
|
+
const trimmed = apiKey.trim();
|
|
1361
|
+
if (trimmed.length === 0) {
|
|
1362
|
+
return { valid: false, reason: "API key cannot be empty" };
|
|
1363
|
+
}
|
|
1364
|
+
if (trimmed.length < rule.minLength) {
|
|
1365
|
+
return {
|
|
1366
|
+
valid: false,
|
|
1367
|
+
reason: `API key must be at least ${rule.minLength} characters`
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1370
|
+
return { valid: true };
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1344
1373
|
// src/providers/groq/groq.constants.ts
|
|
1345
1374
|
var GROQ_MODELS = {
|
|
1346
1375
|
LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
|
|
@@ -1357,11 +1386,13 @@ var DEFAULT_GENERATION_CONFIG = {
|
|
|
1357
1386
|
topP: 1
|
|
1358
1387
|
};
|
|
1359
1388
|
var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
|
|
1389
|
+
var DEFAULT_BASE_URL = GROQ_BASE_URL;
|
|
1360
1390
|
var CHAT_COMPLETIONS_PATH = "/chat/completions";
|
|
1361
1391
|
var GROQ_TIMEOUTS = {
|
|
1362
1392
|
DEFAULT: 3e4,
|
|
1363
1393
|
STREAMING: 6e4
|
|
1364
1394
|
};
|
|
1395
|
+
var TIMEOUTS = GROQ_TIMEOUTS;
|
|
1365
1396
|
var HTTP_HEADERS = {
|
|
1366
1397
|
CONTENT_TYPE: "Content-Type",
|
|
1367
1398
|
APPLICATION_JSON: "application/json",
|
|
@@ -1420,13 +1451,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
|
|
|
1420
1451
|
}
|
|
1421
1452
|
};
|
|
1422
1453
|
|
|
1454
|
+
// src/providers/groq/groq-config-store.ts
|
|
1455
|
+
var GroqConfigStore = class {
|
|
1456
|
+
config = null;
|
|
1457
|
+
initialize(config) {
|
|
1458
|
+
const validation = validateApiKey(config.apiKey, { minLength: 1 });
|
|
1459
|
+
if (!validation.valid) {
|
|
1460
|
+
throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
|
|
1461
|
+
}
|
|
1462
|
+
const normalized = {
|
|
1463
|
+
apiKey: config.apiKey.trim(),
|
|
1464
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
1465
|
+
timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
|
|
1466
|
+
...config.models !== void 0 ? { models: config.models } : {},
|
|
1467
|
+
...config.textModel !== void 0 ? { textModel: config.textModel } : {}
|
|
1468
|
+
};
|
|
1469
|
+
this.config = normalized;
|
|
1470
|
+
return normalized;
|
|
1471
|
+
}
|
|
1472
|
+
get() {
|
|
1473
|
+
if (!this.config) {
|
|
1474
|
+
throw new GroqError(
|
|
1475
|
+
"MISSING_CONFIG" /* MISSING_CONFIG */,
|
|
1476
|
+
"Client not initialized. Call configureProvider first."
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
return this.config;
|
|
1480
|
+
}
|
|
1481
|
+
getOrNull() {
|
|
1482
|
+
return this.config;
|
|
1483
|
+
}
|
|
1484
|
+
isInitialized() {
|
|
1485
|
+
return this.config !== null;
|
|
1486
|
+
}
|
|
1487
|
+
setApiKey(apiKey) {
|
|
1488
|
+
const current = this.get();
|
|
1489
|
+
const validation = validateApiKey(apiKey, { minLength: 1 });
|
|
1490
|
+
if (!validation.valid) {
|
|
1491
|
+
throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
|
|
1492
|
+
}
|
|
1493
|
+
this.config = { ...current, apiKey: apiKey.trim() };
|
|
1494
|
+
}
|
|
1495
|
+
reset() {
|
|
1496
|
+
this.config = null;
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
|
|
1423
1500
|
// src/providers/groq/groq-http-transport.ts
|
|
1424
1501
|
var GroqHttpTransport = class {
|
|
1425
|
-
constructor(
|
|
1426
|
-
this.
|
|
1502
|
+
constructor(configSource) {
|
|
1503
|
+
this.configSource = configSource;
|
|
1427
1504
|
}
|
|
1428
|
-
|
|
1505
|
+
configSource;
|
|
1429
1506
|
pendingAbortControllers = /* @__PURE__ */ new Set();
|
|
1507
|
+
getConfig() {
|
|
1508
|
+
if ("get" in this.configSource && typeof this.configSource.get === "function") {
|
|
1509
|
+
return this.configSource.get();
|
|
1510
|
+
}
|
|
1511
|
+
return this.configSource;
|
|
1512
|
+
}
|
|
1430
1513
|
cancelPendingRequests() {
|
|
1431
1514
|
for (const controller of this.pendingAbortControllers) {
|
|
1432
1515
|
controller.abort();
|
|
@@ -1441,7 +1524,7 @@ var GroqHttpTransport = class {
|
|
|
1441
1524
|
}
|
|
1442
1525
|
async streamChatCompletion(request, options = {}) {
|
|
1443
1526
|
const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
|
|
1444
|
-
const timeout = this.
|
|
1527
|
+
const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
|
|
1445
1528
|
const controller = new AbortController();
|
|
1446
1529
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1447
1530
|
const onExternalAbort = () => controller.abort();
|
|
@@ -1468,11 +1551,11 @@ var GroqHttpTransport = class {
|
|
|
1468
1551
|
}
|
|
1469
1552
|
}
|
|
1470
1553
|
baseUrl() {
|
|
1471
|
-
return this.
|
|
1554
|
+
return this.getConfig().baseUrl ?? GROQ_BASE_URL;
|
|
1472
1555
|
}
|
|
1473
1556
|
async request(endpoint, body) {
|
|
1474
1557
|
const url = `${this.baseUrl()}${endpoint}`;
|
|
1475
|
-
const timeout = this.
|
|
1558
|
+
const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
|
|
1476
1559
|
const controller = new AbortController();
|
|
1477
1560
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1478
1561
|
this.pendingAbortControllers.add(controller);
|
|
@@ -1495,7 +1578,7 @@ var GroqHttpTransport = class {
|
|
|
1495
1578
|
buildHeaders() {
|
|
1496
1579
|
return {
|
|
1497
1580
|
[HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
|
|
1498
|
-
[HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.
|
|
1581
|
+
[HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
|
|
1499
1582
|
};
|
|
1500
1583
|
}
|
|
1501
1584
|
async throwFromErrorResponse(response) {
|
|
@@ -1597,21 +1680,40 @@ var CompositeExecutor = class {
|
|
|
1597
1680
|
|
|
1598
1681
|
// src/providers/groq/groq-http-client.ts
|
|
1599
1682
|
var GroqHttpClient = class {
|
|
1683
|
+
configStore;
|
|
1600
1684
|
transport;
|
|
1601
1685
|
executor;
|
|
1602
|
-
constructor(
|
|
1603
|
-
|
|
1686
|
+
constructor(configOrDeps, maybeDeps = {}) {
|
|
1687
|
+
const isConfig = typeof configOrDeps === "object" && configOrDeps !== null && "apiKey" in configOrDeps && typeof configOrDeps.apiKey === "string";
|
|
1688
|
+
const deps = isConfig ? maybeDeps : configOrDeps ?? {};
|
|
1689
|
+
this.configStore = deps.configStore ?? new GroqConfigStore();
|
|
1690
|
+
if (isConfig) {
|
|
1691
|
+
this.configStore.initialize(configOrDeps);
|
|
1692
|
+
}
|
|
1693
|
+
this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
|
|
1604
1694
|
const retryManager2 = deps.retryManager ?? new RetryManager();
|
|
1605
1695
|
const queue = deps.queue ?? new RequestQueue();
|
|
1606
1696
|
const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
|
|
1607
1697
|
this.executor = new CompositeExecutor([
|
|
1608
1698
|
new DedupeExecutor(deduplicator, {
|
|
1609
|
-
generateKey: (request) => deduplicatorKey(request,
|
|
1699
|
+
generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
|
|
1610
1700
|
}),
|
|
1611
1701
|
new RetryExecutor(retryManager2),
|
|
1612
1702
|
new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
|
|
1613
1703
|
]);
|
|
1614
1704
|
}
|
|
1705
|
+
initialize(config) {
|
|
1706
|
+
this.configStore.initialize(config);
|
|
1707
|
+
}
|
|
1708
|
+
isInitialized() {
|
|
1709
|
+
return this.configStore.isInitialized();
|
|
1710
|
+
}
|
|
1711
|
+
getApiKey() {
|
|
1712
|
+
return this.configStore.get().apiKey;
|
|
1713
|
+
}
|
|
1714
|
+
setApiKey(apiKey) {
|
|
1715
|
+
this.configStore.setApiKey(apiKey);
|
|
1716
|
+
}
|
|
1615
1717
|
cancelPendingRequests() {
|
|
1616
1718
|
this.transport.cancelPendingRequests();
|
|
1617
1719
|
}
|
|
@@ -1624,6 +1726,10 @@ var GroqHttpClient = class {
|
|
|
1624
1726
|
streamChatCompletion(request, options) {
|
|
1625
1727
|
return this.transport.streamChatCompletion(request, options);
|
|
1626
1728
|
}
|
|
1729
|
+
reset() {
|
|
1730
|
+
this.transport.cancelPendingRequests();
|
|
1731
|
+
this.configStore.reset();
|
|
1732
|
+
}
|
|
1627
1733
|
};
|
|
1628
1734
|
function deduplicatorKey(request, apiKey) {
|
|
1629
1735
|
const { model, messages, stream: _stream, ...rest } = request;
|
|
@@ -2521,39 +2627,814 @@ var FalProvider = class extends BaseProvider {
|
|
|
2521
2627
|
}
|
|
2522
2628
|
};
|
|
2523
2629
|
|
|
2524
|
-
// src/providers/pruna/
|
|
2525
|
-
var
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2630
|
+
// src/providers/pruna/client/core/constants/limits.ts
|
|
2631
|
+
var DEFAULT_ASPECT_RATIO = "16:9";
|
|
2632
|
+
var P_VIDEO_DEFAULTS = {
|
|
2633
|
+
duration: 5,
|
|
2634
|
+
resolution: "720p",
|
|
2635
|
+
fps: 24,
|
|
2636
|
+
draft: false,
|
|
2637
|
+
promptUpsampling: true
|
|
2638
|
+
};
|
|
2639
|
+
var POLL_DEFAULTS = {
|
|
2640
|
+
intervalMs: 3e3,
|
|
2641
|
+
maxAttempts: 120,
|
|
2642
|
+
maxIntervalMs: 1e4,
|
|
2643
|
+
backoffBase: 1.5,
|
|
2644
|
+
jitterPercent: 0.1
|
|
2645
|
+
};
|
|
2646
|
+
var DEFAULT_DEDUPLICATOR_TTL_MS = 3e4;
|
|
2647
|
+
var DEDUPLICATOR_CLEANUP_GRACE_MS = 100;
|
|
2648
|
+
|
|
2649
|
+
// src/providers/pruna/client/core/constants/network.ts
|
|
2650
|
+
var HTTPS_PREFIX = "https://";
|
|
2651
|
+
var HTTP_PREFIX = "http://";
|
|
2652
|
+
var BASE64_MARKER = "base64,";
|
|
2653
|
+
var PRUNA_BASE_URL = "https://api.pruna.ai";
|
|
2654
|
+
var PRUNA_PREDICTIONS_URL = `${PRUNA_BASE_URL}/v1/predictions`;
|
|
2655
|
+
var PRUNA_FILES_URL = `${PRUNA_BASE_URL}/v1/files`;
|
|
2656
|
+
var AUTH_FAILURE_STATUSES = [401, 403];
|
|
2657
|
+
var MIN_API_KEY_LENGTH = 1;
|
|
2658
|
+
var MAX_API_KEY_LENGTH = 256;
|
|
2659
|
+
|
|
2660
|
+
// src/providers/pruna/client/core/constants/mime-types.ts
|
|
2661
|
+
var MIME_PNG = "image/png";
|
|
2662
|
+
var MIME_JPEG = "image/jpeg";
|
|
2663
|
+
var MIME_WEBP = "image/webp";
|
|
2664
|
+
var DEFAULT_IMAGE_MIME = MIME_PNG;
|
|
2665
|
+
var MIME_FILE_EXTENSION = {
|
|
2666
|
+
[MIME_PNG]: "png",
|
|
2667
|
+
[MIME_JPEG]: "jpg",
|
|
2668
|
+
[MIME_WEBP]: "webp"
|
|
2669
|
+
};
|
|
2670
|
+
var IMAGE_SIGNATURES = {
|
|
2671
|
+
PNG: [137, 80],
|
|
2672
|
+
JPEG: [255, 216],
|
|
2673
|
+
WEBP_RIFF: [82, 73]
|
|
2674
|
+
};
|
|
2675
|
+
|
|
2676
|
+
// src/providers/pruna/client/core/errors/cancellation-error.ts
|
|
2677
|
+
var CANCELLATION_MESSAGE = "Request cancelled by user";
|
|
2678
|
+
var CancellationError = class extends Error {
|
|
2679
|
+
constructor(message = CANCELLATION_MESSAGE) {
|
|
2680
|
+
super(message);
|
|
2681
|
+
this.name = "CancellationError";
|
|
2530
2682
|
}
|
|
2531
|
-
|
|
2683
|
+
};
|
|
2684
|
+
function isCancellationError(value) {
|
|
2685
|
+
return value instanceof CancellationError;
|
|
2532
2686
|
}
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2687
|
+
|
|
2688
|
+
// src/providers/pruna/client/core/errors/authentication-error.ts
|
|
2689
|
+
var AUTHENTICATION_MESSAGE = "Authentication failed. Please check your API key.";
|
|
2690
|
+
var AuthenticationError = class extends Error {
|
|
2691
|
+
constructor(message = AUTHENTICATION_MESSAGE) {
|
|
2692
|
+
super(message);
|
|
2693
|
+
this.name = "AuthenticationError";
|
|
2694
|
+
}
|
|
2695
|
+
};
|
|
2696
|
+
|
|
2697
|
+
// src/providers/pruna/client/core/errors/timeout-error.ts
|
|
2698
|
+
var TIMEOUT_MESSAGE = "Generation timed out. Maximum polling attempts reached.";
|
|
2699
|
+
var TimeoutError = class extends Error {
|
|
2700
|
+
constructor(message = TIMEOUT_MESSAGE) {
|
|
2701
|
+
super(message);
|
|
2702
|
+
this.name = "TimeoutError";
|
|
2703
|
+
}
|
|
2704
|
+
};
|
|
2705
|
+
|
|
2706
|
+
// src/providers/pruna/client/core/errors/validation-error.ts
|
|
2707
|
+
var ValidationError2 = class extends Error {
|
|
2708
|
+
field;
|
|
2709
|
+
reason;
|
|
2710
|
+
receivedValue;
|
|
2711
|
+
constructor(context) {
|
|
2712
|
+
super(`Invalid ${context.field}: ${context.reason}`);
|
|
2713
|
+
this.name = "ValidationError";
|
|
2714
|
+
this.field = context.field;
|
|
2715
|
+
this.reason = context.reason;
|
|
2716
|
+
this.receivedValue = context.receivedValue;
|
|
2717
|
+
}
|
|
2718
|
+
};
|
|
2719
|
+
|
|
2720
|
+
// src/providers/pruna/client/core/utils/base64.ts
|
|
2721
|
+
var globalScope = globalThis;
|
|
2722
|
+
var hasBuffer = typeof globalScope.Buffer?.from === "function";
|
|
2723
|
+
function base64ToBytes(raw) {
|
|
2724
|
+
if (hasBuffer) {
|
|
2725
|
+
const bufferFactory = globalScope.Buffer;
|
|
2726
|
+
if (!bufferFactory) {
|
|
2727
|
+
throw new Error("Buffer unavailable despite detection flag");
|
|
2728
|
+
}
|
|
2729
|
+
const buf = bufferFactory.from(raw, "base64");
|
|
2730
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
2731
|
+
}
|
|
2732
|
+
const decoded = atob(raw);
|
|
2733
|
+
return Uint8Array.from(decoded, (char) => char.charCodeAt(0));
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
// src/providers/pruna/client/core/utils/mime-detector.ts
|
|
2737
|
+
var MIN_SIGNATURE_BYTES = 2;
|
|
2738
|
+
function matchesSignature(bytes, signature) {
|
|
2739
|
+
if (bytes.length < signature.length) return false;
|
|
2740
|
+
return signature.every((value, index) => bytes[index] === value);
|
|
2741
|
+
}
|
|
2742
|
+
function detectImageMime(bytes) {
|
|
2743
|
+
if (bytes.length < MIN_SIGNATURE_BYTES) return DEFAULT_IMAGE_MIME;
|
|
2744
|
+
if (matchesSignature(bytes, IMAGE_SIGNATURES.PNG)) return MIME_PNG;
|
|
2745
|
+
if (matchesSignature(bytes, IMAGE_SIGNATURES.JPEG)) return MIME_JPEG;
|
|
2746
|
+
if (matchesSignature(bytes, IMAGE_SIGNATURES.WEBP_RIFF)) return MIME_WEBP;
|
|
2747
|
+
return DEFAULT_IMAGE_MIME;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
// src/providers/pruna/client/core/utils/api-error-parser.ts
|
|
2751
|
+
async function parseApiError(response, fallback) {
|
|
2752
|
+
const payload = await response.json().catch(() => ({ message: response.statusText }));
|
|
2753
|
+
const message = payload.message ?? `${fallback}: ${response.status}`;
|
|
2754
|
+
const error = new Error(message);
|
|
2755
|
+
error.statusCode = response.status;
|
|
2756
|
+
return error;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
// src/providers/pruna/client/core/protocol/http-status.ts
|
|
2760
|
+
function isAuthFailureStatus(status) {
|
|
2761
|
+
return AUTH_FAILURE_STATUSES.includes(status);
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
// src/providers/pruna/client/core/protocol/job-status.ts
|
|
2765
|
+
var TERMINAL_SUCCESS_STATUSES = ["succeeded", "completed"];
|
|
2766
|
+
var TERMINAL_FAILURE_STATUSES = ["failed"];
|
|
2767
|
+
function isTerminalSuccessStatus(status) {
|
|
2768
|
+
if (!status) return false;
|
|
2769
|
+
return TERMINAL_SUCCESS_STATUSES.includes(status);
|
|
2770
|
+
}
|
|
2771
|
+
function isTerminalFailureStatus(status) {
|
|
2772
|
+
if (!status) return false;
|
|
2773
|
+
return TERMINAL_FAILURE_STATUSES.includes(status);
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// src/providers/pruna/client/core/protocol/abort-signal-classifier.ts
|
|
2777
|
+
function isAbortError(err) {
|
|
2778
|
+
if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true;
|
|
2779
|
+
if (err instanceof Error && err.name === "AbortError") return true;
|
|
2780
|
+
return false;
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
// src/providers/pruna/client/core/protocol/pruna-url-builder.ts
|
|
2784
|
+
function isAbsoluteHttpUrl(value) {
|
|
2785
|
+
return value.startsWith(HTTPS_PREFIX) || value.startsWith(HTTP_PREFIX);
|
|
2786
|
+
}
|
|
2787
|
+
function isHttpsUrl(value) {
|
|
2788
|
+
return value.startsWith(HTTPS_PREFIX);
|
|
2789
|
+
}
|
|
2790
|
+
function buildPrunaApiUrl(pathOrUrl) {
|
|
2791
|
+
if (isAbsoluteHttpUrl(pathOrUrl)) return pathOrUrl;
|
|
2792
|
+
return `${PRUNA_BASE_URL}${pathOrUrl}`;
|
|
2793
|
+
}
|
|
2794
|
+
function buildFileUrlById(fileId) {
|
|
2795
|
+
return `${PRUNA_FILES_URL}/${fileId}`;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
// src/providers/pruna/client/core/math/exponential-backoff.ts
|
|
2799
|
+
function calculateExponentialBackoff2(inputs) {
|
|
2800
|
+
const {
|
|
2801
|
+
attempt,
|
|
2802
|
+
baseIntervalMs,
|
|
2803
|
+
maxIntervalMs = POLL_DEFAULTS.maxIntervalMs,
|
|
2804
|
+
jitterPercent = POLL_DEFAULTS.jitterPercent,
|
|
2805
|
+
growthBase = POLL_DEFAULTS.backoffBase
|
|
2806
|
+
} = inputs;
|
|
2807
|
+
const safeAttempt = Math.max(0, attempt);
|
|
2808
|
+
const safeBase = Math.max(0, baseIntervalMs);
|
|
2809
|
+
const exponentialDelay = Math.min(safeBase * Math.pow(growthBase, safeAttempt), maxIntervalMs);
|
|
2810
|
+
const jitter = exponentialDelay * jitterPercent * Math.random();
|
|
2811
|
+
return exponentialDelay + jitter;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
// src/providers/pruna/client/core/utils/uri-resolver.ts
|
|
2815
|
+
var uriCache = /* @__PURE__ */ new WeakMap();
|
|
2816
|
+
function stripBase64Prefix(image) {
|
|
2817
|
+
if (isHttpsUrl(image)) return image;
|
|
2818
|
+
const base64Index = image.indexOf(BASE64_MARKER);
|
|
2819
|
+
return base64Index !== -1 ? image.slice(base64Index + BASE64_MARKER.length) : image;
|
|
2820
|
+
}
|
|
2821
|
+
function readOutput(output) {
|
|
2822
|
+
if (typeof output === "string") return output;
|
|
2823
|
+
if (Array.isArray(output) && output.length > 0) return output[0] ?? null;
|
|
2824
|
+
if (output !== null && typeof output === "object" && "url" in output) {
|
|
2825
|
+
return output.url;
|
|
2826
|
+
}
|
|
2827
|
+
return null;
|
|
2828
|
+
}
|
|
2829
|
+
function extractUri(data) {
|
|
2830
|
+
const cached = uriCache.get(data);
|
|
2831
|
+
if (cached !== void 0) return cached;
|
|
2832
|
+
if (!isTerminalSuccessStatus(data.status)) {
|
|
2833
|
+
uriCache.set(data, null);
|
|
2834
|
+
return null;
|
|
2835
|
+
}
|
|
2836
|
+
const result = data.generation_url ?? readOutput(data.output) ?? data.video_url ?? data.data ?? null;
|
|
2837
|
+
uriCache.set(data, result);
|
|
2838
|
+
return result;
|
|
2839
|
+
}
|
|
2840
|
+
function resolveUri(uri) {
|
|
2841
|
+
return uri.startsWith("/") ? `${PRUNA_BASE_URL}${uri}` : uri;
|
|
2842
|
+
}
|
|
2843
|
+
|
|
2844
|
+
// src/providers/pruna/client/core/services/pruna-client.service.ts
|
|
2845
|
+
function buildFileExtension(mime) {
|
|
2846
|
+
return MIME_FILE_EXTENSION[mime] ?? "bin";
|
|
2847
|
+
}
|
|
2848
|
+
async function uploadImage(base64OrUrl, apiKey, onProgress) {
|
|
2849
|
+
if (isHttpsUrl(base64OrUrl)) return base64OrUrl;
|
|
2850
|
+
if (base64OrUrl.startsWith(HTTP_PREFIX)) {
|
|
2851
|
+
throw new Error("Only HTTPS URLs are supported for security.");
|
|
2852
|
+
}
|
|
2853
|
+
onProgress?.("uploading");
|
|
2854
|
+
const raw = stripBase64Prefix(base64OrUrl);
|
|
2855
|
+
let bytes;
|
|
2856
|
+
try {
|
|
2857
|
+
bytes = base64ToBytes(raw);
|
|
2858
|
+
} catch {
|
|
2859
|
+
throw new Error("Invalid image format. Provide base64 or a valid HTTPS URL.");
|
|
2860
|
+
}
|
|
2861
|
+
const mime = detectImageMime(bytes);
|
|
2862
|
+
const buffer = bytes.buffer.slice(
|
|
2863
|
+
bytes.byteOffset,
|
|
2864
|
+
bytes.byteOffset + bytes.byteLength
|
|
2865
|
+
);
|
|
2866
|
+
const blob = new Blob([buffer], { type: mime });
|
|
2867
|
+
const formData = new FormData();
|
|
2868
|
+
formData.append("content", blob, `upload.${buildFileExtension(mime)}`);
|
|
2869
|
+
const res = await fetch(PRUNA_FILES_URL, {
|
|
2870
|
+
method: "POST",
|
|
2871
|
+
headers: { apikey: apiKey },
|
|
2872
|
+
body: formData
|
|
2873
|
+
});
|
|
2874
|
+
if (!res.ok) {
|
|
2875
|
+
throw await parseApiError(res, "File upload error");
|
|
2876
|
+
}
|
|
2877
|
+
const data = await res.json();
|
|
2878
|
+
return data.urls?.get ?? buildFileUrlById(data.id ?? "");
|
|
2879
|
+
}
|
|
2880
|
+
async function submitPrediction(model, input, apiKey, signal, onProgress) {
|
|
2881
|
+
onProgress?.("predicting");
|
|
2882
|
+
const res = await fetch(PRUNA_PREDICTIONS_URL, {
|
|
2883
|
+
method: "POST",
|
|
2884
|
+
headers: {
|
|
2885
|
+
apikey: apiKey,
|
|
2886
|
+
Model: model,
|
|
2887
|
+
"Try-Sync": "true",
|
|
2888
|
+
"Content-Type": "application/json"
|
|
2889
|
+
},
|
|
2890
|
+
body: JSON.stringify({ input }),
|
|
2891
|
+
signal
|
|
2892
|
+
});
|
|
2893
|
+
if (!res.ok) {
|
|
2894
|
+
throw await parseApiError(res, "API error");
|
|
2895
|
+
}
|
|
2896
|
+
return res.json();
|
|
2897
|
+
}
|
|
2898
|
+
var GenerationFailedError = class extends Error {
|
|
2899
|
+
constructor(message) {
|
|
2900
|
+
super(message);
|
|
2901
|
+
this.name = "GenerationFailedError";
|
|
2902
|
+
}
|
|
2903
|
+
};
|
|
2904
|
+
async function attemptPoll(fullUrl, apiKey, signal) {
|
|
2905
|
+
let res;
|
|
2906
|
+
try {
|
|
2907
|
+
res = await fetch(fullUrl, { headers: { apikey: apiKey }, signal });
|
|
2908
|
+
} catch (err) {
|
|
2909
|
+
if (isAbortError(err)) throw new CancellationError();
|
|
2910
|
+
return { outcome: "transient-failure" };
|
|
2911
|
+
}
|
|
2912
|
+
if (!res.ok) {
|
|
2913
|
+
if (isAuthFailureStatus(res.status)) throw new AuthenticationError();
|
|
2914
|
+
return { outcome: "transient-failure" };
|
|
2915
|
+
}
|
|
2916
|
+
let data;
|
|
2917
|
+
try {
|
|
2918
|
+
data = await res.json();
|
|
2919
|
+
} catch {
|
|
2920
|
+
return { outcome: "transient-failure" };
|
|
2921
|
+
}
|
|
2922
|
+
if (isTerminalSuccessStatus(data.status)) {
|
|
2923
|
+
const uri = extractUri(data);
|
|
2924
|
+
if (uri) return { outcome: "succeeded", uri: resolveUri(uri) };
|
|
2925
|
+
return { outcome: "transient-failure" };
|
|
2926
|
+
}
|
|
2927
|
+
if (isTerminalFailureStatus(data.status)) {
|
|
2928
|
+
throw new GenerationFailedError(data.error ?? "Generation failed during processing.");
|
|
2929
|
+
}
|
|
2930
|
+
return { outcome: "continue" };
|
|
2931
|
+
}
|
|
2932
|
+
async function pollForResult(pollUrl, apiKey, maxAttempts = POLL_DEFAULTS.maxAttempts, intervalMs = POLL_DEFAULTS.intervalMs, signal, onProgress) {
|
|
2933
|
+
const fullUrl = isAbsoluteHttpUrl(pollUrl) ? pollUrl : buildPrunaApiUrl(pollUrl);
|
|
2934
|
+
const controller = new AbortController();
|
|
2935
|
+
const onExternalAbort = () => controller.abort();
|
|
2936
|
+
signal?.addEventListener("abort", onExternalAbort);
|
|
2937
|
+
try {
|
|
2938
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
2939
|
+
if (signal?.aborted) throw new CancellationError();
|
|
2940
|
+
const delay = calculateExponentialBackoff2({ attempt: i, baseIntervalMs: intervalMs });
|
|
2941
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
2942
|
+
if (signal?.aborted) throw new CancellationError();
|
|
2943
|
+
onProgress?.("polling", i + 1);
|
|
2944
|
+
const result = await attemptPoll(fullUrl, apiKey, controller.signal);
|
|
2945
|
+
if (result.outcome === "succeeded" && result.uri) {
|
|
2946
|
+
return result.uri;
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
throw new TimeoutError();
|
|
2950
|
+
} finally {
|
|
2951
|
+
signal?.removeEventListener("abort", onExternalAbort);
|
|
2952
|
+
controller.abort();
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2955
|
+
|
|
2956
|
+
// src/providers/pruna/client/core/math/comparison-hash.ts
|
|
2957
|
+
function sortKeysDeep(value) {
|
|
2958
|
+
if (Array.isArray(value)) {
|
|
2959
|
+
return value.map(sortKeysDeep);
|
|
2960
|
+
}
|
|
2961
|
+
if (value !== null && typeof value === "object") {
|
|
2962
|
+
const record = value;
|
|
2963
|
+
return Object.keys(record).sort().reduce((acc, key) => {
|
|
2964
|
+
acc[key] = sortKeysDeep(record[key]);
|
|
2965
|
+
return acc;
|
|
2966
|
+
}, {});
|
|
2967
|
+
}
|
|
2968
|
+
return value;
|
|
2969
|
+
}
|
|
2970
|
+
function hashValue(value) {
|
|
2971
|
+
if (value === null || typeof value !== "object") {
|
|
2972
|
+
return String(value);
|
|
2973
|
+
}
|
|
2974
|
+
try {
|
|
2975
|
+
return JSON.stringify(sortKeysDeep(value));
|
|
2976
|
+
} catch {
|
|
2977
|
+
return String(value);
|
|
2978
|
+
}
|
|
2979
|
+
}
|
|
2980
|
+
function compositeHash(...values) {
|
|
2981
|
+
return values.map(hashValue).join("|");
|
|
2982
|
+
}
|
|
2983
|
+
|
|
2984
|
+
// src/providers/pruna/client/core/math/cache-key-derivation.ts
|
|
2985
|
+
function deriveGenerationCacheKey(apiKey, input) {
|
|
2986
|
+
return `${apiKey}:${compositeHash(input)}`;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
// src/providers/pruna/client/core/validation/api-key-validator.ts
|
|
2990
|
+
function assertValidApiKey(apiKey) {
|
|
2991
|
+
if (typeof apiKey !== "string") {
|
|
2992
|
+
throw new ValidationError2({
|
|
2993
|
+
field: "apiKey",
|
|
2994
|
+
reason: "must be a string",
|
|
2995
|
+
receivedValue: typeof apiKey
|
|
2996
|
+
});
|
|
2997
|
+
}
|
|
2998
|
+
if (apiKey.length < MIN_API_KEY_LENGTH) {
|
|
2999
|
+
throw new ValidationError2({
|
|
3000
|
+
field: "apiKey",
|
|
3001
|
+
reason: "must not be empty",
|
|
3002
|
+
receivedValue: apiKey
|
|
3003
|
+
});
|
|
3004
|
+
}
|
|
3005
|
+
if (apiKey.length > MAX_API_KEY_LENGTH) {
|
|
3006
|
+
throw new ValidationError2({
|
|
3007
|
+
field: "apiKey",
|
|
3008
|
+
reason: `exceeds maximum length of ${MAX_API_KEY_LENGTH}`,
|
|
3009
|
+
receivedValue: apiKey.length
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
|
|
3014
|
+
// src/providers/pruna/client/core/validation/image-source-validator.ts
|
|
3015
|
+
function classifyImageSource(source) {
|
|
3016
|
+
if (source.startsWith(HTTPS_PREFIX)) return "https-url";
|
|
3017
|
+
if (source.startsWith(HTTP_PREFIX)) return "http-url";
|
|
3018
|
+
if (source.includes(BASE64_MARKER) || /^[A-Za-z0-9+/=]+$/.test(source)) return "base64";
|
|
3019
|
+
return "unknown";
|
|
3020
|
+
}
|
|
3021
|
+
function assertSecureImageSource(image, fieldName = "image") {
|
|
3022
|
+
if (typeof image !== "string") {
|
|
3023
|
+
throw new ValidationError2({
|
|
3024
|
+
field: fieldName,
|
|
3025
|
+
reason: "must be a string",
|
|
3026
|
+
receivedValue: typeof image
|
|
3027
|
+
});
|
|
3028
|
+
}
|
|
3029
|
+
const kind = classifyImageSource(image);
|
|
3030
|
+
if (kind === "http-url") {
|
|
3031
|
+
throw new ValidationError2({
|
|
3032
|
+
field: fieldName,
|
|
3033
|
+
reason: "HTTP URLs are not supported; use HTTPS or a base64 data URI",
|
|
3034
|
+
receivedValue: image
|
|
3035
|
+
});
|
|
3036
|
+
}
|
|
3037
|
+
if (kind === "unknown") {
|
|
3038
|
+
throw new ValidationError2({
|
|
3039
|
+
field: fieldName,
|
|
3040
|
+
reason: "must be an HTTPS URL or a base64 data URI",
|
|
3041
|
+
receivedValue: typeof image
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
|
|
3046
|
+
// src/providers/pruna/client/core/validation/aspect-ratio-validator.ts
|
|
3047
|
+
var SUPPORTED_ASPECT_RATIOS = [
|
|
3048
|
+
"16:9",
|
|
3049
|
+
"9:16",
|
|
3050
|
+
"1:1",
|
|
3051
|
+
"4:3",
|
|
3052
|
+
"3:4",
|
|
3053
|
+
"3:2",
|
|
3054
|
+
"2:3"
|
|
3055
|
+
];
|
|
3056
|
+
function isSupportedAspectRatio(value) {
|
|
3057
|
+
return typeof value === "string" && SUPPORTED_ASPECT_RATIOS.includes(value);
|
|
3058
|
+
}
|
|
3059
|
+
function assertValidAspectRatio(value, fieldName = "aspect_ratio") {
|
|
3060
|
+
if (value === void 0 || value === null) return;
|
|
3061
|
+
if (!isSupportedAspectRatio(value)) {
|
|
3062
|
+
throw new ValidationError2({
|
|
3063
|
+
field: fieldName,
|
|
3064
|
+
reason: `must be one of ${SUPPORTED_ASPECT_RATIOS.join(", ")}`,
|
|
3065
|
+
receivedValue: value
|
|
3066
|
+
});
|
|
3067
|
+
}
|
|
3068
|
+
}
|
|
3069
|
+
|
|
3070
|
+
// src/providers/pruna/client/core/validation/resolution-validator.ts
|
|
3071
|
+
var SUPPORTED_RESOLUTIONS = ["720p", "1080p"];
|
|
3072
|
+
function isSupportedResolution(value) {
|
|
3073
|
+
return typeof value === "string" && SUPPORTED_RESOLUTIONS.includes(value);
|
|
3074
|
+
}
|
|
3075
|
+
function assertValidResolution(value, fieldName = "resolution") {
|
|
3076
|
+
if (value === void 0 || value === null) return;
|
|
3077
|
+
if (!isSupportedResolution(value)) {
|
|
3078
|
+
throw new ValidationError2({
|
|
3079
|
+
field: fieldName,
|
|
3080
|
+
reason: `must be one of ${SUPPORTED_RESOLUTIONS.join(", ")}`,
|
|
3081
|
+
receivedValue: value
|
|
3082
|
+
});
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
|
|
3086
|
+
// src/providers/pruna/client/core/validation/duration-validator.ts
|
|
3087
|
+
var MIN_VIDEO_DURATION_SECONDS = 1;
|
|
3088
|
+
var MAX_VIDEO_DURATION_SECONDS = 30;
|
|
3089
|
+
function isValidDuration(value) {
|
|
3090
|
+
return typeof value === "number" && Number.isFinite(value) && value >= MIN_VIDEO_DURATION_SECONDS && value <= MAX_VIDEO_DURATION_SECONDS && Number.isInteger(value);
|
|
3091
|
+
}
|
|
3092
|
+
function assertValidDuration(value, fieldName = "duration") {
|
|
3093
|
+
if (value === void 0 || value === null) return;
|
|
3094
|
+
if (!isValidDuration(value)) {
|
|
3095
|
+
throw new ValidationError2({
|
|
3096
|
+
field: fieldName,
|
|
3097
|
+
reason: `must be an integer between ${MIN_VIDEO_DURATION_SECONDS} and ${MAX_VIDEO_DURATION_SECONDS} seconds`,
|
|
3098
|
+
receivedValue: value
|
|
3099
|
+
});
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
|
|
3103
|
+
// src/providers/pruna/client/core/validation/prompt-validator.ts
|
|
3104
|
+
var MIN_PROMPT_LENGTH = 1;
|
|
3105
|
+
var MAX_PROMPT_LENGTH = 4e3;
|
|
3106
|
+
function assertValidPrompt(value, fieldName = "prompt") {
|
|
3107
|
+
if (typeof value !== "string") {
|
|
3108
|
+
throw new ValidationError2({
|
|
3109
|
+
field: fieldName,
|
|
3110
|
+
reason: "must be a string",
|
|
3111
|
+
receivedValue: typeof value
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
if (value.trim().length < MIN_PROMPT_LENGTH) {
|
|
3115
|
+
throw new ValidationError2({
|
|
3116
|
+
field: fieldName,
|
|
3117
|
+
reason: "must not be empty or whitespace-only",
|
|
3118
|
+
receivedValue: value
|
|
3119
|
+
});
|
|
3120
|
+
}
|
|
3121
|
+
if (value.length > MAX_PROMPT_LENGTH) {
|
|
3122
|
+
throw new ValidationError2({
|
|
3123
|
+
field: fieldName,
|
|
3124
|
+
reason: `exceeds maximum length of ${MAX_PROMPT_LENGTH} characters`,
|
|
3125
|
+
receivedValue: value.length
|
|
3126
|
+
});
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
// src/providers/pruna/client/core/validation/seed-validator.ts
|
|
3131
|
+
var MIN_SEED = -2147483648;
|
|
3132
|
+
var MAX_SEED = 2147483647;
|
|
3133
|
+
function isValidSeed(value) {
|
|
3134
|
+
return typeof value === "number" && Number.isFinite(value) && Number.isInteger(value) && value >= MIN_SEED && value <= MAX_SEED;
|
|
3135
|
+
}
|
|
3136
|
+
function assertValidSeed(value, fieldName = "seed") {
|
|
3137
|
+
if (value === void 0 || value === null) return;
|
|
3138
|
+
if (!isValidSeed(value)) {
|
|
3139
|
+
throw new ValidationError2({
|
|
3140
|
+
field: fieldName,
|
|
3141
|
+
reason: `must be an integer in [${MIN_SEED}, ${MAX_SEED}]`,
|
|
3142
|
+
receivedValue: value
|
|
3143
|
+
});
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
|
|
3147
|
+
// src/providers/pruna/client/core/protocol/input-type-guard.ts
|
|
3148
|
+
function isTextToImageInput(input) {
|
|
3149
|
+
return input.model === "p-image";
|
|
3150
|
+
}
|
|
3151
|
+
function isImageToImageInput(input) {
|
|
3152
|
+
return input.model === "p-image-edit";
|
|
3153
|
+
}
|
|
3154
|
+
function isImageToVideoInput(input) {
|
|
3155
|
+
return input.model === "p-video";
|
|
3156
|
+
}
|
|
3157
|
+
|
|
3158
|
+
// src/providers/pruna/client/core/utils/request-deduplicator.ts
|
|
3159
|
+
var RequestDeduplicator2 = class {
|
|
3160
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
3161
|
+
ttl;
|
|
3162
|
+
constructor(ttl = DEFAULT_DEDUPLICATOR_TTL_MS) {
|
|
3163
|
+
this.ttl = ttl;
|
|
3164
|
+
}
|
|
3165
|
+
execute(key, fn) {
|
|
3166
|
+
const existing = this.pendingRequests.get(key);
|
|
3167
|
+
if (existing) {
|
|
3168
|
+
if (Date.now() - existing.timestamp < this.ttl) {
|
|
3169
|
+
return existing.promise;
|
|
3170
|
+
}
|
|
3171
|
+
this.pendingRequests.delete(key);
|
|
3172
|
+
}
|
|
3173
|
+
const promise = fn().finally(() => {
|
|
3174
|
+
setTimeout(() => {
|
|
3175
|
+
this.pendingRequests.delete(key);
|
|
3176
|
+
}, DEDUPLICATOR_CLEANUP_GRACE_MS);
|
|
3177
|
+
});
|
|
3178
|
+
this.pendingRequests.set(key, { promise, timestamp: Date.now() });
|
|
3179
|
+
return promise;
|
|
3180
|
+
}
|
|
3181
|
+
clear() {
|
|
3182
|
+
this.pendingRequests.clear();
|
|
3183
|
+
}
|
|
3184
|
+
size() {
|
|
3185
|
+
this.purgeExpired();
|
|
3186
|
+
return this.pendingRequests.size;
|
|
3187
|
+
}
|
|
3188
|
+
purgeExpired() {
|
|
3189
|
+
const now = Date.now();
|
|
3190
|
+
for (const [key, value] of this.pendingRequests) {
|
|
3191
|
+
if (now - value.timestamp >= this.ttl) {
|
|
3192
|
+
this.pendingRequests.delete(key);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
var globalDeduplicator = new RequestDeduplicator2();
|
|
3198
|
+
|
|
3199
|
+
// src/providers/pruna/client/generation/builders/text-to-image.builder.ts
|
|
3200
|
+
var TextToImageInputBuilder = class {
|
|
3201
|
+
model = "p-image";
|
|
3202
|
+
build(input, _context) {
|
|
3203
|
+
const payload = {
|
|
3204
|
+
prompt: input.prompt,
|
|
3205
|
+
aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
|
|
2548
3206
|
};
|
|
2549
|
-
|
|
3207
|
+
if (input.seed !== void 0) {
|
|
3208
|
+
payload.seed = input.seed;
|
|
3209
|
+
}
|
|
3210
|
+
return Promise.resolve(payload);
|
|
2550
3211
|
}
|
|
2551
|
-
|
|
3212
|
+
};
|
|
3213
|
+
|
|
3214
|
+
// src/providers/pruna/client/generation/builders/image-to-image.builder.ts
|
|
3215
|
+
var ImageToImageInputBuilder = class {
|
|
3216
|
+
model = "p-image-edit";
|
|
3217
|
+
build(input, _context) {
|
|
3218
|
+
const payload = {
|
|
3219
|
+
images: [stripBase64Prefix(input.image)],
|
|
3220
|
+
prompt: input.prompt,
|
|
3221
|
+
aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO
|
|
3222
|
+
};
|
|
3223
|
+
if (input.seed !== void 0) {
|
|
3224
|
+
payload.seed = input.seed;
|
|
3225
|
+
}
|
|
3226
|
+
return Promise.resolve(payload);
|
|
3227
|
+
}
|
|
3228
|
+
};
|
|
3229
|
+
|
|
3230
|
+
// src/providers/pruna/client/generation/builders/image-to-video.builder.ts
|
|
3231
|
+
var ImageToVideoInputBuilder = class {
|
|
3232
|
+
model = "p-video";
|
|
3233
|
+
async build(input, context) {
|
|
3234
|
+
if (context.signal?.aborted) {
|
|
3235
|
+
throw new CancellationError();
|
|
3236
|
+
}
|
|
3237
|
+
const fileUrl = await uploadImage(
|
|
3238
|
+
input.image,
|
|
3239
|
+
context.apiKey,
|
|
3240
|
+
context.onProgress
|
|
3241
|
+
);
|
|
3242
|
+
return {
|
|
3243
|
+
image: fileUrl,
|
|
3244
|
+
prompt: input.prompt,
|
|
3245
|
+
duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
|
|
3246
|
+
resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
|
|
3247
|
+
fps: P_VIDEO_DEFAULTS.fps,
|
|
3248
|
+
draft: input.draft ?? P_VIDEO_DEFAULTS.draft,
|
|
3249
|
+
aspect_ratio: input.aspect_ratio ?? DEFAULT_ASPECT_RATIO,
|
|
3250
|
+
prompt_upsampling: P_VIDEO_DEFAULTS.promptUpsampling
|
|
3251
|
+
};
|
|
3252
|
+
}
|
|
3253
|
+
};
|
|
3254
|
+
|
|
3255
|
+
// src/providers/pruna/client/generation/builders/model-input-builder.ts
|
|
3256
|
+
var ModelInputBuilderRegistry = class {
|
|
3257
|
+
builders = /* @__PURE__ */ new Map();
|
|
3258
|
+
register(builder) {
|
|
3259
|
+
this.builders.set(builder.model, builder);
|
|
3260
|
+
return this;
|
|
3261
|
+
}
|
|
3262
|
+
resolve(model) {
|
|
3263
|
+
const builder = this.builders.get(model);
|
|
3264
|
+
if (!builder) {
|
|
3265
|
+
throw new Error(`No input builder registered for model: ${model}`);
|
|
3266
|
+
}
|
|
3267
|
+
return builder;
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
|
|
3271
|
+
// src/providers/pruna/client/generation/builders/builder-factory.ts
|
|
3272
|
+
function createDefaultBuilderRegistry() {
|
|
3273
|
+
return new ModelInputBuilderRegistry().register(new TextToImageInputBuilder()).register(new ImageToImageInputBuilder()).register(new ImageToVideoInputBuilder());
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
// src/providers/pruna/client/generation/services/generation.service.ts
|
|
3277
|
+
var builderRegistry = createDefaultBuilderRegistry();
|
|
3278
|
+
function validateGenerateInput(input) {
|
|
3279
|
+
assertValidPrompt(input.prompt, `${input.model}.prompt`);
|
|
3280
|
+
assertValidAspectRatio(input.aspect_ratio, `${input.model}.aspect_ratio`);
|
|
3281
|
+
if (isTextToImageInput(input)) {
|
|
3282
|
+
assertValidSeed(input.seed, `${input.model}.seed`);
|
|
3283
|
+
} else if (isImageToImageInput(input)) {
|
|
3284
|
+
assertSecureImageSource(input.image, `${input.model}.image`);
|
|
3285
|
+
assertValidSeed(input.seed, `${input.model}.seed`);
|
|
3286
|
+
} else if (isImageToVideoInput(input)) {
|
|
3287
|
+
assertSecureImageSource(input.image, `${input.model}.image`);
|
|
3288
|
+
assertValidDuration(input.duration, `${input.model}.duration`);
|
|
3289
|
+
assertValidResolution(input.resolution, `${input.model}.resolution`);
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
async function generate(apiKey, input, options = {}) {
|
|
3293
|
+
assertValidApiKey(apiKey);
|
|
3294
|
+
validateGenerateInput(input);
|
|
3295
|
+
const { signal, onProgress, useCache = true } = options;
|
|
3296
|
+
if (useCache) {
|
|
3297
|
+
const cacheKey = deriveGenerationCacheKey(apiKey, input);
|
|
3298
|
+
return globalDeduplicator.execute(
|
|
3299
|
+
cacheKey,
|
|
3300
|
+
() => generateInternal(apiKey, input, { signal, onProgress })
|
|
3301
|
+
);
|
|
3302
|
+
}
|
|
3303
|
+
return generateInternal(apiKey, input, { signal, onProgress });
|
|
3304
|
+
}
|
|
3305
|
+
async function generateInternal(apiKey, input, options) {
|
|
3306
|
+
const { signal, onProgress } = options;
|
|
3307
|
+
const builder = builderRegistry.resolve(input.model);
|
|
3308
|
+
const modelInput = await builder.build(input, { apiKey, signal, onProgress });
|
|
3309
|
+
const response = await submitPrediction(input.model, modelInput, apiKey, signal, onProgress);
|
|
3310
|
+
const syncUri = extractUri(response);
|
|
3311
|
+
if (syncUri) {
|
|
3312
|
+
return { url: resolveUri(syncUri), model: input.model };
|
|
3313
|
+
}
|
|
3314
|
+
const pollUrl = response.get_url ?? response.status_url;
|
|
2552
3315
|
if (!pollUrl) {
|
|
2553
|
-
throw new Error("Pruna
|
|
3316
|
+
throw new Error("Pruna API returned no result and no polling URL.");
|
|
2554
3317
|
}
|
|
2555
|
-
const
|
|
2556
|
-
|
|
3318
|
+
const url = await pollForResult(
|
|
3319
|
+
pollUrl,
|
|
3320
|
+
apiKey,
|
|
3321
|
+
POLL_DEFAULTS.maxAttempts,
|
|
3322
|
+
POLL_DEFAULTS.intervalMs,
|
|
3323
|
+
signal,
|
|
3324
|
+
onProgress
|
|
3325
|
+
);
|
|
3326
|
+
return { url, model: input.model };
|
|
3327
|
+
}
|
|
3328
|
+
async function generateImageThenVideo(apiKey, input, options = {}) {
|
|
3329
|
+
assertValidApiKey(apiKey);
|
|
3330
|
+
assertValidPrompt(input.prompt, "text-to-video.prompt");
|
|
3331
|
+
assertValidAspectRatio(input.aspect_ratio, "text-to-video.aspect_ratio");
|
|
3332
|
+
assertValidDuration(input.duration, "text-to-video.duration");
|
|
3333
|
+
assertValidResolution(input.resolution, "text-to-video.resolution");
|
|
3334
|
+
const { signal, onProgress } = options;
|
|
3335
|
+
const aspectRatio = input.aspect_ratio ?? DEFAULT_ASPECT_RATIO;
|
|
3336
|
+
onProgress?.("predicting", 1);
|
|
3337
|
+
const imageResult = await generate(
|
|
3338
|
+
apiKey,
|
|
3339
|
+
{ model: "p-image", prompt: input.prompt, aspect_ratio: aspectRatio },
|
|
3340
|
+
{ signal }
|
|
3341
|
+
);
|
|
3342
|
+
onProgress?.("predicting", 2);
|
|
3343
|
+
return generate(
|
|
3344
|
+
apiKey,
|
|
3345
|
+
{
|
|
3346
|
+
model: "p-video",
|
|
3347
|
+
prompt: input.prompt,
|
|
3348
|
+
image: imageResult.url,
|
|
3349
|
+
duration: input.duration ?? P_VIDEO_DEFAULTS.duration,
|
|
3350
|
+
resolution: input.resolution ?? P_VIDEO_DEFAULTS.resolution,
|
|
3351
|
+
aspect_ratio: aspectRatio,
|
|
3352
|
+
draft: input.draft ?? P_VIDEO_DEFAULTS.draft
|
|
3353
|
+
},
|
|
3354
|
+
{ signal, onProgress }
|
|
3355
|
+
);
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
// src/providers/pruna/pruna-generate.ts
|
|
3359
|
+
async function* generateWithPruna(request, apiKey, signal, onProgress) {
|
|
3360
|
+
const stages = [];
|
|
3361
|
+
let notify = null;
|
|
3362
|
+
let done = false;
|
|
3363
|
+
const wake = () => {
|
|
3364
|
+
const fn = notify;
|
|
3365
|
+
if (fn) {
|
|
3366
|
+
notify = null;
|
|
3367
|
+
fn();
|
|
3368
|
+
}
|
|
3369
|
+
};
|
|
3370
|
+
const handleProgress = (stage) => {
|
|
3371
|
+
stages.push({ stage });
|
|
3372
|
+
onProgress?.(stage);
|
|
3373
|
+
wake();
|
|
3374
|
+
};
|
|
3375
|
+
const promise = (async () => {
|
|
3376
|
+
try {
|
|
3377
|
+
const model = request.model ?? (request.image ? "p-image-edit" : "p-image");
|
|
3378
|
+
if (model === "p-video" && !request.image && request.prompt) {
|
|
3379
|
+
const videoRes = await generateImageThenVideo(
|
|
3380
|
+
apiKey,
|
|
3381
|
+
{
|
|
3382
|
+
prompt: request.prompt,
|
|
3383
|
+
duration: request.duration ?? 5,
|
|
3384
|
+
aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "16:9"
|
|
3385
|
+
},
|
|
3386
|
+
{ signal, onProgress: handleProgress }
|
|
3387
|
+
);
|
|
3388
|
+
stages.push({
|
|
3389
|
+
stage: "polling",
|
|
3390
|
+
result: { url: videoRes.url, model: "p-video" }
|
|
3391
|
+
});
|
|
3392
|
+
return;
|
|
3393
|
+
}
|
|
3394
|
+
const input = {
|
|
3395
|
+
model,
|
|
3396
|
+
prompt: request.prompt ?? "",
|
|
3397
|
+
aspect_ratio: request.aspectRatio ?? request.aspect_ratio ?? "1:1",
|
|
3398
|
+
...request.image ? { image: request.image } : {},
|
|
3399
|
+
...request.duration ? { duration: request.duration } : {},
|
|
3400
|
+
...request.resolution ? { resolution: request.resolution } : {},
|
|
3401
|
+
...request.seed !== void 0 ? { seed: request.seed } : {}
|
|
3402
|
+
};
|
|
3403
|
+
const res = await generate(apiKey, input, {
|
|
3404
|
+
signal,
|
|
3405
|
+
onProgress: handleProgress
|
|
3406
|
+
});
|
|
3407
|
+
stages.push({
|
|
3408
|
+
stage: "polling",
|
|
3409
|
+
result: { url: res.url, model: res.model }
|
|
3410
|
+
});
|
|
3411
|
+
} catch (err) {
|
|
3412
|
+
stages.push({
|
|
3413
|
+
stage: "processing",
|
|
3414
|
+
error: err instanceof Error ? err : new Error(String(err))
|
|
3415
|
+
});
|
|
3416
|
+
} finally {
|
|
3417
|
+
done = true;
|
|
3418
|
+
wake();
|
|
3419
|
+
}
|
|
3420
|
+
})();
|
|
3421
|
+
while (!done || stages.length > 0) {
|
|
3422
|
+
if (stages.length === 0 && !done) {
|
|
3423
|
+
await new Promise((resolve) => {
|
|
3424
|
+
notify = resolve;
|
|
3425
|
+
});
|
|
3426
|
+
notify = null;
|
|
3427
|
+
}
|
|
3428
|
+
while (stages.length > 0) {
|
|
3429
|
+
const nextStage = stages.shift();
|
|
3430
|
+
if (!nextStage) continue;
|
|
3431
|
+
yield nextStage;
|
|
3432
|
+
if (nextStage.error) {
|
|
3433
|
+
throw nextStage.error;
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
}
|
|
3437
|
+
await promise;
|
|
2557
3438
|
}
|
|
2558
3439
|
|
|
2559
3440
|
// src/providers/pruna/pruna.provider.ts
|
|
@@ -4864,7 +5745,7 @@ function useText(options = {}, deps) {
|
|
|
4864
5745
|
(request, onChunk) => engine instanceof ProviderFactory ? engine.chatStream(request, { onChunk }) : engine.stream(request, { onChunk }),
|
|
4865
5746
|
[engine]
|
|
4866
5747
|
);
|
|
4867
|
-
const
|
|
5748
|
+
const generate2 = react.useCallback(
|
|
4868
5749
|
(prompt, config) => runWithState(async (signal) => {
|
|
4869
5750
|
const response = await run(buildRequest(prompt, config, signal));
|
|
4870
5751
|
setResult(response.text);
|
|
@@ -4920,7 +5801,7 @@ function useText(options = {}, deps) {
|
|
|
4920
5801
|
isLoading,
|
|
4921
5802
|
error,
|
|
4922
5803
|
result,
|
|
4923
|
-
generate,
|
|
5804
|
+
generate: generate2,
|
|
4924
5805
|
generateJSON,
|
|
4925
5806
|
stream,
|
|
4926
5807
|
cancel,
|
|
@@ -5232,13 +6113,285 @@ function useChat(options, deps) {
|
|
|
5232
6113
|
};
|
|
5233
6114
|
}
|
|
5234
6115
|
|
|
6116
|
+
// src/providers/groq/groq-container.ts
|
|
6117
|
+
var container = null;
|
|
6118
|
+
function getProvider() {
|
|
6119
|
+
return container;
|
|
6120
|
+
}
|
|
6121
|
+
|
|
6122
|
+
// src/hooks/use-groq.hook.ts
|
|
6123
|
+
function useGroq(options = {}, deps) {
|
|
6124
|
+
const textGeneration = deps?.textGeneration ?? getProvider()?.textGeneration;
|
|
6125
|
+
if (!textGeneration) {
|
|
6126
|
+
throw new Error(
|
|
6127
|
+
"useGroq requires a TextGenerationService dependency. Provide it via the second argument or wire it through configureProvider()."
|
|
6128
|
+
);
|
|
6129
|
+
}
|
|
6130
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
6131
|
+
const [error, setError] = react.useState(null);
|
|
6132
|
+
const [result, setResult] = react.useState(null);
|
|
6133
|
+
const optionsRef = react.useRef(options);
|
|
6134
|
+
optionsRef.current = options;
|
|
6135
|
+
const abortControllerRef = react.useRef(null);
|
|
6136
|
+
const getOrCreateAbortController = react.useCallback(() => {
|
|
6137
|
+
if (!abortControllerRef.current || abortControllerRef.current.signal.aborted) {
|
|
6138
|
+
abortControllerRef.current = new AbortController();
|
|
6139
|
+
}
|
|
6140
|
+
return abortControllerRef.current;
|
|
6141
|
+
}, []);
|
|
6142
|
+
const stableConfig = react.useMemo(
|
|
6143
|
+
() => ({
|
|
6144
|
+
model: options.model,
|
|
6145
|
+
generationConfig: options.generationConfig
|
|
6146
|
+
}),
|
|
6147
|
+
[
|
|
6148
|
+
options.model,
|
|
6149
|
+
options.generationConfig?.temperature,
|
|
6150
|
+
options.generationConfig?.maxTokens,
|
|
6151
|
+
options.generationConfig?.topP
|
|
6152
|
+
]
|
|
6153
|
+
);
|
|
6154
|
+
react.useEffect(() => {
|
|
6155
|
+
abortControllerRef.current = new AbortController();
|
|
6156
|
+
return () => {
|
|
6157
|
+
abortControllerRef.current?.abort();
|
|
6158
|
+
abortControllerRef.current = null;
|
|
6159
|
+
};
|
|
6160
|
+
}, []);
|
|
6161
|
+
const runWithState = react.useCallback(
|
|
6162
|
+
async (task) => {
|
|
6163
|
+
const controller = getOrCreateAbortController();
|
|
6164
|
+
const { signal } = controller;
|
|
6165
|
+
if (signal.aborted) return null;
|
|
6166
|
+
setIsLoading(true);
|
|
6167
|
+
setError(null);
|
|
6168
|
+
setResult(null);
|
|
6169
|
+
const currentOptions = optionsRef.current;
|
|
6170
|
+
currentOptions.onStart?.();
|
|
6171
|
+
try {
|
|
6172
|
+
const value = await task(signal);
|
|
6173
|
+
return value;
|
|
6174
|
+
} catch (err) {
|
|
6175
|
+
if (signal.aborted) {
|
|
6176
|
+
return null;
|
|
6177
|
+
}
|
|
6178
|
+
const message = resolveUserFriendlyMessage(err);
|
|
6179
|
+
setError(message);
|
|
6180
|
+
currentOptions.onError?.(message);
|
|
6181
|
+
throw err;
|
|
6182
|
+
} finally {
|
|
6183
|
+
if (!signal.aborted) {
|
|
6184
|
+
setIsLoading(false);
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
},
|
|
6188
|
+
[getOrCreateAbortController]
|
|
6189
|
+
);
|
|
6190
|
+
const generate2 = react.useCallback(
|
|
6191
|
+
(prompt, config) => runWithState(async () => {
|
|
6192
|
+
const response = await textGeneration.generateCompletion(prompt, {
|
|
6193
|
+
model: stableConfig.model,
|
|
6194
|
+
generationConfig: {
|
|
6195
|
+
...stableConfig.generationConfig,
|
|
6196
|
+
...config
|
|
6197
|
+
}
|
|
6198
|
+
});
|
|
6199
|
+
setResult(response);
|
|
6200
|
+
optionsRef.current.onSuccess?.(response);
|
|
6201
|
+
return response;
|
|
6202
|
+
}),
|
|
6203
|
+
[runWithState, textGeneration, stableConfig]
|
|
6204
|
+
);
|
|
6205
|
+
const generateJSON = react.useCallback(
|
|
6206
|
+
(prompt, config) => runWithState(async () => {
|
|
6207
|
+
const response = await textGeneration.generateStructured(prompt, {
|
|
6208
|
+
model: stableConfig.model,
|
|
6209
|
+
generationConfig: {
|
|
6210
|
+
...stableConfig.generationConfig,
|
|
6211
|
+
...config
|
|
6212
|
+
},
|
|
6213
|
+
...config?.schema !== void 0 ? { schema: config.schema } : {}
|
|
6214
|
+
});
|
|
6215
|
+
const json = JSON.stringify(response, null, 2);
|
|
6216
|
+
setResult(json);
|
|
6217
|
+
optionsRef.current.onSuccess?.(json);
|
|
6218
|
+
return response;
|
|
6219
|
+
}),
|
|
6220
|
+
[runWithState, textGeneration, stableConfig]
|
|
6221
|
+
);
|
|
6222
|
+
const stream = react.useCallback(
|
|
6223
|
+
(prompt, onChunk, config) => runWithState(async () => {
|
|
6224
|
+
const callbacks = {
|
|
6225
|
+
onChunk,
|
|
6226
|
+
onComplete: (text) => {
|
|
6227
|
+
setResult(text);
|
|
6228
|
+
optionsRef.current.onSuccess?.(text);
|
|
6229
|
+
}
|
|
6230
|
+
};
|
|
6231
|
+
return textGeneration.streamCompletion(
|
|
6232
|
+
prompt,
|
|
6233
|
+
callbacks,
|
|
6234
|
+
{
|
|
6235
|
+
model: stableConfig.model,
|
|
6236
|
+
generationConfig: {
|
|
6237
|
+
...stableConfig.generationConfig,
|
|
6238
|
+
...config
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
);
|
|
6242
|
+
}),
|
|
6243
|
+
[runWithState, textGeneration, stableConfig]
|
|
6244
|
+
);
|
|
6245
|
+
const cancel = react.useCallback(() => {
|
|
6246
|
+
if (abortControllerRef.current) {
|
|
6247
|
+
abortControllerRef.current.abort();
|
|
6248
|
+
abortControllerRef.current = new AbortController();
|
|
6249
|
+
}
|
|
6250
|
+
setIsLoading(false);
|
|
6251
|
+
}, []);
|
|
6252
|
+
const reset = react.useCallback(() => {
|
|
6253
|
+
cancel();
|
|
6254
|
+
setError(null);
|
|
6255
|
+
setResult(null);
|
|
6256
|
+
}, [cancel]);
|
|
6257
|
+
const clearError = react.useCallback(() => setError(null), []);
|
|
6258
|
+
return {
|
|
6259
|
+
isLoading,
|
|
6260
|
+
error,
|
|
6261
|
+
result,
|
|
6262
|
+
generate: generate2,
|
|
6263
|
+
generateJSON,
|
|
6264
|
+
stream,
|
|
6265
|
+
cancel,
|
|
6266
|
+
reset,
|
|
6267
|
+
clearError
|
|
6268
|
+
};
|
|
6269
|
+
}
|
|
6270
|
+
function isAbortError2(err) {
|
|
6271
|
+
if (!(err instanceof Error)) return false;
|
|
6272
|
+
if (err.name === "AbortError") return true;
|
|
6273
|
+
if (err instanceof DOMException && err.code === DOMException.ABORT_ERR) return true;
|
|
6274
|
+
return isCancellationError(err);
|
|
6275
|
+
}
|
|
6276
|
+
function toError(value) {
|
|
6277
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
6278
|
+
}
|
|
6279
|
+
function useAsyncAction(executor, options = {}) {
|
|
6280
|
+
const [result, setResult] = react.useState(null);
|
|
6281
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
6282
|
+
const [error, setError] = react.useState(null);
|
|
6283
|
+
const abortRef = react.useRef(null);
|
|
6284
|
+
const executorRef = react.useRef(executor);
|
|
6285
|
+
const optionsRef = react.useRef(options);
|
|
6286
|
+
const mountedRef = react.useRef(true);
|
|
6287
|
+
react.useEffect(() => {
|
|
6288
|
+
executorRef.current = executor;
|
|
6289
|
+
optionsRef.current = options;
|
|
6290
|
+
});
|
|
6291
|
+
react.useEffect(() => {
|
|
6292
|
+
mountedRef.current = true;
|
|
6293
|
+
return () => {
|
|
6294
|
+
mountedRef.current = false;
|
|
6295
|
+
abortRef.current?.abort();
|
|
6296
|
+
abortRef.current = null;
|
|
6297
|
+
};
|
|
6298
|
+
}, []);
|
|
6299
|
+
const run = react.useCallback(
|
|
6300
|
+
async (input) => {
|
|
6301
|
+
abortRef.current?.abort();
|
|
6302
|
+
const controller = new AbortController();
|
|
6303
|
+
abortRef.current = controller;
|
|
6304
|
+
setIsLoading(true);
|
|
6305
|
+
setError(null);
|
|
6306
|
+
setResult(null);
|
|
6307
|
+
try {
|
|
6308
|
+
const value = await executorRef.current(input, controller.signal);
|
|
6309
|
+
if (!mountedRef.current) return null;
|
|
6310
|
+
setResult(value);
|
|
6311
|
+
optionsRef.current.onSuccess?.(value);
|
|
6312
|
+
return value;
|
|
6313
|
+
} catch (err) {
|
|
6314
|
+
if (isAbortError2(err)) {
|
|
6315
|
+
if (mountedRef.current) {
|
|
6316
|
+
optionsRef.current.onCancelled?.();
|
|
6317
|
+
}
|
|
6318
|
+
return null;
|
|
6319
|
+
}
|
|
6320
|
+
if (!mountedRef.current) return null;
|
|
6321
|
+
const normalized = toError(err);
|
|
6322
|
+
setError(normalized);
|
|
6323
|
+
optionsRef.current.onError?.(normalized);
|
|
6324
|
+
return null;
|
|
6325
|
+
} finally {
|
|
6326
|
+
if (mountedRef.current) {
|
|
6327
|
+
setIsLoading(false);
|
|
6328
|
+
}
|
|
6329
|
+
}
|
|
6330
|
+
},
|
|
6331
|
+
[]
|
|
6332
|
+
);
|
|
6333
|
+
const cancel = react.useCallback(() => {
|
|
6334
|
+
abortRef.current?.abort();
|
|
6335
|
+
abortRef.current = null;
|
|
6336
|
+
}, []);
|
|
6337
|
+
const reset = react.useCallback(() => {
|
|
6338
|
+
abortRef.current?.abort();
|
|
6339
|
+
abortRef.current = null;
|
|
6340
|
+
setResult(null);
|
|
6341
|
+
setError(null);
|
|
6342
|
+
setIsLoading(false);
|
|
6343
|
+
}, []);
|
|
6344
|
+
return react.useMemo(
|
|
6345
|
+
() => ({ result, isLoading, error, run, cancel, reset }),
|
|
6346
|
+
[result, isLoading, error, run, cancel, reset]
|
|
6347
|
+
);
|
|
6348
|
+
}
|
|
6349
|
+
|
|
6350
|
+
// src/hooks/usePrunaGeneration.ts
|
|
6351
|
+
function usePrunaGeneration(apiKey, options = {}) {
|
|
6352
|
+
return useAsyncAction(
|
|
6353
|
+
(input, signal) => generate(apiKey, input, { signal, onProgress: options.onProgress }),
|
|
6354
|
+
options
|
|
6355
|
+
);
|
|
6356
|
+
}
|
|
6357
|
+
|
|
6358
|
+
// src/hooks/usePrunaProxy.ts
|
|
6359
|
+
function createProxyExecutor(proxyUrl, executorOptions) {
|
|
6360
|
+
return async (input, signal) => {
|
|
6361
|
+
executorOptions.onProgress?.("uploading");
|
|
6362
|
+
const res = await fetch(proxyUrl, {
|
|
6363
|
+
method: "POST",
|
|
6364
|
+
headers: { "Content-Type": "application/json" },
|
|
6365
|
+
body: JSON.stringify(input),
|
|
6366
|
+
signal
|
|
6367
|
+
});
|
|
6368
|
+
if (!res.ok) {
|
|
6369
|
+
const payload = await res.json().catch(() => ({ message: res.statusText }));
|
|
6370
|
+
throw new Error(payload.message ?? `Proxy error: ${res.status}`);
|
|
6371
|
+
}
|
|
6372
|
+
return res.json();
|
|
6373
|
+
};
|
|
6374
|
+
}
|
|
6375
|
+
function usePrunaProxy(options) {
|
|
6376
|
+
const { proxyUrl, onProgress, onSuccess, onError } = options;
|
|
6377
|
+
return useAsyncAction(
|
|
6378
|
+
createProxyExecutor(proxyUrl, { onProgress }),
|
|
6379
|
+
{ onSuccess, onError }
|
|
6380
|
+
);
|
|
6381
|
+
}
|
|
6382
|
+
|
|
6383
|
+
exports.CancellationError = CancellationError;
|
|
5235
6384
|
exports.useAIContent = useAIContent;
|
|
5236
6385
|
exports.useAIGeneration = useAIGeneration;
|
|
6386
|
+
exports.useAsyncAction = useAsyncAction;
|
|
5237
6387
|
exports.useBlogGenerator = useBlogGenerator;
|
|
5238
6388
|
exports.useCacheStats = useCacheStats;
|
|
5239
6389
|
exports.useChat = useChat;
|
|
5240
6390
|
exports.useContentWizard = useContentWizard;
|
|
6391
|
+
exports.useGroq = useGroq;
|
|
5241
6392
|
exports.useImageWizard = useImageWizard;
|
|
6393
|
+
exports.usePrunaGeneration = usePrunaGeneration;
|
|
6394
|
+
exports.usePrunaProxy = usePrunaProxy;
|
|
5242
6395
|
exports.useSocialContentGenerator = useSocialContentGenerator;
|
|
5243
6396
|
exports.useText = useText;
|
|
5244
6397
|
exports.useVideoWizard = useVideoWizard;
|