@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
@@ -3,6 +3,17 @@ import { useMemo, useCallback, useState, useRef, useEffect } from 'react';
3
3
  // src/hooks/useAIContent.ts
4
4
 
5
5
  // src/core/errors/ai-error-types.ts
6
+ var AIErrorType = /* @__PURE__ */ ((AIErrorType2) => {
7
+ AIErrorType2["INVALID_API_KEY"] = "INVALID_API_KEY";
8
+ AIErrorType2["MISSING_CONFIG"] = "MISSING_CONFIG";
9
+ AIErrorType2["NETWORK_ERROR"] = "NETWORK_ERROR";
10
+ AIErrorType2["ABORT_ERROR"] = "ABORT_ERROR";
11
+ AIErrorType2["RATE_LIMIT_ERROR"] = "RATE_LIMIT_ERROR";
12
+ AIErrorType2["SERVER_ERROR"] = "SERVER_ERROR";
13
+ AIErrorType2["CLIENT_ERROR"] = "CLIENT_ERROR";
14
+ AIErrorType2["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
15
+ return AIErrorType2;
16
+ })(AIErrorType || {});
6
17
  var USER_FRIENDLY_ERROR_MESSAGES = {
7
18
  ["INVALID_API_KEY" /* INVALID_API_KEY */]: "Please check your API key and try again.",
8
19
  ["MISSING_CONFIG" /* MISSING_CONFIG */]: "The provider is not configured. Please create a provider with createAI first.",
@@ -15,17 +26,80 @@ var USER_FRIENDLY_ERROR_MESSAGES = {
15
26
  };
16
27
 
17
28
  // src/core/errors/ai-error.ts
18
- var AIError = class extends Error {
29
+ var KNOWN_ERROR_TYPES = new Set(Object.values(AIErrorType));
30
+ function mapCodeToType(code) {
31
+ switch (code) {
32
+ case "VALIDATION_ERROR":
33
+ case "CLIENT_ERROR":
34
+ return "CLIENT_ERROR" /* CLIENT_ERROR */;
35
+ case "INVALID_API_KEY":
36
+ return "INVALID_API_KEY" /* INVALID_API_KEY */;
37
+ case "MISSING_CONFIG":
38
+ case "PROVIDER_UNAVAILABLE":
39
+ return "MISSING_CONFIG" /* MISSING_CONFIG */;
40
+ case "PROVIDER_QUOTA_EXHAUSTED":
41
+ case "RATE_LIMIT_ERROR":
42
+ return "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */;
43
+ case "NETWORK_ERROR":
44
+ case "TIMEOUT":
45
+ return "NETWORK_ERROR" /* NETWORK_ERROR */;
46
+ case "ABORT_ERROR":
47
+ case "ABORTED":
48
+ return "ABORT_ERROR" /* ABORT_ERROR */;
49
+ case "SERVER_ERROR":
50
+ case "PROVIDER_ERROR":
51
+ case "NO_RESULT":
52
+ return "SERVER_ERROR" /* SERVER_ERROR */;
53
+ default:
54
+ return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
55
+ }
56
+ }
57
+ function mapMessageToCode(message, fallback = "UNKNOWN") {
58
+ const lower = message.toLowerCase();
59
+ if (lower.includes("validation")) return "VALIDATION_ERROR";
60
+ if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
61
+ if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
62
+ if (lower.includes("timeout") || lower.includes("aborted")) {
63
+ return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
64
+ }
65
+ if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
66
+ if (lower.includes("no result")) return "NO_RESULT";
67
+ if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
68
+ return fallback;
69
+ }
70
+ var AIError = class _AIError extends Error {
19
71
  type;
72
+ code;
20
73
  cause;
21
74
  metadata;
22
- constructor(type, message, cause, metadata = {}) {
23
- super(message);
75
+ constructor(typeOrMessage, messageOrCode, cause, metadata = {}) {
76
+ let effectiveType;
77
+ let effectiveMessage;
78
+ let effectiveCode;
79
+ if (KNOWN_ERROR_TYPES.has(typeOrMessage)) {
80
+ effectiveType = typeOrMessage;
81
+ effectiveMessage = messageOrCode ?? "";
82
+ effectiveCode = effectiveType;
83
+ } else {
84
+ effectiveMessage = typeOrMessage;
85
+ effectiveCode = messageOrCode ?? "UNKNOWN";
86
+ effectiveType = mapCodeToType(effectiveCode);
87
+ }
88
+ super(effectiveMessage);
24
89
  this.name = "AIError";
25
- this.type = type;
90
+ this.type = effectiveType;
91
+ this.code = effectiveCode;
26
92
  this.cause = cause;
27
93
  this.metadata = metadata;
28
94
  }
95
+ static from(error, fallbackCode = "UNKNOWN") {
96
+ if (error instanceof _AIError) return error;
97
+ if (error instanceof Error) {
98
+ const code = mapMessageToCode(error.message, fallbackCode);
99
+ return new _AIError(error.message, code, error);
100
+ }
101
+ return new _AIError(String(error), fallbackCode, error);
102
+ }
29
103
  toString() {
30
104
  const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
31
105
  return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
@@ -34,6 +108,7 @@ var AIError = class extends Error {
34
108
  return {
35
109
  name: this.name,
36
110
  type: this.type,
111
+ code: this.code,
37
112
  message: this.message,
38
113
  metadata: this.metadata
39
114
  };
@@ -222,12 +297,16 @@ var ProviderFactory = class {
222
297
  let wake = null;
223
298
  let finished = false;
224
299
  let failure;
300
+ const controller = new AbortController();
301
+ const onAbort = () => controller.abort();
302
+ request.signal?.addEventListener("abort", onAbort);
303
+ const effectiveRequest = { ...request, signal: controller.signal };
225
304
  const notify = () => {
226
305
  const resolve = wake;
227
306
  wake = null;
228
307
  resolve?.();
229
308
  };
230
- const completion = this.chatStream(request, {
309
+ const completion = this.chatStream(effectiveRequest, {
231
310
  onChunk: (chunk) => {
232
311
  queue.push(chunk);
233
312
  notify();
@@ -243,18 +322,23 @@ var ProviderFactory = class {
243
322
  notify();
244
323
  }
245
324
  );
246
- while (!finished || queue.length > 0) {
247
- if (queue.length === 0 && !finished) {
248
- await new Promise((resolve) => {
249
- wake = resolve;
250
- });
251
- }
252
- while (queue.length > 0) {
253
- yield queue.shift();
325
+ try {
326
+ while (!finished || queue.length > 0) {
327
+ if (queue.length === 0 && !finished) {
328
+ await new Promise((resolve) => {
329
+ wake = resolve;
330
+ });
331
+ }
332
+ while (queue.length > 0) {
333
+ yield queue.shift();
334
+ }
335
+ if (failure) throw failure;
254
336
  }
255
- if (failure) throw failure;
337
+ await completion;
338
+ } finally {
339
+ controller.abort();
340
+ request.signal?.removeEventListener("abort", onAbort);
256
341
  }
257
- await completion;
258
342
  }
259
343
  async generateJson(request) {
260
344
  const effective = request.model || request.tier ? request : { ...request, tier: this.defaultTier };
@@ -401,15 +485,22 @@ var ProviderFactory = class {
401
485
  // src/core/constants/protocol.constants.ts
402
486
  var SSE_PREFIX = "data: ";
403
487
  var SSE_DONE_MARKER = "[DONE]";
404
- var JSON_CODE_BLOCK_PATTERN = /```json\n([\s\S]*?)\n```/;
488
+ var JSON_CODE_BLOCK_PATTERN = /```(?:json)?\s*\n?([\s\S]*?)\n?```/;
405
489
  var JSON_OBJECT_PATTERN = /\{[\s\S]*\}/;
490
+ var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
406
491
 
407
492
  // src/core/parsers/sse-lines.ts
408
493
  function isSseDataLine(line) {
409
- return line.startsWith(SSE_PREFIX);
494
+ return line.startsWith(SSE_PREFIX) || line.startsWith("data:");
410
495
  }
411
496
  function extractSseDataPayload(line) {
412
- return line.slice(SSE_PREFIX.length);
497
+ if (line.startsWith(SSE_PREFIX)) {
498
+ return line.slice(SSE_PREFIX.length);
499
+ }
500
+ if (line.startsWith("data:")) {
501
+ return line.slice(5);
502
+ }
503
+ return line;
413
504
  }
414
505
  function isSseDoneMarker(payload) {
415
506
  return payload === SSE_DONE_MARKER;
@@ -418,7 +509,7 @@ function isSseDoneMarker(payload) {
418
509
  // src/core/parsers/json-content-extractor.ts
419
510
  function extractJsonFromCodeFence(content) {
420
511
  const match = content.match(JSON_CODE_BLOCK_PATTERN);
421
- if (match && match[1]) return match[1];
512
+ if (match && match[1]) return match[1].trim();
422
513
  return null;
423
514
  }
424
515
  function extractJsonObject(content) {
@@ -426,8 +517,27 @@ function extractJsonObject(content) {
426
517
  if (match && match[0]) return match[0];
427
518
  return null;
428
519
  }
520
+ function extractJsonArray(content) {
521
+ const match = content.match(JSON_ARRAY_PATTERN);
522
+ if (match && match[0]) return match[0];
523
+ return null;
524
+ }
525
+ function repairCommonJsonDefects(text) {
526
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
527
+ }
429
528
  function extractJsonCandidate(content) {
430
- return extractJsonFromCodeFence(content) ?? extractJsonObject(content) ?? content;
529
+ const fromFence = extractJsonFromCodeFence(content);
530
+ if (fromFence) return fromFence;
531
+ const objectIndex = content.indexOf("{");
532
+ const arrayIndex = content.indexOf("[");
533
+ if (objectIndex !== -1 && (arrayIndex === -1 || objectIndex < arrayIndex)) {
534
+ const obj = extractJsonObject(content);
535
+ if (obj) return obj;
536
+ } else if (arrayIndex !== -1) {
537
+ const arr = extractJsonArray(content);
538
+ if (arr) return arr;
539
+ }
540
+ return content.trim();
431
541
  }
432
542
 
433
543
  // src/core/parsers/json-response.parser.ts
@@ -436,12 +546,17 @@ var JsonResponseParser = class {
436
546
  const candidate = extractJsonCandidate(rawContent);
437
547
  try {
438
548
  return JSON.parse(candidate);
439
- } catch (error) {
440
- throw new AIError(
441
- "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
442
- "Failed to parse structured response as JSON",
443
- error instanceof Error ? error : void 0
444
- );
549
+ } catch {
550
+ try {
551
+ return JSON.parse(repairCommonJsonDefects(candidate));
552
+ } catch (error) {
553
+ throw new AIError(
554
+ "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
555
+ "Failed to parse structured response as JSON",
556
+ error instanceof Error ? error : void 0,
557
+ { rawContent }
558
+ );
559
+ }
445
560
  }
446
561
  }
447
562
  };
@@ -755,11 +870,14 @@ async function consumeAnthropicStream(stream, callbacks) {
755
870
  break;
756
871
  case "message_stop":
757
872
  break;
758
- case "error":
873
+ case "error": {
874
+ const errType = event.error.type;
875
+ 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 */;
759
876
  throw new AIError(
760
- event.error.type === "rate_limit_error" ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
877
+ mappedType,
761
878
  event.error.message || "Anthropic stream failed mid-flight"
762
879
  );
880
+ }
763
881
  }
764
882
  };
765
883
  try {
@@ -1551,6 +1669,13 @@ async function consumeGroqStream(stream, callbacks) {
1551
1669
  } catch {
1552
1670
  return;
1553
1671
  }
1672
+ const errorBody = chunk.error;
1673
+ if (errorBody) {
1674
+ throw new AIError(
1675
+ errorBody.message?.toLowerCase().includes("rate limit") ? "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ : "SERVER_ERROR" /* SERVER_ERROR */,
1676
+ errorBody.message || "Groq stream failed mid-flight"
1677
+ );
1678
+ }
1554
1679
  if (chunk.model) model = chunk.model;
1555
1680
  const choice = chunk.choices?.[0];
1556
1681
  const delta = choice?.delta?.content;
@@ -1565,13 +1690,6 @@ async function consumeGroqStream(stream, callbacks) {
1565
1690
  if (tailUsage) {
1566
1691
  usage = mapGroqUsage(tailUsage);
1567
1692
  }
1568
- const errorBody = chunk.error;
1569
- if (errorBody) {
1570
- throw new AIError(
1571
- "SERVER_ERROR" /* SERVER_ERROR */,
1572
- errorBody.message || "Groq stream failed mid-flight"
1573
- );
1574
- }
1575
1693
  };
1576
1694
  try {
1577
1695
  while (true) {
@@ -1772,37 +1890,7 @@ function findLastIndex2(items, predicate) {
1772
1890
  }
1773
1891
 
1774
1892
  // src/content/domain/errors/AIErrors.ts
1775
- var AIError2 = class _AIError extends Error {
1776
- code;
1777
- cause;
1778
- constructor(message, code, cause) {
1779
- super(message);
1780
- this.name = "AIError";
1781
- this.code = code;
1782
- this.cause = cause;
1783
- }
1784
- static from(error, fallbackCode = "UNKNOWN") {
1785
- if (error instanceof _AIError) return error;
1786
- if (error instanceof Error) {
1787
- return new _AIError(error.message, mapMessageToCode(error.message, fallbackCode), error);
1788
- }
1789
- return new _AIError(String(error), fallbackCode, error);
1790
- }
1791
- };
1792
- function mapMessageToCode(message, fallback) {
1793
- const lower = message.toLowerCase();
1794
- if (lower.includes("validation")) return "VALIDATION_ERROR";
1795
- if (lower.includes("quota")) return "PROVIDER_QUOTA_EXHAUSTED";
1796
- if (lower.includes("unavailable")) return "PROVIDER_UNAVAILABLE";
1797
- if (lower.includes("timeout") || lower.includes("aborted")) {
1798
- return lower.includes("aborted") ? "ABORTED" : "TIMEOUT";
1799
- }
1800
- if (lower.includes("parse") || lower.includes("json")) return "PARSE_ERROR";
1801
- if (lower.includes("no result")) return "NO_RESULT";
1802
- if (lower.includes("network") || lower.includes("fetch")) return "NETWORK_ERROR";
1803
- return fallback;
1804
- }
1805
- var ValidationError = class extends AIError2 {
1893
+ var ValidationError = class extends AIError {
1806
1894
  constructor(message) {
1807
1895
  super(message, "VALIDATION_ERROR");
1808
1896
  this.name = "ValidationError";
@@ -1933,7 +2021,9 @@ var GeminiProvider = class extends BaseProvider {
1933
2021
  headers: this.authHeaders()
1934
2022
  }
1935
2023
  );
1936
- return response.ok ? "healthy" : "unavailable";
2024
+ if (response.ok) return "healthy";
2025
+ if (response.status === 401 || response.status === 403) return "unavailable";
2026
+ return "degraded";
1937
2027
  } catch {
1938
2028
  return "unavailable";
1939
2029
  }
@@ -1944,6 +2034,8 @@ var GeminiProvider = class extends BaseProvider {
1944
2034
  async generate(request) {
1945
2035
  try {
1946
2036
  const model = request.model ?? this.resolveModel(request.tier ?? "balanced");
2037
+ const systemMessages = request.messages.filter((m) => m.role === "system");
2038
+ const systemPrompt = request.systemPrompt && request.systemPrompt.length > 0 ? request.systemPrompt : systemMessages.length > 0 ? systemMessages.map((m) => m.content).join("\n") : void 0;
1947
2039
  const response = await this.withRetry(async () => {
1948
2040
  return await this.fetchWithTimeout(
1949
2041
  `${this.config.baseUrl}/models/${model}:generateContent`,
@@ -1951,22 +2043,28 @@ var GeminiProvider = class extends BaseProvider {
1951
2043
  method: "POST",
1952
2044
  headers: this.authHeaders(),
1953
2045
  body: JSON.stringify({
1954
- systemInstruction: request.systemPrompt && request.systemPrompt.length > 0 ? { parts: [{ text: request.systemPrompt }] } : void 0,
2046
+ systemInstruction: systemPrompt && systemPrompt.length > 0 ? { parts: [{ text: systemPrompt }] } : void 0,
1955
2047
  contents: toGeminiContents(request.messages),
1956
2048
  generationConfig: {
1957
- temperature: request.temperature || 0.7,
1958
- maxOutputTokens: request.maxTokens || 2048,
1959
- topP: request.topP || 0.9
2049
+ temperature: request.temperature ?? 0.7,
2050
+ maxOutputTokens: request.maxTokens ?? 2048,
2051
+ topP: request.topP ?? 0.9
1960
2052
  }
1961
2053
  })
1962
2054
  }
1963
2055
  );
1964
2056
  });
1965
2057
  if (!response.ok) {
1966
- throw new Error(`Gemini API error: ${response.statusText}`);
2058
+ const message = await extractErrorMessageFromResponse(response);
2059
+ throw new AIError(
2060
+ classifyHttpStatus(response.status),
2061
+ `Gemini API error (${response.status}): ${message}`,
2062
+ void 0,
2063
+ { status: response.status, model }
2064
+ );
1967
2065
  }
1968
2066
  const data = await response.json();
1969
- const text = data.candidates[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
2067
+ const text = data.candidates?.[0]?.content?.parts?.map((part) => part.text ?? "").join("") ?? "";
1970
2068
  if (text.length === 0) {
1971
2069
  throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No content generated from Gemini API", void 0, { model });
1972
2070
  }
@@ -1979,7 +2077,7 @@ var GeminiProvider = class extends BaseProvider {
1979
2077
  } : void 0,
1980
2078
  model,
1981
2079
  providerId: this.id,
1982
- finishReason: mapGeminiFinishReason(data.candidates[0]?.finishReason)
2080
+ finishReason: mapGeminiFinishReason(data.candidates?.[0]?.finishReason)
1983
2081
  };
1984
2082
  } catch (error) {
1985
2083
  if (error instanceof AIError) throw error;
@@ -2031,12 +2129,18 @@ var GeminiProvider = class extends BaseProvider {
2031
2129
  );
2032
2130
  });
2033
2131
  if (!response.ok) {
2034
- throw new Error(`Gemini API error: ${response.statusText}`);
2132
+ const message = await extractErrorMessageFromResponse(response);
2133
+ throw new AIError(
2134
+ classifyHttpStatus(response.status),
2135
+ `Gemini image generation error (${response.status}): ${message}`,
2136
+ void 0,
2137
+ { status: response.status }
2138
+ );
2035
2139
  }
2036
2140
  const data = await response.json();
2037
- const imageData = data.candidates[0]?.content?.parts[0]?.inlineData;
2141
+ const imageData = data.candidates?.[0]?.content?.parts?.[0]?.inlineData;
2038
2142
  if (!imageData) {
2039
- throw new Error("No image data in response");
2143
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image data in Gemini response");
2040
2144
  }
2041
2145
  const url = `data:${imageData.mimeType};base64,${imageData.data}`;
2042
2146
  return {
@@ -2465,17 +2569,70 @@ var PrunaProvider = class {
2465
2569
  async estimateCost(_request) {
2466
2570
  return 1;
2467
2571
  }
2468
- async generateImage(_request) {
2469
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2572
+ async generateImage(request) {
2573
+ let finalUrl;
2574
+ const model = this.config.models?.textToImage;
2575
+ for await (const stage of generateWithPruna(
2576
+ { prompt: request.prompt, model, quality: request.quality },
2577
+ this.config.apiKey
2578
+ )) {
2579
+ if (stage.error) throw stage.error;
2580
+ if (stage.result?.url) finalUrl = stage.result.url;
2581
+ }
2582
+ if (!finalUrl) {
2583
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No image generated by Pruna AI");
2584
+ }
2585
+ return {
2586
+ id: Date.now().toString(),
2587
+ type: "image",
2588
+ url: finalUrl,
2589
+ metadata: { provider: this.id, model: model ?? "p-image" },
2590
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2591
+ };
2470
2592
  }
2471
- async generateVideo(_request) {
2472
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2593
+ async generateVideo(request) {
2594
+ let finalUrl;
2595
+ const model = this.config.models?.textToVideo;
2596
+ for await (const stage of generateWithPruna(
2597
+ { prompt: request.prompt, model, duration: request.duration, aspectRatio: request.aspectRatio },
2598
+ this.config.apiKey
2599
+ )) {
2600
+ if (stage.error) throw stage.error;
2601
+ if (stage.result?.url) finalUrl = stage.result.url;
2602
+ }
2603
+ if (!finalUrl) {
2604
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated by Pruna AI");
2605
+ }
2606
+ return {
2607
+ id: Date.now().toString(),
2608
+ type: "video",
2609
+ url: finalUrl,
2610
+ metadata: { provider: this.id, model: model ?? "p-video" },
2611
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2612
+ };
2473
2613
  }
2474
- async imageToVideo(_request) {
2475
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2614
+ async imageToVideo(request) {
2615
+ let finalUrl;
2616
+ for await (const stage of generateWithPruna(
2617
+ { image: request.imageUrl, prompt: request.prompt, duration: request.duration, motion: request.motion },
2618
+ this.config.apiKey
2619
+ )) {
2620
+ if (stage.error) throw stage.error;
2621
+ if (stage.result?.url) finalUrl = stage.result.url;
2622
+ }
2623
+ if (!finalUrl) {
2624
+ throw new AIError("SERVER_ERROR" /* SERVER_ERROR */, "No video generated from image by Pruna AI");
2625
+ }
2626
+ return {
2627
+ id: Date.now().toString(),
2628
+ type: "video",
2629
+ url: finalUrl,
2630
+ metadata: { provider: this.id, model: "p-image-to-video" },
2631
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
2632
+ };
2476
2633
  }
2477
2634
  async videoToVideo(_request) {
2478
- throw new AIError("UNKNOWN_ERROR" /* UNKNOWN_ERROR */, "Use generateWithPruna instead");
2635
+ throw new AIError("CLIENT_ERROR" /* CLIENT_ERROR */, "Pruna AI provider does not support video-to-video conversion");
2479
2636
  }
2480
2637
  };
2481
2638
 
@@ -3082,11 +3239,12 @@ var buildScriptResult = (parsed, duration) => {
3082
3239
  if (!script) {
3083
3240
  throw new Error("AI response missing script field");
3084
3241
  }
3242
+ const wordCount = script.trim().length === 0 ? 0 : script.trim().split(/\s+/).length;
3085
3243
  return {
3086
3244
  ...parsed,
3087
3245
  script,
3088
3246
  duration,
3089
- wordCount: script.split(" ").length
3247
+ wordCount
3090
3248
  };
3091
3249
  };
3092
3250
  var VideoScriptService = class {
@@ -3281,7 +3439,6 @@ var SeoService = class {
3281
3439
  analyzeKeywords(content, keywords) {
3282
3440
  const validatedContent = validateContentBody(content);
3283
3441
  const validatedKeywords = validateKeywordList(keywords);
3284
- void countWordsInContent(validatedContent);
3285
3442
  return validatedKeywords.map((keyword) => {
3286
3443
  const count = countKeywordOccurrences(validatedContent, keyword);
3287
3444
  const density = calculateKeywordDensity(validatedContent, keyword);
@@ -3376,68 +3533,20 @@ var ABTestService = class {
3376
3533
  });
3377
3534
  const scoreA = predictions[0]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3378
3535
  const scoreB = predictions[1]?.predictedEngagement ?? FALLBACK_ENGAGEMENT;
3536
+ const isTie = scoreA === scoreB;
3379
3537
  const winner = selectHigherScoreVariantId(scoreA, scoreB, VARIANT_A_ID, VARIANT_B_ID);
3380
3538
  const improvement = calculateImprovementPercentage(scoreA, scoreB);
3381
3539
  return {
3382
3540
  winner,
3383
3541
  confidence: AB_TEST_DEFAULT_CONFIDENCE,
3384
- improvement: `+${improvement}% engagement`,
3385
- reasoning: winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
3542
+ improvement: isTie ? "0%" : `+${improvement}% engagement`,
3543
+ reasoning: isTie ? "Both variants have equal predicted engagement" : winner === VARIANT_A_ID ? "Variant A has more engaging content" : "Variant B has more engaging content",
3386
3544
  recommendations: [...DEFAULT_RECOMMENDATIONS]
3387
3545
  };
3388
3546
  }
3389
3547
  };
3390
3548
 
3391
- // src/content/infrastructure/utils/jsonParser.ts
3392
- function repairCommonJsonDefects(text) {
3393
- return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3394
- }
3395
- function safeJSONParse(text, fallback, onError) {
3396
- try {
3397
- return JSON.parse(text);
3398
- } catch (error) {
3399
- try {
3400
- return JSON.parse(repairCommonJsonDefects(text));
3401
- } catch {
3402
- }
3403
- if (error instanceof SyntaxError) {
3404
- const positionMatch = error.message.match(/position (\d+)/);
3405
- const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3406
- if (position > 0) {
3407
- text.substring(Math.max(0, position - 50), position + 50);
3408
- }
3409
- }
3410
- return fallback;
3411
- }
3412
- }
3413
- function extractJSON(text) {
3414
- const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3415
- if (codeBlockMatch && codeBlockMatch[1]) {
3416
- const candidate = codeBlockMatch[1].trim();
3417
- if (candidate.startsWith("{") || candidate.startsWith("[")) {
3418
- return candidate;
3419
- }
3420
- }
3421
- const objectMatch = text.match(/\{[\s\S]*\}/);
3422
- if (objectMatch) {
3423
- return objectMatch[0];
3424
- }
3425
- const arrayMatch = text.match(/\[[\s\S]*\]/);
3426
- if (arrayMatch) {
3427
- return arrayMatch[0];
3428
- }
3429
- return null;
3430
- }
3431
- function parseAIResponse(response, fallback, onError) {
3432
- const json = extractJSON(response);
3433
- if (!json) {
3434
- return fallback;
3435
- }
3436
- return safeJSONParse(json, fallback);
3437
- }
3438
-
3439
3549
  // src/content/application/services/ImagePromptService.ts
3440
- var JSON_ARRAY_PATTERN = /\[[\s\S]*\]/;
3441
3550
  var ImagePromptService = class {
3442
3551
  constructor(executor) {
3443
3552
  this.executor = executor;
@@ -3451,10 +3560,11 @@ var ImagePromptService = class {
3451
3560
  async generateImagePromptsForBlog(blogContent) {
3452
3561
  const truncated = truncateForBlogPromptPreview(validateContentBody(blogContent));
3453
3562
  const prompt = PromptBuilder.blogImagePrompts(truncated);
3454
- const response = await this.executor.runText(prompt, { maxTokens: 500, temperature: 0.8 });
3455
- const jsonMatch = response.match(JSON_ARRAY_PATTERN);
3456
- if (!jsonMatch) return [];
3457
- return safeJSONParse(jsonMatch[0], []);
3563
+ const prompts = await this.executor.runJson(prompt, [], {
3564
+ maxTokens: 500,
3565
+ temperature: 0.8
3566
+ });
3567
+ return Array.isArray(prompts) ? prompts : [];
3458
3568
  }
3459
3569
  };
3460
3570
 
@@ -3498,22 +3608,27 @@ var ContentCalendarService = class {
3498
3608
  const validatedNiche = validateTopicText(niche);
3499
3609
  const validatedDays = validateCalendarDays(days);
3500
3610
  const prompt = PromptBuilder.contentCalendar(validatedNiche, validatedDays);
3501
- const data = await this.executor.runJson(prompt, [], {
3502
- maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3503
- temperature: 0.8
3504
- });
3505
- return data.map(toCalendarEntry);
3611
+ const data = await this.executor.runJson(
3612
+ prompt,
3613
+ [],
3614
+ {
3615
+ maxTokens: estimateTokenBudgetForCalendarDays(validatedDays),
3616
+ temperature: 0.8
3617
+ }
3618
+ );
3619
+ const items = Array.isArray(data) ? data : Array.isArray(data?.calendar) ? data.calendar : Array.isArray(data?.entries) ? data.entries : [];
3620
+ return items.map(toCalendarEntry);
3506
3621
  }
3507
3622
  };
3508
3623
 
3509
3624
  // src/content/application/services/ProviderServices.ts
3510
3625
  var wrapProviderError = (operation, error) => {
3511
3626
  const message = error instanceof Error ? error.message : "Unknown error";
3512
- return new AIError2(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
3627
+ return new AIError(`${operation} failed: ${message}`, "PROVIDER_ERROR", error);
3513
3628
  };
3514
3629
  var ensureFactory = (factory, operation) => {
3515
3630
  if (factory.getAllProviders().length === 0) {
3516
- throw new AIError2(
3631
+ throw new AIError(
3517
3632
  `${operation} failed: No providers are registered. Provide a ProviderConfig with at least one enabled provider when constructing AIContentService.`,
3518
3633
  "PROVIDER_UNAVAILABLE"
3519
3634
  );
@@ -3550,6 +3665,54 @@ var MediaGenerationService = class {
3550
3665
  }
3551
3666
  };
3552
3667
 
3668
+ // src/content/infrastructure/utils/jsonParser.ts
3669
+ function repairCommonJsonDefects2(text) {
3670
+ return text.replace(/(:\s*-?\d+(?:\.\d+)?)\s*"/g, "$1").replace(/(:\s*"[^"]*")"/g, "$1");
3671
+ }
3672
+ function safeJSONParse(text, fallback, onError) {
3673
+ try {
3674
+ return JSON.parse(text);
3675
+ } catch (error) {
3676
+ try {
3677
+ return JSON.parse(repairCommonJsonDefects2(text));
3678
+ } catch {
3679
+ }
3680
+ if (error instanceof SyntaxError) {
3681
+ const positionMatch = error.message.match(/position (\d+)/);
3682
+ const position = positionMatch ? parseInt(positionMatch[1], 10) : -1;
3683
+ if (position > 0) {
3684
+ text.substring(Math.max(0, position - 50), position + 50);
3685
+ }
3686
+ }
3687
+ return fallback;
3688
+ }
3689
+ }
3690
+ function extractJSON(text) {
3691
+ const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)```/);
3692
+ if (codeBlockMatch && codeBlockMatch[1]) {
3693
+ const candidate = codeBlockMatch[1].trim();
3694
+ if (candidate.startsWith("{") || candidate.startsWith("[")) {
3695
+ return candidate;
3696
+ }
3697
+ }
3698
+ const objectMatch = text.match(/\{[\s\S]*\}/);
3699
+ if (objectMatch) {
3700
+ return objectMatch[0];
3701
+ }
3702
+ const arrayMatch = text.match(/\[[\s\S]*\]/);
3703
+ if (arrayMatch) {
3704
+ return arrayMatch[0];
3705
+ }
3706
+ return null;
3707
+ }
3708
+ function parseAIResponse(response, fallback, onError) {
3709
+ const json = extractJSON(response);
3710
+ if (!json) {
3711
+ return fallback;
3712
+ }
3713
+ return safeJSONParse(json, fallback);
3714
+ }
3715
+
3553
3716
  // src/content/application/services/GenerationExecutor.ts
3554
3717
  var GenerationExecutor = class {
3555
3718
  constructor(generator) {
@@ -3566,7 +3729,7 @@ var GenerationExecutor = class {
3566
3729
  /** Usage-complete pass-through; requires a ProviderFactory engine. */
3567
3730
  runChat(request) {
3568
3731
  if (!(this.generator instanceof ProviderFactory)) {
3569
- throw new AIError2(
3732
+ throw new AIError(
3570
3733
  "runChat requires a ProviderFactory engine \u2014 construct AIContentService with a provider config, not a custom ITextGenerator",
3571
3734
  "PROVIDER_UNAVAILABLE"
3572
3735
  );
@@ -3580,6 +3743,13 @@ var isTextGenerator = (value) => typeof value === "object" && value !== null &&
3580
3743
  function toAIProviderConfig(config) {
3581
3744
  const legacy = config;
3582
3745
  const modern = config;
3746
+ const anthropic = modern.anthropic ?? (legacy.anthropic?.enabled ? {
3747
+ apiKey: legacy.anthropic.apiKey,
3748
+ baseUrl: legacy.anthropic.baseUrl,
3749
+ timeoutMs: legacy.anthropic.timeout,
3750
+ models: legacy.anthropic.models,
3751
+ effort: legacy.anthropic.effort
3752
+ } : void 0);
3583
3753
  const groq = modern.groq ?? (legacy.groq?.enabled ? {
3584
3754
  apiKey: legacy.groq.apiKey,
3585
3755
  baseUrl: legacy.groq.baseUrl,
@@ -3594,7 +3764,7 @@ function toAIProviderConfig(config) {
3594
3764
  priority: modern.priority,
3595
3765
  fallbackEnabled: modern.fallbackEnabled,
3596
3766
  defaultTier: modern.defaultTier,
3597
- anthropic: modern.anthropic,
3767
+ ...anthropic ? { anthropic } : {},
3598
3768
  ...groq ? { groq } : {},
3599
3769
  ...gemini ? { gemini } : {},
3600
3770
  ...fal ? { fal } : {},
@@ -3760,8 +3930,8 @@ function useAIContentContext(options) {
3760
3930
  callbacksRef.current?.onProgress?.(value);
3761
3931
  }, []);
3762
3932
  const reportError = useCallback((err, fallbackMessage) => {
3763
- const aiError = AIError2.from(err, "UNKNOWN");
3764
- const finalError = aiError.message ? aiError : new AIError2(fallbackMessage, "UNKNOWN", err);
3933
+ const aiError = AIError.from(err, "UNKNOWN");
3934
+ const finalError = aiError.message ? aiError : new AIError(fallbackMessage, "UNKNOWN", err);
3765
3935
  if (mountedRef.current) {
3766
3936
  setError(finalError.message);
3767
3937
  setErrorCode(finalError.code);
@@ -3787,7 +3957,7 @@ function useAIContentContext(options) {
3787
3957
  }
3788
3958
  return result;
3789
3959
  } catch (err) {
3790
- const aiError = AIError2.from(err, "UNKNOWN");
3960
+ const aiError = AIError.from(err, "UNKNOWN");
3791
3961
  if (aiError.message) {
3792
3962
  reportError(aiError, fallbackMessage);
3793
3963
  } else {
@@ -4351,10 +4521,13 @@ function useWizardFlow(options) {
4351
4521
  if (steps.length <= 1) return 0;
4352
4522
  return Math.round(currentIndex / (steps.length - 1) * 100);
4353
4523
  }, [currentIndex, steps.length]);
4524
+ const completedRef = useRef(false);
4354
4525
  const navigateTo = useCallback((to) => {
4355
4526
  setCurrentStep((prev) => {
4356
4527
  if (prev === to) return prev;
4357
- optionsRef.current.onStepChange?.(prev, to);
4528
+ queueMicrotask(() => {
4529
+ optionsRef.current.onStepChange?.(prev, to);
4530
+ });
4358
4531
  return to;
4359
4532
  });
4360
4533
  }, []);
@@ -4395,13 +4568,17 @@ function useWizardFlow(options) {
4395
4568
  [currentIndex, steps.length]
4396
4569
  );
4397
4570
  const reset = useCallback(() => {
4571
+ completedRef.current = false;
4398
4572
  setCurrentStep(optionsRef.current.initialStep);
4399
4573
  setWizardData({});
4400
4574
  setIsProcessing(false);
4401
4575
  }, []);
4402
4576
  useEffect(() => {
4403
- if (currentIndex === steps.length - 1 && steps.length > 0) {
4577
+ if (currentIndex === steps.length - 1 && steps.length > 0 && !completedRef.current) {
4578
+ completedRef.current = true;
4404
4579
  optionsRef.current.onComplete?.(wizardData);
4580
+ } else if (currentIndex < steps.length - 1) {
4581
+ completedRef.current = false;
4405
4582
  }
4406
4583
  }, [currentIndex, steps.length, wizardData]);
4407
4584
  return {
@@ -4630,21 +4807,17 @@ function useText(options = {}, deps) {
4630
4807
  ]
4631
4808
  );
4632
4809
  useEffect(() => {
4633
- const controller = new AbortController();
4634
- abortControllerRef.current = controller;
4635
4810
  return () => {
4636
- controller.abort();
4811
+ abortControllerRef.current?.abort();
4637
4812
  abortControllerRef.current = null;
4638
4813
  };
4639
4814
  }, []);
4640
4815
  const runWithState = useCallback(
4641
4816
  async (task) => {
4642
- const controller = abortControllerRef.current;
4643
- if (!controller) {
4644
- return null;
4645
- }
4817
+ abortControllerRef.current?.abort();
4818
+ const controller = new AbortController();
4819
+ abortControllerRef.current = controller;
4646
4820
  const { signal } = controller;
4647
- if (signal.aborted) return null;
4648
4821
  setIsLoading(true);
4649
4822
  setError(null);
4650
4823
  setResult(null);
@@ -4665,6 +4838,9 @@ function useText(options = {}, deps) {
4665
4838
  if (!signal.aborted) {
4666
4839
  setIsLoading(false);
4667
4840
  }
4841
+ if (abortControllerRef.current === controller) {
4842
+ abortControllerRef.current = null;
4843
+ }
4668
4844
  }
4669
4845
  },
4670
4846
  []
@@ -4729,6 +4905,7 @@ function useText(options = {}, deps) {
4729
4905
  );
4730
4906
  const cancel = useCallback(() => {
4731
4907
  abortControllerRef.current?.abort();
4908
+ abortControllerRef.current = null;
4732
4909
  setIsLoading(false);
4733
4910
  }, []);
4734
4911
  const reset = useCallback(() => {
@@ -4871,10 +5048,8 @@ function useChat(options, deps) {
4871
5048
  };
4872
5049
  }, [conversationId, surfaceError]);
4873
5050
  useEffect(() => {
4874
- const controller = new AbortController();
4875
- abortControllerRef.current = controller;
4876
5051
  return () => {
4877
- controller.abort();
5052
+ abortControllerRef.current?.abort();
4878
5053
  abortControllerRef.current = null;
4879
5054
  };
4880
5055
  }, []);
@@ -4915,9 +5090,11 @@ function useChat(options, deps) {
4915
5090
  });
4916
5091
  void persist(userMessage);
4917
5092
  callbacksRef.current.onMessageSent?.(userMessage);
5093
+ let streamingPlaceholderId = null;
4918
5094
  try {
4919
5095
  if (streamEnabled) {
4920
5096
  const placeholderId = chatIdGenerator.next("assistant");
5097
+ streamingPlaceholderId = placeholderId;
4921
5098
  const placeholder = {
4922
5099
  id: placeholderId,
4923
5100
  sender: "assistant",
@@ -4936,7 +5113,8 @@ function useChat(options, deps) {
4936
5113
  (m) => m.id === placeholderId ? { ...m, content: m.content + chunk } : m
4937
5114
  )
4938
5115
  );
4939
- }
5116
+ },
5117
+ controller.signal
4940
5118
  );
4941
5119
  const finalMessage = {
4942
5120
  ...placeholder,
@@ -4953,13 +5131,18 @@ function useChat(options, deps) {
4953
5131
  const aiResponse = await chatService.generateAIResponse(
4954
5132
  conversationId,
4955
5133
  trimmed,
4956
- contextForAI
5134
+ contextForAI,
5135
+ controller.signal
4957
5136
  );
4958
5137
  setMessages((prev) => [...prev, aiResponse]);
4959
5138
  void persist(aiResponse);
4960
5139
  callbacksRef.current.onAIResponse?.(aiResponse);
4961
5140
  return aiResponse;
4962
5141
  } catch (err) {
5142
+ if (streamingPlaceholderId) {
5143
+ const failedId = streamingPlaceholderId;
5144
+ setMessages((prev) => prev.filter((m) => m.id !== failedId));
5145
+ }
4963
5146
  surfaceError(err);
4964
5147
  return null;
4965
5148
  } finally {
@@ -4985,6 +5168,9 @@ function useChat(options, deps) {
4985
5168
  const lastUserMessage = [...currentMessages].reverse().find((m) => m.sender === "user");
4986
5169
  if (!lastUserMessage) return null;
4987
5170
  const trimmed = currentMessages.length > 0 && currentMessages[currentMessages.length - 1].sender === "assistant" ? currentMessages.slice(0, -1) : currentMessages;
5171
+ const controller = new AbortController();
5172
+ abortControllerRef.current?.abort();
5173
+ abortControllerRef.current = controller;
4988
5174
  setIsLoading(true);
4989
5175
  setError(null);
4990
5176
  setIsTyping(true);
@@ -4992,7 +5178,8 @@ function useChat(options, deps) {
4992
5178
  const aiResponse = await chatService.generateAIResponse(
4993
5179
  conversationId,
4994
5180
  lastUserMessage.content,
4995
- trimmed
5181
+ trimmed,
5182
+ controller.signal
4996
5183
  );
4997
5184
  setMessages((prev) => {
4998
5185
  const lastIsAI = prev.length > 0 && prev[prev.length - 1].sender === "assistant";
@@ -5008,6 +5195,9 @@ function useChat(options, deps) {
5008
5195
  } finally {
5009
5196
  setIsLoading(false);
5010
5197
  setIsTyping(false);
5198
+ if (abortControllerRef.current === controller) {
5199
+ abortControllerRef.current = null;
5200
+ }
5011
5201
  }
5012
5202
  }, [chatService, conversationId, isLoading, messages, persist, surfaceError]);
5013
5203
  const clearMessages = useCallback(() => {