@umituz/web-ai 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/{ABTesting-DDqNhHtP.d.ts → AIErrors-B2ogd1PB.d.ts} +14 -1
  2. package/dist/{ABTesting-DWBtKTMQ.d.cts → AIErrors-C5EasV_c.d.cts} +14 -1
  3. package/dist/{ProviderConfig-a55tLhWT.d.cts → ProviderConfig-DIFa9nmG.d.cts} +10 -1
  4. package/dist/{ProviderConfig-a55tLhWT.d.ts → ProviderConfig-DIFa9nmG.d.ts} +10 -1
  5. package/dist/chat/index.cjs +114 -23
  6. package/dist/chat/index.cjs.map +1 -1
  7. package/dist/chat/index.d.cts +4 -4
  8. package/dist/chat/index.d.ts +4 -4
  9. package/dist/chat/index.js +114 -23
  10. package/dist/chat/index.js.map +1 -1
  11. package/dist/{chat.service-DBKq9I2c.d.cts → chat.service-CL1qtYxe.d.cts} +7 -7
  12. package/dist/{chat.service-RQn8UTXD.d.ts → chat.service-q05GnhEm.d.ts} +7 -7
  13. package/dist/content/index.cjs +393 -186
  14. package/dist/content/index.cjs.map +1 -1
  15. package/dist/content/index.d.cts +7 -8
  16. package/dist/content/index.d.ts +7 -8
  17. package/dist/content/index.js +393 -186
  18. package/dist/content/index.js.map +1 -1
  19. package/dist/core/index.cjs +112 -15
  20. package/dist/core/index.cjs.map +1 -1
  21. package/dist/core/index.d.cts +5 -33
  22. package/dist/core/index.d.ts +5 -33
  23. package/dist/core/index.js +111 -16
  24. package/dist/core/index.js.map +1 -1
  25. package/dist/{create-ai-DGZFBOA1.d.cts → create-ai-DOI9jD3v.d.cts} +4 -21
  26. package/dist/{create-ai-BhNaz86i.d.ts → create-ai-DXEoke8-.d.ts} +4 -21
  27. package/dist/hooks/index.cjs +359 -169
  28. package/dist/hooks/index.cjs.map +1 -1
  29. package/dist/hooks/index.d.cts +6 -6
  30. package/dist/hooks/index.d.ts +6 -6
  31. package/dist/hooks/index.js +359 -169
  32. package/dist/hooks/index.js.map +1 -1
  33. package/dist/index-DIHz8_OJ.d.ts +48 -0
  34. package/dist/index-DKHNcMbl.d.cts +48 -0
  35. package/dist/index.cjs +245 -94
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +6 -6
  38. package/dist/index.d.ts +6 -6
  39. package/dist/index.js +244 -95
  40. package/dist/index.js.map +1 -1
  41. package/dist/{provider.factory-COsnprLH.d.cts → provider.factory-BxsfK4WW.d.cts} +1 -1
  42. package/dist/{provider.factory-CpWFyjbw.d.ts → provider.factory-DfDfk0dK.d.ts} +1 -1
  43. package/dist/providers/index.cjs +281 -124
  44. package/dist/providers/index.cjs.map +1 -1
  45. package/dist/providers/index.d.cts +11 -12
  46. package/dist/providers/index.d.ts +11 -12
  47. package/dist/providers/index.js +281 -124
  48. package/dist/providers/index.js.map +1 -1
  49. package/dist/wizard/index.d.cts +1 -1
  50. package/dist/wizard/index.d.ts +1 -1
  51. package/package.json +4 -1
  52. package/dist/index-DjX9ZKuM.d.cts +0 -14
  53. package/dist/index-DxLvE588.d.ts +0 -14
@@ -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 AIError = class extends Error {
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(type, message, cause, metadata = {}) {
25
- super(message);
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 = 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,6 +110,7 @@ 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
  };
@@ -224,12 +299,16 @@ var ProviderFactory = class {
224
299
  let wake = null;
225
300
  let finished = false;
226
301
  let failure;
302
+ const controller = new AbortController();
303
+ const onAbort = () => controller.abort();
304
+ request.signal?.addEventListener("abort", onAbort);
305
+ const effectiveRequest = { ...request, signal: controller.signal };
227
306
  const notify = () => {
228
307
  const resolve = wake;
229
308
  wake = null;
230
309
  resolve?.();
231
310
  };
232
- const completion = this.chatStream(request, {
311
+ const completion = this.chatStream(effectiveRequest, {
233
312
  onChunk: (chunk) => {
234
313
  queue.push(chunk);
235
314
  notify();
@@ -245,18 +324,23 @@ var ProviderFactory = class {
245
324
  notify();
246
325
  }
247
326
  );
248
- while (!finished || queue.length > 0) {
249
- if (queue.length === 0 && !finished) {
250
- await new Promise((resolve) => {
251
- wake = resolve;
252
- });
253
- }
254
- while (queue.length > 0) {
255
- yield queue.shift();
327
+ try {
328
+ while (!finished || queue.length > 0) {
329
+ if (queue.length === 0 && !finished) {
330
+ await new Promise((resolve) => {
331
+ wake = resolve;
332
+ });
333
+ }
334
+ while (queue.length > 0) {
335
+ yield queue.shift();
336
+ }
337
+ if (failure) throw failure;
256
338
  }
257
- if (failure) throw failure;
339
+ await completion;
340
+ } finally {
341
+ controller.abort();
342
+ request.signal?.removeEventListener("abort", onAbort);
258
343
  }
259
- await completion;
260
344
  }
261
345
  async generateJson(request) {
262
346
  const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
@@ -403,15 +487,22 @@ var ProviderFactory = class {
403
487
  // src/core/constants/protocol.constants.ts
404
488
  var SSE_PREFIX = "data: ";
405
489
  var SSE_DONE_MARKER = "[DONE]";
406
- var JSON_CODE_BLOCK_PATTERN = /```json\n([\s\S]*?)\n```/;
490
+ var JSON_CODE_BLOCK_PATTERN = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
407
491
  var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
492
+ var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
408
493
 
409
494
  // src/core/parsers/sse-lines.ts
410
495
  function isSseDataLine(line) {
411
- return line.startsWith(SSE_PREFIX);
496
+ return line.startsWith(SSE_PREFIX) || line.startsWith("data:");
412
497
  }
413
498
  function extractSseDataPayload(line) {
414
- return line.slice(SSE_PREFIX.length);
499
+ if (line.startsWith(SSE_PREFIX)) {
500
+ return line.slice(SSE_PREFIX.length);
501
+ }
502
+ if (line.startsWith("data:")) {
503
+ return line.slice(5);
504
+ }
505
+ return line;
415
506
  }
416
507
  function isSseDoneMarker(payload) {
417
508
  return payload === SSE_DONE_MARKER;
@@ -420,7 +511,7 @@ function isSseDoneMarker(payload) {
420
511
  // src/core/parsers/json-content-extractor.ts
421
512
  function extractJsonFromCodeFence(content) {
422
513
  const match = content.match(JSON_CODE_BLOCK_PATTERN);
423
- if (match && match[1]) return match[1];
514
+ if (match && match[1]) return match[1].trim();
424
515
  return null;
425
516
  }
426
517
  function extractJsonObject(content) {
@@ -428,8 +519,27 @@ function extractJsonObject(content) {
428
519
  if (match && match[0]) return match[0];
429
520
  return null;
430
521
  }
522
+ function extractJsonArray(content) {
523
+ const match = content.match(JSON_ARRAY_PATTERN);
524
+ if (match && match[0]) return match[0];
525
+ return null;
526
+ }
527
+ function repairCommonJsonDefects(text) {
528
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
529
+ }
431
530
  function extractJsonCandidate(content) {
432
- return extractJsonFromCodeFence(content) ?? extractJsonObject(content) ?? content;
531
+ const fromFence = extractJsonFromCodeFence(content);
532
+ if (fromFence) return fromFence;
533
+ const objectIndex = content.indexOf("{");
534
+ const arrayIndex = content.indexOf("[");
535
+ if (objectIndex !== -1 && (arrayIndex === -1 || objectIndex < arrayIndex)) {
536
+ const obj = extractJsonObject(content);
537
+ if (obj) return obj;
538
+ } else if (arrayIndex !== -1) {
539
+ const arr = extractJsonArray(content);
540
+ if (arr) return arr;
541
+ }
542
+ return content.trim();
433
543
  }
434
544
 
435
545
  // src/core/parsers/json-response.parser.ts
@@ -438,12 +548,17 @@ var JsonResponseParser = class {
438
548
  const candidate = extractJsonCandidate(rawContent);
439
549
  try {
440
550
  return JSON.parse(candidate);
441
- } catch (error) {
442
- throw new AIError(
443
- "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
444
- "Failed to parse structured response as JSON",
445
- error instanceof Error ? error : void 0
446
- );
551
+ } catch {
552
+ try {
553
+ return JSON.parse(repairCommonJsonDefects(candidate));
554
+ } catch (error) {
555
+ throw new AIError(
556
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
557
+ "Failed to parse structured response as JSON",
558
+ error instanceof Error ? error : void 0,
559
+ { rawContent }
560
+ );
561
+ }
447
562
  }
448
563
  }
449
564
  };
@@ -757,11 +872,14 @@ async function consumeAnthropicStream(stream, callbacks) {
757
872
  break;
758
873
  case "message_stop":
759
874
  break;
760
- case "error":
875
+ case "error": {
876
+ const errType = event.error.type;
877
+ 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
878
  throw new AIError(
762
- event.error.type === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
879
+ mappedType,
763
880
  event.error.message || "Anthropic stream failed mid-flight"
764
881
  );
882
+ }
765
883
  }
766
884
  };
767
885
  try {
@@ -1553,6 +1671,13 @@ async function consumeGroqStream(stream, callbacks) {
1553
1671
  } catch {
1554
1672
  return;
1555
1673
  }
1674
+ const errorBody = chunk.error;
1675
+ if (errorBody) {
1676
+ throw new AIError(
1677
+ errorBody.message?.toLowerCase().includes("rate limit") ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
1678
+ errorBody.message || "Groq stream failed mid-flight"
1679
+ );
1680
+ }
1556
1681
  if (chunk.model) model = chunk.model;
1557
1682
  const choice = chunk.choices?.[0];
1558
1683
  const delta = choice?.delta?.content;
@@ -1567,13 +1692,6 @@ async function consumeGroqStream(stream, callbacks) {
1567
1692
  if (tailUsage) {
1568
1693
  usage = mapGroqUsage(tailUsage);
1569
1694
  }
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
1695
  };
1578
1696
  try {
1579
1697
  while (true) {
@@ -1774,37 +1892,7 @@ function findLastIndex2(items, predicate) {
1774
1892
  }
1775
1893
 
1776
1894
  // src/content/domain/errors/AIErrors.ts
1777
- var AIError2 = class _AIError extends Error {
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 {
1895
+ var ValidationError = class extends AIError {
1808
1896
  constructor(message) {
1809
1897
  super(message, "VALIDATION_ERROR");
1810
1898
  this.name = "ValidationError";
@@ -1935,7 +2023,9 @@ var GeminiProvider = class extends BaseProvider {
1935
2023
  headers: this.authHeaders()
1936
2024
  }
1937
2025
  );
1938
- return response.ok ? "healthy" : "unavailable";
2026
+ if (response.ok) return "healthy";
2027
+ if (response.status === 401 || response.status === 403) return "unavailable";
2028
+ return "degraded";
1939
2029
  } catch {
1940
2030
  return "unavailable";
1941
2031
  }
@@ -1946,6 +2036,8 @@ var GeminiProvider = class extends BaseProvider {
1946
2036
  async generate(request) {
1947
2037
  try {
1948
2038
  const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
2039
+ const systemMessages = request.messages.filter((m) => m.role === "system");
2040
+ const systemPrompt = request.systemPrompt && request.systemPrompt.length > 0 ? request.systemPrompt : systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n") : void 0;
1949
2041
  const response = await this.withRetry(async () => {
1950
2042
  return await this.fetchWithTimeout(
1951
2043
  `${this.config.baseUrl}/models/${model}:generateContent`,
@@ -1953,22 +2045,28 @@ var GeminiProvider = class extends BaseProvider {
1953
2045
  method: "POST",
1954
2046
  headers: this.authHeaders(),
1955
2047
  body: JSON.stringify({
1956
- systemInstruction: request.systemPrompt && request.systemPrompt.length > 0 ? { parts: [{ text: request.systemPrompt }] } : void 0,
2048
+ systemInstruction: systemPrompt && systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
1957
2049
  contents: toGeminiContents(request.messages),
1958
2050
  generationConfig: {
1959
- temperature: request.temperature || 0.7,
1960
- maxOutputTokens: request.maxTokens || 2048,
1961
- topP: request.topP || 0.9
2051
+ temperature: request.temperature ?? 0.7,
2052
+ maxOutputTokens: request.maxTokens ?? 2048,
2053
+ topP: request.topP ?? 0.9
1962
2054
  }
1963
2055
  })
1964
2056
  }
1965
2057
  );
1966
2058
  });
1967
2059
  if (!response.ok) {
1968
- throw new Error(`Gemini API error: ${response.statusText}`);
2060
+ const message = await extractErrorMessageFromResponse(response);
2061
+ throw new AIError(
2062
+ classifyHttpStatus(response.status),
2063
+ `Gemini API error (${response.status}): ${message}`,
2064
+ void 0,
2065
+ { status: response.status, model }
2066
+ );
1969
2067
  }
1970
2068
  const data = await response.json();
1971
- const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2069
+ const text = data.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
1972
2070
  if (text.length === 0) {
1973
2071
  throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
1974
2072
  }
@@ -1981,7 +2079,7 @@ var GeminiProvider = class extends BaseProvider {
1981
2079
  } : void 0,
1982
2080
  model,
1983
2081
  providerId: this.id,
1984
- finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
2082
+ finishReason: mapGeminiFinishReason(data.candidates?.[0]?.finishReason)
1985
2083
  };
1986
2084
  } catch (error) {
1987
2085
  if (error instanceof AIError) throw error;
@@ -2033,12 +2131,18 @@ var GeminiProvider = class extends BaseProvider {
2033
2131
  );
2034
2132
  });
2035
2133
  if (!response.ok) {
2036
- throw new Error(`Gemini API error: ${response.statusText}`);
2134
+ const message = await extractErrorMessageFromResponse(response);
2135
+ throw new AIError(
2136
+ classifyHttpStatus(response.status),
2137
+ `Gemini image generation error (${response.status}): ${message}`,
2138
+ void 0,
2139
+ { status: response.status }
2140
+ );
2037
2141
  }
2038
2142
  const data = await response.json();
2039
- const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
2143
+ const imageData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData;
2040
2144
  if (!imageData) {
2041
- throw new Error("No image data in response");
2145
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image data in Gemini response");
2042
2146
  }
2043
2147
  const url = `data:${imageData.mimeType};base64,${imageData.data}`;
2044
2148
  return {
@@ -2467,17 +2571,70 @@ var PrunaProvider = class {
2467
2571
  async estimateCost(_request) {
2468
2572
  return 1;
2469
2573
  }
2470
- async generateImage(_request) {
2471
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2574
+ async generateImage(request) {
2575
+ let finalUrl;
2576
+ const model = this.config.models?.textToImage;
2577
+ for await (const stage of generateWithPruna(
2578
+ { prompt: request.prompt, model, quality: request.quality },
2579
+ this.config.apiKey
2580
+ )) {
2581
+ if (stage.error) throw stage.error;
2582
+ if (stage.result?.url) finalUrl = stage.result.url;
2583
+ }
2584
+ if (!finalUrl) {
2585
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
2586
+ }
2587
+ return {
2588
+ id: Date.now().toString(),
2589
+ type: "image",
2590
+ url: finalUrl,
2591
+ metadata: { provider: this.id, model: model ?? "p-image" },
2592
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2593
+ };
2472
2594
  }
2473
- async generateVideo(_request) {
2474
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2595
+ async generateVideo(request) {
2596
+ let finalUrl;
2597
+ const model = this.config.models?.textToVideo;
2598
+ for await (const stage of generateWithPruna(
2599
+ { prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
2600
+ this.config.apiKey
2601
+ )) {
2602
+ if (stage.error) throw stage.error;
2603
+ if (stage.result?.url) finalUrl = stage.result.url;
2604
+ }
2605
+ if (!finalUrl) {
2606
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
2607
+ }
2608
+ return {
2609
+ id: Date.now().toString(),
2610
+ type: "video",
2611
+ url: finalUrl,
2612
+ metadata: { provider: this.id, model: model ?? "p-video" },
2613
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2614
+ };
2475
2615
  }
2476
- async imageToVideo(_request) {
2477
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2616
+ async imageToVideo(request) {
2617
+ let finalUrl;
2618
+ for await (const stage of generateWithPruna(
2619
+ { image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
2620
+ this.config.apiKey
2621
+ )) {
2622
+ if (stage.error) throw stage.error;
2623
+ if (stage.result?.url) finalUrl = stage.result.url;
2624
+ }
2625
+ if (!finalUrl) {
2626
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
2627
+ }
2628
+ return {
2629
+ id: Date.now().toString(),
2630
+ type: "video",
2631
+ url: finalUrl,
2632
+ metadata: { provider: this.id, model: "p-image-to-video" },
2633
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2634
+ };
2478
2635
  }
2479
2636
  async videoToVideo(_request) {
2480
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2637
+ throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
2481
2638
  }
2482
2639
  };
2483
2640
 
@@ -3084,11 +3241,12 @@ var buildScriptResult = (parsed, duration) => {
3084
3241
  if (!script) {
3085
3242
  throw new Error("AI response missing script field");
3086
3243
  }
3244
+ const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
3087
3245
  return {
3088
3246
  ...parsed,
3089
3247
  script,
3090
3248
  duration,
3091
- wordCount: script.split(" ").length
3249
+ wordCount
3092
3250
  };
3093
3251
  };
3094
3252
  var VideoScriptService = class {
@@ -3283,7 +3441,6 @@ var SeoService = class {
3283
3441
  analyzeKeywords(content, keywords) {
3284
3442
  const validatedContent = validateContentBody(content);
3285
3443
  const validatedKeywords = validateKeywordList(keywords);
3286
- void countWordsInContent(validatedContent);
3287
3444
  return validatedKeywords.map((keyword) => {
3288
3445
  const count = countKeywordOccurrences(validatedContent, keyword);
3289
3446
  const density = calculateKeywordDensity(validatedContent, keyword);
@@ -3378,68 +3535,20 @@ var ABTestService = class {
3378
3535
  });
3379
3536
  const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3380
3537
  const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3538
+ const isTie = scoreA === scoreB;
3381
3539
  const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
3382
3540
  const improvement = calculateImprovementPercentage(scoreA, scoreB);
3383
3541
  return {
3384
3542
  winner,
3385
3543
  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",
3544
+ improvement: isTie ? "0%" : `+${improvement}% engagement`,
3545
+ 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
3546
  recommendations: [...DEFAULT_RECOMMENDATIONS]
3389
3547
  };
3390
3548
  }
3391
3549
  };
3392
3550
 
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
3551
  // src/content/application/services/ImagePromptService.ts
3442
- var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
3443
3552
  var ImagePromptService = class {
3444
3553
  constructor(executor) {
3445
3554
  this.executor = executor;
@@ -3453,10 +3562,11 @@ var ImagePromptService = class {
3453
3562
  async generateImagePromptsForBlog(blogContent) {
3454
3563
  const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
3455
3564
  const prompt = PromptBuilder.blogImagePrompts(truncated);
3456
- const response = await this.executor.runText(prompt, { maxTokens: 500, temperature: 0.8 });
3457
- const jsonMatch = response.match(JSON_ARRAY_PATTERN);
3458
- if (!jsonMatch) return [];
3459
- return safeJSONParse(jsonMatch[0], []);
3565
+ const prompts = await this.executor.runJson(prompt, [], {
3566
+ maxTokens: 500,
3567
+ temperature: 0.8
3568
+ });
3569
+ return Array.isArray(prompts) ? prompts : [];
3460
3570
  }
3461
3571
  };
3462
3572
 
@@ -3500,22 +3610,27 @@ var ContentCalendarService = class {
3500
3610
  const validatedNiche = validateTopicText(niche);
3501
3611
  const validatedDays = validateCalendarDays(days);
3502
3612
  const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
3503
- const data = await this.executor.runJson(prompt, [], {
3504
- maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3505
- temperature: 0.8
3506
- });
3507
- return data.map(toCalendarEntry);
3613
+ const data = await this.executor.runJson(
3614
+ prompt,
3615
+ [],
3616
+ {
3617
+ maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3618
+ temperature: 0.8
3619
+ }
3620
+ );
3621
+ const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
3622
+ return items.map(toCalendarEntry);
3508
3623
  }
3509
3624
  };
3510
3625
 
3511
3626
  // src/content/application/services/ProviderServices.ts
3512
3627
  var wrapProviderError = (operation, error) => {
3513
3628
  const message = error instanceof Error ? error.message : "Unknown error";
3514
- return new AIError2(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
3629
+ return new AIError(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
3515
3630
  };
3516
3631
  var ensureFactory = (factory, operation) => {
3517
3632
  if (factory.getAllProviders().length === 0) {
3518
- throw new AIError2(
3633
+ throw new AIError(
3519
3634
  `${operation} failed: No providers are registered. Provide a ProviderConfig with at least one enabled provider when constructing AIContentService.`,
3520
3635
  "PROVIDER_UNAVAILABLE"
3521
3636
  );
@@ -3552,6 +3667,54 @@ var MediaGenerationService = class {
3552
3667
  }
3553
3668
  };
3554
3669
 
3670
+ // src/content/infrastructure/utils/jsonParser.ts
3671
+ function repairCommonJsonDefects2(text) {
3672
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3673
+ }
3674
+ function safeJSONParse(text, fallback, onError) {
3675
+ try {
3676
+ return JSON.parse(text);
3677
+ } catch (error) {
3678
+ try {
3679
+ return JSON.parse(repairCommonJsonDefects2(text));
3680
+ } catch {
3681
+ }
3682
+ if (error instanceof SyntaxError) {
3683
+ const positionMatch = error.message.match(/position (\d+)/);
3684
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3685
+ if (position > 0) {
3686
+ text.substring(Math.max(0, position - 50), position + 50);
3687
+ }
3688
+ }
3689
+ return fallback;
3690
+ }
3691
+ }
3692
+ function extractJSON(text) {
3693
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3694
+ if (codeBlockMatch && codeBlockMatch[1]) {
3695
+ const candidate = codeBlockMatch[1].trim();
3696
+ if (candidate.startsWith("{") || candidate.startsWith("[")) {
3697
+ return candidate;
3698
+ }
3699
+ }
3700
+ const objectMatch = text.match(/\{[\s\S]*\}/);
3701
+ if (objectMatch) {
3702
+ return objectMatch[0];
3703
+ }
3704
+ const arrayMatch = text.match(/\[[\s\S]*\]/);
3705
+ if (arrayMatch) {
3706
+ return arrayMatch[0];
3707
+ }
3708
+ return null;
3709
+ }
3710
+ function parseAIResponse(response, fallback, onError) {
3711
+ const json = extractJSON(response);
3712
+ if (!json) {
3713
+ return fallback;
3714
+ }
3715
+ return safeJSONParse(json, fallback);
3716
+ }
3717
+
3555
3718
  // src/content/application/services/GenerationExecutor.ts
3556
3719
  var GenerationExecutor = class {
3557
3720
  constructor(generator) {
@@ -3568,7 +3731,7 @@ var GenerationExecutor = class {
3568
3731
  /** Usage-complete pass-through; requires a ProviderFactory engine. */
3569
3732
  runChat(request) {
3570
3733
  if (!(this.generator instanceof ProviderFactory)) {
3571
- throw new AIError2(
3734
+ throw new AIError(
3572
3735
  "runChat requires a ProviderFactory engine \u2014 construct AIContentService with a provider config, not a custom ITextGenerator",
3573
3736
  "PROVIDER_UNAVAILABLE"
3574
3737
  );
@@ -3582,6 +3745,13 @@ var isTextGenerator = (value) => typeof value === "object" && value !== null &&
3582
3745
  function toAIProviderConfig(config) {
3583
3746
  const legacy = config;
3584
3747
  const modern = config;
3748
+ const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
3749
+ apiKey: legacy.anthropic.apiKey,
3750
+ baseUrl: legacy.anthropic.baseUrl,
3751
+ timeoutMs: legacy.anthropic.timeout,
3752
+ models: legacy.anthropic.models,
3753
+ effort: legacy.anthropic.effort
3754
+ } : void 0);
3585
3755
  const groq = modern.groq ?? (legacy.groq?.enabled ? {
3586
3756
  apiKey: legacy.groq.apiKey,
3587
3757
  baseUrl: legacy.groq.baseUrl,
@@ -3596,7 +3766,7 @@ function toAIProviderConfig(config) {
3596
3766
  priority: modern.priority,
3597
3767
  fallbackEnabled: modern.fallbackEnabled,
3598
3768
  defaultTier: modern.defaultTier,
3599
- anthropic: modern.anthropic,
3769
+ ...anthropic ? { anthropic } : {},
3600
3770
  ...groq ? { groq } : {},
3601
3771
  ...gemini ? { gemini } : {},
3602
3772
  ...fal ? { fal } : {},
@@ -3762,8 +3932,8 @@ function useAIContentContext(options) {
3762
3932
  callbacksRef.current?.onProgress?.(value);
3763
3933
  }, []);
3764
3934
  const reportError = react.useCallback((err, fallbackMessage) => {
3765
- const aiError = AIError2.from(err, "UNKNOWN");
3766
- const finalError = aiError.message ? aiError : new AIError2(fallbackMessage, "UNKNOWN", err);
3935
+ const aiError = AIError.from(err, "UNKNOWN");
3936
+ const finalError = aiError.message ? aiError : new AIError(fallbackMessage, "UNKNOWN", err);
3767
3937
  if (mountedRef.current) {
3768
3938
  setError(finalError.message);
3769
3939
  setErrorCode(finalError.code);
@@ -3789,7 +3959,7 @@ function useAIContentContext(options) {
3789
3959
  }
3790
3960
  return result;
3791
3961
  } catch (err) {
3792
- const aiError = AIError2.from(err, "UNKNOWN");
3962
+ const aiError = AIError.from(err, "UNKNOWN");
3793
3963
  if (aiError.message) {
3794
3964
  reportError(aiError, fallbackMessage);
3795
3965
  } else {
@@ -4353,10 +4523,13 @@ function useWizardFlow(options) {
4353
4523
  if (steps.length <= 1) return 0;
4354
4524
  return Math.round(currentIndex / (steps.length - 1) * 100);
4355
4525
  }, [currentIndex, steps.length]);
4526
+ const completedRef = react.useRef(false);
4356
4527
  const navigateTo = react.useCallback((to) => {
4357
4528
  setCurrentStep((prev) => {
4358
4529
  if (prev === to) return prev;
4359
- optionsRef.current.onStepChange?.(prev, to);
4530
+ queueMicrotask(() => {
4531
+ optionsRef.current.onStepChange?.(prev, to);
4532
+ });
4360
4533
  return to;
4361
4534
  });
4362
4535
  }, []);
@@ -4397,13 +4570,17 @@ function useWizardFlow(options) {
4397
4570
  [currentIndex, steps.length]
4398
4571
  );
4399
4572
  const reset = react.useCallback(() => {
4573
+ completedRef.current = false;
4400
4574
  setCurrentStep(optionsRef.current.initialStep);
4401
4575
  setWizardData({});
4402
4576
  setIsProcessing(false);
4403
4577
  }, []);
4404
4578
  react.useEffect(() => {
4405
- if (currentIndex === steps.length - 1 && steps.length > 0) {
4579
+ if (currentIndex === steps.length - 1 && steps.length > 0 && !completedRef.current) {
4580
+ completedRef.current = true;
4406
4581
  optionsRef.current.onComplete?.(wizardData);
4582
+ } else if (currentIndex < steps.length - 1) {
4583
+ completedRef.current = false;
4407
4584
  }
4408
4585
  }, [currentIndex, steps.length, wizardData]);
4409
4586
  return {
@@ -4632,21 +4809,17 @@ function useText(options = {}, deps) {
4632
4809
  ]
4633
4810
  );
4634
4811
  react.useEffect(() => {
4635
- const controller = new AbortController();
4636
- abortControllerRef.current = controller;
4637
4812
  return () => {
4638
- controller.abort();
4813
+ abortControllerRef.current?.abort();
4639
4814
  abortControllerRef.current = null;
4640
4815
  };
4641
4816
  }, []);
4642
4817
  const runWithState = react.useCallback(
4643
4818
  async (task) => {
4644
- const controller = abortControllerRef.current;
4645
- if (!controller) {
4646
- return null;
4647
- }
4819
+ abortControllerRef.current?.abort();
4820
+ const controller = new AbortController();
4821
+ abortControllerRef.current = controller;
4648
4822
  const { signal } = controller;
4649
- if (signal.aborted) return null;
4650
4823
  setIsLoading(true);
4651
4824
  setError(null);
4652
4825
  setResult(null);
@@ -4667,6 +4840,9 @@ function useText(options = {}, deps) {
4667
4840
  if (!signal.aborted) {
4668
4841
  setIsLoading(false);
4669
4842
  }
4843
+ if (abortControllerRef.current === controller) {
4844
+ abortControllerRef.current = null;
4845
+ }
4670
4846
  }
4671
4847
  },
4672
4848
  []
@@ -4731,6 +4907,7 @@ function useText(options = {}, deps) {
4731
4907
  );
4732
4908
  const cancel = react.useCallback(() => {
4733
4909
  abortControllerRef.current?.abort();
4910
+ abortControllerRef.current = null;
4734
4911
  setIsLoading(false);
4735
4912
  }, []);
4736
4913
  const reset = react.useCallback(() => {
@@ -4873,10 +5050,8 @@ function useChat(options, deps) {
4873
5050
  };
4874
5051
  }, [conversationId, surfaceError]);
4875
5052
  react.useEffect(() => {
4876
- const controller = new AbortController();
4877
- abortControllerRef.current = controller;
4878
5053
  return () => {
4879
- controller.abort();
5054
+ abortControllerRef.current?.abort();
4880
5055
  abortControllerRef.current = null;
4881
5056
  };
4882
5057
  }, []);
@@ -4917,9 +5092,11 @@ function useChat(options, deps) {
4917
5092
  });
4918
5093
  void persist(userMessage);
4919
5094
  callbacksRef.current.onMessageSent?.(userMessage);
5095
+ let streamingPlaceholderId = null;
4920
5096
  try {
4921
5097
  if (streamEnabled) {
4922
5098
  const placeholderId = chatIdGenerator.next("assistant");
5099
+ streamingPlaceholderId = placeholderId;
4923
5100
  const placeholder = {
4924
5101
  id: placeholderId,
4925
5102
  sender: "assistant",
@@ -4938,7 +5115,8 @@ function useChat(options, deps) {
4938
5115
  (m) => m.id === placeholderId ? { ...m, content: m.content + chunk } : m
4939
5116
  )
4940
5117
  );
4941
- }
5118
+ },
5119
+ controller.signal
4942
5120
  );
4943
5121
  const finalMessage = {
4944
5122
  ...placeholder,
@@ -4955,13 +5133,18 @@ function useChat(options, deps) {
4955
5133
  const aiResponse = await chatService.generateAIResponse(
4956
5134
  conversationId,
4957
5135
  trimmed,
4958
- contextForAI
5136
+ contextForAI,
5137
+ controller.signal
4959
5138
  );
4960
5139
  setMessages((prev) => [...prev, aiResponse]);
4961
5140
  void persist(aiResponse);
4962
5141
  callbacksRef.current.onAIResponse?.(aiResponse);
4963
5142
  return aiResponse;
4964
5143
  } catch (err) {
5144
+ if (streamingPlaceholderId) {
5145
+ const failedId = streamingPlaceholderId;
5146
+ setMessages((prev) => prev.filter((m) => m.id !== failedId));
5147
+ }
4965
5148
  surfaceError(err);
4966
5149
  return null;
4967
5150
  } finally {
@@ -4987,6 +5170,9 @@ function useChat(options, deps) {
4987
5170
  const lastUserMessage = [...currentMessages].reverse().find((m) => m.sender === "user");
4988
5171
  if (!lastUserMessage) return null;
4989
5172
  const trimmed = currentMessages.length > 0 && currentMessages[currentMessages.length - 1].sender === "assistant" ? currentMessages.slice(0, -1) : currentMessages;
5173
+ const controller = new AbortController();
5174
+ abortControllerRef.current?.abort();
5175
+ abortControllerRef.current = controller;
4990
5176
  setIsLoading(true);
4991
5177
  setError(null);
4992
5178
  setIsTyping(true);
@@ -4994,7 +5180,8 @@ function useChat(options, deps) {
4994
5180
  const aiResponse = await chatService.generateAIResponse(
4995
5181
  conversationId,
4996
5182
  lastUserMessage.content,
4997
- trimmed
5183
+ trimmed,
5184
+ controller.signal
4998
5185
  );
4999
5186
  setMessages((prev) => {
5000
5187
  const lastIsAI = prev.length > 0 && prev[prev.length - 1].sender === "assistant";
@@ -5010,6 +5197,9 @@ function useChat(options, deps) {
5010
5197
  } finally {
5011
5198
  setIsLoading(false);
5012
5199
  setIsTyping(false);
5200
+ if (abortControllerRef.current === controller) {
5201
+ abortControllerRef.current = null;
5202
+ }
5013
5203
  }
5014
5204
  }, [chatService, conversationId, isLoading, messages, persist, surfaceError]);
5015
5205
  const clearMessages = react.useCallback(() => {