@umituz/web-ai 1.0.0 → 1.0.2
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/{ABTesting-DDqNhHtP.d.ts → AIErrors-B2ogd1PB.d.ts} +14 -1
- package/dist/{ABTesting-DWBtKTMQ.d.cts → AIErrors-C5EasV_c.d.cts} +14 -1
- package/dist/{ProviderConfig-a55tLhWT.d.cts → ProviderConfig-DIFa9nmG.d.cts} +10 -1
- package/dist/{ProviderConfig-a55tLhWT.d.ts → ProviderConfig-DIFa9nmG.d.ts} +10 -1
- package/dist/chat/index.cjs +114 -23
- package/dist/chat/index.cjs.map +1 -1
- package/dist/chat/index.d.cts +4 -4
- package/dist/chat/index.d.ts +4 -4
- package/dist/chat/index.js +114 -23
- package/dist/chat/index.js.map +1 -1
- package/dist/{chat.service-DBKq9I2c.d.cts → chat.service-CL1qtYxe.d.cts} +7 -7
- package/dist/{chat.service-RQn8UTXD.d.ts → chat.service-q05GnhEm.d.ts} +7 -7
- package/dist/content/index.cjs +506 -193
- package/dist/content/index.cjs.map +1 -1
- package/dist/content/index.d.cts +7 -8
- package/dist/content/index.d.ts +7 -8
- package/dist/content/index.js +506 -193
- package/dist/content/index.js.map +1 -1
- package/dist/core/index.cjs +122 -15
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +13 -31
- package/dist/core/index.d.ts +13 -31
- package/dist/core/index.js +119 -16
- package/dist/core/index.js.map +1 -1
- package/dist/{create-ai-BhNaz86i.d.ts → create-ai-9PW752z8.d.ts} +14 -21
- package/dist/{create-ai-DGZFBOA1.d.cts → create-ai-CXYKPK6G.d.cts} +14 -21
- package/dist/hooks/index.cjs +631 -179
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.cts +33 -7
- package/dist/hooks/index.d.ts +33 -7
- package/dist/hooks/index.js +631 -180
- package/dist/hooks/index.js.map +1 -1
- package/dist/index-DIHz8_OJ.d.ts +48 -0
- package/dist/index-DKHNcMbl.d.cts +48 -0
- package/dist/index.cjs +666 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +645 -105
- package/dist/index.js.map +1 -1
- package/dist/{provider.factory-COsnprLH.d.cts → provider.factory-BxsfK4WW.d.cts} +1 -1
- package/dist/{provider.factory-CpWFyjbw.d.ts → provider.factory-DfDfk0dK.d.ts} +1 -1
- package/dist/providers/index.cjs +973 -130
- package/dist/providers/index.cjs.map +1 -1
- package/dist/providers/index.d.cts +84 -18
- package/dist/providers/index.d.ts +84 -18
- package/dist/providers/index.js +956 -131
- package/dist/providers/index.js.map +1 -1
- package/dist/text-generation.service-BQO3Tvz2.d.cts +66 -0
- package/dist/text-generation.service-CcYQM6Ch.d.ts +66 -0
- package/dist/wizard/index.d.cts +1 -1
- package/dist/wizard/index.d.ts +1 -1
- package/package.json +9 -1
- package/dist/index-DjX9ZKuM.d.cts +0 -14
- package/dist/index-DxLvE588.d.ts +0 -14
package/dist/hooks/index.cjs
CHANGED
|
@@ -5,6 +5,17 @@ var react = require('react');
|
|
|
5
5
|
// src/hooks/useAIContent.ts
|
|
6
6
|
|
|
7
7
|
// src/core/errors/ai-error-types.ts
|
|
8
|
+
var AIErrorType = /* @__PURE__ */ ((AIErrorType2) => {
|
|
9
|
+
AIErrorType2["INVALID_API_KEY"] = "INVALID_API_KEY";
|
|
10
|
+
AIErrorType2["MISSING_CONFIG"] = "MISSING_CONFIG";
|
|
11
|
+
AIErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
12
|
+
AIErrorType2["ABORT_ERROR"] = "ABORT_ERROR";
|
|
13
|
+
AIErrorType2["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
|
|
14
|
+
AIErrorType2["SERVER_ERROR"] = "SERVER_ERROR";
|
|
15
|
+
AIErrorType2["CLIENT_ERROR"] = "CLIENT_ERROR";
|
|
16
|
+
AIErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
17
|
+
return AIErrorType2;
|
|
18
|
+
})(AIErrorType || {});
|
|
8
19
|
var USER_FRIENDLY_ERROR_MESSAGES = {
|
|
9
20
|
["INVALID_API_KEY" /* INVALID_API_KEY */]: "Please check your API key and try again.",
|
|
10
21
|
["MISSING_CONFIG" /* MISSING_CONFIG */]: "The provider is not configured. Please create a provider with createAI first.",
|
|
@@ -17,17 +28,80 @@ var USER_FRIENDLY_ERROR_MESSAGES = {
|
|
|
17
28
|
};
|
|
18
29
|
|
|
19
30
|
// src/core/errors/ai-error.ts
|
|
20
|
-
var
|
|
31
|
+
var KNOWN_ERROR_TYPES = new Set(Object.values(AIErrorType));
|
|
32
|
+
function mapCodeToType(code) {
|
|
33
|
+
switch (code) {
|
|
34
|
+
case "VALIDATION_ERROR":
|
|
35
|
+
case "CLIENT_ERROR":
|
|
36
|
+
return "CLIENT_ERROR" /* CLIENT_ERROR */;
|
|
37
|
+
case "INVALID_API_KEY":
|
|
38
|
+
return "INVALID_API_KEY" /* INVALID_API_KEY */;
|
|
39
|
+
case "MISSING_CONFIG":
|
|
40
|
+
case "PROVIDER_UNAVAILABLE":
|
|
41
|
+
return "MISSING_CONFIG" /* MISSING_CONFIG */;
|
|
42
|
+
case "PROVIDER_QUOTA_EXHAUSTED":
|
|
43
|
+
case "RATE_LIMIT_ERROR":
|
|
44
|
+
return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
|
|
45
|
+
case "NETWORK_ERROR":
|
|
46
|
+
case "TIMEOUT":
|
|
47
|
+
return "NETWORK_ERROR" /* NETWORK_ERROR */;
|
|
48
|
+
case "ABORT_ERROR":
|
|
49
|
+
case "ABORTED":
|
|
50
|
+
return "ABORT_ERROR" /* ABORT_ERROR */;
|
|
51
|
+
case "SERVER_ERROR":
|
|
52
|
+
case "PROVIDER_ERROR":
|
|
53
|
+
case "NO_RESULT":
|
|
54
|
+
return "SERVER_ERROR" /* SERVER_ERROR */;
|
|
55
|
+
default:
|
|
56
|
+
return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function mapMessageToCode(message, fallback = "UNKNOWN") {
|
|
60
|
+
const lower = message.toLowerCase();
|
|
61
|
+
if (lower.includes("validation")) return "VALIDATION_ERROR";
|
|
62
|
+
if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
|
|
63
|
+
if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
|
|
64
|
+
if (lower.includes("timeout") || lower.includes("aborted")) {
|
|
65
|
+
return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
|
|
66
|
+
}
|
|
67
|
+
if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
|
|
68
|
+
if (lower.includes("no result")) return "NO_RESULT";
|
|
69
|
+
if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
|
|
70
|
+
return fallback;
|
|
71
|
+
}
|
|
72
|
+
var AIError = class _AIError extends Error {
|
|
21
73
|
type;
|
|
74
|
+
code;
|
|
22
75
|
cause;
|
|
23
76
|
metadata;
|
|
24
|
-
constructor(
|
|
25
|
-
|
|
77
|
+
constructor(typeOrMessage, messageOrCode, cause, metadata = {}) {
|
|
78
|
+
let effectiveType;
|
|
79
|
+
let effectiveMessage;
|
|
80
|
+
let effectiveCode;
|
|
81
|
+
if (KNOWN_ERROR_TYPES.has(typeOrMessage)) {
|
|
82
|
+
effectiveType = typeOrMessage;
|
|
83
|
+
effectiveMessage = messageOrCode ?? "";
|
|
84
|
+
effectiveCode = effectiveType;
|
|
85
|
+
} else {
|
|
86
|
+
effectiveMessage = typeOrMessage;
|
|
87
|
+
effectiveCode = messageOrCode ?? "UNKNOWN";
|
|
88
|
+
effectiveType = mapCodeToType(effectiveCode);
|
|
89
|
+
}
|
|
90
|
+
super(effectiveMessage);
|
|
26
91
|
this.name = "AIError";
|
|
27
|
-
this.type =
|
|
92
|
+
this.type = effectiveType;
|
|
93
|
+
this.code = effectiveCode;
|
|
28
94
|
this.cause = cause;
|
|
29
95
|
this.metadata = metadata;
|
|
30
96
|
}
|
|
97
|
+
static from(error, fallbackCode = "UNKNOWN") {
|
|
98
|
+
if (error instanceof _AIError) return error;
|
|
99
|
+
if (error instanceof Error) {
|
|
100
|
+
const code = mapMessageToCode(error.message, fallbackCode);
|
|
101
|
+
return new _AIError(error.message, code, error);
|
|
102
|
+
}
|
|
103
|
+
return new _AIError(String(error), fallbackCode, error);
|
|
104
|
+
}
|
|
31
105
|
toString() {
|
|
32
106
|
const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
|
|
33
107
|
return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
|
|
@@ -36,12 +110,21 @@ var AIError = class extends Error {
|
|
|
36
110
|
return {
|
|
37
111
|
name: this.name,
|
|
38
112
|
type: this.type,
|
|
113
|
+
code: this.code,
|
|
39
114
|
message: this.message,
|
|
40
115
|
metadata: this.metadata
|
|
41
116
|
};
|
|
42
117
|
}
|
|
43
118
|
};
|
|
44
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
|
+
|
|
45
128
|
// src/core/errors/error-classifier.ts
|
|
46
129
|
var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
|
|
47
130
|
"NETWORK_ERROR" /* NETWORK_ERROR */,
|
|
@@ -224,12 +307,16 @@ var ProviderFactory = class {
|
|
|
224
307
|
let wake = null;
|
|
225
308
|
let finished = false;
|
|
226
309
|
let failure;
|
|
310
|
+
const controller = new AbortController();
|
|
311
|
+
const onAbort = () => controller.abort();
|
|
312
|
+
request.signal?.addEventListener("abort", onAbort);
|
|
313
|
+
const effectiveRequest = { ...request, signal: controller.signal };
|
|
227
314
|
const notify = () => {
|
|
228
315
|
const resolve = wake;
|
|
229
316
|
wake = null;
|
|
230
317
|
resolve?.();
|
|
231
318
|
};
|
|
232
|
-
const completion = this.chatStream(
|
|
319
|
+
const completion = this.chatStream(effectiveRequest, {
|
|
233
320
|
onChunk: (chunk) => {
|
|
234
321
|
queue.push(chunk);
|
|
235
322
|
notify();
|
|
@@ -245,18 +332,23 @@ var ProviderFactory = class {
|
|
|
245
332
|
notify();
|
|
246
333
|
}
|
|
247
334
|
);
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
335
|
+
try {
|
|
336
|
+
while (!finished || queue.length > 0) {
|
|
337
|
+
if (queue.length === 0 && !finished) {
|
|
338
|
+
await new Promise((resolve) => {
|
|
339
|
+
wake = resolve;
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
while (queue.length > 0) {
|
|
343
|
+
yield queue.shift();
|
|
344
|
+
}
|
|
345
|
+
if (failure) throw failure;
|
|
256
346
|
}
|
|
257
|
-
|
|
347
|
+
await completion;
|
|
348
|
+
} finally {
|
|
349
|
+
controller.abort();
|
|
350
|
+
request.signal?.removeEventListener("abort", onAbort);
|
|
258
351
|
}
|
|
259
|
-
await completion;
|
|
260
352
|
}
|
|
261
353
|
async generateJson(request) {
|
|
262
354
|
const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
|
|
@@ -403,15 +495,22 @@ var ProviderFactory = class {
|
|
|
403
495
|
// src/core/constants/protocol.constants.ts
|
|
404
496
|
var SSE_PREFIX = "data: ";
|
|
405
497
|
var SSE_DONE_MARKER = "[DONE]";
|
|
406
|
-
var JSON_CODE_BLOCK_PATTERN = /```json
|
|
498
|
+
var JSON_CODE_BLOCK_PATTERN = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
|
|
407
499
|
var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
|
|
500
|
+
var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
|
|
408
501
|
|
|
409
502
|
// src/core/parsers/sse-lines.ts
|
|
410
503
|
function isSseDataLine(line) {
|
|
411
|
-
return line.startsWith(SSE_PREFIX);
|
|
504
|
+
return line.startsWith(SSE_PREFIX) || line.startsWith("data:");
|
|
412
505
|
}
|
|
413
506
|
function extractSseDataPayload(line) {
|
|
414
|
-
|
|
507
|
+
if (line.startsWith(SSE_PREFIX)) {
|
|
508
|
+
return line.slice(SSE_PREFIX.length);
|
|
509
|
+
}
|
|
510
|
+
if (line.startsWith("data:")) {
|
|
511
|
+
return line.slice(5);
|
|
512
|
+
}
|
|
513
|
+
return line;
|
|
415
514
|
}
|
|
416
515
|
function isSseDoneMarker(payload) {
|
|
417
516
|
return payload === SSE_DONE_MARKER;
|
|
@@ -420,7 +519,7 @@ function isSseDoneMarker(payload) {
|
|
|
420
519
|
// src/core/parsers/json-content-extractor.ts
|
|
421
520
|
function extractJsonFromCodeFence(content) {
|
|
422
521
|
const match = content.match(JSON_CODE_BLOCK_PATTERN);
|
|
423
|
-
if (match && match[1]) return match[1];
|
|
522
|
+
if (match && match[1]) return match[1].trim();
|
|
424
523
|
return null;
|
|
425
524
|
}
|
|
426
525
|
function extractJsonObject(content) {
|
|
@@ -428,8 +527,27 @@ function extractJsonObject(content) {
|
|
|
428
527
|
if (match && match[0]) return match[0];
|
|
429
528
|
return null;
|
|
430
529
|
}
|
|
530
|
+
function extractJsonArray(content) {
|
|
531
|
+
const match = content.match(JSON_ARRAY_PATTERN);
|
|
532
|
+
if (match && match[0]) return match[0];
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
function repairCommonJsonDefects(text) {
|
|
536
|
+
return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
|
|
537
|
+
}
|
|
431
538
|
function extractJsonCandidate(content) {
|
|
432
|
-
|
|
539
|
+
const fromFence = extractJsonFromCodeFence(content);
|
|
540
|
+
if (fromFence) return fromFence;
|
|
541
|
+
const objectIndex = content.indexOf("{");
|
|
542
|
+
const arrayIndex = content.indexOf("[");
|
|
543
|
+
if (objectIndex !== -1 && (arrayIndex === -1 || objectIndex < arrayIndex)) {
|
|
544
|
+
const obj = extractJsonObject(content);
|
|
545
|
+
if (obj) return obj;
|
|
546
|
+
} else if (arrayIndex !== -1) {
|
|
547
|
+
const arr = extractJsonArray(content);
|
|
548
|
+
if (arr) return arr;
|
|
549
|
+
}
|
|
550
|
+
return content.trim();
|
|
433
551
|
}
|
|
434
552
|
|
|
435
553
|
// src/core/parsers/json-response.parser.ts
|
|
@@ -438,12 +556,17 @@ var JsonResponseParser = class {
|
|
|
438
556
|
const candidate = extractJsonCandidate(rawContent);
|
|
439
557
|
try {
|
|
440
558
|
return JSON.parse(candidate);
|
|
441
|
-
} catch
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
559
|
+
} catch {
|
|
560
|
+
try {
|
|
561
|
+
return JSON.parse(repairCommonJsonDefects(candidate));
|
|
562
|
+
} catch (error) {
|
|
563
|
+
throw new AIError(
|
|
564
|
+
"UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
|
|
565
|
+
"Failed to parse structured response as JSON",
|
|
566
|
+
error instanceof Error ? error : void 0,
|
|
567
|
+
{ rawContent }
|
|
568
|
+
);
|
|
569
|
+
}
|
|
447
570
|
}
|
|
448
571
|
}
|
|
449
572
|
};
|
|
@@ -757,11 +880,14 @@ async function consumeAnthropicStream(stream, callbacks) {
|
|
|
757
880
|
break;
|
|
758
881
|
case "message_stop":
|
|
759
882
|
break;
|
|
760
|
-
case "error":
|
|
883
|
+
case "error": {
|
|
884
|
+
const errType = event.error.type;
|
|
885
|
+
const mappedType = errType === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : errType === "authentication_error" ? "INVALID_API_KEY" /* INVALID_API_KEY */ : errType === "invalid_request_error" ? "CLIENT_ERROR" /* CLIENT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */;
|
|
761
886
|
throw new AIError(
|
|
762
|
-
|
|
887
|
+
mappedType,
|
|
763
888
|
event.error.message || "Anthropic stream failed mid-flight"
|
|
764
889
|
);
|
|
890
|
+
}
|
|
765
891
|
}
|
|
766
892
|
};
|
|
767
893
|
try {
|
|
@@ -1223,6 +1349,27 @@ var RequestDeduplicator = class {
|
|
|
1223
1349
|
}
|
|
1224
1350
|
};
|
|
1225
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
|
+
|
|
1226
1373
|
// src/providers/groq/groq.constants.ts
|
|
1227
1374
|
var GROQ_MODELS = {
|
|
1228
1375
|
LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
|
|
@@ -1239,11 +1386,13 @@ var DEFAULT_GENERATION_CONFIG = {
|
|
|
1239
1386
|
topP: 1
|
|
1240
1387
|
};
|
|
1241
1388
|
var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
|
|
1389
|
+
var DEFAULT_BASE_URL = GROQ_BASE_URL;
|
|
1242
1390
|
var CHAT_COMPLETIONS_PATH = "/chat/completions";
|
|
1243
1391
|
var GROQ_TIMEOUTS = {
|
|
1244
1392
|
DEFAULT: 3e4,
|
|
1245
1393
|
STREAMING: 6e4
|
|
1246
1394
|
};
|
|
1395
|
+
var TIMEOUTS = GROQ_TIMEOUTS;
|
|
1247
1396
|
var HTTP_HEADERS = {
|
|
1248
1397
|
CONTENT_TYPE: "Content-Type",
|
|
1249
1398
|
APPLICATION_JSON: "application/json",
|
|
@@ -1302,13 +1451,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
|
|
|
1302
1451
|
}
|
|
1303
1452
|
};
|
|
1304
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
|
+
|
|
1305
1500
|
// src/providers/groq/groq-http-transport.ts
|
|
1306
1501
|
var GroqHttpTransport = class {
|
|
1307
|
-
constructor(
|
|
1308
|
-
this.
|
|
1502
|
+
constructor(configSource) {
|
|
1503
|
+
this.configSource = configSource;
|
|
1309
1504
|
}
|
|
1310
|
-
|
|
1505
|
+
configSource;
|
|
1311
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
|
+
}
|
|
1312
1513
|
cancelPendingRequests() {
|
|
1313
1514
|
for (const controller of this.pendingAbortControllers) {
|
|
1314
1515
|
controller.abort();
|
|
@@ -1323,7 +1524,7 @@ var GroqHttpTransport = class {
|
|
|
1323
1524
|
}
|
|
1324
1525
|
async streamChatCompletion(request, options = {}) {
|
|
1325
1526
|
const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
|
|
1326
|
-
const timeout = this.
|
|
1527
|
+
const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
|
|
1327
1528
|
const controller = new AbortController();
|
|
1328
1529
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1329
1530
|
const onExternalAbort = () => controller.abort();
|
|
@@ -1350,11 +1551,11 @@ var GroqHttpTransport = class {
|
|
|
1350
1551
|
}
|
|
1351
1552
|
}
|
|
1352
1553
|
baseUrl() {
|
|
1353
|
-
return this.
|
|
1554
|
+
return this.getConfig().baseUrl ?? GROQ_BASE_URL;
|
|
1354
1555
|
}
|
|
1355
1556
|
async request(endpoint, body) {
|
|
1356
1557
|
const url = `${this.baseUrl()}${endpoint}`;
|
|
1357
|
-
const timeout = this.
|
|
1558
|
+
const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
|
|
1358
1559
|
const controller = new AbortController();
|
|
1359
1560
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
1360
1561
|
this.pendingAbortControllers.add(controller);
|
|
@@ -1377,7 +1578,7 @@ var GroqHttpTransport = class {
|
|
|
1377
1578
|
buildHeaders() {
|
|
1378
1579
|
return {
|
|
1379
1580
|
[HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
|
|
1380
|
-
[HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.
|
|
1581
|
+
[HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
|
|
1381
1582
|
};
|
|
1382
1583
|
}
|
|
1383
1584
|
async throwFromErrorResponse(response) {
|
|
@@ -1479,21 +1680,40 @@ var CompositeExecutor = class {
|
|
|
1479
1680
|
|
|
1480
1681
|
// src/providers/groq/groq-http-client.ts
|
|
1481
1682
|
var GroqHttpClient = class {
|
|
1683
|
+
configStore;
|
|
1482
1684
|
transport;
|
|
1483
1685
|
executor;
|
|
1484
|
-
constructor(
|
|
1485
|
-
|
|
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);
|
|
1486
1694
|
const retryManager2 = deps.retryManager ?? new RetryManager();
|
|
1487
1695
|
const queue = deps.queue ?? new RequestQueue();
|
|
1488
1696
|
const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
|
|
1489
1697
|
this.executor = new CompositeExecutor([
|
|
1490
1698
|
new DedupeExecutor(deduplicator, {
|
|
1491
|
-
generateKey: (request) => deduplicatorKey(request,
|
|
1699
|
+
generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
|
|
1492
1700
|
}),
|
|
1493
1701
|
new RetryExecutor(retryManager2),
|
|
1494
1702
|
new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
|
|
1495
1703
|
]);
|
|
1496
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
|
+
}
|
|
1497
1717
|
cancelPendingRequests() {
|
|
1498
1718
|
this.transport.cancelPendingRequests();
|
|
1499
1719
|
}
|
|
@@ -1506,6 +1726,10 @@ var GroqHttpClient = class {
|
|
|
1506
1726
|
streamChatCompletion(request, options) {
|
|
1507
1727
|
return this.transport.streamChatCompletion(request, options);
|
|
1508
1728
|
}
|
|
1729
|
+
reset() {
|
|
1730
|
+
this.transport.cancelPendingRequests();
|
|
1731
|
+
this.configStore.reset();
|
|
1732
|
+
}
|
|
1509
1733
|
};
|
|
1510
1734
|
function deduplicatorKey(request, apiKey) {
|
|
1511
1735
|
const { model, messages, stream: _stream, ...rest } = request;
|
|
@@ -1553,6 +1777,13 @@ async function consumeGroqStream(stream, callbacks) {
|
|
|
1553
1777
|
} catch {
|
|
1554
1778
|
return;
|
|
1555
1779
|
}
|
|
1780
|
+
const errorBody = chunk.error;
|
|
1781
|
+
if (errorBody) {
|
|
1782
|
+
throw new AIError(
|
|
1783
|
+
errorBody.message?.toLowerCase().includes("rate limit") ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
|
|
1784
|
+
errorBody.message || "Groq stream failed mid-flight"
|
|
1785
|
+
);
|
|
1786
|
+
}
|
|
1556
1787
|
if (chunk.model) model = chunk.model;
|
|
1557
1788
|
const choice = chunk.choices?.[0];
|
|
1558
1789
|
const delta = choice?.delta?.content;
|
|
@@ -1567,13 +1798,6 @@ async function consumeGroqStream(stream, callbacks) {
|
|
|
1567
1798
|
if (tailUsage) {
|
|
1568
1799
|
usage = mapGroqUsage(tailUsage);
|
|
1569
1800
|
}
|
|
1570
|
-
const errorBody = chunk.error;
|
|
1571
|
-
if (errorBody) {
|
|
1572
|
-
throw new AIError(
|
|
1573
|
-
"SERVER_ERROR" /* SERVER_ERROR */,
|
|
1574
|
-
errorBody.message || "Groq stream failed mid-flight"
|
|
1575
|
-
);
|
|
1576
|
-
}
|
|
1577
1801
|
};
|
|
1578
1802
|
try {
|
|
1579
1803
|
while (true) {
|
|
@@ -1774,37 +1998,7 @@ function findLastIndex2(items, predicate) {
|
|
|
1774
1998
|
}
|
|
1775
1999
|
|
|
1776
2000
|
// src/content/domain/errors/AIErrors.ts
|
|
1777
|
-
var
|
|
1778
|
-
code;
|
|
1779
|
-
cause;
|
|
1780
|
-
constructor(message, code, cause) {
|
|
1781
|
-
super(message);
|
|
1782
|
-
this.name = "AIError";
|
|
1783
|
-
this.code = code;
|
|
1784
|
-
this.cause = cause;
|
|
1785
|
-
}
|
|
1786
|
-
static from(error, fallbackCode = "UNKNOWN") {
|
|
1787
|
-
if (error instanceof _AIError) return error;
|
|
1788
|
-
if (error instanceof Error) {
|
|
1789
|
-
return new _AIError(error.message, mapMessageToCode(error.message, fallbackCode), error);
|
|
1790
|
-
}
|
|
1791
|
-
return new _AIError(String(error), fallbackCode, error);
|
|
1792
|
-
}
|
|
1793
|
-
};
|
|
1794
|
-
function mapMessageToCode(message, fallback) {
|
|
1795
|
-
const lower = message.toLowerCase();
|
|
1796
|
-
if (lower.includes("validation")) return "VALIDATION_ERROR";
|
|
1797
|
-
if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
|
|
1798
|
-
if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
|
|
1799
|
-
if (lower.includes("timeout") || lower.includes("aborted")) {
|
|
1800
|
-
return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
|
|
1801
|
-
}
|
|
1802
|
-
if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
|
|
1803
|
-
if (lower.includes("no result")) return "NO_RESULT";
|
|
1804
|
-
if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
|
|
1805
|
-
return fallback;
|
|
1806
|
-
}
|
|
1807
|
-
var ValidationError = class extends AIError2 {
|
|
2001
|
+
var ValidationError = class extends AIError {
|
|
1808
2002
|
constructor(message) {
|
|
1809
2003
|
super(message, "VALIDATION_ERROR");
|
|
1810
2004
|
this.name = "ValidationError";
|
|
@@ -1935,7 +2129,9 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
1935
2129
|
headers: this.authHeaders()
|
|
1936
2130
|
}
|
|
1937
2131
|
);
|
|
1938
|
-
|
|
2132
|
+
if (response.ok) return "healthy";
|
|
2133
|
+
if (response.status === 401 || response.status === 403) return "unavailable";
|
|
2134
|
+
return "degraded";
|
|
1939
2135
|
} catch {
|
|
1940
2136
|
return "unavailable";
|
|
1941
2137
|
}
|
|
@@ -1946,6 +2142,8 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
1946
2142
|
async generate(request) {
|
|
1947
2143
|
try {
|
|
1948
2144
|
const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
|
|
2145
|
+
const systemMessages = request.messages.filter((m) => m.role === "system");
|
|
2146
|
+
const systemPrompt = request.systemPrompt && request.systemPrompt.length > 0 ? request.systemPrompt : systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n") : void 0;
|
|
1949
2147
|
const response = await this.withRetry(async () => {
|
|
1950
2148
|
return await this.fetchWithTimeout(
|
|
1951
2149
|
`${this.config.baseUrl}/models/${model}:generateContent`,
|
|
@@ -1953,22 +2151,28 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
1953
2151
|
method: "POST",
|
|
1954
2152
|
headers: this.authHeaders(),
|
|
1955
2153
|
body: JSON.stringify({
|
|
1956
|
-
systemInstruction:
|
|
2154
|
+
systemInstruction: systemPrompt && systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
|
|
1957
2155
|
contents: toGeminiContents(request.messages),
|
|
1958
2156
|
generationConfig: {
|
|
1959
|
-
temperature: request.temperature
|
|
1960
|
-
maxOutputTokens: request.maxTokens
|
|
1961
|
-
topP: request.topP
|
|
2157
|
+
temperature: request.temperature ?? 0.7,
|
|
2158
|
+
maxOutputTokens: request.maxTokens ?? 2048,
|
|
2159
|
+
topP: request.topP ?? 0.9
|
|
1962
2160
|
}
|
|
1963
2161
|
})
|
|
1964
2162
|
}
|
|
1965
2163
|
);
|
|
1966
2164
|
});
|
|
1967
2165
|
if (!response.ok) {
|
|
1968
|
-
|
|
2166
|
+
const message = await extractErrorMessageFromResponse(response);
|
|
2167
|
+
throw new AIError(
|
|
2168
|
+
classifyHttpStatus(response.status),
|
|
2169
|
+
`Gemini API error (${response.status}): ${message}`,
|
|
2170
|
+
void 0,
|
|
2171
|
+
{ status: response.status, model }
|
|
2172
|
+
);
|
|
1969
2173
|
}
|
|
1970
2174
|
const data = await response.json();
|
|
1971
|
-
const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
|
|
2175
|
+
const text = data.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
|
|
1972
2176
|
if (text.length === 0) {
|
|
1973
2177
|
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
|
|
1974
2178
|
}
|
|
@@ -1981,7 +2185,7 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
1981
2185
|
} : void 0,
|
|
1982
2186
|
model,
|
|
1983
2187
|
providerId: this.id,
|
|
1984
|
-
finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
|
|
2188
|
+
finishReason: mapGeminiFinishReason(data.candidates?.[0]?.finishReason)
|
|
1985
2189
|
};
|
|
1986
2190
|
} catch (error) {
|
|
1987
2191
|
if (error instanceof AIError) throw error;
|
|
@@ -2033,12 +2237,18 @@ var GeminiProvider = class extends BaseProvider {
|
|
|
2033
2237
|
);
|
|
2034
2238
|
});
|
|
2035
2239
|
if (!response.ok) {
|
|
2036
|
-
|
|
2240
|
+
const message = await extractErrorMessageFromResponse(response);
|
|
2241
|
+
throw new AIError(
|
|
2242
|
+
classifyHttpStatus(response.status),
|
|
2243
|
+
`Gemini image generation error (${response.status}): ${message}`,
|
|
2244
|
+
void 0,
|
|
2245
|
+
{ status: response.status }
|
|
2246
|
+
);
|
|
2037
2247
|
}
|
|
2038
2248
|
const data = await response.json();
|
|
2039
|
-
const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
|
|
2249
|
+
const imageData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData;
|
|
2040
2250
|
if (!imageData) {
|
|
2041
|
-
throw new
|
|
2251
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image data in Gemini response");
|
|
2042
2252
|
}
|
|
2043
2253
|
const url = `data:${imageData.mimeType};base64,${imageData.data}`;
|
|
2044
2254
|
return {
|
|
@@ -2467,17 +2677,70 @@ var PrunaProvider = class {
|
|
|
2467
2677
|
async estimateCost(_request) {
|
|
2468
2678
|
return 1;
|
|
2469
2679
|
}
|
|
2470
|
-
async generateImage(
|
|
2471
|
-
|
|
2680
|
+
async generateImage(request) {
|
|
2681
|
+
let finalUrl;
|
|
2682
|
+
const model = this.config.models?.textToImage;
|
|
2683
|
+
for await (const stage of generateWithPruna(
|
|
2684
|
+
{ prompt: request.prompt, model, quality: request.quality },
|
|
2685
|
+
this.config.apiKey
|
|
2686
|
+
)) {
|
|
2687
|
+
if (stage.error) throw stage.error;
|
|
2688
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
2689
|
+
}
|
|
2690
|
+
if (!finalUrl) {
|
|
2691
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
|
|
2692
|
+
}
|
|
2693
|
+
return {
|
|
2694
|
+
id: Date.now().toString(),
|
|
2695
|
+
type: "image",
|
|
2696
|
+
url: finalUrl,
|
|
2697
|
+
metadata: { provider: this.id, model: model ?? "p-image" },
|
|
2698
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2699
|
+
};
|
|
2472
2700
|
}
|
|
2473
|
-
async generateVideo(
|
|
2474
|
-
|
|
2701
|
+
async generateVideo(request) {
|
|
2702
|
+
let finalUrl;
|
|
2703
|
+
const model = this.config.models?.textToVideo;
|
|
2704
|
+
for await (const stage of generateWithPruna(
|
|
2705
|
+
{ prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
|
|
2706
|
+
this.config.apiKey
|
|
2707
|
+
)) {
|
|
2708
|
+
if (stage.error) throw stage.error;
|
|
2709
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
2710
|
+
}
|
|
2711
|
+
if (!finalUrl) {
|
|
2712
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
|
|
2713
|
+
}
|
|
2714
|
+
return {
|
|
2715
|
+
id: Date.now().toString(),
|
|
2716
|
+
type: "video",
|
|
2717
|
+
url: finalUrl,
|
|
2718
|
+
metadata: { provider: this.id, model: model ?? "p-video" },
|
|
2719
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2720
|
+
};
|
|
2475
2721
|
}
|
|
2476
|
-
async imageToVideo(
|
|
2477
|
-
|
|
2722
|
+
async imageToVideo(request) {
|
|
2723
|
+
let finalUrl;
|
|
2724
|
+
for await (const stage of generateWithPruna(
|
|
2725
|
+
{ image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
|
|
2726
|
+
this.config.apiKey
|
|
2727
|
+
)) {
|
|
2728
|
+
if (stage.error) throw stage.error;
|
|
2729
|
+
if (stage.result?.url) finalUrl = stage.result.url;
|
|
2730
|
+
}
|
|
2731
|
+
if (!finalUrl) {
|
|
2732
|
+
throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
|
|
2733
|
+
}
|
|
2734
|
+
return {
|
|
2735
|
+
id: Date.now().toString(),
|
|
2736
|
+
type: "video",
|
|
2737
|
+
url: finalUrl,
|
|
2738
|
+
metadata: { provider: this.id, model: "p-image-to-video" },
|
|
2739
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2740
|
+
};
|
|
2478
2741
|
}
|
|
2479
2742
|
async videoToVideo(_request) {
|
|
2480
|
-
throw new AIError("
|
|
2743
|
+
throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
|
|
2481
2744
|
}
|
|
2482
2745
|
};
|
|
2483
2746
|
|
|
@@ -3084,11 +3347,12 @@ var buildScriptResult = (parsed, duration) => {
|
|
|
3084
3347
|
if (!script) {
|
|
3085
3348
|
throw new Error("AI response missing script field");
|
|
3086
3349
|
}
|
|
3350
|
+
const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
|
|
3087
3351
|
return {
|
|
3088
3352
|
...parsed,
|
|
3089
3353
|
script,
|
|
3090
3354
|
duration,
|
|
3091
|
-
wordCount
|
|
3355
|
+
wordCount
|
|
3092
3356
|
};
|
|
3093
3357
|
};
|
|
3094
3358
|
var VideoScriptService = class {
|
|
@@ -3283,7 +3547,6 @@ var SeoService = class {
|
|
|
3283
3547
|
analyzeKeywords(content, keywords) {
|
|
3284
3548
|
const validatedContent = validateContentBody(content);
|
|
3285
3549
|
const validatedKeywords = validateKeywordList(keywords);
|
|
3286
|
-
void countWordsInContent(validatedContent);
|
|
3287
3550
|
return validatedKeywords.map((keyword) => {
|
|
3288
3551
|
const count = countKeywordOccurrences(validatedContent, keyword);
|
|
3289
3552
|
const density = calculateKeywordDensity(validatedContent, keyword);
|
|
@@ -3378,68 +3641,20 @@ var ABTestService = class {
|
|
|
3378
3641
|
});
|
|
3379
3642
|
const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
|
|
3380
3643
|
const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
|
|
3644
|
+
const isTie = scoreA === scoreB;
|
|
3381
3645
|
const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
|
|
3382
3646
|
const improvement = calculateImprovementPercentage(scoreA, scoreB);
|
|
3383
3647
|
return {
|
|
3384
3648
|
winner,
|
|
3385
3649
|
confidence: AB_TEST_DEFAULT_CONFIDENCE,
|
|
3386
|
-
improvement: `+${improvement}% engagement`,
|
|
3387
|
-
reasoning: winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
|
|
3650
|
+
improvement: isTie ? "0%" : `+${improvement}% engagement`,
|
|
3651
|
+
reasoning: isTie ? "Both variants have equal predicted engagement" : winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
|
|
3388
3652
|
recommendations: [...DEFAULT_RECOMMENDATIONS]
|
|
3389
3653
|
};
|
|
3390
3654
|
}
|
|
3391
3655
|
};
|
|
3392
3656
|
|
|
3393
|
-
// src/content/infrastructure/utils/jsonParser.ts
|
|
3394
|
-
function repairCommonJsonDefects(text) {
|
|
3395
|
-
return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
|
|
3396
|
-
}
|
|
3397
|
-
function safeJSONParse(text, fallback, onError) {
|
|
3398
|
-
try {
|
|
3399
|
-
return JSON.parse(text);
|
|
3400
|
-
} catch (error) {
|
|
3401
|
-
try {
|
|
3402
|
-
return JSON.parse(repairCommonJsonDefects(text));
|
|
3403
|
-
} catch {
|
|
3404
|
-
}
|
|
3405
|
-
if (error instanceof SyntaxError) {
|
|
3406
|
-
const positionMatch = error.message.match(/position (\d+)/);
|
|
3407
|
-
const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
|
|
3408
|
-
if (position > 0) {
|
|
3409
|
-
text.substring(Math.max(0, position - 50), position + 50);
|
|
3410
|
-
}
|
|
3411
|
-
}
|
|
3412
|
-
return fallback;
|
|
3413
|
-
}
|
|
3414
|
-
}
|
|
3415
|
-
function extractJSON(text) {
|
|
3416
|
-
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
3417
|
-
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
3418
|
-
const candidate = codeBlockMatch[1].trim();
|
|
3419
|
-
if (candidate.startsWith("{") || candidate.startsWith("[")) {
|
|
3420
|
-
return candidate;
|
|
3421
|
-
}
|
|
3422
|
-
}
|
|
3423
|
-
const objectMatch = text.match(/\{[\s\S]*\}/);
|
|
3424
|
-
if (objectMatch) {
|
|
3425
|
-
return objectMatch[0];
|
|
3426
|
-
}
|
|
3427
|
-
const arrayMatch = text.match(/\[[\s\S]*\]/);
|
|
3428
|
-
if (arrayMatch) {
|
|
3429
|
-
return arrayMatch[0];
|
|
3430
|
-
}
|
|
3431
|
-
return null;
|
|
3432
|
-
}
|
|
3433
|
-
function parseAIResponse(response, fallback, onError) {
|
|
3434
|
-
const json = extractJSON(response);
|
|
3435
|
-
if (!json) {
|
|
3436
|
-
return fallback;
|
|
3437
|
-
}
|
|
3438
|
-
return safeJSONParse(json, fallback);
|
|
3439
|
-
}
|
|
3440
|
-
|
|
3441
3657
|
// src/content/application/services/ImagePromptService.ts
|
|
3442
|
-
var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
|
|
3443
3658
|
var ImagePromptService = class {
|
|
3444
3659
|
constructor(executor) {
|
|
3445
3660
|
this.executor = executor;
|
|
@@ -3453,10 +3668,11 @@ var ImagePromptService = class {
|
|
|
3453
3668
|
async generateImagePromptsForBlog(blogContent) {
|
|
3454
3669
|
const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
|
|
3455
3670
|
const prompt = PromptBuilder.blogImagePrompts(truncated);
|
|
3456
|
-
const
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3671
|
+
const prompts = await this.executor.runJson(prompt, [], {
|
|
3672
|
+
maxTokens: 500,
|
|
3673
|
+
temperature: 0.8
|
|
3674
|
+
});
|
|
3675
|
+
return Array.isArray(prompts) ? prompts : [];
|
|
3460
3676
|
}
|
|
3461
3677
|
};
|
|
3462
3678
|
|
|
@@ -3500,22 +3716,27 @@ var ContentCalendarService = class {
|
|
|
3500
3716
|
const validatedNiche = validateTopicText(niche);
|
|
3501
3717
|
const validatedDays = validateCalendarDays(days);
|
|
3502
3718
|
const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
|
|
3503
|
-
const data = await this.executor.runJson(
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3719
|
+
const data = await this.executor.runJson(
|
|
3720
|
+
prompt,
|
|
3721
|
+
[],
|
|
3722
|
+
{
|
|
3723
|
+
maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
|
|
3724
|
+
temperature: 0.8
|
|
3725
|
+
}
|
|
3726
|
+
);
|
|
3727
|
+
const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
|
|
3728
|
+
return items.map(toCalendarEntry);
|
|
3508
3729
|
}
|
|
3509
3730
|
};
|
|
3510
3731
|
|
|
3511
3732
|
// src/content/application/services/ProviderServices.ts
|
|
3512
3733
|
var wrapProviderError = (operation, error) => {
|
|
3513
3734
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3514
|
-
return new
|
|
3735
|
+
return new AIError(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
|
|
3515
3736
|
};
|
|
3516
3737
|
var ensureFactory = (factory, operation) => {
|
|
3517
3738
|
if (factory.getAllProviders().length === 0) {
|
|
3518
|
-
throw new
|
|
3739
|
+
throw new AIError(
|
|
3519
3740
|
`${operation} failed: No providers are registered. Provide a ProviderConfig with at least one enabled provider when constructing AIContentService.`,
|
|
3520
3741
|
"PROVIDER_UNAVAILABLE"
|
|
3521
3742
|
);
|
|
@@ -3552,6 +3773,54 @@ var MediaGenerationService = class {
|
|
|
3552
3773
|
}
|
|
3553
3774
|
};
|
|
3554
3775
|
|
|
3776
|
+
// src/content/infrastructure/utils/jsonParser.ts
|
|
3777
|
+
function repairCommonJsonDefects2(text) {
|
|
3778
|
+
return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
|
|
3779
|
+
}
|
|
3780
|
+
function safeJSONParse(text, fallback, onError) {
|
|
3781
|
+
try {
|
|
3782
|
+
return JSON.parse(text);
|
|
3783
|
+
} catch (error) {
|
|
3784
|
+
try {
|
|
3785
|
+
return JSON.parse(repairCommonJsonDefects2(text));
|
|
3786
|
+
} catch {
|
|
3787
|
+
}
|
|
3788
|
+
if (error instanceof SyntaxError) {
|
|
3789
|
+
const positionMatch = error.message.match(/position (\d+)/);
|
|
3790
|
+
const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
|
|
3791
|
+
if (position > 0) {
|
|
3792
|
+
text.substring(Math.max(0, position - 50), position + 50);
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
return fallback;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
function extractJSON(text) {
|
|
3799
|
+
const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
3800
|
+
if (codeBlockMatch && codeBlockMatch[1]) {
|
|
3801
|
+
const candidate = codeBlockMatch[1].trim();
|
|
3802
|
+
if (candidate.startsWith("{") || candidate.startsWith("[")) {
|
|
3803
|
+
return candidate;
|
|
3804
|
+
}
|
|
3805
|
+
}
|
|
3806
|
+
const objectMatch = text.match(/\{[\s\S]*\}/);
|
|
3807
|
+
if (objectMatch) {
|
|
3808
|
+
return objectMatch[0];
|
|
3809
|
+
}
|
|
3810
|
+
const arrayMatch = text.match(/\[[\s\S]*\]/);
|
|
3811
|
+
if (arrayMatch) {
|
|
3812
|
+
return arrayMatch[0];
|
|
3813
|
+
}
|
|
3814
|
+
return null;
|
|
3815
|
+
}
|
|
3816
|
+
function parseAIResponse(response, fallback, onError) {
|
|
3817
|
+
const json = extractJSON(response);
|
|
3818
|
+
if (!json) {
|
|
3819
|
+
return fallback;
|
|
3820
|
+
}
|
|
3821
|
+
return safeJSONParse(json, fallback);
|
|
3822
|
+
}
|
|
3823
|
+
|
|
3555
3824
|
// src/content/application/services/GenerationExecutor.ts
|
|
3556
3825
|
var GenerationExecutor = class {
|
|
3557
3826
|
constructor(generator) {
|
|
@@ -3568,7 +3837,7 @@ var GenerationExecutor = class {
|
|
|
3568
3837
|
/** Usage-complete pass-through; requires a ProviderFactory engine. */
|
|
3569
3838
|
runChat(request) {
|
|
3570
3839
|
if (!(this.generator instanceof ProviderFactory)) {
|
|
3571
|
-
throw new
|
|
3840
|
+
throw new AIError(
|
|
3572
3841
|
"runChat requires a ProviderFactory engine \u2014 construct AIContentService with a provider config, not a custom ITextGenerator",
|
|
3573
3842
|
"PROVIDER_UNAVAILABLE"
|
|
3574
3843
|
);
|
|
@@ -3582,6 +3851,13 @@ var isTextGenerator = (value) => typeof value === "object" && value !== null &&
|
|
|
3582
3851
|
function toAIProviderConfig(config) {
|
|
3583
3852
|
const legacy = config;
|
|
3584
3853
|
const modern = config;
|
|
3854
|
+
const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
|
|
3855
|
+
apiKey: legacy.anthropic.apiKey,
|
|
3856
|
+
baseUrl: legacy.anthropic.baseUrl,
|
|
3857
|
+
timeoutMs: legacy.anthropic.timeout,
|
|
3858
|
+
models: legacy.anthropic.models,
|
|
3859
|
+
effort: legacy.anthropic.effort
|
|
3860
|
+
} : void 0);
|
|
3585
3861
|
const groq = modern.groq ?? (legacy.groq?.enabled ? {
|
|
3586
3862
|
apiKey: legacy.groq.apiKey,
|
|
3587
3863
|
baseUrl: legacy.groq.baseUrl,
|
|
@@ -3596,7 +3872,7 @@ function toAIProviderConfig(config) {
|
|
|
3596
3872
|
priority: modern.priority,
|
|
3597
3873
|
fallbackEnabled: modern.fallbackEnabled,
|
|
3598
3874
|
defaultTier: modern.defaultTier,
|
|
3599
|
-
anthropic:
|
|
3875
|
+
...anthropic ? { anthropic } : {},
|
|
3600
3876
|
...groq ? { groq } : {},
|
|
3601
3877
|
...gemini ? { gemini } : {},
|
|
3602
3878
|
...fal ? { fal } : {},
|
|
@@ -3762,8 +4038,8 @@ function useAIContentContext(options) {
|
|
|
3762
4038
|
callbacksRef.current?.onProgress?.(value);
|
|
3763
4039
|
}, []);
|
|
3764
4040
|
const reportError = react.useCallback((err, fallbackMessage) => {
|
|
3765
|
-
const aiError =
|
|
3766
|
-
const finalError = aiError.message ? aiError : new
|
|
4041
|
+
const aiError = AIError.from(err, "UNKNOWN");
|
|
4042
|
+
const finalError = aiError.message ? aiError : new AIError(fallbackMessage, "UNKNOWN", err);
|
|
3767
4043
|
if (mountedRef.current) {
|
|
3768
4044
|
setError(finalError.message);
|
|
3769
4045
|
setErrorCode(finalError.code);
|
|
@@ -3789,7 +4065,7 @@ function useAIContentContext(options) {
|
|
|
3789
4065
|
}
|
|
3790
4066
|
return result;
|
|
3791
4067
|
} catch (err) {
|
|
3792
|
-
const aiError =
|
|
4068
|
+
const aiError = AIError.from(err, "UNKNOWN");
|
|
3793
4069
|
if (aiError.message) {
|
|
3794
4070
|
reportError(aiError, fallbackMessage);
|
|
3795
4071
|
} else {
|
|
@@ -4353,10 +4629,13 @@ function useWizardFlow(options) {
|
|
|
4353
4629
|
if (steps.length <= 1) return 0;
|
|
4354
4630
|
return Math.round(currentIndex / (steps.length - 1) * 100);
|
|
4355
4631
|
}, [currentIndex, steps.length]);
|
|
4632
|
+
const completedRef = react.useRef(false);
|
|
4356
4633
|
const navigateTo = react.useCallback((to) => {
|
|
4357
4634
|
setCurrentStep((prev) => {
|
|
4358
4635
|
if (prev === to) return prev;
|
|
4359
|
-
|
|
4636
|
+
queueMicrotask(() => {
|
|
4637
|
+
optionsRef.current.onStepChange?.(prev, to);
|
|
4638
|
+
});
|
|
4360
4639
|
return to;
|
|
4361
4640
|
});
|
|
4362
4641
|
}, []);
|
|
@@ -4397,13 +4676,17 @@ function useWizardFlow(options) {
|
|
|
4397
4676
|
[currentIndex, steps.length]
|
|
4398
4677
|
);
|
|
4399
4678
|
const reset = react.useCallback(() => {
|
|
4679
|
+
completedRef.current = false;
|
|
4400
4680
|
setCurrentStep(optionsRef.current.initialStep);
|
|
4401
4681
|
setWizardData({});
|
|
4402
4682
|
setIsProcessing(false);
|
|
4403
4683
|
}, []);
|
|
4404
4684
|
react.useEffect(() => {
|
|
4405
|
-
if (currentIndex === steps.length - 1 && steps.length > 0) {
|
|
4685
|
+
if (currentIndex === steps.length - 1 && steps.length > 0 && !completedRef.current) {
|
|
4686
|
+
completedRef.current = true;
|
|
4406
4687
|
optionsRef.current.onComplete?.(wizardData);
|
|
4688
|
+
} else if (currentIndex < steps.length - 1) {
|
|
4689
|
+
completedRef.current = false;
|
|
4407
4690
|
}
|
|
4408
4691
|
}, [currentIndex, steps.length, wizardData]);
|
|
4409
4692
|
return {
|
|
@@ -4632,21 +4915,17 @@ function useText(options = {}, deps) {
|
|
|
4632
4915
|
]
|
|
4633
4916
|
);
|
|
4634
4917
|
react.useEffect(() => {
|
|
4635
|
-
const controller = new AbortController();
|
|
4636
|
-
abortControllerRef.current = controller;
|
|
4637
4918
|
return () => {
|
|
4638
|
-
|
|
4919
|
+
abortControllerRef.current?.abort();
|
|
4639
4920
|
abortControllerRef.current = null;
|
|
4640
4921
|
};
|
|
4641
4922
|
}, []);
|
|
4642
4923
|
const runWithState = react.useCallback(
|
|
4643
4924
|
async (task) => {
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
}
|
|
4925
|
+
abortControllerRef.current?.abort();
|
|
4926
|
+
const controller = new AbortController();
|
|
4927
|
+
abortControllerRef.current = controller;
|
|
4648
4928
|
const { signal } = controller;
|
|
4649
|
-
if (signal.aborted) return null;
|
|
4650
4929
|
setIsLoading(true);
|
|
4651
4930
|
setError(null);
|
|
4652
4931
|
setResult(null);
|
|
@@ -4667,6 +4946,9 @@ function useText(options = {}, deps) {
|
|
|
4667
4946
|
if (!signal.aborted) {
|
|
4668
4947
|
setIsLoading(false);
|
|
4669
4948
|
}
|
|
4949
|
+
if (abortControllerRef.current === controller) {
|
|
4950
|
+
abortControllerRef.current = null;
|
|
4951
|
+
}
|
|
4670
4952
|
}
|
|
4671
4953
|
},
|
|
4672
4954
|
[]
|
|
@@ -4731,6 +5013,7 @@ function useText(options = {}, deps) {
|
|
|
4731
5013
|
);
|
|
4732
5014
|
const cancel = react.useCallback(() => {
|
|
4733
5015
|
abortControllerRef.current?.abort();
|
|
5016
|
+
abortControllerRef.current = null;
|
|
4734
5017
|
setIsLoading(false);
|
|
4735
5018
|
}, []);
|
|
4736
5019
|
const reset = react.useCallback(() => {
|
|
@@ -4873,10 +5156,8 @@ function useChat(options, deps) {
|
|
|
4873
5156
|
};
|
|
4874
5157
|
}, [conversationId, surfaceError]);
|
|
4875
5158
|
react.useEffect(() => {
|
|
4876
|
-
const controller = new AbortController();
|
|
4877
|
-
abortControllerRef.current = controller;
|
|
4878
5159
|
return () => {
|
|
4879
|
-
|
|
5160
|
+
abortControllerRef.current?.abort();
|
|
4880
5161
|
abortControllerRef.current = null;
|
|
4881
5162
|
};
|
|
4882
5163
|
}, []);
|
|
@@ -4917,9 +5198,11 @@ function useChat(options, deps) {
|
|
|
4917
5198
|
});
|
|
4918
5199
|
void persist(userMessage);
|
|
4919
5200
|
callbacksRef.current.onMessageSent?.(userMessage);
|
|
5201
|
+
let streamingPlaceholderId = null;
|
|
4920
5202
|
try {
|
|
4921
5203
|
if (streamEnabled) {
|
|
4922
5204
|
const placeholderId = chatIdGenerator.next("assistant");
|
|
5205
|
+
streamingPlaceholderId = placeholderId;
|
|
4923
5206
|
const placeholder = {
|
|
4924
5207
|
id: placeholderId,
|
|
4925
5208
|
sender: "assistant",
|
|
@@ -4938,7 +5221,8 @@ function useChat(options, deps) {
|
|
|
4938
5221
|
(m) => m.id === placeholderId ? { ...m, content: m.content + chunk } : m
|
|
4939
5222
|
)
|
|
4940
5223
|
);
|
|
4941
|
-
}
|
|
5224
|
+
},
|
|
5225
|
+
controller.signal
|
|
4942
5226
|
);
|
|
4943
5227
|
const finalMessage = {
|
|
4944
5228
|
...placeholder,
|
|
@@ -4955,13 +5239,18 @@ function useChat(options, deps) {
|
|
|
4955
5239
|
const aiResponse = await chatService.generateAIResponse(
|
|
4956
5240
|
conversationId,
|
|
4957
5241
|
trimmed,
|
|
4958
|
-
contextForAI
|
|
5242
|
+
contextForAI,
|
|
5243
|
+
controller.signal
|
|
4959
5244
|
);
|
|
4960
5245
|
setMessages((prev) => [...prev, aiResponse]);
|
|
4961
5246
|
void persist(aiResponse);
|
|
4962
5247
|
callbacksRef.current.onAIResponse?.(aiResponse);
|
|
4963
5248
|
return aiResponse;
|
|
4964
5249
|
} catch (err) {
|
|
5250
|
+
if (streamingPlaceholderId) {
|
|
5251
|
+
const failedId = streamingPlaceholderId;
|
|
5252
|
+
setMessages((prev) => prev.filter((m) => m.id !== failedId));
|
|
5253
|
+
}
|
|
4965
5254
|
surfaceError(err);
|
|
4966
5255
|
return null;
|
|
4967
5256
|
} finally {
|
|
@@ -4987,6 +5276,9 @@ function useChat(options, deps) {
|
|
|
4987
5276
|
const lastUserMessage = [...currentMessages].reverse().find((m) => m.sender === "user");
|
|
4988
5277
|
if (!lastUserMessage) return null;
|
|
4989
5278
|
const trimmed = currentMessages.length > 0 && currentMessages[currentMessages.length - 1].sender === "assistant" ? currentMessages.slice(0, -1) : currentMessages;
|
|
5279
|
+
const controller = new AbortController();
|
|
5280
|
+
abortControllerRef.current?.abort();
|
|
5281
|
+
abortControllerRef.current = controller;
|
|
4990
5282
|
setIsLoading(true);
|
|
4991
5283
|
setError(null);
|
|
4992
5284
|
setIsTyping(true);
|
|
@@ -4994,7 +5286,8 @@ function useChat(options, deps) {
|
|
|
4994
5286
|
const aiResponse = await chatService.generateAIResponse(
|
|
4995
5287
|
conversationId,
|
|
4996
5288
|
lastUserMessage.content,
|
|
4997
|
-
trimmed
|
|
5289
|
+
trimmed,
|
|
5290
|
+
controller.signal
|
|
4998
5291
|
);
|
|
4999
5292
|
setMessages((prev) => {
|
|
5000
5293
|
const lastIsAI = prev.length > 0 && prev[prev.length - 1].sender === "assistant";
|
|
@@ -5010,6 +5303,9 @@ function useChat(options, deps) {
|
|
|
5010
5303
|
} finally {
|
|
5011
5304
|
setIsLoading(false);
|
|
5012
5305
|
setIsTyping(false);
|
|
5306
|
+
if (abortControllerRef.current === controller) {
|
|
5307
|
+
abortControllerRef.current = null;
|
|
5308
|
+
}
|
|
5013
5309
|
}
|
|
5014
5310
|
}, [chatService, conversationId, isLoading, messages, persist, surfaceError]);
|
|
5015
5311
|
const clearMessages = react.useCallback(() => {
|
|
@@ -5042,12 +5338,168 @@ function useChat(options, deps) {
|
|
|
5042
5338
|
};
|
|
5043
5339
|
}
|
|
5044
5340
|
|
|
5341
|
+
// src/providers/groq/groq-container.ts
|
|
5342
|
+
var container = null;
|
|
5343
|
+
function getProvider() {
|
|
5344
|
+
return container;
|
|
5345
|
+
}
|
|
5346
|
+
|
|
5347
|
+
// src/hooks/use-groq.hook.ts
|
|
5348
|
+
function useGroq(options = {}, deps) {
|
|
5349
|
+
const textGeneration = deps?.textGeneration ?? getProvider()?.textGeneration;
|
|
5350
|
+
if (!textGeneration) {
|
|
5351
|
+
throw new Error(
|
|
5352
|
+
"useGroq requires a TextGenerationService dependency. Provide it via the second argument or wire it through configureProvider()."
|
|
5353
|
+
);
|
|
5354
|
+
}
|
|
5355
|
+
const [isLoading, setIsLoading] = react.useState(false);
|
|
5356
|
+
const [error, setError] = react.useState(null);
|
|
5357
|
+
const [result, setResult] = react.useState(null);
|
|
5358
|
+
const optionsRef = react.useRef(options);
|
|
5359
|
+
optionsRef.current = options;
|
|
5360
|
+
const abortControllerRef = react.useRef(null);
|
|
5361
|
+
const getOrCreateAbortController = react.useCallback(() => {
|
|
5362
|
+
if (!abortControllerRef.current || abortControllerRef.current.signal.aborted) {
|
|
5363
|
+
abortControllerRef.current = new AbortController();
|
|
5364
|
+
}
|
|
5365
|
+
return abortControllerRef.current;
|
|
5366
|
+
}, []);
|
|
5367
|
+
const stableConfig = react.useMemo(
|
|
5368
|
+
() => ({
|
|
5369
|
+
model: options.model,
|
|
5370
|
+
generationConfig: options.generationConfig
|
|
5371
|
+
}),
|
|
5372
|
+
[
|
|
5373
|
+
options.model,
|
|
5374
|
+
options.generationConfig?.temperature,
|
|
5375
|
+
options.generationConfig?.maxTokens,
|
|
5376
|
+
options.generationConfig?.topP
|
|
5377
|
+
]
|
|
5378
|
+
);
|
|
5379
|
+
react.useEffect(() => {
|
|
5380
|
+
abortControllerRef.current = new AbortController();
|
|
5381
|
+
return () => {
|
|
5382
|
+
abortControllerRef.current?.abort();
|
|
5383
|
+
abortControllerRef.current = null;
|
|
5384
|
+
};
|
|
5385
|
+
}, []);
|
|
5386
|
+
const runWithState = react.useCallback(
|
|
5387
|
+
async (task) => {
|
|
5388
|
+
const controller = getOrCreateAbortController();
|
|
5389
|
+
const { signal } = controller;
|
|
5390
|
+
if (signal.aborted) return null;
|
|
5391
|
+
setIsLoading(true);
|
|
5392
|
+
setError(null);
|
|
5393
|
+
setResult(null);
|
|
5394
|
+
const currentOptions = optionsRef.current;
|
|
5395
|
+
currentOptions.onStart?.();
|
|
5396
|
+
try {
|
|
5397
|
+
const value = await task(signal);
|
|
5398
|
+
return value;
|
|
5399
|
+
} catch (err) {
|
|
5400
|
+
if (signal.aborted) {
|
|
5401
|
+
return null;
|
|
5402
|
+
}
|
|
5403
|
+
const message = resolveUserFriendlyMessage(err);
|
|
5404
|
+
setError(message);
|
|
5405
|
+
currentOptions.onError?.(message);
|
|
5406
|
+
throw err;
|
|
5407
|
+
} finally {
|
|
5408
|
+
if (!signal.aborted) {
|
|
5409
|
+
setIsLoading(false);
|
|
5410
|
+
}
|
|
5411
|
+
}
|
|
5412
|
+
},
|
|
5413
|
+
[getOrCreateAbortController]
|
|
5414
|
+
);
|
|
5415
|
+
const generate = react.useCallback(
|
|
5416
|
+
(prompt, config) => runWithState(async () => {
|
|
5417
|
+
const response = await textGeneration.generateCompletion(prompt, {
|
|
5418
|
+
model: stableConfig.model,
|
|
5419
|
+
generationConfig: {
|
|
5420
|
+
...stableConfig.generationConfig,
|
|
5421
|
+
...config
|
|
5422
|
+
}
|
|
5423
|
+
});
|
|
5424
|
+
setResult(response);
|
|
5425
|
+
optionsRef.current.onSuccess?.(response);
|
|
5426
|
+
return response;
|
|
5427
|
+
}),
|
|
5428
|
+
[runWithState, textGeneration, stableConfig]
|
|
5429
|
+
);
|
|
5430
|
+
const generateJSON = react.useCallback(
|
|
5431
|
+
(prompt, config) => runWithState(async () => {
|
|
5432
|
+
const response = await textGeneration.generateStructured(prompt, {
|
|
5433
|
+
model: stableConfig.model,
|
|
5434
|
+
generationConfig: {
|
|
5435
|
+
...stableConfig.generationConfig,
|
|
5436
|
+
...config
|
|
5437
|
+
},
|
|
5438
|
+
...config?.schema !== void 0 ? { schema: config.schema } : {}
|
|
5439
|
+
});
|
|
5440
|
+
const json = JSON.stringify(response, null, 2);
|
|
5441
|
+
setResult(json);
|
|
5442
|
+
optionsRef.current.onSuccess?.(json);
|
|
5443
|
+
return response;
|
|
5444
|
+
}),
|
|
5445
|
+
[runWithState, textGeneration, stableConfig]
|
|
5446
|
+
);
|
|
5447
|
+
const stream = react.useCallback(
|
|
5448
|
+
(prompt, onChunk, config) => runWithState(async () => {
|
|
5449
|
+
const callbacks = {
|
|
5450
|
+
onChunk,
|
|
5451
|
+
onComplete: (text) => {
|
|
5452
|
+
setResult(text);
|
|
5453
|
+
optionsRef.current.onSuccess?.(text);
|
|
5454
|
+
}
|
|
5455
|
+
};
|
|
5456
|
+
return textGeneration.streamCompletion(
|
|
5457
|
+
prompt,
|
|
5458
|
+
callbacks,
|
|
5459
|
+
{
|
|
5460
|
+
model: stableConfig.model,
|
|
5461
|
+
generationConfig: {
|
|
5462
|
+
...stableConfig.generationConfig,
|
|
5463
|
+
...config
|
|
5464
|
+
}
|
|
5465
|
+
}
|
|
5466
|
+
);
|
|
5467
|
+
}),
|
|
5468
|
+
[runWithState, textGeneration, stableConfig]
|
|
5469
|
+
);
|
|
5470
|
+
const cancel = react.useCallback(() => {
|
|
5471
|
+
if (abortControllerRef.current) {
|
|
5472
|
+
abortControllerRef.current.abort();
|
|
5473
|
+
abortControllerRef.current = new AbortController();
|
|
5474
|
+
}
|
|
5475
|
+
setIsLoading(false);
|
|
5476
|
+
}, []);
|
|
5477
|
+
const reset = react.useCallback(() => {
|
|
5478
|
+
cancel();
|
|
5479
|
+
setError(null);
|
|
5480
|
+
setResult(null);
|
|
5481
|
+
}, [cancel]);
|
|
5482
|
+
const clearError = react.useCallback(() => setError(null), []);
|
|
5483
|
+
return {
|
|
5484
|
+
isLoading,
|
|
5485
|
+
error,
|
|
5486
|
+
result,
|
|
5487
|
+
generate,
|
|
5488
|
+
generateJSON,
|
|
5489
|
+
stream,
|
|
5490
|
+
cancel,
|
|
5491
|
+
reset,
|
|
5492
|
+
clearError
|
|
5493
|
+
};
|
|
5494
|
+
}
|
|
5495
|
+
|
|
5045
5496
|
exports.useAIContent = useAIContent;
|
|
5046
5497
|
exports.useAIGeneration = useAIGeneration;
|
|
5047
5498
|
exports.useBlogGenerator = useBlogGenerator;
|
|
5048
5499
|
exports.useCacheStats = useCacheStats;
|
|
5049
5500
|
exports.useChat = useChat;
|
|
5050
5501
|
exports.useContentWizard = useContentWizard;
|
|
5502
|
+
exports.useGroq = useGroq;
|
|
5051
5503
|
exports.useImageWizard = useImageWizard;
|
|
5052
5504
|
exports.useSocialContentGenerator = useSocialContentGenerator;
|
|
5053
5505
|
exports.useText = useText;
|