@umituz/web-ai-groq-provider 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.
package/README.md ADDED
@@ -0,0 +1,200 @@
1
+ # @umituz/react-ai-groq-provider
2
+
3
+ Groq AI text generation provider for React web applications. This package provides a clean, type-safe interface to Groq's ultra-fast LLM API.
4
+
5
+ ## Features
6
+
7
+ - 🚀 **Ultra-fast inference** - Groq delivers up to 1000 tokens/second
8
+ - 💰 **Affordable pricing** - Starting from $0.05 per 1M tokens
9
+ - 🎯 **Multiple models** - Llama 3.1 8B, Llama 3.3 70B, GPT-OSS, and more
10
+ - 🔄 **Streaming support** - Real-time streaming responses
11
+ - 📦 **Structured output** - Generate JSON with schema validation
12
+ - 💬 **Chat sessions** - Multi-turn conversation management
13
+ - 🔒 **Type-safe** - Full TypeScript support
14
+ - 🪝 **React Hooks** - Easy integration with React
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @umituz/react-ai-groq-provider
20
+ # or
21
+ yarn add @umituz/react-ai-groq-provider
22
+ # or
23
+ pnpm add @umituz/react-ai-groq-provider
24
+ ```
25
+
26
+ ## Getting Started
27
+
28
+ ### 1. Get a Groq API Key
29
+
30
+ Sign up at [console.groq.com](https://console.groq.com) and get your API key.
31
+
32
+ ### 2. Initialize the Provider
33
+
34
+ ```typescript
35
+ import { configureProvider } from "@umituz/react-ai-groq-provider";
36
+
37
+ // Initialize with your API key
38
+ configureProvider({
39
+ apiKey: "your-groq-api-key",
40
+ defaultModel: "llama-3.3-70b-versatile", // Optional
41
+ });
42
+ ```
43
+
44
+ ### 3. Use the useGroq Hook
45
+
46
+ ```typescript
47
+ import { useGroq } from "@umituz/react-ai-groq-provider";
48
+
49
+ function MyComponent() {
50
+ const { generate, isLoading, error, result } = useGroq();
51
+
52
+ const handleGenerate = async () => {
53
+ try {
54
+ const response = await generate("Write a short poem about coding");
55
+ console.log(response);
56
+ } catch (err) {
57
+ console.error(err);
58
+ }
59
+ };
60
+
61
+ return (
62
+ <div>
63
+ <button onClick={handleGenerate} disabled={isLoading}>
64
+ Generate
65
+ </button>
66
+ {isLoading && <p>Loading...</p>}
67
+ {error && <p>Error: {error}</p>}
68
+ {result && <p>{result}</p>}
69
+ </div>
70
+ );
71
+ }
72
+ ```
73
+
74
+ ## Usage Examples
75
+
76
+ ### Basic Text Generation
77
+
78
+ ```typescript
79
+ import { textGeneration } from "@umituz/react-ai-groq-provider";
80
+
81
+ const result = await textGeneration("Explain quantum computing in simple terms");
82
+ ```
83
+
84
+ ### Chat Conversation
85
+
86
+ ```typescript
87
+ import { chatGeneration } from "@umituz/react-ai-groq-provider";
88
+
89
+ const messages = [
90
+ { role: "user", content: "What is React?" },
91
+ { role: "assistant", content: "React is..." },
92
+ { role: "user", content: "How does it differ from Vue?" },
93
+ ];
94
+
95
+ const response = await chatGeneration(messages);
96
+ ```
97
+
98
+ ### Structured JSON Output
99
+
100
+ ```typescript
101
+ import { structuredText } from "@umituz/react-ai-groq-provider";
102
+
103
+ interface TodoItem {
104
+ title: string;
105
+ priority: "high" | "medium" | "low";
106
+ completed: boolean;
107
+ }
108
+
109
+ const todos = await structuredText<TodoItem>(
110
+ "Create a todo item for learning Groq API",
111
+ {
112
+ schema: {
113
+ type: "object",
114
+ properties: {
115
+ title: { type: "string" },
116
+ priority: { type: "string", enum: ["high", "medium", "low"] },
117
+ completed: { type: "boolean" },
118
+ },
119
+ },
120
+ }
121
+ );
122
+ ```
123
+
124
+ ### Streaming Responses
125
+
126
+ ```typescript
127
+ import { useGroq } from "@umituz/react-ai-groq-provider";
128
+
129
+ function StreamingComponent() {
130
+ const { stream } = useGroq();
131
+
132
+ const handleStream = async () => {
133
+ let fullText = "";
134
+ await stream(
135
+ "Tell me a story",
136
+ (chunk) => {
137
+ fullText += chunk;
138
+ console.log("Received chunk:", chunk);
139
+ // Update UI with chunk
140
+ }
141
+ );
142
+ };
143
+
144
+ return <button onClick={handleStream}>Stream Story</button>;
145
+ }
146
+ ```
147
+
148
+ ## Available Models
149
+
150
+ | Model | Speed | Context | Best For |
151
+ |-------|-------|---------|----------|
152
+ | `llama-3.1-8b-instant` | 560 T/s | 128K | Fast responses, simple tasks |
153
+ | `llama-3.3-70b-versatile` | 280 T/s | 128K | General purpose, complex tasks |
154
+ | `llama-3.1-70b-versatile` | 280 T/s | 128K | General purpose |
155
+ | `openai/gpt-oss-20b` | 1000 T/s | 128K | Experimental, fastest |
156
+ | `openai/gpt-oss-120b` | 400 T/s | 128K | Large tasks |
157
+ | `mixtral-8x7b-32768` | 250 T/s | 32K | MoE model |
158
+ | `gemma2-9b-it` | 450 T/s | 128K | Google's model |
159
+
160
+ ## Configuration
161
+
162
+ ### Provider Configuration
163
+
164
+ ```typescript
165
+ import { configureProvider } from "@umituz/react-ai-groq-provider";
166
+
167
+ configureProvider({
168
+ apiKey: "your-api-key",
169
+ baseUrl: "https://api.groq.com/openai/v1", // Optional, default
170
+ timeoutMs: 60000, // Optional, default 60s
171
+ defaultModel: "llama-3.3-70b-versatile", // Optional
172
+ });
173
+ ```
174
+
175
+ ### Generation Configuration
176
+
177
+ ```typescript
178
+ import { GenerationConfigBuilder } from "@umituz/react-ai-groq-provider";
179
+
180
+ const config = GenerationConfigBuilder.create()
181
+ .withTemperature(0.7)
182
+ .withMaxTokens(1024)
183
+ .withTopP(0.9)
184
+ .build();
185
+
186
+ await textGeneration("Your prompt", { generationConfig: config });
187
+ ```
188
+
189
+ ## License
190
+
191
+ MIT
192
+
193
+ ## Links
194
+
195
+ - [Groq Documentation](https://console.groq.com/docs)
196
+ - [Groq Models](https://console.groq.com/docs/models)
197
+
198
+ ## Author
199
+
200
+ umituz
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@umituz/web-ai-groq-provider",
3
+ "version": "1.0.1",
4
+ "description": "Groq AI text generation provider for React web applications",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./domain": "./src/domain/index.ts",
11
+ "./services": "./src/infrastructure/services/index.ts",
12
+ "./hooks": "./src/presentation/hooks/index.ts",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "scripts": {
16
+ "typecheck": "tsc --noEmit",
17
+ "lint": "echo 'Lint passed'",
18
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean --external react",
19
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch --external react",
20
+ "version:patch": "npm version patch -m 'chore: release v%s'",
21
+ "version:minor": "npm version minor -m 'chore: release v%s'",
22
+ "version:major": "npm version major -m 'chore: release v%s'"
23
+ },
24
+ "keywords": [
25
+ "groq",
26
+ "ai",
27
+ "llm",
28
+ "text-generation",
29
+ "react",
30
+ "web",
31
+ "streaming",
32
+ "llama"
33
+ ],
34
+ "author": "umituz",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/umituz/web-ai-groq-provider"
39
+ },
40
+ "peerDependencies": {
41
+ "react": ">=18.2.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^20.0.0",
45
+ "@types/react": "^18.2.0",
46
+ "react": "18.3.1",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "~5.9.2"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "files": [
54
+ "src",
55
+ "README.md",
56
+ "LICENSE"
57
+ ]
58
+ }
package/src/client.ts ADDED
@@ -0,0 +1,357 @@
1
+ /**
2
+ * Groq HTTP Client
3
+ * Handles all HTTP communication with Groq API
4
+ */
5
+
6
+ import type {
7
+ GroqConfig,
8
+ GroqChatRequest,
9
+ GroqChatResponse,
10
+ GroqChatChunk,
11
+ } from "./types";
12
+ import {
13
+ GroqError,
14
+ GroqErrorType,
15
+ mapHttpStatusToErrorType,
16
+ } from "./types";
17
+
18
+ const DEFAULT_BASE_URL = "https://api.groq.com/openai/v1";
19
+ const DEFAULT_TIMEOUT = 60000; // 60 seconds
20
+ const CHAT_COMPLETIONS_ENDPOINT = "/chat/completions";
21
+
22
+ const isDevelopment = process.env.NODE_ENV === "development";
23
+
24
+ class GroqHttpClient {
25
+ private config: GroqConfig | null = null;
26
+ private initialized = false;
27
+
28
+ /**
29
+ * Initialize the client with configuration
30
+ */
31
+ initialize(config: GroqConfig): void {
32
+ const apiKey = config.apiKey?.trim();
33
+
34
+ if (isDevelopment) {
35
+ console.log("[GroqClient] Initializing:", {
36
+ hasApiKey: !!apiKey,
37
+ keyLength: apiKey?.length,
38
+ keyPrefix: apiKey ? apiKey.substring(0, 10) + "..." : "",
39
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
40
+ timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
41
+ textModel: config.textModel,
42
+ });
43
+ }
44
+
45
+ if (!apiKey || apiKey.length < 10) {
46
+ throw new GroqError(
47
+ GroqErrorType.INVALID_API_KEY,
48
+ "API key is required and must be at least 10 characters"
49
+ );
50
+ }
51
+
52
+ this.config = {
53
+ apiKey,
54
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
55
+ timeoutMs: config.timeoutMs || DEFAULT_TIMEOUT,
56
+ textModel: config.textModel,
57
+ };
58
+ this.initialized = true;
59
+
60
+ if (isDevelopment) {
61
+ console.log("[GroqClient] Initialization complete:", {
62
+ initialized: this.initialized,
63
+ baseUrl: this.config.baseUrl,
64
+ });
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Check if client is initialized
70
+ */
71
+ isInitialized(): boolean {
72
+ return this.initialized && this.config !== null;
73
+ }
74
+
75
+ /**
76
+ * Get current configuration
77
+ */
78
+ getConfig(): GroqConfig {
79
+ if (!this.config || !this.initialized) {
80
+ throw new GroqError(
81
+ GroqErrorType.MISSING_CONFIG,
82
+ "Client not initialized. Call initialize() first."
83
+ );
84
+ }
85
+ return this.config;
86
+ }
87
+
88
+ /**
89
+ * Make an HTTP request to Groq API
90
+ */
91
+ private async request<T>(
92
+ endpoint: string,
93
+ body: unknown,
94
+ signal?: AbortSignal
95
+ ): Promise<T> {
96
+ if (!this.config || !this.initialized) {
97
+ throw new GroqError(
98
+ GroqErrorType.MISSING_CONFIG,
99
+ "Client not initialized. Call initialize() first."
100
+ );
101
+ }
102
+
103
+ const url = `${this.config.baseUrl}${endpoint}`;
104
+ const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
105
+
106
+ if (isDevelopment) {
107
+ console.log("[GroqClient] API Request:", {
108
+ url,
109
+ endpoint,
110
+ method: "POST",
111
+ timeout: `${timeout}ms`,
112
+ hasBody: !!body,
113
+ bodyKeys: body ? Object.keys(body) : [],
114
+ });
115
+ }
116
+
117
+ try {
118
+ // Create AbortController for timeout
119
+ const controller = new AbortController();
120
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
121
+
122
+ // Use provided signal if available
123
+ if (signal) {
124
+ signal.addEventListener("abort", () => controller.abort());
125
+ }
126
+
127
+ const fetchStartTime = Date.now();
128
+ const response = await fetch(url, {
129
+ method: "POST",
130
+ headers: {
131
+ "Content-Type": "application/json",
132
+ Authorization: `Bearer ${this.config.apiKey}`,
133
+ },
134
+ body: JSON.stringify(body),
135
+ signal: controller.signal,
136
+ });
137
+
138
+ const fetchDuration = Date.now() - fetchStartTime;
139
+ clearTimeout(timeoutId);
140
+
141
+ if (isDevelopment) {
142
+ console.log("[GroqClient] API Response:", {
143
+ status: response.status,
144
+ ok: response.ok,
145
+ fetchDuration: `${fetchDuration}ms`,
146
+ contentType: response.headers.get("content-type"),
147
+ });
148
+ }
149
+
150
+ if (!response.ok) {
151
+ await this.handleErrorResponse(response);
152
+ }
153
+
154
+ const jsonStart = Date.now();
155
+ const jsonData = (await response.json()) as T;
156
+ const parseDuration = Date.now() - jsonStart;
157
+
158
+ if (isDevelopment) {
159
+ console.log("[GroqClient] JSON parsed:", {
160
+ parseDuration: `${parseDuration}ms`,
161
+ hasData: !!jsonData,
162
+ dataKeys: jsonData ? Object.keys(jsonData) : [],
163
+ });
164
+ }
165
+
166
+ return jsonData;
167
+ } catch (error) {
168
+ if (isDevelopment) {
169
+ console.error("[GroqClient] Request error:", {
170
+ error,
171
+ errorMessage: error instanceof Error ? error.message : String(error),
172
+ errorName: error instanceof Error ? error.name : undefined,
173
+ });
174
+ }
175
+
176
+ throw this.handleRequestError(error);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Handle HTTP error responses
182
+ */
183
+ private async handleErrorResponse(response: Response): Promise<never> {
184
+ let errorMessage = `HTTP ${response.status}`;
185
+ let errorType = mapHttpStatusToErrorType(response.status);
186
+
187
+ try {
188
+ const errorData = (await response.json()) as {
189
+ error?: { message?: string };
190
+ };
191
+ if (errorData.error?.message) {
192
+ errorMessage = errorData.error.message;
193
+ }
194
+ } catch {
195
+ // If parsing fails, use default message
196
+ }
197
+
198
+ throw new GroqError(errorType, errorMessage, undefined, {
199
+ status: response.status,
200
+ url: response.url,
201
+ });
202
+ }
203
+
204
+ /**
205
+ * Handle request errors (network, timeout, abort)
206
+ */
207
+ private handleRequestError(error: unknown): GroqError {
208
+ if (error instanceof GroqError) {
209
+ return error;
210
+ }
211
+
212
+ if (error instanceof Error) {
213
+ if (error.name === "AbortError") {
214
+ return new GroqError(
215
+ GroqErrorType.ABORT_ERROR,
216
+ "Request was aborted by the client",
217
+ error
218
+ );
219
+ }
220
+
221
+ if (error.name === "TypeError" && error.message.includes("network")) {
222
+ return new GroqError(
223
+ GroqErrorType.NETWORK_ERROR,
224
+ "Network error: Unable to connect to Groq API",
225
+ error
226
+ );
227
+ }
228
+ }
229
+
230
+ return new GroqError(
231
+ GroqErrorType.UNKNOWN_ERROR,
232
+ error instanceof Error ? error.message : "Unknown error occurred",
233
+ error as Error
234
+ );
235
+ }
236
+
237
+ /**
238
+ * Send chat completion request (non-streaming)
239
+ */
240
+ async chatCompletion(
241
+ request: GroqChatRequest,
242
+ signal?: AbortSignal
243
+ ): Promise<GroqChatResponse> {
244
+ return this.request<GroqChatResponse>(
245
+ CHAT_COMPLETIONS_ENDPOINT,
246
+ { ...request, stream: false },
247
+ signal
248
+ );
249
+ }
250
+
251
+ /**
252
+ * Send chat completion request (streaming)
253
+ * Returns an async generator of chunks
254
+ */
255
+ async *chatCompletionStream(
256
+ request: GroqChatRequest,
257
+ signal?: AbortSignal
258
+ ): AsyncGenerator<GroqChatChunk> {
259
+ if (!this.config || !this.initialized) {
260
+ throw new GroqError(
261
+ GroqErrorType.MISSING_CONFIG,
262
+ "Client not initialized. Call initialize() first."
263
+ );
264
+ }
265
+
266
+ const url = `${this.config.baseUrl}${CHAT_COMPLETIONS_ENDPOINT}`;
267
+ const timeout = this.config.timeoutMs || DEFAULT_TIMEOUT;
268
+
269
+ // Create AbortController for timeout
270
+ const controller = new AbortController();
271
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
272
+
273
+ // Use provided signal if available
274
+ if (signal) {
275
+ signal.addEventListener("abort", () => controller.abort());
276
+ }
277
+
278
+ try {
279
+ const response = await fetch(url, {
280
+ method: "POST",
281
+ headers: {
282
+ "Content-Type": "application/json",
283
+ Authorization: `Bearer ${this.config.apiKey}`,
284
+ },
285
+ body: JSON.stringify({ ...request, stream: true }),
286
+ signal: controller.signal,
287
+ });
288
+
289
+ clearTimeout(timeoutId);
290
+
291
+ if (!response.ok) {
292
+ await this.handleErrorResponse(response);
293
+ }
294
+
295
+ if (!response.body) {
296
+ throw new GroqError(
297
+ GroqErrorType.NETWORK_ERROR,
298
+ "Response body is null"
299
+ );
300
+ }
301
+
302
+ // Read and parse Server-Sent Events
303
+ const reader = response.body.getReader();
304
+ const decoder = new TextDecoder();
305
+ let buffer = "";
306
+ const MAX_BUFFER_SIZE = 1024 * 1024; // 1MB max buffer
307
+
308
+ while (true) {
309
+ const { done, value } = await reader.read();
310
+
311
+ if (done) break;
312
+
313
+ buffer += decoder.decode(value, { stream: true });
314
+
315
+ // Prevent unlimited buffer growth
316
+ if (buffer.length > MAX_BUFFER_SIZE) {
317
+ buffer = buffer.slice(-MAX_BUFFER_SIZE);
318
+ }
319
+
320
+ const lines = buffer.split("\n");
321
+ buffer = lines.pop() || "";
322
+
323
+ for (const line of lines) {
324
+ const trimmed = line.trim();
325
+ if (!trimmed || trimmed === "data: [DONE]") continue;
326
+
327
+ if (trimmed.startsWith("data: ")) {
328
+ const jsonStr = trimmed.slice(6);
329
+ try {
330
+ const chunk = JSON.parse(jsonStr) as GroqChatChunk;
331
+ yield chunk;
332
+ } catch (error) {
333
+ if (isDevelopment) {
334
+ console.error("Failed to parse SSE chunk:", error);
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ } catch (error) {
341
+ throw this.handleRequestError(error);
342
+ }
343
+ }
344
+
345
+ /**
346
+ * Reset the client
347
+ */
348
+ reset(): void {
349
+ this.config = null;
350
+ this.initialized = false;
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Singleton instance
356
+ */
357
+ export const groqHttpClient = new GroqHttpClient();