@umituz/web-ai-groq-provider 1.1.7 → 1.1.9

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 umituz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-ai-groq-provider",
3
- "version": "1.1.7",
3
+ "version": "1.1.9",
4
4
  "description": "Groq AI text generation provider for React web applications",
5
5
  "main": "./src/index.ts",
6
6
  "types": "./src/index.ts",
@@ -13,7 +13,7 @@
13
13
  },
14
14
  "scripts": {
15
15
  "typecheck": "tsc --noEmit",
16
- "lint": "echo 'Lint passed'",
16
+ "lint": "eslint .",
17
17
  "build": "tsup src/index.ts --format cjs,esm --dts --clean --external react",
18
18
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch --external react",
19
19
  "version:patch": "npm version patch -m 'chore: release v%s'",
@@ -40,11 +40,14 @@
40
40
  "react": ">=18.2.0"
41
41
  },
42
42
  "devDependencies": {
43
+ "@eslint/js": "^9.32.0",
43
44
  "@types/node": "^20.0.0",
44
45
  "@types/react": "^18.2.0",
46
+ "eslint": "^9.32.0",
45
47
  "react": "18.3.1",
46
48
  "tsup": "^8.0.0",
47
- "typescript": "~5.9.2"
49
+ "typescript": "~5.9.2",
50
+ "typescript-eslint": "^8.38.0"
48
51
  },
49
52
  "publishConfig": {
50
53
  "access": "public"
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * useChat Hook
3
- * @description Main React hook for chat functionality
3
+ * @description Main React hook for chat functionality with performance optimizations
4
4
  */
5
5
 
6
- import { useState, useCallback, useEffect } from "react";
6
+ import { useState, useCallback, useEffect, useRef } from "react";
7
7
  import type {
8
8
  ChatMessage,
9
9
  ChatConfig,
@@ -19,7 +19,7 @@ const MESSAGE_ID_PREFIX = "msg-";
19
19
  const MESSAGE_ID_USER_SUFFIX = "user";
20
20
 
21
21
  /**
22
- * Hook for chat functionality with AI integration
22
+ * Hook for chat functionality with AI integration and performance optimizations
23
23
  */
24
24
  export function useChat(options: UseChatOptions): UseChatReturn {
25
25
  const {
@@ -37,6 +37,11 @@ export function useChat(options: UseChatOptions): UseChatReturn {
37
37
  const [isTyping, setIsTyping] = useState(false);
38
38
  const [error, setError] = useState<string | null>(null);
39
39
 
40
+ // Refs for deduplication and callback tracking
41
+ const pendingMessageRef = useRef<string | null>(null);
42
+ const optionsRef = useRef(options);
43
+ optionsRef.current = options;
44
+
40
45
  // Initialize chat service config
41
46
  useEffect(() => {
42
47
  if (config) {
@@ -44,6 +49,13 @@ export function useChat(options: UseChatOptions): UseChatReturn {
44
49
  }
45
50
  }, [config]);
46
51
 
52
+ // Cleanup on unmount
53
+ useEffect(() => {
54
+ return () => {
55
+ pendingMessageRef.current = null;
56
+ };
57
+ }, []);
58
+
47
59
  // Load messages from storage on mount
48
60
  useEffect(() => {
49
61
  if (!storage) return;
@@ -60,7 +72,7 @@ export function useChat(options: UseChatOptions): UseChatReturn {
60
72
  void loadMessages();
61
73
  }, [conversationId, storage]);
62
74
 
63
- // Save message to storage
75
+ // Save message to storage (stable reference)
64
76
  const saveToStorage = useCallback(
65
77
  async (message: ChatMessage) => {
66
78
  if (!autoSave || !storage) return;
@@ -74,16 +86,47 @@ export function useChat(options: UseChatOptions): UseChatReturn {
74
86
  [conversationId, storage, autoSave]
75
87
  );
76
88
 
77
- // Send message
89
+ // Batch save messages to storage
90
+ const saveMessagesToStorage = useCallback(
91
+ async (messagesToSave: ChatMessage[]) => {
92
+ if (!autoSave || !storage || messagesToSave.length === 0) return;
93
+
94
+ try {
95
+ // Try batch save if supported
96
+ if ('saveMessages' in storage) {
97
+ await (storage as any).saveMessages(conversationId, messagesToSave);
98
+ } else {
99
+ // Fallback to individual saves in parallel
100
+ await Promise.all(
101
+ messagesToSave.map(msg => storage.saveMessage(conversationId, msg))
102
+ );
103
+ }
104
+ } catch (err) {
105
+ // Silently fail
106
+ }
107
+ },
108
+ [conversationId, storage, autoSave]
109
+ );
110
+
111
+ // Send message with deduplication and functional updates
78
112
  const sendMessage = useCallback(
79
113
  async (text: string, type?: ChatMessage["type"]): Promise<void> => {
80
114
  if (!text.trim() || isLoading) return;
81
115
 
116
+ // Prevent duplicate messages
117
+ if (pendingMessageRef.current === text) {
118
+ return;
119
+ }
120
+
121
+ pendingMessageRef.current = text;
82
122
  setIsLoading(true);
83
123
  setError(null);
84
124
 
125
+ // Get latest callbacks from ref
126
+ const currentOptions = optionsRef.current;
127
+
85
128
  try {
86
- // Create user message first
129
+ // Create user message
87
130
  const userMessage: ChatMessage = {
88
131
  id: `${MESSAGE_ID_PREFIX}${Date.now()}-${MESSAGE_ID_USER_SUFFIX}`,
89
132
  sender: "user",
@@ -92,102 +135,107 @@ export function useChat(options: UseChatOptions): UseChatReturn {
92
135
  type: type || "text",
93
136
  };
94
137
 
95
- // Add user message immediately
96
- const messagesWithUser = [...messages, userMessage];
97
- setMessages(messagesWithUser);
98
- await saveToStorage(userMessage);
99
- onMessageSent?.(userMessage);
138
+ // Use functional update to avoid stale closure
139
+ setMessages(prev => {
140
+ const updated = [...prev, userMessage];
141
+ saveToStorage(userMessage);
142
+ currentOptions.onMessageSent?.(userMessage);
143
+ return updated;
144
+ });
100
145
 
101
146
  // Generate AI response
102
147
  setIsTyping(true);
148
+
149
+ // Get current messages for context
150
+ const contextForAI = messages; // Use closure messages, not functional update
151
+
103
152
  const aiResponse = await chatService.generateAIResponse(
104
153
  conversationId,
105
154
  text,
106
- messages
155
+ contextForAI
107
156
  );
108
157
 
109
- // Update messages with AI response
110
- const newMessages = [...messagesWithUser, aiResponse];
111
- setMessages(newMessages);
112
-
113
- // Save AI response to storage
114
- await saveToStorage(aiResponse);
115
- onAIResponse?.(aiResponse);
158
+ // Use functional update for AI response
159
+ setMessages(prev => {
160
+ const updated = [...prev, aiResponse];
161
+ saveToStorage(aiResponse);
162
+ currentOptions.onAIResponse?.(aiResponse);
163
+ return updated;
164
+ });
116
165
  } catch (err) {
117
166
  const errorMessage =
118
167
  err instanceof Error ? err.message : "Failed to send message";
119
168
  setError(errorMessage);
120
- onError?.(errorMessage);
169
+ currentOptions.onError?.(errorMessage);
121
170
  } finally {
122
171
  setIsLoading(false);
123
172
  setIsTyping(false);
173
+ pendingMessageRef.current = null;
124
174
  }
125
175
  },
126
- [
127
- conversationId,
128
- messages,
129
- isLoading,
130
- saveToStorage,
131
- onMessageSent,
132
- onAIResponse,
133
- onError,
134
- ]
176
+ [conversationId, isLoading, saveToStorage, messages]
135
177
  );
136
178
 
137
- // Regenerate last AI response
179
+ // Regenerate last AI response with functional updates
138
180
  const regenerate = useCallback(async (): Promise<void> => {
139
- if (messages.length === 0 || isLoading) return;
181
+ // Use functional update to get latest messages
182
+ setMessages(currentMessages => {
183
+ if (currentMessages.length === 0 || isLoading) {
184
+ return currentMessages;
185
+ }
140
186
 
141
- const lastUserMessage = [...messages]
142
- .reverse()
143
- .find((m) => m.sender === "user");
187
+ const lastUserMessage = [...currentMessages]
188
+ .reverse()
189
+ .find((m) => m.sender === "user");
144
190
 
145
- if (!lastUserMessage) return;
191
+ if (!lastUserMessage) return currentMessages;
146
192
 
147
- setIsLoading(true);
148
- setError(null);
193
+ // Async operation - need to handle differently
194
+ setIsLoading(true);
195
+ setError(null);
196
+
197
+ const currentOptions = optionsRef.current;
149
198
 
150
- try {
151
199
  // Remove last AI message if exists
152
- const messagesWithoutAI = messages.filter(
153
- (m, i) => !(i === messages.length - 1 && m.sender === "assistant")
200
+ const messagesWithoutAI = currentMessages.filter(
201
+ (m, i) => !(i === currentMessages.length - 1 && m.sender === "assistant")
154
202
  );
155
203
 
156
- setMessages(messagesWithoutAI);
204
+ // Trigger async update
205
+ (async () => {
206
+ try {
207
+ // Generate new response
208
+ setIsTyping(true);
209
+ const aiResponse = await chatService.generateAIResponse(
210
+ conversationId,
211
+ lastUserMessage.content,
212
+ messagesWithoutAI.slice(0, -1)
213
+ );
157
214
 
158
- // Generate new response
159
- setIsTyping(true);
160
- const aiResponse = await chatService.generateAIResponse(
161
- conversationId,
162
- lastUserMessage.content,
163
- messagesWithoutAI.slice(0, -1)
164
- );
215
+ // Update messages with new response
216
+ setMessages(prev => {
217
+ const lastIsAI = prev.length > 0 && prev[prev.length - 1].sender === "assistant";
218
+ const base = lastIsAI ? prev.slice(0, -1) : prev;
219
+ return [...base, aiResponse];
220
+ });
165
221
 
166
- // Update messages
167
- const newMessages = [...messagesWithoutAI, aiResponse];
168
- setMessages(newMessages);
169
-
170
- // Save to storage
171
- await saveToStorage(aiResponse);
172
-
173
- onAIResponse?.(aiResponse);
174
- } catch (err) {
175
- const errorMessage =
176
- err instanceof Error ? err.message : "Failed to regenerate";
177
- setError(errorMessage);
178
- onError?.(errorMessage);
179
- } finally {
180
- setIsLoading(false);
181
- setIsTyping(false);
182
- }
183
- }, [
184
- conversationId,
185
- messages,
186
- isLoading,
187
- saveToStorage,
188
- onAIResponse,
189
- onError,
190
- ]);
222
+ // Save to storage
223
+ await saveToStorage(aiResponse);
224
+ currentOptions.onAIResponse?.(aiResponse);
225
+ } catch (err) {
226
+ const errorMessage =
227
+ err instanceof Error ? err.message : "Failed to regenerate";
228
+ setError(errorMessage);
229
+ currentOptions.onError?.(errorMessage);
230
+ } finally {
231
+ setIsLoading(false);
232
+ setIsTyping(false);
233
+ }
234
+ })();
235
+
236
+ return messagesWithoutAI;
237
+ });
238
+ }, [conversationId, isLoading, saveToStorage]);
191
239
 
192
240
  // Clear all messages
193
241
  const clearMessages = useCallback(() => {
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Chat Service
3
- * @description Core chat logic with AI integration
3
+ * @description Core chat logic with AI integration and performance optimizations
4
4
  */
5
5
 
6
6
  import type {
7
7
  IChatService,
8
- IMessageFormatter,
9
8
  } from "../interfaces";
10
9
  import type {
11
10
  ChatMessage,
@@ -17,6 +16,7 @@ import type {
17
16
  import { textGenerationService } from "../../groq/services";
18
17
  import { messageFormatter } from "../utils/message-formatter";
19
18
  import { DEFAULT_MODELS } from "../../groq/constants";
19
+ import { cacheManager } from "../../groq/utils/cache-manager.util";
20
20
 
21
21
  const DEFAULT_CONFIG: ChatConfig = {
22
22
  temperature: 0.8,
@@ -27,13 +27,19 @@ const DEFAULT_CONFIG: ChatConfig = {
27
27
 
28
28
  class ChatService implements IChatService {
29
29
  private config: ChatConfig = DEFAULT_CONFIG;
30
+ private systemPromptCache = new Map<string, string>();
31
+ private readonly PROMPT_CACHE_KEY_PREFIX = "system-prompt";
30
32
 
31
33
  initialize(config: ChatConfig): void {
32
34
  this.config = { ...DEFAULT_CONFIG, ...config };
35
+ // Clear system prompt cache when config changes
36
+ this.clearSystemPromptCache();
33
37
  }
34
38
 
35
39
  updateConfig(config: Partial<ChatConfig>): void {
36
40
  this.config = { ...this.config, ...config };
41
+ // Clear system prompt cache when config changes
42
+ this.clearSystemPromptCache();
37
43
  }
38
44
 
39
45
  async sendMessage(input: SendMessageInput): Promise<SendMessageResult> {
@@ -57,7 +63,7 @@ class ChatService implements IChatService {
57
63
  };
58
64
  }
59
65
 
60
- async getConversation(conversationId: string): Promise<ChatConversation | null> {
66
+ async getConversation(_conversationId: string): Promise<ChatConversation | null> {
61
67
  // This would fetch from storage
62
68
  // For now, return null - storage is handled by application
63
69
  return null;
@@ -74,7 +80,7 @@ class ChatService implements IChatService {
74
80
  context: ChatMessage[] = []
75
81
  ): Promise<ChatMessage> {
76
82
  try {
77
- // Build system prompt from config
83
+ // Build system prompt from config (with caching)
78
84
  const systemPrompt = this.buildSystemPrompt();
79
85
 
80
86
  // Format messages for Groq
@@ -89,17 +95,34 @@ class ChatService implements IChatService {
89
95
  content: userMessage,
90
96
  });
91
97
 
92
- // Generate response
93
- const response = await textGenerationService.generateChatCompletion(
94
- groqMessages,
95
- {
96
- model: DEFAULT_MODELS.TEXT,
97
- generationConfig: {
98
- temperature: this.config.temperature,
99
- maxTokens: this.config.maxTokens,
100
- },
101
- }
102
- );
98
+ // Check cache for response
99
+ const cacheKey = cacheManager.generateKey({
100
+ companionId,
101
+ userMessage,
102
+ context: context.map(m => ({ sender: m.sender, content: m.content })),
103
+ config: {
104
+ temperature: this.config.temperature,
105
+ maxTokens: this.config.maxTokens,
106
+ },
107
+ });
108
+
109
+ const cachedResponse = cacheManager.get<string>(cacheKey);
110
+ const response = cachedResponse ||
111
+ await textGenerationService.generateChatCompletion(
112
+ groqMessages,
113
+ {
114
+ model: DEFAULT_MODELS.TEXT,
115
+ generationConfig: {
116
+ temperature: this.config.temperature,
117
+ maxTokens: this.config.maxTokens,
118
+ },
119
+ }
120
+ );
121
+
122
+ // Cache the response if it wasn't cached
123
+ if (!cachedResponse) {
124
+ cacheManager.set(cacheKey, response);
125
+ }
103
126
 
104
127
  // Format response as chat message
105
128
  const aiMessage: ChatMessage = {
@@ -107,6 +130,7 @@ class ChatService implements IChatService {
107
130
  metadata: {
108
131
  companionId,
109
132
  model: DEFAULT_MODELS.TEXT,
133
+ cached: !!cachedResponse,
110
134
  },
111
135
  };
112
136
 
@@ -122,11 +146,38 @@ class ChatService implements IChatService {
122
146
 
123
147
  private buildSystemPrompt(): string {
124
148
  const language = this.config.language || "tr";
125
- const basePrompt = this.config.systemPrompt || this.getDefaultPrompt(language);
149
+ const customPrompt = this.config.systemPrompt;
150
+
151
+ // Generate cache key
152
+ const cacheKey = `${this.PROMPT_CACHE_KEY_PREFIX}-${language}-${customPrompt?.length || 0}`;
153
+
154
+ // Check cache
155
+ if (this.systemPromptCache.has(cacheKey)) {
156
+ return this.systemPromptCache.get(cacheKey)!;
157
+ }
158
+
159
+ // Build prompt
160
+ const basePrompt = customPrompt || this.getDefaultPrompt(language);
161
+
162
+ // Cache it
163
+ this.systemPromptCache.set(cacheKey, basePrompt);
126
164
 
127
165
  return basePrompt;
128
166
  }
129
167
 
168
+ private clearSystemPromptCache(): void {
169
+ this.systemPromptCache.clear();
170
+ }
171
+
172
+ /**
173
+ * Clear all cached data (system prompts and response cache)
174
+ */
175
+ clearCache(): void {
176
+ this.clearSystemPromptCache();
177
+ // Note: Response cache is managed by textGenerationService
178
+ // Export cache clearing function from there if needed
179
+ }
180
+
130
181
  private getDefaultPrompt(language: string): string {
131
182
  if (language === "tr") {
132
183
  return `Sen samimi, eğlenceli ve ilgi çekici bir AI companionsın. Türkçe konuşuyorsun.
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * useGroq Hook
3
- * @description Main React hook for Groq text generation
3
+ * @description Main React hook for Groq text generation with performance optimizations
4
4
  */
5
5
 
6
- import { useState, useCallback, useMemo } from "react";
6
+ import { useState, useCallback, useMemo, useRef, useEffect } from "react";
7
7
  import type { GroqGenerationConfig } from "../interfaces";
8
8
  import type { TextGenerationOptions, StreamingCallbacks } from "../interfaces";
9
9
  import { textGenerationService } from "../services";
10
10
  import { GroqError } from "../utils/groq-error.util";
11
11
  import { getUserFriendlyError } from "../utils/error.util";
12
12
  import { DEFAULT_MODELS } from "../constants";
13
+ import { groqHttpClient } from "../services/http-client.service";
13
14
 
14
15
  export interface UseGroqOptions {
15
16
  /** Initial model to use */
@@ -51,46 +52,54 @@ export interface UseGroqReturn {
51
52
  }
52
53
 
53
54
  /**
54
- * Hook for Groq text generation
55
+ * Hook for Groq text generation with performance optimizations
55
56
  */
56
57
  export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
57
58
  const [isLoading, setIsLoading] = useState(false);
58
59
  const [error, setError] = useState<string | null>(null);
59
60
  const [result, setResult] = useState<string | null>(null);
60
61
 
61
- // Memoize options to prevent unnecessary callback recreations
62
- const stableOptions = useMemo(
63
- () => options,
64
- [
65
- options.model,
66
- options.generationConfig?.temperature,
67
- options.generationConfig?.maxTokens,
68
- options.generationConfig?.topP,
69
- options.onStart,
70
- options.onSuccess,
71
- options.onError,
72
- ]
62
+ // Use ref for callbacks to avoid stale closures and unstable references
63
+ const optionsRef = useRef(options);
64
+ optionsRef.current = options;
65
+
66
+ // Memoize only the stable configuration values (not callbacks)
67
+ const stableConfig = useMemo(
68
+ () => ({
69
+ model: options.model,
70
+ generationConfig: options.generationConfig,
71
+ }),
72
+ [options.model, options.generationConfig?.temperature, options.generationConfig?.maxTokens, options.generationConfig?.topP]
73
73
  );
74
74
 
75
+ // Cleanup pending requests on unmount
76
+ useEffect(() => {
77
+ return () => {
78
+ groqHttpClient.cancelPendingRequests();
79
+ };
80
+ }, []);
81
+
75
82
  const generate = useCallback(
76
83
  async (prompt: string, config?: GroqGenerationConfig): Promise<string> => {
77
84
  setIsLoading(true);
78
85
  setError(null);
79
86
  setResult(null);
80
87
 
81
- stableOptions.onStart?.();
88
+ // Use ref to get latest callbacks without adding to dependencies
89
+ const currentOptions = optionsRef.current;
90
+ currentOptions.onStart?.();
82
91
 
83
92
  try {
84
93
  const response = await textGenerationService.generateCompletion(prompt, {
85
- model: stableOptions.model,
94
+ model: stableConfig.model,
86
95
  generationConfig: {
87
- ...stableOptions.generationConfig,
96
+ ...stableConfig.generationConfig,
88
97
  ...config,
89
98
  },
90
99
  });
91
100
 
92
101
  setResult(response);
93
- stableOptions.onSuccess?.(response);
102
+ currentOptions.onSuccess?.(response);
94
103
 
95
104
  return response;
96
105
  } catch (err) {
@@ -98,14 +107,14 @@ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
98
107
  err instanceof Error ? err : new Error("Unknown error")
99
108
  );
100
109
  setError(errorMessage);
101
- stableOptions.onError?.(errorMessage);
110
+ currentOptions.onError?.(errorMessage);
102
111
 
103
112
  throw err;
104
113
  } finally {
105
114
  setIsLoading(false);
106
115
  }
107
116
  },
108
- [stableOptions]
117
+ [stableConfig]
109
118
  );
110
119
 
111
120
  const generateJSON = useCallback(
@@ -117,13 +126,14 @@ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
117
126
  setError(null);
118
127
  setResult(null);
119
128
 
120
- stableOptions.onStart?.();
129
+ const currentOptions = optionsRef.current;
130
+ currentOptions.onStart?.();
121
131
 
122
132
  try {
123
133
  const response = await textGenerationService.generateStructured<T>(prompt, {
124
- model: stableOptions.model,
134
+ model: stableConfig.model,
125
135
  generationConfig: {
126
- ...stableOptions.generationConfig,
136
+ ...stableConfig.generationConfig,
127
137
  ...config,
128
138
  },
129
139
  schema: config?.schema,
@@ -131,7 +141,7 @@ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
131
141
 
132
142
  const jsonString = JSON.stringify(response, null, 2);
133
143
  setResult(jsonString);
134
- stableOptions.onSuccess?.(jsonString);
144
+ currentOptions.onSuccess?.(jsonString);
135
145
 
136
146
  return response;
137
147
  } catch (err) {
@@ -139,14 +149,14 @@ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
139
149
  err instanceof Error ? err : new Error("Unknown error")
140
150
  );
141
151
  setError(errorMessage);
142
- stableOptions.onError?.(errorMessage);
152
+ currentOptions.onError?.(errorMessage);
143
153
 
144
154
  throw err;
145
155
  } finally {
146
156
  setIsLoading(false);
147
157
  }
148
158
  },
149
- [stableOptions]
159
+ [stableConfig]
150
160
  );
151
161
 
152
162
  const stream = useCallback(
@@ -159,19 +169,19 @@ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
159
169
  setError(null);
160
170
  setResult(null);
161
171
 
162
- let fullContent = "";
163
-
164
- stableOptions.onStart?.();
172
+ const contentChunks: string[] = [];
173
+ const currentOptions = optionsRef.current;
174
+ currentOptions.onStart?.();
165
175
 
166
176
  try {
167
177
  const callbacks: StreamingCallbacks = {
168
178
  onChunk: (c) => {
169
- fullContent += c;
179
+ contentChunks.push(c); // Use array accumulation
170
180
  onChunk(c);
171
181
  },
172
182
  onComplete: (text) => {
173
183
  setResult(text);
174
- stableOptions.onSuccess?.(text);
184
+ currentOptions.onSuccess?.(text);
175
185
  },
176
186
  };
177
187
 
@@ -179,9 +189,9 @@ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
179
189
  prompt,
180
190
  callbacks,
181
191
  {
182
- model: stableOptions.model,
192
+ model: stableConfig.model,
183
193
  generationConfig: {
184
- ...stableOptions.generationConfig,
194
+ ...stableConfig.generationConfig,
185
195
  ...config,
186
196
  },
187
197
  }
@@ -194,14 +204,14 @@ export function useGroq(options: UseGroqOptions = {}): UseGroqReturn {
194
204
  err instanceof Error ? err : new Error("Unknown error")
195
205
  );
196
206
  setError(errorMessage);
197
- stableOptions.onError?.(errorMessage);
207
+ currentOptions.onError?.(errorMessage);
198
208
 
199
209
  throw err;
200
210
  } finally {
201
211
  setIsLoading(false);
202
212
  }
203
213
  },
204
- [stableOptions]
214
+ [stableConfig]
205
215
  );
206
216
 
207
217
  const reset = useCallback(() => {