@umituz/web-ai 1.0.1 → 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.
@@ -117,6 +117,14 @@ var AIError = class _AIError extends Error {
117
117
  }
118
118
  };
119
119
 
120
+ // src/core/errors/groq-error.ts
121
+ var GroqError = class extends AIError {
122
+ constructor(type, message, cause, metadata = {}) {
123
+ super(type, message, cause, metadata);
124
+ this.name = "GroqError";
125
+ }
126
+ };
127
+
120
128
  // src/core/errors/error-classifier.ts
121
129
  var DEFAULT_RETRYABLE_TYPES = /* @__PURE__ */ new Set([
122
130
  "NETWORK_ERROR" /* NETWORK_ERROR */,
@@ -1341,6 +1349,27 @@ var RequestDeduplicator = class {
1341
1349
  }
1342
1350
  };
1343
1351
 
1352
+ // src/core/utils/api-key-validator.ts
1353
+ var DEFAULT_API_KEY_RULE = {
1354
+ minLength: 10
1355
+ };
1356
+ function validateApiKey(apiKey, rule = DEFAULT_API_KEY_RULE) {
1357
+ if (!apiKey) {
1358
+ return { valid: false, reason: "API key is required" };
1359
+ }
1360
+ const trimmed = apiKey.trim();
1361
+ if (trimmed.length === 0) {
1362
+ return { valid: false, reason: "API key cannot be empty" };
1363
+ }
1364
+ if (trimmed.length < rule.minLength) {
1365
+ return {
1366
+ valid: false,
1367
+ reason: `API key must be at least ${rule.minLength} characters`
1368
+ };
1369
+ }
1370
+ return { valid: true };
1371
+ }
1372
+
1344
1373
  // src/providers/groq/groq.constants.ts
1345
1374
  var GROQ_MODELS = {
1346
1375
  LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant",
@@ -1357,11 +1386,13 @@ var DEFAULT_GENERATION_CONFIG = {
1357
1386
  topP: 1
1358
1387
  };
1359
1388
  var GROQ_BASE_URL = "https://api.groq.com/openai/v1";
1389
+ var DEFAULT_BASE_URL = GROQ_BASE_URL;
1360
1390
  var CHAT_COMPLETIONS_PATH = "/chat/completions";
1361
1391
  var GROQ_TIMEOUTS = {
1362
1392
  DEFAULT: 3e4,
1363
1393
  STREAMING: 6e4
1364
1394
  };
1395
+ var TIMEOUTS = GROQ_TIMEOUTS;
1365
1396
  var HTTP_HEADERS = {
1366
1397
  CONTENT_TYPE: "Content-Type",
1367
1398
  APPLICATION_JSON: "application/json",
@@ -1420,13 +1451,65 @@ var GroqChatRequestBuilder = class _GroqChatRequestBuilder {
1420
1451
  }
1421
1452
  };
1422
1453
 
1454
+ // src/providers/groq/groq-config-store.ts
1455
+ var GroqConfigStore = class {
1456
+ config = null;
1457
+ initialize(config) {
1458
+ const validation = validateApiKey(config.apiKey, { minLength: 1 });
1459
+ if (!validation.valid) {
1460
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1461
+ }
1462
+ const normalized = {
1463
+ apiKey: config.apiKey.trim(),
1464
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
1465
+ timeoutMs: config.timeoutMs ?? TIMEOUTS.DEFAULT,
1466
+ ...config.models !== void 0 ? { models: config.models } : {},
1467
+ ...config.textModel !== void 0 ? { textModel: config.textModel } : {}
1468
+ };
1469
+ this.config = normalized;
1470
+ return normalized;
1471
+ }
1472
+ get() {
1473
+ if (!this.config) {
1474
+ throw new GroqError(
1475
+ "MISSING_CONFIG" /* MISSING_CONFIG */,
1476
+ "Client not initialized. Call configureProvider first."
1477
+ );
1478
+ }
1479
+ return this.config;
1480
+ }
1481
+ getOrNull() {
1482
+ return this.config;
1483
+ }
1484
+ isInitialized() {
1485
+ return this.config !== null;
1486
+ }
1487
+ setApiKey(apiKey) {
1488
+ const current = this.get();
1489
+ const validation = validateApiKey(apiKey, { minLength: 1 });
1490
+ if (!validation.valid) {
1491
+ throw new GroqError("INVALID_API_KEY" /* INVALID_API_KEY */, validation.reason ?? "Invalid API key");
1492
+ }
1493
+ this.config = { ...current, apiKey: apiKey.trim() };
1494
+ }
1495
+ reset() {
1496
+ this.config = null;
1497
+ }
1498
+ };
1499
+
1423
1500
  // src/providers/groq/groq-http-transport.ts
1424
1501
  var GroqHttpTransport = class {
1425
- constructor(config) {
1426
- this.config = config;
1502
+ constructor(configSource) {
1503
+ this.configSource = configSource;
1427
1504
  }
1428
- config;
1505
+ configSource;
1429
1506
  pendingAbortControllers = /* @__PURE__ */ new Set();
1507
+ getConfig() {
1508
+ if ("get" in this.configSource && typeof this.configSource.get === "function") {
1509
+ return this.configSource.get();
1510
+ }
1511
+ return this.configSource;
1512
+ }
1430
1513
  cancelPendingRequests() {
1431
1514
  for (const controller of this.pendingAbortControllers) {
1432
1515
  controller.abort();
@@ -1441,7 +1524,7 @@ var GroqHttpTransport = class {
1441
1524
  }
1442
1525
  async streamChatCompletion(request, options = {}) {
1443
1526
  const url = `${this.baseUrl()}${CHAT_COMPLETIONS_PATH}`;
1444
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1527
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.STREAMING;
1445
1528
  const controller = new AbortController();
1446
1529
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1447
1530
  const onExternalAbort = () => controller.abort();
@@ -1468,11 +1551,11 @@ var GroqHttpTransport = class {
1468
1551
  }
1469
1552
  }
1470
1553
  baseUrl() {
1471
- return this.config.baseUrl ?? GROQ_BASE_URL;
1554
+ return this.getConfig().baseUrl ?? GROQ_BASE_URL;
1472
1555
  }
1473
1556
  async request(endpoint, body) {
1474
1557
  const url = `${this.baseUrl()}${endpoint}`;
1475
- const timeout = this.config.timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1558
+ const timeout = this.getConfig().timeoutMs ?? GROQ_TIMEOUTS.DEFAULT;
1476
1559
  const controller = new AbortController();
1477
1560
  const timeoutId = setTimeout(() => controller.abort(), timeout);
1478
1561
  this.pendingAbortControllers.add(controller);
@@ -1495,7 +1578,7 @@ var GroqHttpTransport = class {
1495
1578
  buildHeaders() {
1496
1579
  return {
1497
1580
  [HTTP_HEADERS.CONTENT_TYPE]: HTTP_HEADERS.APPLICATION_JSON,
1498
- [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.config.apiKey}`
1581
+ [HTTP_HEADERS.AUTHORIZATION]: `${HTTP_HEADERS.BEARER_PREFIX}${this.getConfig().apiKey}`
1499
1582
  };
1500
1583
  }
1501
1584
  async throwFromErrorResponse(response) {
@@ -1597,21 +1680,40 @@ var CompositeExecutor = class {
1597
1680
 
1598
1681
  // src/providers/groq/groq-http-client.ts
1599
1682
  var GroqHttpClient = class {
1683
+ configStore;
1600
1684
  transport;
1601
1685
  executor;
1602
- constructor(config, deps = {}) {
1603
- this.transport = deps.transport ?? new GroqHttpTransport(config);
1686
+ constructor(configOrDeps, maybeDeps = {}) {
1687
+ const isConfig = typeof configOrDeps === "object" && configOrDeps !== null && "apiKey" in configOrDeps && typeof configOrDeps.apiKey === "string";
1688
+ const deps = isConfig ? maybeDeps : configOrDeps ?? {};
1689
+ this.configStore = deps.configStore ?? new GroqConfigStore();
1690
+ if (isConfig) {
1691
+ this.configStore.initialize(configOrDeps);
1692
+ }
1693
+ this.transport = deps.transport ?? new GroqHttpTransport(this.configStore);
1604
1694
  const retryManager2 = deps.retryManager ?? new RetryManager();
1605
1695
  const queue = deps.queue ?? new RequestQueue();
1606
1696
  const deduplicator = deps.deduplicator ?? new RequestDeduplicator();
1607
1697
  this.executor = new CompositeExecutor([
1608
1698
  new DedupeExecutor(deduplicator, {
1609
- generateKey: (request) => deduplicatorKey(request, config.apiKey)
1699
+ generateKey: (request) => deduplicatorKey(request, this.configStore.isInitialized() ? this.configStore.get().apiKey : "")
1610
1700
  }),
1611
1701
  new RetryExecutor(retryManager2),
1612
1702
  new QueueExecutor(queue, QUEUE_PRIORITY_USER_REQUEST)
1613
1703
  ]);
1614
1704
  }
1705
+ initialize(config) {
1706
+ this.configStore.initialize(config);
1707
+ }
1708
+ isInitialized() {
1709
+ return this.configStore.isInitialized();
1710
+ }
1711
+ getApiKey() {
1712
+ return this.configStore.get().apiKey;
1713
+ }
1714
+ setApiKey(apiKey) {
1715
+ this.configStore.setApiKey(apiKey);
1716
+ }
1615
1717
  cancelPendingRequests() {
1616
1718
  this.transport.cancelPendingRequests();
1617
1719
  }
@@ -1624,6 +1726,10 @@ var GroqHttpClient = class {
1624
1726
  streamChatCompletion(request, options) {
1625
1727
  return this.transport.streamChatCompletion(request, options);
1626
1728
  }
1729
+ reset() {
1730
+ this.transport.cancelPendingRequests();
1731
+ this.configStore.reset();
1732
+ }
1627
1733
  };
1628
1734
  function deduplicatorKey(request, apiKey) {
1629
1735
  const { model, messages, stream: _stream, ...rest } = request;
@@ -5232,12 +5338,168 @@ function useChat(options, deps) {
5232
5338
  };
5233
5339
  }
5234
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
+
5235
5496
  exports.useAIContent = useAIContent;
5236
5497
  exports.useAIGeneration = useAIGeneration;
5237
5498
  exports.useBlogGenerator = useBlogGenerator;
5238
5499
  exports.useCacheStats = useCacheStats;
5239
5500
  exports.useChat = useChat;
5240
5501
  exports.useContentWizard = useContentWizard;
5502
+ exports.useGroq = useGroq;
5241
5503
  exports.useImageWizard = useImageWizard;
5242
5504
  exports.useSocialContentGenerator = useSocialContentGenerator;
5243
5505
  exports.useText = useText;