@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 +21 -0
- package/package.json +6 -3
- package/src/domains/chat/hooks/use-chat.hook.ts +121 -73
- package/src/domains/chat/services/chat.service.ts +67 -16
- package/src/domains/groq/hooks/use-groq.hook.ts +46 -36
- package/src/domains/groq/interfaces/groq.interface.ts +2 -4
- package/src/domains/groq/services/http-client.service.ts +91 -32
- package/src/domains/groq/services/text-generation.service.ts +55 -5
- package/src/domains/groq/utils/cache-manager.util.ts +215 -0
- package/src/domains/groq/utils/debounce.util.ts +268 -0
- package/src/domains/groq/utils/index.ts +15 -1
- package/src/domains/groq/utils/request-deduplicator.util.ts +123 -0
- package/src/domains/groq/utils/request-queue.util.ts +178 -0
- package/src/domains/groq/utils/retry.util.ts +207 -0
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.
|
|
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": "
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
155
|
+
contextForAI
|
|
107
156
|
);
|
|
108
157
|
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
181
|
+
// Use functional update to get latest messages
|
|
182
|
+
setMessages(currentMessages => {
|
|
183
|
+
if (currentMessages.length === 0 || isLoading) {
|
|
184
|
+
return currentMessages;
|
|
185
|
+
}
|
|
140
186
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
187
|
+
const lastUserMessage = [...currentMessages]
|
|
188
|
+
.reverse()
|
|
189
|
+
.find((m) => m.sender === "user");
|
|
144
190
|
|
|
145
|
-
|
|
191
|
+
if (!lastUserMessage) return currentMessages;
|
|
146
192
|
|
|
147
|
-
|
|
148
|
-
|
|
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 =
|
|
153
|
-
(m, i) => !(i ===
|
|
200
|
+
const messagesWithoutAI = currentMessages.filter(
|
|
201
|
+
(m, i) => !(i === currentMessages.length - 1 && m.sender === "assistant")
|
|
154
202
|
);
|
|
155
203
|
|
|
156
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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(
|
|
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
|
-
//
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
-
//
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
options.
|
|
69
|
-
options.
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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:
|
|
94
|
+
model: stableConfig.model,
|
|
86
95
|
generationConfig: {
|
|
87
|
-
...
|
|
96
|
+
...stableConfig.generationConfig,
|
|
88
97
|
...config,
|
|
89
98
|
},
|
|
90
99
|
});
|
|
91
100
|
|
|
92
101
|
setResult(response);
|
|
93
|
-
|
|
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
|
-
|
|
110
|
+
currentOptions.onError?.(errorMessage);
|
|
102
111
|
|
|
103
112
|
throw err;
|
|
104
113
|
} finally {
|
|
105
114
|
setIsLoading(false);
|
|
106
115
|
}
|
|
107
116
|
},
|
|
108
|
-
[
|
|
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
|
-
|
|
129
|
+
const currentOptions = optionsRef.current;
|
|
130
|
+
currentOptions.onStart?.();
|
|
121
131
|
|
|
122
132
|
try {
|
|
123
133
|
const response = await textGenerationService.generateStructured<T>(prompt, {
|
|
124
|
-
model:
|
|
134
|
+
model: stableConfig.model,
|
|
125
135
|
generationConfig: {
|
|
126
|
-
...
|
|
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
|
-
|
|
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
|
-
|
|
152
|
+
currentOptions.onError?.(errorMessage);
|
|
143
153
|
|
|
144
154
|
throw err;
|
|
145
155
|
} finally {
|
|
146
156
|
setIsLoading(false);
|
|
147
157
|
}
|
|
148
158
|
},
|
|
149
|
-
[
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
179
|
+
contentChunks.push(c); // Use array accumulation
|
|
170
180
|
onChunk(c);
|
|
171
181
|
},
|
|
172
182
|
onComplete: (text) => {
|
|
173
183
|
setResult(text);
|
|
174
|
-
|
|
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:
|
|
192
|
+
model: stableConfig.model,
|
|
183
193
|
generationConfig: {
|
|
184
|
-
...
|
|
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
|
-
|
|
207
|
+
currentOptions.onError?.(errorMessage);
|
|
198
208
|
|
|
199
209
|
throw err;
|
|
200
210
|
} finally {
|
|
201
211
|
setIsLoading(false);
|
|
202
212
|
}
|
|
203
213
|
},
|
|
204
|
-
[
|
|
214
|
+
[stableConfig]
|
|
205
215
|
);
|
|
206
216
|
|
|
207
217
|
const reset = useCallback(() => {
|