@umituz/web-ai-groq-provider 1.1.13 → 2.0.0
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 +154 -72
- package/dist/domains/chat/index.cjs +539 -0
- package/dist/domains/chat/index.cjs.map +1 -0
- package/dist/domains/chat/index.d.cts +322 -0
- package/dist/domains/chat/index.d.ts +322 -0
- package/dist/domains/chat/index.js +531 -0
- package/dist/domains/chat/index.js.map +1 -0
- package/dist/domains/groq/index.cjs +1499 -0
- package/dist/domains/groq/index.cjs.map +1 -0
- package/dist/domains/groq/index.d.cts +561 -0
- package/dist/domains/groq/index.d.ts +561 -0
- package/dist/domains/groq/index.js +1453 -0
- package/dist/domains/groq/index.js.map +1 -0
- package/dist/index.cjs +1993 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +53 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +1937 -0
- package/dist/index.js.map +1 -0
- package/dist/text-generation.service-BP4Q_SH0.d.cts +252 -0
- package/dist/text-generation.service-BP4Q_SH0.d.ts +252 -0
- package/package.json +25 -11
- package/src/domains/chat/entities/chat.entities.ts +0 -113
- package/src/domains/chat/entities/index.ts +0 -14
- package/src/domains/chat/hooks/index.ts +0 -5
- package/src/domains/chat/hooks/use-chat.hook.ts +0 -259
- package/src/domains/chat/index.ts +0 -32
- package/src/domains/chat/interfaces/chat.interface.ts +0 -80
- package/src/domains/chat/interfaces/index.ts +0 -10
- package/src/domains/chat/services/chat.service.ts +0 -195
- package/src/domains/chat/services/index.ts +0 -5
- package/src/domains/chat/utils/message-formatter.ts +0 -49
- package/src/domains/groq/constants/error.constants.ts +0 -46
- package/src/domains/groq/constants/groq.constants.ts +0 -68
- package/src/domains/groq/constants/index.ts +0 -20
- package/src/domains/groq/entities/groq.entities.ts +0 -196
- package/src/domains/groq/entities/index.ts +0 -20
- package/src/domains/groq/hooks/index.ts +0 -7
- package/src/domains/groq/hooks/use-groq.hook.ts +0 -232
- package/src/domains/groq/index.ts +0 -72
- package/src/domains/groq/interfaces/groq.interface.ts +0 -77
- package/src/domains/groq/interfaces/index.ts +0 -22
- package/src/domains/groq/services/http-client.service.ts +0 -265
- package/src/domains/groq/services/index.ts +0 -9
- package/src/domains/groq/services/text-generation.service.ts +0 -238
- package/src/domains/groq/utils/cache-manager.util.ts +0 -215
- package/src/domains/groq/utils/debounce.util.ts +0 -268
- package/src/domains/groq/utils/error.util.ts +0 -57
- package/src/domains/groq/utils/groq-error.util.ts +0 -18
- package/src/domains/groq/utils/index.ts +0 -37
- package/src/domains/groq/utils/message.util.ts +0 -57
- package/src/domains/groq/utils/request-deduplicator.util.ts +0 -123
- package/src/domains/groq/utils/request-queue.util.ts +0 -178
- package/src/domains/groq/utils/retry.util.ts +0 -207
- package/src/index.ts +0 -15
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
import { useState, useRef, useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
// src/domains/groq/constants/groq.constants.ts
|
|
4
|
+
var GROQ_MODELS = {
|
|
5
|
+
/** Llama 3.1 8B - Fast and efficient (560 T/s) */
|
|
6
|
+
LLAMA_3_1_8B_INSTANT: "llama-3.1-8b-instant"};
|
|
7
|
+
var DEFAULT_MODELS = {
|
|
8
|
+
TEXT: GROQ_MODELS.LLAMA_3_1_8B_INSTANT};
|
|
9
|
+
|
|
10
|
+
// src/domains/groq/calculations/random-base36-suffix.ts
|
|
11
|
+
var BASE_36_RADIX = 36;
|
|
12
|
+
var DECIMAL_OFFSET = 2;
|
|
13
|
+
function generateRandomBase36Suffix(length, random = Math.random) {
|
|
14
|
+
if (length <= 0) return "";
|
|
15
|
+
const raw = random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
|
|
16
|
+
if (raw.length >= length) return raw.slice(0, length);
|
|
17
|
+
let result = raw;
|
|
18
|
+
while (result.length < length) {
|
|
19
|
+
result += random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);
|
|
20
|
+
}
|
|
21
|
+
return result.slice(0, length);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/domains/groq/constants/identifier.constants.ts
|
|
25
|
+
var MESSAGE_ID_PREFIX = "msg-";
|
|
26
|
+
var CHAT_MESSAGE_ID_SUFFIX_LENGTH = 9;
|
|
27
|
+
var USER_ROLE = "user";
|
|
28
|
+
var ASSISTANT_ROLE = "assistant";
|
|
29
|
+
|
|
30
|
+
// src/domains/chat/utils/chat-id-generator.ts
|
|
31
|
+
var ID_PART_DELIMITER = "-";
|
|
32
|
+
var ChatIdGenerator = class {
|
|
33
|
+
counter = 0;
|
|
34
|
+
randomLength;
|
|
35
|
+
constructor(randomLength = CHAT_MESSAGE_ID_SUFFIX_LENGTH) {
|
|
36
|
+
this.randomLength = randomLength;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate a new unique message ID.
|
|
40
|
+
*/
|
|
41
|
+
next(sender) {
|
|
42
|
+
this.counter += 1;
|
|
43
|
+
const random = generateRandomBase36Suffix(this.randomLength);
|
|
44
|
+
return [
|
|
45
|
+
MESSAGE_ID_PREFIX,
|
|
46
|
+
Date.now(),
|
|
47
|
+
sender,
|
|
48
|
+
this.counter,
|
|
49
|
+
random
|
|
50
|
+
].join(ID_PART_DELIMITER);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
var chatIdGenerator = new ChatIdGenerator();
|
|
54
|
+
|
|
55
|
+
// src/domains/chat/utils/message-formatter.ts
|
|
56
|
+
var MessageFormatter = class {
|
|
57
|
+
constructor(idGenerator = chatIdGenerator) {
|
|
58
|
+
this.idGenerator = idGenerator;
|
|
59
|
+
}
|
|
60
|
+
toGroqMessages(messages, systemPrompt) {
|
|
61
|
+
const groqMessages = [];
|
|
62
|
+
if (systemPrompt) {
|
|
63
|
+
groqMessages.push({ role: "system", content: systemPrompt });
|
|
64
|
+
}
|
|
65
|
+
for (const message of messages) {
|
|
66
|
+
if (message.sender === "system") continue;
|
|
67
|
+
groqMessages.push({
|
|
68
|
+
role: message.sender === "user" ? USER_ROLE : ASSISTANT_ROLE,
|
|
69
|
+
content: message.content
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return groqMessages;
|
|
73
|
+
}
|
|
74
|
+
toChatMessage(content, sender) {
|
|
75
|
+
return {
|
|
76
|
+
id: this.idGenerator.next(sender),
|
|
77
|
+
sender,
|
|
78
|
+
content: content.trim(),
|
|
79
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
80
|
+
type: "text"
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var messageFormatter = new MessageFormatter();
|
|
85
|
+
|
|
86
|
+
// src/domains/chat/utils/prompt-strategy.ts
|
|
87
|
+
var DEFAULT_PROMPT_STRATEGY = {
|
|
88
|
+
resolve: (config) => {
|
|
89
|
+
if (config.systemPrompt && config.systemPrompt.length > 0) {
|
|
90
|
+
return config.systemPrompt;
|
|
91
|
+
}
|
|
92
|
+
return void 0;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/domains/groq/utils/groq-error.ts
|
|
97
|
+
var GroqError = class extends Error {
|
|
98
|
+
type;
|
|
99
|
+
cause;
|
|
100
|
+
metadata;
|
|
101
|
+
constructor(type, message, cause, metadata = {}) {
|
|
102
|
+
super(message);
|
|
103
|
+
this.name = "GroqError";
|
|
104
|
+
this.type = type;
|
|
105
|
+
this.cause = cause;
|
|
106
|
+
this.metadata = metadata;
|
|
107
|
+
}
|
|
108
|
+
toString() {
|
|
109
|
+
const statusPart = typeof this.metadata.status === "number" ? ` (HTTP ${this.metadata.status})` : "";
|
|
110
|
+
return `${this.name} [${this.type}]: ${this.message}${statusPart}`;
|
|
111
|
+
}
|
|
112
|
+
toJSON() {
|
|
113
|
+
return {
|
|
114
|
+
name: this.name,
|
|
115
|
+
type: this.type,
|
|
116
|
+
message: this.message,
|
|
117
|
+
metadata: this.metadata
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/domains/groq/constants/error-types.ts
|
|
123
|
+
var USER_FRIENDLY_ERROR_MESSAGES = {
|
|
124
|
+
["INVALID_API_KEY" /* INVALID_API_KEY */]: "Please check your Groq API key and try again.",
|
|
125
|
+
["MISSING_CONFIG" /* MISSING_CONFIG */]: "The provider is not configured. Please call configureProvider first.",
|
|
126
|
+
["NETWORK_ERROR" /* NETWORK_ERROR */]: "Network error. Please check your connection and try again.",
|
|
127
|
+
["ABORT_ERROR" /* ABORT_ERROR */]: "Request was cancelled.",
|
|
128
|
+
["RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */]: "You've reached the rate limit. Please wait a moment and try again.",
|
|
129
|
+
["SERVER_ERROR" /* SERVER_ERROR */]: "Groq service is temporarily unavailable. Please try again later.",
|
|
130
|
+
["CLIENT_ERROR" /* CLIENT_ERROR */]: "The request was invalid. Please check your input and try again.",
|
|
131
|
+
["UNKNOWN_ERROR" /* UNKNOWN_ERROR */]: "An unexpected error occurred. Please try again."
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// src/domains/chat/services/chat.service.ts
|
|
135
|
+
var DEFAULT_CONFIG = {
|
|
136
|
+
temperature: 0.8,
|
|
137
|
+
maxTokens: 500
|
|
138
|
+
};
|
|
139
|
+
var ChatService = class {
|
|
140
|
+
constructor(textGeneration) {
|
|
141
|
+
this.textGeneration = textGeneration;
|
|
142
|
+
}
|
|
143
|
+
config = DEFAULT_CONFIG;
|
|
144
|
+
promptStrategy = DEFAULT_PROMPT_STRATEGY;
|
|
145
|
+
storage = null;
|
|
146
|
+
initialize(config) {
|
|
147
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
148
|
+
}
|
|
149
|
+
updateConfig(config) {
|
|
150
|
+
this.config = { ...this.config, ...config };
|
|
151
|
+
}
|
|
152
|
+
setPromptStrategy(strategy) {
|
|
153
|
+
this.promptStrategy = strategy;
|
|
154
|
+
}
|
|
155
|
+
setStorage(storage) {
|
|
156
|
+
this.storage = storage;
|
|
157
|
+
}
|
|
158
|
+
async getConversation(conversationId) {
|
|
159
|
+
const storage = this.requireStorage();
|
|
160
|
+
const messages = await storage.getMessages(conversationId);
|
|
161
|
+
if (messages.length === 0) return null;
|
|
162
|
+
const last = messages[messages.length - 1];
|
|
163
|
+
const first = messages[0];
|
|
164
|
+
const companionId = String(first.metadata?.["companionId"] ?? "unknown");
|
|
165
|
+
const companionName = String(first.metadata?.["companionName"] ?? "Unknown");
|
|
166
|
+
return {
|
|
167
|
+
id: conversationId,
|
|
168
|
+
companionId,
|
|
169
|
+
companionName,
|
|
170
|
+
lastMessage: last,
|
|
171
|
+
messageCount: messages.length,
|
|
172
|
+
updatedAt: last.timestamp
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
async getConversations() {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
async sendMessage(input) {
|
|
179
|
+
const userMessage = {
|
|
180
|
+
...messageFormatter.toChatMessage(input.text, "user"),
|
|
181
|
+
type: input.type ?? "text",
|
|
182
|
+
...input.imageUrl !== void 0 ? { imageUrl: input.imageUrl } : {}
|
|
183
|
+
};
|
|
184
|
+
const aiResponse = await this.generateAIResponse(
|
|
185
|
+
input.companionId,
|
|
186
|
+
input.text,
|
|
187
|
+
[]
|
|
188
|
+
);
|
|
189
|
+
return { userMessage, aiResponse };
|
|
190
|
+
}
|
|
191
|
+
async generateAIResponse(companionId, userMessage, context = []) {
|
|
192
|
+
const systemPrompt = this.promptStrategy.resolve(this.config);
|
|
193
|
+
const groqMessages = messageFormatter.toGroqMessages(context, systemPrompt);
|
|
194
|
+
groqMessages.push({ role: "user", content: userMessage });
|
|
195
|
+
const response = await this.textGeneration.generateChatCompletion(
|
|
196
|
+
groqMessages,
|
|
197
|
+
{
|
|
198
|
+
model: DEFAULT_MODELS.TEXT,
|
|
199
|
+
generationConfig: {
|
|
200
|
+
temperature: this.config.temperature,
|
|
201
|
+
maxTokens: this.config.maxTokens
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
return {
|
|
206
|
+
...messageFormatter.toChatMessage(response, "assistant"),
|
|
207
|
+
metadata: {
|
|
208
|
+
companionId,
|
|
209
|
+
model: DEFAULT_MODELS.TEXT
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Generate a streaming AI response. Emits content chunks via the
|
|
215
|
+
* `onChunk` callback. Resolves once the stream completes.
|
|
216
|
+
*
|
|
217
|
+
* @param userMessage - The latest user message.
|
|
218
|
+
* @param context - The message history to send as context.
|
|
219
|
+
* @param onChunk - Called for each content chunk.
|
|
220
|
+
*/
|
|
221
|
+
async generateStreamingResponse(userMessage, context, onChunk) {
|
|
222
|
+
const systemPrompt = this.promptStrategy.resolve(this.config);
|
|
223
|
+
const groqMessages = messageFormatter.toGroqMessages(context, systemPrompt);
|
|
224
|
+
groqMessages.push({ role: "user", content: userMessage });
|
|
225
|
+
return this.textGeneration.streamChatCompletion(
|
|
226
|
+
groqMessages,
|
|
227
|
+
{
|
|
228
|
+
onChunk
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
model: DEFAULT_MODELS.TEXT,
|
|
232
|
+
generationConfig: {
|
|
233
|
+
temperature: this.config.temperature,
|
|
234
|
+
maxTokens: this.config.maxTokens
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
requireStorage() {
|
|
240
|
+
if (!this.storage) {
|
|
241
|
+
throw new GroqError(
|
|
242
|
+
"MISSING_CONFIG" /* MISSING_CONFIG */,
|
|
243
|
+
"ChatService.getConversation requires setStorage() to be called first."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return this.storage;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
function extractTokenUsage(message) {
|
|
250
|
+
const meta = message.metadata;
|
|
251
|
+
if (!meta) return null;
|
|
252
|
+
const usage = meta["usage"];
|
|
253
|
+
if (!usage || typeof usage !== "object") return null;
|
|
254
|
+
const u = usage;
|
|
255
|
+
const promptTokens = typeof u["prompt_tokens"] === "number" ? u["prompt_tokens"] : null;
|
|
256
|
+
const completionTokens = typeof u["completion_tokens"] === "number" ? u["completion_tokens"] : null;
|
|
257
|
+
const totalTokens = typeof u["total_tokens"] === "number" ? u["total_tokens"] : null;
|
|
258
|
+
if (promptTokens === null || completionTokens === null || totalTokens === null) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
return { promptTokens, completionTokens, totalTokens };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/domains/groq/resolvers/user-friendly-error-resolver.ts
|
|
265
|
+
var KEYWORD_FALLBACK_RULES = [
|
|
266
|
+
{ keywords: ["api key", "unauthorized"], type: "INVALID_API_KEY" /* INVALID_API_KEY */ },
|
|
267
|
+
{ keywords: ["rate limit"], type: "RATE_LIMIT_ERROR" /* RATE_LIMIT_ERROR */ },
|
|
268
|
+
{ keywords: ["network", "fetch"], type: "NETWORK_ERROR" /* NETWORK_ERROR */ },
|
|
269
|
+
{ keywords: ["abort"], type: "ABORT_ERROR" /* ABORT_ERROR */ }
|
|
270
|
+
];
|
|
271
|
+
function resolveUserFriendlyErrorMessage(error) {
|
|
272
|
+
if (error instanceof GroqError) {
|
|
273
|
+
return USER_FRIENDLY_ERROR_MESSAGES[error.type];
|
|
274
|
+
}
|
|
275
|
+
if (error instanceof Error) {
|
|
276
|
+
const lower = error.message.toLowerCase();
|
|
277
|
+
for (const rule of KEYWORD_FALLBACK_RULES) {
|
|
278
|
+
if (rule.keywords.some((kw) => lower.includes(kw))) {
|
|
279
|
+
return USER_FRIENDLY_ERROR_MESSAGES[rule.type];
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (error.message.length > 0) return error.message;
|
|
283
|
+
}
|
|
284
|
+
return USER_FRIENDLY_ERROR_MESSAGES["UNKNOWN_ERROR" /* UNKNOWN_ERROR */];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/domains/groq/utils/error-resolver.ts
|
|
288
|
+
var resolveUserFriendlyMessage = resolveUserFriendlyErrorMessage;
|
|
289
|
+
|
|
290
|
+
// src/domains/chat/validators/message-content-validator.ts
|
|
291
|
+
var DEFAULT_MESSAGE_CONTENT_RULE = {
|
|
292
|
+
minLength: 1
|
|
293
|
+
};
|
|
294
|
+
function isMeaningfulMessageContent(text, rule = DEFAULT_MESSAGE_CONTENT_RULE) {
|
|
295
|
+
if (!text) return false;
|
|
296
|
+
return text.trim().length >= rule.minLength;
|
|
297
|
+
}
|
|
298
|
+
function trimMessageContent(text) {
|
|
299
|
+
return text.trim();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/domains/chat/hooks/use-chat.hook.ts
|
|
303
|
+
function useChat(options, deps) {
|
|
304
|
+
const chatService = deps?.chatService;
|
|
305
|
+
if (!chatService) {
|
|
306
|
+
throw new Error(
|
|
307
|
+
"useChat requires a ChatService dependency. Provide it via the second argument or wire it through the app container."
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
const {
|
|
311
|
+
conversationId,
|
|
312
|
+
storage,
|
|
313
|
+
config,
|
|
314
|
+
autoSave = true,
|
|
315
|
+
stream: streamEnabled = false,
|
|
316
|
+
onMessageSent,
|
|
317
|
+
onAIResponse,
|
|
318
|
+
onError
|
|
319
|
+
} = options;
|
|
320
|
+
const [messages, setMessages] = useState([]);
|
|
321
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
322
|
+
const [isTyping, setIsTyping] = useState(false);
|
|
323
|
+
const [error, setError] = useState(null);
|
|
324
|
+
const storageRef = useRef(storage);
|
|
325
|
+
storageRef.current = storage;
|
|
326
|
+
const callbacksRef = useRef({ onMessageSent, onAIResponse, onError });
|
|
327
|
+
callbacksRef.current = { onMessageSent, onAIResponse, onError };
|
|
328
|
+
const abortControllerRef = useRef(null);
|
|
329
|
+
const surfaceError = useCallback((err) => {
|
|
330
|
+
const message = resolveUserFriendlyMessage(err);
|
|
331
|
+
setError(message);
|
|
332
|
+
callbacksRef.current.onError?.(message);
|
|
333
|
+
}, []);
|
|
334
|
+
useEffect(() => {
|
|
335
|
+
if (config) {
|
|
336
|
+
chatService.initialize(config);
|
|
337
|
+
}
|
|
338
|
+
}, [chatService, config]);
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
const currentStorage = storageRef.current;
|
|
341
|
+
if (!currentStorage) return;
|
|
342
|
+
let cancelled = false;
|
|
343
|
+
(async () => {
|
|
344
|
+
try {
|
|
345
|
+
const loaded = await currentStorage.getMessages(conversationId);
|
|
346
|
+
if (!cancelled) {
|
|
347
|
+
setMessages(loaded);
|
|
348
|
+
}
|
|
349
|
+
} catch (err) {
|
|
350
|
+
if (!cancelled) {
|
|
351
|
+
surfaceError(err);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
})();
|
|
355
|
+
return () => {
|
|
356
|
+
cancelled = true;
|
|
357
|
+
};
|
|
358
|
+
}, [conversationId, surfaceError]);
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
const controller = new AbortController();
|
|
361
|
+
abortControllerRef.current = controller;
|
|
362
|
+
return () => {
|
|
363
|
+
controller.abort();
|
|
364
|
+
abortControllerRef.current = null;
|
|
365
|
+
};
|
|
366
|
+
}, []);
|
|
367
|
+
const persist = useCallback(
|
|
368
|
+
async (message) => {
|
|
369
|
+
const currentStorage = storageRef.current;
|
|
370
|
+
if (!autoSave || !currentStorage) return;
|
|
371
|
+
try {
|
|
372
|
+
await currentStorage.saveMessage(conversationId, message);
|
|
373
|
+
} catch (err) {
|
|
374
|
+
surfaceError(err);
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
[autoSave, conversationId, surfaceError]
|
|
378
|
+
);
|
|
379
|
+
const sendMessage = useCallback(
|
|
380
|
+
async (text, type) => {
|
|
381
|
+
const trimmed = trimMessageContent(text);
|
|
382
|
+
if (!isMeaningfulMessageContent(trimmed)) return null;
|
|
383
|
+
if (isLoading) return null;
|
|
384
|
+
const controller = new AbortController();
|
|
385
|
+
abortControllerRef.current?.abort();
|
|
386
|
+
abortControllerRef.current = controller;
|
|
387
|
+
setIsLoading(true);
|
|
388
|
+
setError(null);
|
|
389
|
+
setIsTyping(true);
|
|
390
|
+
const userMessage = {
|
|
391
|
+
id: chatIdGenerator.next("user"),
|
|
392
|
+
sender: "user",
|
|
393
|
+
content: trimmed,
|
|
394
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
395
|
+
type: type ?? "text"
|
|
396
|
+
};
|
|
397
|
+
let contextForAI = [];
|
|
398
|
+
setMessages((prev) => {
|
|
399
|
+
contextForAI = prev;
|
|
400
|
+
return [...prev, userMessage];
|
|
401
|
+
});
|
|
402
|
+
void persist(userMessage);
|
|
403
|
+
callbacksRef.current.onMessageSent?.(userMessage);
|
|
404
|
+
try {
|
|
405
|
+
if (streamEnabled) {
|
|
406
|
+
const placeholderId = chatIdGenerator.next("assistant");
|
|
407
|
+
const placeholder = {
|
|
408
|
+
id: placeholderId,
|
|
409
|
+
sender: "assistant",
|
|
410
|
+
content: "",
|
|
411
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
412
|
+
type: "text",
|
|
413
|
+
metadata: { streaming: true }
|
|
414
|
+
};
|
|
415
|
+
setMessages((prev) => [...prev, placeholder]);
|
|
416
|
+
const fullText = await chatService.generateStreamingResponse(
|
|
417
|
+
trimmed,
|
|
418
|
+
contextForAI,
|
|
419
|
+
(chunk) => {
|
|
420
|
+
setMessages(
|
|
421
|
+
(prev) => prev.map(
|
|
422
|
+
(m) => m.id === placeholderId ? { ...m, content: m.content + chunk } : m
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
);
|
|
427
|
+
const finalMessage = {
|
|
428
|
+
...placeholder,
|
|
429
|
+
content: fullText,
|
|
430
|
+
metadata: { ...placeholder.metadata, streaming: false }
|
|
431
|
+
};
|
|
432
|
+
setMessages(
|
|
433
|
+
(prev) => prev.map((m) => m.id === placeholderId ? finalMessage : m)
|
|
434
|
+
);
|
|
435
|
+
void persist(finalMessage);
|
|
436
|
+
callbacksRef.current.onAIResponse?.(finalMessage);
|
|
437
|
+
return finalMessage;
|
|
438
|
+
}
|
|
439
|
+
const aiResponse = await chatService.generateAIResponse(
|
|
440
|
+
conversationId,
|
|
441
|
+
trimmed,
|
|
442
|
+
contextForAI
|
|
443
|
+
);
|
|
444
|
+
setMessages((prev) => [...prev, aiResponse]);
|
|
445
|
+
void persist(aiResponse);
|
|
446
|
+
callbacksRef.current.onAIResponse?.(aiResponse);
|
|
447
|
+
return aiResponse;
|
|
448
|
+
} catch (err) {
|
|
449
|
+
surfaceError(err);
|
|
450
|
+
return null;
|
|
451
|
+
} finally {
|
|
452
|
+
setIsLoading(false);
|
|
453
|
+
setIsTyping(false);
|
|
454
|
+
if (abortControllerRef.current === controller) {
|
|
455
|
+
abortControllerRef.current = null;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
[chatService, conversationId, isLoading, persist, streamEnabled, surfaceError]
|
|
460
|
+
);
|
|
461
|
+
const cancel = useCallback(() => {
|
|
462
|
+
abortControllerRef.current?.abort();
|
|
463
|
+
abortControllerRef.current = null;
|
|
464
|
+
setIsLoading(false);
|
|
465
|
+
setIsTyping(false);
|
|
466
|
+
}, []);
|
|
467
|
+
const regenerate = useCallback(async () => {
|
|
468
|
+
if (isLoading) return null;
|
|
469
|
+
const currentMessages = messages;
|
|
470
|
+
if (currentMessages.length === 0) return null;
|
|
471
|
+
const lastUserMessage = [...currentMessages].reverse().find((m) => m.sender === "user");
|
|
472
|
+
if (!lastUserMessage) return null;
|
|
473
|
+
const trimmed = currentMessages.length > 0 && currentMessages[currentMessages.length - 1].sender === "assistant" ? currentMessages.slice(0, -1) : currentMessages;
|
|
474
|
+
setIsLoading(true);
|
|
475
|
+
setError(null);
|
|
476
|
+
setIsTyping(true);
|
|
477
|
+
try {
|
|
478
|
+
const aiResponse = await chatService.generateAIResponse(
|
|
479
|
+
conversationId,
|
|
480
|
+
lastUserMessage.content,
|
|
481
|
+
trimmed
|
|
482
|
+
);
|
|
483
|
+
setMessages((prev) => {
|
|
484
|
+
const lastIsAI = prev.length > 0 && prev[prev.length - 1].sender === "assistant";
|
|
485
|
+
const base = lastIsAI ? prev.slice(0, -1) : prev;
|
|
486
|
+
return [...base, aiResponse];
|
|
487
|
+
});
|
|
488
|
+
void persist(aiResponse);
|
|
489
|
+
callbacksRef.current.onAIResponse?.(aiResponse);
|
|
490
|
+
return aiResponse;
|
|
491
|
+
} catch (err) {
|
|
492
|
+
surfaceError(err);
|
|
493
|
+
return null;
|
|
494
|
+
} finally {
|
|
495
|
+
setIsLoading(false);
|
|
496
|
+
setIsTyping(false);
|
|
497
|
+
}
|
|
498
|
+
}, [chatService, conversationId, isLoading, messages, persist, surfaceError]);
|
|
499
|
+
const clearMessages = useCallback(() => {
|
|
500
|
+
setMessages([]);
|
|
501
|
+
setError(null);
|
|
502
|
+
}, []);
|
|
503
|
+
const clearError = useCallback(() => setError(null), []);
|
|
504
|
+
const reset = useCallback(() => {
|
|
505
|
+
cancel();
|
|
506
|
+
setError(null);
|
|
507
|
+
}, [cancel]);
|
|
508
|
+
const updateConfig = useCallback(
|
|
509
|
+
(newConfig) => {
|
|
510
|
+
chatService.updateConfig(newConfig);
|
|
511
|
+
},
|
|
512
|
+
[chatService]
|
|
513
|
+
);
|
|
514
|
+
return {
|
|
515
|
+
messages,
|
|
516
|
+
isLoading,
|
|
517
|
+
isTyping,
|
|
518
|
+
error,
|
|
519
|
+
sendMessage,
|
|
520
|
+
regenerate,
|
|
521
|
+
cancel,
|
|
522
|
+
clearMessages,
|
|
523
|
+
clearError,
|
|
524
|
+
reset,
|
|
525
|
+
updateConfig
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export { ChatIdGenerator, ChatService, DEFAULT_PROMPT_STRATEGY, chatIdGenerator, extractTokenUsage, messageFormatter, useChat };
|
|
530
|
+
//# sourceMappingURL=index.js.map
|
|
531
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/domains/groq/constants/groq.constants.ts","../../../src/domains/groq/calculations/random-base36-suffix.ts","../../../src/domains/groq/constants/identifier.constants.ts","../../../src/domains/chat/utils/chat-id-generator.ts","../../../src/domains/chat/utils/message-formatter.ts","../../../src/domains/chat/utils/prompt-strategy.ts","../../../src/domains/groq/utils/groq-error.ts","../../../src/domains/groq/constants/error-types.ts","../../../src/domains/chat/services/chat.service.ts","../../../src/domains/groq/resolvers/user-friendly-error-resolver.ts","../../../src/domains/groq/utils/error-resolver.ts","../../../src/domains/chat/validators/message-content-validator.ts","../../../src/domains/chat/hooks/use-chat.hook.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Groq Constants\n * @description Available models and default configurations\n */\n\n/**\n * Available Groq models\n */\nexport const GROQ_MODELS = {\n /** Llama 3.1 8B - Fast and efficient (560 T/s) */\n LLAMA_3_1_8B_INSTANT: \"llama-3.1-8b-instant\",\n /** Llama 3.3 70B - Versatile and powerful (280 T/s) */\n LLAMA_3_3_70B_VERSATILE: \"llama-3.3-70b-versatile\",\n /** Llama 3.1 70B - Versatile (280 T/s) */\n LLAMA_3_1_70B_VERSATILE: \"llama-3.1-70b-versatile\",\n /** GPT-OSS 20B - Experimental (1000 T/s) */\n GPT_OSS_20B: \"openai/gpt-oss-20b\",\n /** GPT-OSS 120B - Large experimental model */\n GPT_OSS_120B: \"openai/gpt-oss-120b\",\n /** Mixtral 8x7b - MoE model */\n MIXTRAL_8X7B: \"mixtral-8x7b-32768\",\n /** Gemma 2 9B - Google's model */\n GEMMA_2_9B: \"gemma2-9b-it\",\n /** Llama 4 Scout 17B - New model (30K T/s) */\n LLAMA_4_SCOUT_17B: \"meta-llama/llama-4-scout-17b-16e-instruct\",\n /** Kimi K2 - Moonshot AI model */\n KIMI_K2_INSTRUCT: \"moonshotai/kimi-k2-instruct\",\n /** Qwen 3 32B - Alibaba's model */\n QWEN3_32B: \"qwen/qwen3-32b\",\n} as const;\n\n/**\n * Default models for different use cases\n */\nexport const DEFAULT_MODELS = {\n TEXT: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,\n FAST: GROQ_MODELS.LLAMA_3_1_8B_INSTANT,\n EXPERIMENTAL: GROQ_MODELS.GPT_OSS_20B,\n} as const;\n\n/**\n * Default generation configuration\n */\nexport const DEFAULT_GENERATION_CONFIG = {\n temperature: 0.7,\n maxTokens: 1024,\n topP: 1.0,\n} as const;\n\n/**\n * API endpoints\n */\nexport const API_ENDPOINTS = {\n CHAT: \"/chat/completions\",\n} as const;\n\n/**\n * Default base URL\n */\nexport const DEFAULT_BASE_URL = \"https://api.groq.com/openai/v1\" as const;\n\n/**\n * Default timeouts\n */\nexport const TIMEOUTS = {\n DEFAULT: 30000, // 30 seconds\n STREAMING: 60000, // 60 seconds\n} as const;\n","/**\n * Random Base36 Suffix\n * @description Pure function: generates a random alphanumeric suffix\n * from the base-36 character set (0-9, a-z). No hardcoded\n * values; the character set and length are explicit.\n *\n * @example\n * ```typescript\n * generateRandomBase36Suffix(9); // \"k3m9x4n2p\"\n * ```\n */\n\nconst BASE_36_RADIX = 36;\nconst DECIMAL_OFFSET = 2; // Skip the leading \"0.\" in Math.random().toString(36)\n\nexport function generateRandomBase36Suffix(\n length: number,\n random: () => number = Math.random,\n): string {\n if (length <= 0) return \"\";\n const raw = random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);\n if (raw.length >= length) return raw.slice(0, length);\n\n // Fall back to concatenated calls for longer-than-natural suffixes.\n let result = raw;\n while (result.length < length) {\n result += random().toString(BASE_36_RADIX).slice(DECIMAL_OFFSET);\n }\n return result.slice(0, length);\n}\n","/**\n * Identifier Constants\n * @description Prefixes and identifiers used across the package\n */\n\nexport const MESSAGE_ID_PREFIX = \"msg-\" as const;\nexport const CHAT_MESSAGE_ID_SUFFIX_LENGTH = 9;\nexport const CACHE_KEY_TYPE_COMPLETION = \"completion\" as const;\nexport const CACHE_KEY_TYPE_CHAT = \"chat\" as const;\nexport const CACHE_KEY_TYPE_RESPONSE = \"response\" as const;\nexport const SYSTEM_PROMPT_CACHE_PREFIX = \"system-prompt\" as const;\n\nexport const USER_ROLE = \"user\" as const;\nexport const ASSISTANT_ROLE = \"assistant\" as const;\nexport const SYSTEM_ROLE = \"system\" as const;\n\nexport const QUEUE_PRIORITY_USER_REQUEST = 10;\nexport const DEFAULT_RETRY_MAX_ATTEMPTS = 3;\nexport const DEFAULT_RETRY_INITIAL_DELAY_MS = 1000;\nexport const DEFAULT_RETRY_MAX_DELAY_MS = 10000;\nexport const DEFAULT_RETRY_BACKOFF_MULTIPLIER = 2;\nexport const DEFAULT_RETRY_JITTER_FACTOR = 0.1;\n","/**\n * Chat ID Generator\n * @description Generates unique message IDs. Delegates randomness\n * to calculations/random-base36-suffix so the random\n * source is configurable (and the ID format is unit-testable).\n *\n * Format: `msg-{timestamp}-{sender}-{counter}-{random}`\n */\n\nimport { generateRandomBase36Suffix } from \"../../groq/calculations/random-base36-suffix\";\nimport {\n MESSAGE_ID_PREFIX,\n CHAT_MESSAGE_ID_SUFFIX_LENGTH,\n} from \"../../groq/constants/identifier.constants\";\n\nconst ID_PART_DELIMITER = \"-\";\n\nexport class ChatIdGenerator {\n private counter = 0;\n private readonly randomLength: number;\n\n constructor(randomLength: number = CHAT_MESSAGE_ID_SUFFIX_LENGTH) {\n this.randomLength = randomLength;\n }\n\n /**\n * Generate a new unique message ID.\n */\n next(sender: string): string {\n this.counter += 1;\n const random = generateRandomBase36Suffix(this.randomLength);\n return [\n MESSAGE_ID_PREFIX,\n Date.now(),\n sender,\n this.counter,\n random,\n ].join(ID_PART_DELIMITER);\n }\n}\n\nexport const chatIdGenerator = new ChatIdGenerator();\n","/**\n * Message Formatter\n * @description Converts between ChatMessage (domain) and GroqMessage (transport).\n * Does NOT include any prompts or fallback content; those are\n * injected by the consumer (see prompt-strategy.ts).\n */\n\nimport type { IMessageFormatter } from \"../interfaces\";\nimport type { ChatMessage } from \"../entities\";\nimport type { GroqMessage } from \"../../groq/entities\";\nimport type { ChatIdGenerator } from \"./chat-id-generator\";\nimport { chatIdGenerator as defaultIdGenerator } from \"./chat-id-generator\";\nimport { ASSISTANT_ROLE, USER_ROLE } from \"../../groq/constants/identifier.constants\";\n\nexport class MessageFormatter implements IMessageFormatter {\n constructor(private readonly idGenerator: ChatIdGenerator = defaultIdGenerator) {}\n\n toGroqMessages(messages: ReadonlyArray<ChatMessage>, systemPrompt?: string): GroqMessage[] {\n const groqMessages: GroqMessage[] = [];\n\n if (systemPrompt) {\n groqMessages.push({ role: \"system\", content: systemPrompt });\n }\n\n for (const message of messages) {\n if (message.sender === \"system\") continue;\n groqMessages.push({\n role: message.sender === \"user\" ? USER_ROLE : ASSISTANT_ROLE,\n content: message.content,\n });\n }\n\n return groqMessages;\n }\n\n toChatMessage(content: string, sender: ChatMessage[\"sender\"]): ChatMessage {\n return {\n id: this.idGenerator.next(sender),\n sender,\n content: content.trim(),\n timestamp: new Date().toISOString(),\n type: \"text\",\n };\n }\n}\n\nexport const messageFormatter = new MessageFormatter();\n","/**\n * System Prompt Strategy\n * @description Resolves the system prompt to use for a chat session.\n * Allows library consumers to plug in their own prompts\n * without hardcoded language defaults.\n *\n * @example\n * ```typescript\n * const customStrategy: SystemPromptStrategy = {\n * resolve: (config) =>\n * config.language === \"tr\"\n * ? \"Sen yardimsever bir asistansin.\"\n * : \"You are a helpful assistant.\",\n * };\n * chatService.setPromptStrategy(customStrategy);\n * ```\n */\n\nimport type { ChatConfig } from \"../entities\";\n\nexport interface SystemPromptStrategy {\n /**\n * Resolve the system prompt for the given config.\n * Returning `undefined` omits the system message entirely.\n */\n resolve(config: ChatConfig): string | undefined;\n}\n\n/**\n * Default strategy: use the user-supplied `systemPrompt` if present,\n * otherwise return undefined (no system message sent).\n */\nexport const DEFAULT_PROMPT_STRATEGY: SystemPromptStrategy = {\n resolve: (config) => {\n if (config.systemPrompt && config.systemPrompt.length > 0) {\n return config.systemPrompt;\n }\n return undefined;\n },\n};\n","/**\n * Groq Error\n * @description Custom error class for Groq API operations.\n */\n\nimport { GroqErrorType } from \"../constants/error-types\";\n\nexport interface GroqErrorMetadata {\n readonly status?: number;\n readonly url?: string;\n readonly [key: string]: unknown;\n}\n\nexport class GroqError extends Error {\n public readonly type: GroqErrorType;\n public readonly cause?: Error;\n public readonly metadata: GroqErrorMetadata;\n\n constructor(\n type: GroqErrorType,\n message: string,\n cause?: Error,\n metadata: GroqErrorMetadata = {},\n ) {\n super(message);\n this.name = \"GroqError\";\n this.type = type;\n this.cause = cause;\n this.metadata = metadata;\n }\n\n override toString(): string {\n const statusPart =\n typeof this.metadata.status === \"number\" ? ` (HTTP ${this.metadata.status})` : \"\";\n return `${this.name} [${this.type}]: ${this.message}${statusPart}`;\n }\n\n toJSON(): {\n name: string;\n type: GroqErrorType;\n message: string;\n metadata: GroqErrorMetadata;\n } {\n return {\n name: this.name,\n type: this.type,\n message: this.message,\n metadata: this.metadata,\n };\n }\n}\n","/**\n * Error Types\n * @description Error type enum and user-friendly message table.\n * The classification logic (status → type, type → retryable, ...)\n * lives in the validators/ and resolvers/ folders; this file\n * only contains the enum and the message table.\n */\n\nexport enum GroqErrorType {\n INVALID_API_KEY = \"INVALID_API_KEY\",\n MISSING_CONFIG = \"MISSING_CONFIG\",\n NETWORK_ERROR = \"NETWORK_ERROR\",\n ABORT_ERROR = \"ABORT_ERROR\",\n RATE_LIMIT_ERROR = \"RATE_LIMIT_ERROR\",\n SERVER_ERROR = \"SERVER_ERROR\",\n CLIENT_ERROR = \"CLIENT_ERROR\",\n UNKNOWN_ERROR = \"UNKNOWN_ERROR\",\n}\n\nexport const USER_FRIENDLY_ERROR_MESSAGES: Readonly<Record<GroqErrorType, string>> = {\n [GroqErrorType.INVALID_API_KEY]: \"Please check your Groq API key and try again.\",\n [GroqErrorType.MISSING_CONFIG]: \"The provider is not configured. Please call configureProvider first.\",\n [GroqErrorType.NETWORK_ERROR]: \"Network error. Please check your connection and try again.\",\n [GroqErrorType.ABORT_ERROR]: \"Request was cancelled.\",\n [GroqErrorType.RATE_LIMIT_ERROR]: \"You've reached the rate limit. Please wait a moment and try again.\",\n [GroqErrorType.SERVER_ERROR]: \"Groq service is temporarily unavailable. Please try again later.\",\n [GroqErrorType.CLIENT_ERROR]: \"The request was invalid. Please check your input and try again.\",\n [GroqErrorType.UNKNOWN_ERROR]: \"An unexpected error occurred. Please try again.\",\n};\n","/**\n * Chat Service\n * @description Core chat logic. Composes:\n * - text generation (from groq domain)\n * - message formatting\n * - system prompt resolution (via injected strategy)\n *\n * Does NOT hardcode any language or fallback content.\n * Errors propagate to the caller; the consumer decides\n * how to render a user-friendly message.\n */\n\nimport type {\n IChatService,\n SystemPromptStrategy,\n} from \"../interfaces\";\nimport type {\n ChatMessage,\n ChatConfig,\n ChatConversation,\n SendMessageInput,\n SendMessageResult,\n IChatStorage,\n ChatTokenUsage,\n} from \"../entities\";\nimport type { TextGenerationService } from \"../../groq/services\";\nimport { DEFAULT_MODELS } from \"../../groq/constants/groq.constants\";\nimport { messageFormatter } from \"../utils/message-formatter\";\nimport { DEFAULT_PROMPT_STRATEGY } from \"../utils/prompt-strategy\";\nimport { GroqError } from \"../../groq/utils/groq-error\";\nimport { GroqErrorType } from \"../../groq/constants/error-types\";\n\nconst DEFAULT_CONFIG: ChatConfig = {\n temperature: 0.8,\n maxTokens: 500,\n};\n\nexport class ChatService implements IChatService {\n private config: ChatConfig = DEFAULT_CONFIG;\n private promptStrategy: SystemPromptStrategy = DEFAULT_PROMPT_STRATEGY;\n private storage: IChatStorage | null = null;\n\n constructor(\n private readonly textGeneration: TextGenerationService,\n ) {}\n\n initialize(config: ChatConfig): void {\n this.config = { ...DEFAULT_CONFIG, ...config };\n }\n\n updateConfig(config: Partial<ChatConfig>): void {\n this.config = { ...this.config, ...config };\n }\n\n setPromptStrategy(strategy: SystemPromptStrategy): void {\n this.promptStrategy = strategy;\n }\n\n setStorage(storage: IChatStorage): void {\n this.storage = storage;\n }\n\n async getConversation(conversationId: string): Promise<ChatConversation | null> {\n const storage = this.requireStorage();\n const messages = await storage.getMessages(conversationId);\n if (messages.length === 0) return null;\n const last = messages[messages.length - 1];\n const first = messages[0];\n const companionId = String(first.metadata?.[\"companionId\"] ?? \"unknown\");\n const companionName = String(first.metadata?.[\"companionName\"] ?? \"Unknown\");\n return {\n id: conversationId,\n companionId,\n companionName,\n lastMessage: last,\n messageCount: messages.length,\n updatedAt: last.timestamp,\n };\n }\n\n async getConversations(): Promise<ChatConversation[]> {\n // Single-conversation view; multi-conversation aggregation is the\n // responsibility of the host application's storage implementation.\n // Returning an empty list avoids the false impression that we\n // have a fully featured conversation registry.\n return [];\n }\n\n async sendMessage(input: SendMessageInput): Promise<SendMessageResult> {\n const userMessage: ChatMessage = {\n ...messageFormatter.toChatMessage(input.text, \"user\"),\n type: input.type ?? \"text\",\n ...(input.imageUrl !== undefined ? { imageUrl: input.imageUrl } : {}),\n };\n\n const aiResponse = await this.generateAIResponse(\n input.companionId,\n input.text,\n [],\n );\n\n return { userMessage, aiResponse };\n }\n\n async generateAIResponse(\n companionId: string,\n userMessage: string,\n context: ReadonlyArray<ChatMessage> = [],\n ): Promise<ChatMessage> {\n const systemPrompt = this.promptStrategy.resolve(this.config);\n const groqMessages = messageFormatter.toGroqMessages(context, systemPrompt);\n groqMessages.push({ role: \"user\", content: userMessage });\n\n const response = await this.textGeneration.generateChatCompletion(\n groqMessages,\n {\n model: DEFAULT_MODELS.TEXT,\n generationConfig: {\n temperature: this.config.temperature,\n maxTokens: this.config.maxTokens,\n },\n },\n );\n\n return {\n ...messageFormatter.toChatMessage(response, \"assistant\"),\n metadata: {\n companionId,\n model: DEFAULT_MODELS.TEXT,\n },\n };\n }\n\n /**\n * Generate a streaming AI response. Emits content chunks via the\n * `onChunk` callback. Resolves once the stream completes.\n *\n * @param userMessage - The latest user message.\n * @param context - The message history to send as context.\n * @param onChunk - Called for each content chunk.\n */\n async generateStreamingResponse(\n userMessage: string,\n context: ReadonlyArray<ChatMessage>,\n onChunk: (chunk: string) => void,\n ): Promise<string> {\n const systemPrompt = this.promptStrategy.resolve(this.config);\n const groqMessages = messageFormatter.toGroqMessages(context, systemPrompt);\n groqMessages.push({ role: \"user\", content: userMessage });\n\n return this.textGeneration.streamChatCompletion(\n groqMessages,\n {\n onChunk,\n },\n {\n model: DEFAULT_MODELS.TEXT,\n generationConfig: {\n temperature: this.config.temperature,\n maxTokens: this.config.maxTokens,\n },\n },\n );\n }\n\n private requireStorage(): IChatStorage {\n if (!this.storage) {\n throw new GroqError(\n GroqErrorType.MISSING_CONFIG,\n \"ChatService.getConversation requires setStorage() to be called first.\",\n );\n }\n return this.storage;\n }\n}\n\n/**\n * Helper to extract a `ChatTokenUsage` from a ChatMessage's metadata,\n * if the model used reported it.\n *\n * @param message - The chat message to inspect.\n * @returns The token usage, or `null` when not present.\n */\nexport function extractTokenUsage(message: ChatMessage): ChatTokenUsage | null {\n const meta = message.metadata;\n if (!meta) return null;\n const usage = meta[\"usage\"];\n if (!usage || typeof usage !== \"object\") return null;\n const u = usage as Record<string, unknown>;\n const promptTokens = typeof u[\"prompt_tokens\"] === \"number\" ? u[\"prompt_tokens\"] : null;\n const completionTokens =\n typeof u[\"completion_tokens\"] === \"number\" ? u[\"completion_tokens\"] : null;\n const totalTokens = typeof u[\"total_tokens\"] === \"number\" ? u[\"total_tokens\"] : null;\n if (promptTokens === null || completionTokens === null || totalTokens === null) {\n return null;\n }\n return { promptTokens, completionTokens, totalTokens };\n}\n","/**\n * User-Friendly Error Resolver\n * @description Maps any thrown value (Error, GroqError, string, etc.)\n * to a user-facing string suitable for display. No I/O,\n * no hardcoded user-facing strings: the mapping table\n * is explicit and overridable.\n *\n * @example\n * ```typescript\n * resolveUserFriendlyErrorMessage(new GroqError(GroqErrorType.RATE_LIMIT_ERROR, \"x\"));\n * // \"You've reached the rate limit. Please wait a moment and try again.\"\n * ```\n */\n\nimport { GroqError } from \"../utils/groq-error\";\nimport { GroqErrorType, USER_FRIENDLY_ERROR_MESSAGES } from \"../constants/error-types\";\n\n/**\n * Keyword-based fallback messages. Used when the error is not a\n * {@link GroqError} and we want to do best-effort classification\n * by string matching.\n */\nconst KEYWORD_FALLBACK_RULES: ReadonlyArray<{ keywords: ReadonlyArray<string>; type: GroqErrorType }> = [\n { keywords: [\"api key\", \"unauthorized\"], type: GroqErrorType.INVALID_API_KEY },\n { keywords: [\"rate limit\"], type: GroqErrorType.RATE_LIMIT_ERROR },\n { keywords: [\"network\", \"fetch\"], type: GroqErrorType.NETWORK_ERROR },\n { keywords: [\"abort\"], type: GroqErrorType.ABORT_ERROR },\n];\n\nexport function resolveUserFriendlyErrorMessage(error: unknown): string {\n if (error instanceof GroqError) {\n return USER_FRIENDLY_ERROR_MESSAGES[error.type];\n }\n\n if (error instanceof Error) {\n const lower = error.message.toLowerCase();\n for (const rule of KEYWORD_FALLBACK_RULES) {\n if (rule.keywords.some((kw) => lower.includes(kw))) {\n return USER_FRIENDLY_ERROR_MESSAGES[rule.type];\n }\n }\n if (error.message.length > 0) return error.message;\n }\n\n return USER_FRIENDLY_ERROR_MESSAGES[GroqErrorType.UNKNOWN_ERROR];\n}\n","/**\n * Error Resolver\n * @description Resolves any thrown value to a user-friendly message.\n * Delegates the actual classification → resolvers/user-friendly-error-resolver.\n */\n\nimport { resolveUserFriendlyErrorMessage } from \"../resolvers/user-friendly-error-resolver\";\nimport { GroqError } from \"./groq-error\";\nimport { GroqErrorType, USER_FRIENDLY_ERROR_MESSAGES } from \"../constants/error-types\";\nimport type { GroqErrorMetadata } from \"./groq-error\";\n\nexport const resolveUserFriendlyMessage = resolveUserFriendlyErrorMessage;\n\nexport interface LoggedError {\n readonly message: string;\n readonly type: string;\n readonly stack?: string;\n readonly metadata?: GroqErrorMetadata;\n}\n\nexport function formatErrorForLogging(error: unknown): LoggedError {\n if (error instanceof GroqError) {\n return {\n message: error.message,\n type: error.type,\n stack: error.stack,\n metadata: error.metadata,\n };\n }\n if (error instanceof Error) {\n return { message: error.message, type: error.name, stack: error.stack };\n }\n return { message: String(error), type: typeof error };\n}\n\nexport { GroqErrorType, USER_FRIENDLY_ERROR_MESSAGES };\n","/**\n * Message Content Validator\n * @description Pure function: validates a chat message's text content.\n * Used by the chat hook to guard against sending empty\n * or whitespace-only messages to the model.\n *\n * @example\n * ```typescript\n * isMeaningfulMessageContent(\"hi\"); // true\n * isMeaningfulMessageContent(\" \"); // false\n * isMeaningfulMessageContent(\"\"); // false\n * isMeaningfulMessageContent(\"\\n\\t\"); // false\n * ```\n */\n\nexport interface MessageContentValidationRule {\n /** Minimum length after trimming. */\n readonly minLength: number;\n}\n\nexport const DEFAULT_MESSAGE_CONTENT_RULE: MessageContentValidationRule = {\n minLength: 1,\n};\n\nexport function isMeaningfulMessageContent(\n text: string | undefined | null,\n rule: MessageContentValidationRule = DEFAULT_MESSAGE_CONTENT_RULE,\n): boolean {\n if (!text) return false;\n return text.trim().length >= rule.minLength;\n}\n\nexport function trimMessageContent(text: string): string {\n return text.trim();\n}\n","/**\n * useChat Hook\n * @description React hook for chat sessions with AI integration.\n *\n * Features:\n * - Per-conversation message list with optional storage\n * - Streaming AI responses (opt-in via `stream: true`)\n * - Per-component AbortController: cancel in-flight requests\n * without affecting other consumers\n * - Errors surface through `onError` (no silent swallowing)\n * - Storage errors propagate the same way as generation errors\n *\n * Requires an injected ChatService via the second argument\n * (typically obtained from `getProvider().chat`).\n *\n * @example\n * ```tsx\n * import { useChat, getProvider } from \"@umituz/web-ai-groq-provider\";\n *\n * function ChatScreen() {\n * const { messages, sendMessage, isLoading, isTyping, error } = useChat(\n * { conversationId: \"demo-1\" },\n * { chatService: getProvider()!.chat },\n * );\n * // ...\n * }\n * ```\n */\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport type { ChatConfig, ChatMessage } from \"../entities\";\nimport type { UseChatOptions, UseChatReturn } from \"../interfaces\";\nimport type { ChatService } from \"../services/chat.service\";\nimport { chatIdGenerator } from \"../utils/chat-id-generator\";\nimport { resolveUserFriendlyMessage } from \"../../groq/utils/error-resolver\";\nimport { isMeaningfulMessageContent, trimMessageContent } from \"../validators/message-content-validator\";\n\nexport interface UseChatDeps {\n readonly chatService: ChatService;\n}\n\nexport function useChat(options: UseChatOptions, deps?: UseChatDeps): UseChatReturn {\n const chatService = deps?.chatService;\n if (!chatService) {\n throw new Error(\n \"useChat requires a ChatService dependency. \" +\n \"Provide it via the second argument or wire it through the app container.\",\n );\n }\n\n const {\n conversationId,\n storage,\n config,\n autoSave = true,\n stream: streamEnabled = false,\n onMessageSent,\n onAIResponse,\n onError,\n } = options;\n\n const [messages, setMessages] = useState<ChatMessage[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [isTyping, setIsTyping] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n // Stable refs to keep callback identities stable across renders.\n const storageRef = useRef(storage);\n storageRef.current = storage;\n const callbacksRef = useRef({ onMessageSent, onAIResponse, onError });\n callbacksRef.current = { onMessageSent, onAIResponse, onError };\n\n // Per-component abort controller; only this component's requests are cancelled.\n const abortControllerRef = useRef<AbortController | null>(null);\n\n const surfaceError = useCallback((err: unknown) => {\n const message = resolveUserFriendlyMessage(err);\n setError(message);\n callbacksRef.current.onError?.(message);\n }, []);\n\n useEffect(() => {\n if (config) {\n chatService.initialize(config);\n }\n }, [chatService, config]);\n\n useEffect(() => {\n const currentStorage = storageRef.current;\n if (!currentStorage) return;\n\n let cancelled = false;\n (async () => {\n try {\n const loaded = await currentStorage.getMessages(conversationId);\n if (!cancelled) {\n setMessages(loaded);\n }\n } catch (err) {\n if (!cancelled) {\n surfaceError(err);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [conversationId, surfaceError]);\n\n // Cleanup on unmount: cancel any in-flight request.\n useEffect(() => {\n const controller = new AbortController();\n abortControllerRef.current = controller;\n return () => {\n controller.abort();\n abortControllerRef.current = null;\n };\n }, []);\n\n const persist = useCallback(\n async (message: ChatMessage) => {\n const currentStorage = storageRef.current;\n if (!autoSave || !currentStorage) return;\n try {\n await currentStorage.saveMessage(conversationId, message);\n } catch (err) {\n surfaceError(err);\n }\n },\n [autoSave, conversationId, surfaceError],\n );\n\n const sendMessage = useCallback(\n async (text: string, type?: ChatMessage[\"type\"]): Promise<ChatMessage | null> => {\n const trimmed = trimMessageContent(text);\n if (!isMeaningfulMessageContent(trimmed)) return null;\n if (isLoading) return null;\n\n const controller = new AbortController();\n abortControllerRef.current?.abort();\n abortControllerRef.current = controller;\n\n setIsLoading(true);\n setError(null);\n setIsTyping(true);\n\n const userMessage: ChatMessage = {\n id: chatIdGenerator.next(\"user\"),\n sender: \"user\",\n content: trimmed,\n timestamp: new Date().toISOString(),\n type: type ?? \"text\",\n };\n\n // Read the latest messages from the rendered state for AI context.\n // This is the snapshot we send to the model.\n let contextForAI: ReadonlyArray<ChatMessage> = [];\n setMessages((prev) => {\n contextForAI = prev;\n return [...prev, userMessage];\n });\n void persist(userMessage);\n callbacksRef.current.onMessageSent?.(userMessage);\n\n try {\n if (streamEnabled) {\n // Streaming path: build a placeholder, then patch in chunks.\n const placeholderId = chatIdGenerator.next(\"assistant\");\n const placeholder: ChatMessage = {\n id: placeholderId,\n sender: \"assistant\",\n content: \"\",\n timestamp: new Date().toISOString(),\n type: \"text\",\n metadata: { streaming: true },\n };\n setMessages((prev) => [...prev, placeholder]);\n\n const fullText = await chatService.generateStreamingResponse(\n trimmed,\n contextForAI,\n (chunk) => {\n setMessages((prev) =>\n prev.map((m) =>\n m.id === placeholderId\n ? { ...m, content: m.content + chunk }\n : m,\n ),\n );\n },\n );\n\n const finalMessage: ChatMessage = {\n ...placeholder,\n content: fullText,\n metadata: { ...placeholder.metadata, streaming: false },\n };\n // Replace placeholder with final message so the persisted version\n // has stable content and metadata.\n setMessages((prev) =>\n prev.map((m) => (m.id === placeholderId ? finalMessage : m)),\n );\n void persist(finalMessage);\n callbacksRef.current.onAIResponse?.(finalMessage);\n return finalMessage;\n }\n\n const aiResponse = await chatService.generateAIResponse(\n conversationId,\n trimmed,\n contextForAI,\n );\n\n setMessages((prev) => [...prev, aiResponse]);\n void persist(aiResponse);\n callbacksRef.current.onAIResponse?.(aiResponse);\n return aiResponse;\n } catch (err) {\n surfaceError(err);\n return null;\n } finally {\n setIsLoading(false);\n setIsTyping(false);\n if (abortControllerRef.current === controller) {\n abortControllerRef.current = null;\n }\n }\n },\n [chatService, conversationId, isLoading, persist, streamEnabled, surfaceError],\n );\n\n const cancel = useCallback(() => {\n abortControllerRef.current?.abort();\n abortControllerRef.current = null;\n setIsLoading(false);\n setIsTyping(false);\n }, []);\n\n const regenerate = useCallback(async (): Promise<ChatMessage | null> => {\n if (isLoading) return null;\n\n const currentMessages = messages;\n if (currentMessages.length === 0) return null;\n\n const lastUserMessage = [...currentMessages]\n .reverse()\n .find((m) => m.sender === \"user\");\n if (!lastUserMessage) return null;\n\n // Drop the trailing AI message (if any) so we can replace it.\n const trimmed =\n currentMessages.length > 0 &&\n currentMessages[currentMessages.length - 1].sender === \"assistant\"\n ? currentMessages.slice(0, -1)\n : currentMessages;\n\n setIsLoading(true);\n setError(null);\n setIsTyping(true);\n\n try {\n const aiResponse = await chatService.generateAIResponse(\n conversationId,\n lastUserMessage.content,\n trimmed,\n );\n\n setMessages((prev) => {\n const lastIsAI =\n prev.length > 0 && prev[prev.length - 1].sender === \"assistant\";\n const base = lastIsAI ? prev.slice(0, -1) : prev;\n return [...base, aiResponse];\n });\n\n void persist(aiResponse);\n callbacksRef.current.onAIResponse?.(aiResponse);\n return aiResponse;\n } catch (err) {\n surfaceError(err);\n return null;\n } finally {\n setIsLoading(false);\n setIsTyping(false);\n }\n }, [chatService, conversationId, isLoading, messages, persist, surfaceError]);\n\n const clearMessages = useCallback(() => {\n setMessages([]);\n setError(null);\n }, []);\n\n const clearError = useCallback(() => setError(null), []);\n\n const reset = useCallback(() => {\n cancel();\n setError(null);\n }, [cancel]);\n\n const updateConfig = useCallback(\n (newConfig: Partial<ChatConfig>) => {\n chatService.updateConfig(newConfig);\n },\n [chatService],\n );\n\n return {\n messages,\n isLoading,\n isTyping,\n error,\n sendMessage,\n regenerate,\n cancel,\n clearMessages,\n clearError,\n reset,\n updateConfig,\n };\n}\n"]}
|