@umituz/react-native-ai-groq-provider 1.0.20 → 1.0.22

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.
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Streaming Client
3
+ * Handles SSE streaming from Groq API
4
+ */
5
+
6
+ import type { GroqChatRequest, GroqChatChunk } from "../../domain/entities";
7
+ import { GroqError, GroqErrorType, mapHttpStatusToErrorType } from "../../domain/entities/error.types";
8
+ import { logger } from "../../shared/logger";
9
+ import { calculateSafeBufferSize } from "../../utils/calculation.util";
10
+
11
+ const DEFAULT_TIMEOUT = 60000;
12
+
13
+ export async function* streamChatCompletion(
14
+ request: GroqChatRequest,
15
+ config: { apiKey: string; baseUrl: string; timeoutMs?: number }
16
+ ): AsyncGenerator<GroqChatChunk> {
17
+ const url = `${config.baseUrl}/chat/completions`;
18
+ const timeout = config.timeoutMs || DEFAULT_TIMEOUT;
19
+
20
+ logger.debug("StreamingClient", "Starting stream", {
21
+ model: request.model,
22
+ });
23
+
24
+ const controller = new AbortController();
25
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
26
+
27
+ try {
28
+ const response = await fetch(url, {
29
+ method: "POST",
30
+ headers: {
31
+ "Content-Type": "application/json",
32
+ "Authorization": `Bearer ${config.apiKey}`,
33
+ },
34
+ body: JSON.stringify({ ...request, stream: true }),
35
+ signal: controller.signal,
36
+ });
37
+
38
+ clearTimeout(timeoutId);
39
+
40
+ if (!response.ok) {
41
+ await handleErrorResponse(response);
42
+ }
43
+
44
+ if (!response.body) {
45
+ throw new GroqError(GroqErrorType.NETWORK_ERROR, "Response body is null");
46
+ }
47
+
48
+ yield* parseSSE(response.body);
49
+
50
+ } catch (error) {
51
+ throw handleRequestError(error);
52
+ }
53
+ }
54
+
55
+ async function* parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<GroqChatChunk> {
56
+ const reader = body.getReader();
57
+ const decoder = new TextDecoder();
58
+ let buffer = "";
59
+ const MAX_BUFFER_SIZE = 1024 * 1024;
60
+
61
+ try {
62
+ while (true) {
63
+ const { done, value } = await reader.read();
64
+ if (done) break;
65
+
66
+ buffer += decoder.decode(value, { stream: true });
67
+
68
+ const safeSize = calculateSafeBufferSize(buffer.length, MAX_BUFFER_SIZE);
69
+ if (safeSize < buffer.length) {
70
+ buffer = buffer.slice(-safeSize);
71
+ }
72
+
73
+ const lines = buffer.split("\n");
74
+ buffer = lines.pop() || "";
75
+
76
+ for (const line of lines) {
77
+ const trimmed = line.trim();
78
+ if (!trimmed || trimmed === "data: [DONE]") continue;
79
+
80
+ if (trimmed.startsWith("data: ")) {
81
+ try {
82
+ const chunk = JSON.parse(trimmed.slice(6)) as GroqChatChunk;
83
+ yield chunk;
84
+ } catch (error) {
85
+ logger.error("StreamingClient", "Failed to parse SSE chunk", { error });
86
+ }
87
+ }
88
+ }
89
+ }
90
+ } finally {
91
+ reader.releaseLock();
92
+ }
93
+ }
94
+
95
+ async function handleErrorResponse(response: Response): Promise<never> {
96
+ let errorMessage = `HTTP ${response.status}`;
97
+ const errorType = mapHttpStatusToErrorType(response.status);
98
+
99
+ try {
100
+ const errorData = (await response.json()) as { error?: { message?: string } };
101
+ if (errorData.error?.message) errorMessage = errorData.error.message;
102
+ } catch {
103
+ // Use default
104
+ }
105
+
106
+ throw new GroqError(errorType, errorMessage);
107
+ }
108
+
109
+ function handleRequestError(error: unknown): GroqError {
110
+ if (error instanceof GroqError) return error;
111
+
112
+ if (error instanceof Error) {
113
+ if (error.name === "AbortError") {
114
+ return new GroqError(GroqErrorType.ABORT_ERROR, "Request aborted", error);
115
+ }
116
+ if (error.message.includes("network")) {
117
+ return new GroqError(GroqErrorType.NETWORK_ERROR, "Network error", error);
118
+ }
119
+ }
120
+
121
+ return new GroqError(
122
+ GroqErrorType.UNKNOWN_ERROR,
123
+ error instanceof Error ? error.message : "Unknown error"
124
+ );
125
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Presentation Layer - Hooks
3
+ * React hooks for UI integration
4
+ */
5
+
6
+ export { useGroq } from "./use-groq.hook";
7
+ export type { UseGroqOptions, UseGroqReturn } from "./use-groq.hook";
@@ -0,0 +1,184 @@
1
+ /**
2
+ * useGroq Hook
3
+ * Main React hook for Groq text generation
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from "react";
7
+ import type { GroqGenerationConfig } from "../../domain/entities";
8
+ import { generateText, generateStructured, streamText } from "../../application/use-cases";
9
+ import { getUserFriendlyError } from "../../utils/error-mapper.util";
10
+
11
+ export interface UseGroqOptions {
12
+ model?: string;
13
+ generationConfig?: GroqGenerationConfig;
14
+ onStart?: () => void;
15
+ onSuccess?: (result: string) => void;
16
+ onError?: (error: string) => void;
17
+ }
18
+
19
+ export interface UseGroqReturn {
20
+ isLoading: boolean;
21
+ error: string | null;
22
+ result: string | null;
23
+ generate: (prompt: string, config?: GroqGenerationConfig) => Promise<string>;
24
+ generateJSON: <T = Record<string, unknown>>(
25
+ prompt: string,
26
+ config?: GroqGenerationConfig & { schema?: Record<string, unknown> }
27
+ ) => Promise<T>;
28
+ stream: (
29
+ prompt: string,
30
+ onChunk: (chunk: string) => void,
31
+ config?: GroqGenerationConfig
32
+ ) => Promise<void>;
33
+ reset: () => void;
34
+ clearError: () => void;
35
+ }
36
+
37
+ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
38
+ const [isLoading, setIsLoading] = useState(false);
39
+ const [error, setError] = useState<string | null>(null);
40
+ const [result, setResult] = useState<string | null>(null);
41
+
42
+ const stableOptions = useMemo(
43
+ () => ({
44
+ model: options.model,
45
+ generationConfig: options.generationConfig,
46
+ onStart: options.onStart,
47
+ onSuccess: options.onSuccess,
48
+ onError: options.onError,
49
+ }),
50
+ [
51
+ options.model,
52
+ options.generationConfig?.temperature,
53
+ options.generationConfig?.maxTokens,
54
+ options.generationConfig?.topP,
55
+ options.onStart,
56
+ options.onSuccess,
57
+ options.onError,
58
+ ]
59
+ );
60
+
61
+ const generate = useCallback(
62
+ async (prompt: string, config?: GroqGenerationConfig): Promise<string> => {
63
+ setIsLoading(true);
64
+ setError(null);
65
+ setResult(null);
66
+
67
+ stableOptions.onStart?.();
68
+
69
+ try {
70
+ const response = await generateText(prompt, {
71
+ model: stableOptions.model,
72
+ generationConfig: { ...stableOptions.generationConfig, ...config },
73
+ });
74
+
75
+ setResult(response);
76
+ stableOptions.onSuccess?.(response);
77
+ return response;
78
+ } catch (err) {
79
+ const errorMessage = getUserFriendlyError(err);
80
+ setError(errorMessage);
81
+ stableOptions.onError?.(errorMessage);
82
+ throw err;
83
+ } finally {
84
+ setIsLoading(false);
85
+ }
86
+ },
87
+ [stableOptions]
88
+ );
89
+
90
+ const generateJSON = useCallback(
91
+ async <T = Record<string, unknown>,>(
92
+ prompt: string,
93
+ config?: GroqGenerationConfig & { schema?: Record<string, unknown> }
94
+ ): Promise<T> => {
95
+ setIsLoading(true);
96
+ setError(null);
97
+ setResult(null);
98
+
99
+ stableOptions.onStart?.();
100
+
101
+ try {
102
+ const response = await generateStructured<T>(prompt, {
103
+ model: stableOptions.model,
104
+ generationConfig: { ...stableOptions.generationConfig, ...config },
105
+ schema: config?.schema,
106
+ });
107
+
108
+ const jsonStr = JSON.stringify(response, null, 2);
109
+ setResult(jsonStr);
110
+ stableOptions.onSuccess?.(jsonStr);
111
+ return response;
112
+ } catch (err) {
113
+ const errorMessage = getUserFriendlyError(err);
114
+ setError(errorMessage);
115
+ stableOptions.onError?.(errorMessage);
116
+ throw err;
117
+ } finally {
118
+ setIsLoading(false);
119
+ }
120
+ },
121
+ [stableOptions]
122
+ );
123
+
124
+ const stream = useCallback(
125
+ async (
126
+ prompt: string,
127
+ onChunk: (chunk: string) => void,
128
+ config?: GroqGenerationConfig
129
+ ): Promise<void> => {
130
+ setIsLoading(true);
131
+ setError(null);
132
+ setResult(null);
133
+
134
+ let fullContent = "";
135
+
136
+ stableOptions.onStart?.();
137
+
138
+ try {
139
+ for await (const chunk of streamText(prompt, {
140
+ model: stableOptions.model,
141
+ generationConfig: { ...stableOptions.generationConfig, ...config },
142
+ callbacks: { onChunk: (c) => {
143
+ fullContent += c;
144
+ onChunk(c);
145
+ }},
146
+ })) {
147
+ // Consume iterator
148
+ }
149
+
150
+ setResult(fullContent);
151
+ stableOptions.onSuccess?.(fullContent);
152
+ } catch (err) {
153
+ const errorMessage = getUserFriendlyError(err);
154
+ setError(errorMessage);
155
+ stableOptions.onError?.(errorMessage);
156
+ throw err;
157
+ } finally {
158
+ setIsLoading(false);
159
+ }
160
+ },
161
+ [stableOptions]
162
+ );
163
+
164
+ const reset = useCallback(() => {
165
+ setIsLoading(false);
166
+ setError(null);
167
+ setResult(null);
168
+ }, []);
169
+
170
+ const clearError = useCallback(() => {
171
+ setError(null);
172
+ }, []);
173
+
174
+ return {
175
+ isLoading,
176
+ error,
177
+ result,
178
+ generate,
179
+ generateJSON,
180
+ stream,
181
+ reset,
182
+ clearError,
183
+ };
184
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Presentation Layer
3
+ * UI integration components
4
+ */
5
+
6
+ export * from "./hooks";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared Utilities
3
+ * Common utilities used across all layers
4
+ */
5
+
6
+ export { logger, LogLevel } from "./logger";
7
+ export type { LogContext } from "./logger";
8
+
9
+ export { Timer } from "./timer";
10
+ export type { TimerResult } from "./timer";
11
+
12
+ export { RequestBuilder } from "./request-builder";
13
+ export type { RequestBuilderOptions } from "./request-builder";
14
+
15
+ export { ResponseHandler } from "./response-handler";
16
+ export type { ResponseHandlerResult } from "./response-handler";
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Logger Utility
3
+ * Centralized logging for the entire application
4
+ */
5
+
6
+ export enum LogLevel {
7
+ DEBUG = "DEBUG",
8
+ INFO = "INFO",
9
+ WARN = "WARN",
10
+ ERROR = "ERROR",
11
+ }
12
+
13
+ export interface LogContext {
14
+ [key: string]: unknown;
15
+ }
16
+
17
+ class Logger {
18
+ private enabled: boolean;
19
+
20
+ constructor() {
21
+ this.enabled = typeof __DEV__ !== "undefined" && __DEV__;
22
+ }
23
+
24
+ private log(level: LogLevel, tag: string, message: string, context?: LogContext): void {
25
+ if (!this.enabled) return;
26
+
27
+ const timestamp = new Date().toISOString();
28
+ const logMessage = `[${timestamp}] [${level}] [${tag}] ${message}`;
29
+
30
+ switch (level) {
31
+ case LogLevel.ERROR:
32
+ console.error(logMessage, context || "");
33
+ break;
34
+ case LogLevel.WARN:
35
+ console.warn(logMessage, context || "");
36
+ break;
37
+ default:
38
+ console.log(logMessage, context || "");
39
+ }
40
+ }
41
+
42
+ debug(tag: string, message: string, context?: LogContext): void {
43
+ this.log(LogLevel.DEBUG, tag, message, context);
44
+ }
45
+
46
+ info(tag: string, message: string, context?: LogContext): void {
47
+ this.log(LogLevel.INFO, tag, message, context);
48
+ }
49
+
50
+ warn(tag: string, message: string, context?: LogContext): void {
51
+ this.log(LogLevel.WARN, tag, message, context);
52
+ }
53
+
54
+ error(tag: string, message: string, context?: LogContext): void {
55
+ this.log(LogLevel.ERROR, tag, message, context);
56
+ }
57
+
58
+ isEnabled(): boolean {
59
+ return this.enabled;
60
+ }
61
+ }
62
+
63
+ export const logger = new Logger();
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Request Builder Utility
3
+ * Centralized request building logic
4
+ */
5
+
6
+ import type {
7
+ GroqChatRequest,
8
+ GroqGenerationConfig,
9
+ GroqMessage,
10
+ } from "../domain/entities";
11
+ import { DEFAULT_MODELS } from "../domain/entities";
12
+
13
+ export interface RequestBuilderOptions {
14
+ model?: string;
15
+ generationConfig?: GroqGenerationConfig;
16
+ defaultTemperature?: number;
17
+ defaultMaxTokens?: number;
18
+ }
19
+
20
+ export class RequestBuilder {
21
+ static buildChatRequest(
22
+ messages: GroqMessage[],
23
+ options: RequestBuilderOptions = {}
24
+ ): GroqChatRequest {
25
+ const {
26
+ model = DEFAULT_MODELS.TEXT,
27
+ generationConfig = {},
28
+ defaultTemperature = 0.7,
29
+ defaultMaxTokens = 1024,
30
+ } = options;
31
+
32
+ return {
33
+ model,
34
+ messages,
35
+ temperature: generationConfig.temperature ?? defaultTemperature,
36
+ max_tokens: generationConfig.maxTokens ?? defaultMaxTokens,
37
+ top_p: generationConfig.topP,
38
+ n: generationConfig.n,
39
+ stop: generationConfig.stop,
40
+ frequency_penalty: generationConfig.frequencyPenalty,
41
+ presence_penalty: generationConfig.presencePenalty,
42
+ };
43
+ }
44
+
45
+ static buildPromptRequest(
46
+ prompt: string,
47
+ options: RequestBuilderOptions = {}
48
+ ): GroqChatRequest {
49
+ const messages: GroqMessage[] = [{ role: "user", content: prompt }];
50
+ return this.buildChatRequest(messages, options);
51
+ }
52
+
53
+ static buildSystemPromptRequest(
54
+ systemPrompt: string,
55
+ userPrompt: string,
56
+ options: RequestBuilderOptions = {}
57
+ ): GroqChatRequest {
58
+ const messages: GroqMessage[] = [
59
+ { role: "system", content: systemPrompt },
60
+ { role: "user", content: userPrompt },
61
+ ];
62
+ return this.buildChatRequest(messages, options);
63
+ }
64
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Timer Utility
3
+ * Performance measurement helper
4
+ */
5
+
6
+ export interface TimerResult {
7
+ totalMs: number;
8
+ apiMs: number;
9
+ processingMs: number;
10
+ }
11
+
12
+ export class Timer {
13
+ private startTime: number;
14
+ private apiStartTime?: number;
15
+ private apiEndTime?: number;
16
+
17
+ constructor() {
18
+ this.startTime = Date.now();
19
+ }
20
+
21
+ startApiCall(): void {
22
+ this.apiStartTime = Date.now();
23
+ }
24
+
25
+ endApiCall(): void {
26
+ this.apiEndTime = Date.now();
27
+ }
28
+
29
+ getResult(): TimerResult {
30
+ const endTime = Date.now();
31
+ const totalMs = endTime - this.startTime;
32
+ const apiMs = this.apiEndTime && this.apiStartTime
33
+ ? this.apiEndTime - this.apiStartTime
34
+ : 0;
35
+ const processingMs = totalMs - apiMs;
36
+
37
+ return { totalMs, apiMs, processingMs };
38
+ }
39
+
40
+ static format(ms: number): string {
41
+ return `${ms}ms`;
42
+ }
43
+ }