@lastbrain/ai-ui-react 1.0.34 → 1.0.36

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.
Files changed (38) hide show
  1. package/dist/components/AiChipLabel.d.ts.map +1 -1
  2. package/dist/components/AiChipLabel.js +5 -1
  3. package/dist/components/AiContextButton.d.ts.map +1 -1
  4. package/dist/components/AiContextButton.js +1 -0
  5. package/dist/components/AiImageButton.d.ts.map +1 -1
  6. package/dist/components/AiImageButton.js +15 -2
  7. package/dist/components/AiInput.d.ts.map +1 -1
  8. package/dist/components/AiInput.js +5 -1
  9. package/dist/components/AiPromptPanel.d.ts +1 -1
  10. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  11. package/dist/components/AiPromptPanel.js +9 -5
  12. package/dist/components/AiSelect.d.ts.map +1 -1
  13. package/dist/components/AiSelect.js +5 -1
  14. package/dist/components/AiTextarea.d.ts.map +1 -1
  15. package/dist/components/AiTextarea.js +5 -1
  16. package/dist/context/AiProvider.d.ts +28 -0
  17. package/dist/context/AiProvider.d.ts.map +1 -1
  18. package/dist/context/AiProvider.js +159 -1
  19. package/dist/hooks/useAiModels.d.ts +6 -1
  20. package/dist/hooks/useAiModels.d.ts.map +1 -1
  21. package/dist/hooks/useAiModels.js +71 -24
  22. package/dist/hooks/useModelManagement.d.ts +2 -10
  23. package/dist/hooks/useModelManagement.d.ts.map +1 -1
  24. package/dist/hooks/useModelManagement.js +42 -150
  25. package/dist/utils/modelManagement.d.ts.map +1 -1
  26. package/dist/utils/modelManagement.js +7 -3
  27. package/package.json +2 -2
  28. package/src/components/AiChipLabel.tsx +5 -1
  29. package/src/components/AiContextButton.tsx +1 -0
  30. package/src/components/AiImageButton.tsx +16 -2
  31. package/src/components/AiInput.tsx +5 -1
  32. package/src/components/AiPromptPanel.tsx +24 -1
  33. package/src/components/AiSelect.tsx +5 -1
  34. package/src/components/AiTextarea.tsx +5 -1
  35. package/src/context/AiProvider.tsx +223 -1
  36. package/src/hooks/useAiModels.ts +96 -27
  37. package/src/hooks/useModelManagement.ts +52 -203
  38. package/src/utils/modelManagement.ts +7 -3
@@ -1,50 +1,119 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect, useCallback } from "react";
3
+ import { useMemo, useCallback } from "react";
4
4
  import type { ModelRef } from "@lastbrain/ai-ui-core";
5
- import { useAiClient } from "./useAiClient";
5
+ import { useAiContext } from "../context/AiProvider";
6
6
 
7
7
  export interface UseAiModelsOptions {
8
8
  baseUrl?: string;
9
9
  apiKeyId?: string;
10
+ modelType?: "text" | "language" | "image" | "embed" | "text-or-language"; // Filtrer par type
10
11
  }
11
12
 
12
13
  export interface UseAiModelsResult {
13
- models: ModelRef[] | null;
14
+ models: ModelRef[];
14
15
  loading: boolean;
15
16
  error: Error | null;
16
17
  refetch: () => void;
17
18
  }
18
19
 
20
+ /**
21
+ * Hook pour récupérer les modèles disponibles
22
+ * Utilise les données du contexte pour éviter les appels API multiples
23
+ */
19
24
  export function useAiModels(options?: UseAiModelsOptions): UseAiModelsResult {
20
- const client = useAiClient(options);
21
- const [models, setModels] = useState<ModelRef[] | null>(null);
22
- const [loading, setLoading] = useState(false);
23
- const [error, setError] = useState<Error | null>(null);
24
-
25
- const fetchModels = useCallback(async () => {
26
- setLoading(true);
27
- setError(null);
28
- try {
29
- const result = await client.getModels();
30
- setModels(result);
31
- } catch (err) {
32
- setError(
33
- err instanceof Error ? err : new Error("Failed to fetch models")
25
+ const context = useAiContext();
26
+
27
+ // Si les options ne correspondent pas au contexte, on peut faire un appel direct
28
+ // Mais dans la plupart des cas, on utilise le contexte
29
+ const useContextData =
30
+ (!options?.baseUrl || options.baseUrl === context.baseUrl) &&
31
+ (!options?.apiKeyId || options.apiKeyId === context.apiKeyId);
32
+
33
+ // Filtrer les modèles selon le type demandé
34
+ const filteredModels = useMemo(() => {
35
+ console.log("[useAiModels] Filtering models:", {
36
+ useContextData,
37
+ allModelsLength: context.allModels.length,
38
+ modelType: options?.modelType,
39
+ loading: context.loadingProviders,
40
+ });
41
+
42
+ if (!useContextData) {
43
+ console.log("[useAiModels] Not using context data");
44
+ return [];
45
+ }
46
+
47
+ // Pendant le chargement, retourner un tableau vide
48
+ if (context.loadingProviders) {
49
+ console.log("[useAiModels] Still loading...");
50
+ return [];
51
+ }
52
+
53
+ // Si pas de modèles après le chargement
54
+ if (!context.allModels.length) {
55
+ console.log("[useAiModels] No models in context");
56
+ return [];
57
+ }
58
+
59
+ if (!options?.modelType) {
60
+ console.log(
61
+ "[useAiModels] Returning all models:",
62
+ context.allModels.length
63
+ );
64
+ return context.allModels;
65
+ }
66
+
67
+ // Cas spécial: text ou language
68
+ if (options.modelType === "text-or-language") {
69
+ const textModels = context.getTextModels();
70
+ console.log("[useAiModels] Returning text models:", textModels.length);
71
+ return textModels;
72
+ }
73
+
74
+ // Cas spécial: image
75
+ if (options.modelType === "image") {
76
+ const imageModels = context.getImageModels();
77
+ console.log(
78
+ "[useAiModels] Returning image models:",
79
+ imageModels.length,
80
+ imageModels
34
81
  );
35
- } finally {
36
- setLoading(false);
82
+ return imageModels;
83
+ }
84
+ const filtered = context.getModelsByType(options.modelType);
85
+ console.log(
86
+ `[useAiModels] Returning ${options.modelType} models:`,
87
+ filtered.length
88
+ );
89
+ return filtered;
90
+ return filtered;
91
+ }, [useContextData, context, options?.modelType]);
92
+
93
+ const refetch = useCallback(() => {
94
+ if (useContextData) {
95
+ context.refetchProviders();
37
96
  }
38
- }, [client]);
97
+ }, [useContextData, context]);
39
98
 
40
- useEffect(() => {
41
- fetchModels();
42
- }, [fetchModels]);
99
+ if (useContextData) {
100
+ return {
101
+ models: filteredModels,
102
+ loading: context.loadingProviders,
103
+ error: null,
104
+ refetch,
105
+ };
106
+ }
43
107
 
108
+ // Fallback: si les options ne correspondent pas, retourner des valeurs vides
109
+ // (cas rare, la plupart du temps on utilise le contexte)
110
+ console.log("[useAiModels] Using fallback (no context match)");
44
111
  return {
45
- models,
46
- loading,
47
- error,
48
- refetch: fetchModels,
112
+ models: [],
113
+ loading: false,
114
+ error: new Error(
115
+ "useAiModels called with different baseUrl/apiKeyId than context"
116
+ ),
117
+ refetch: () => {},
49
118
  };
50
119
  }
@@ -1,28 +1,9 @@
1
- import { useState, useCallback, useEffect, useMemo } from "react";
2
- import { useAiContext } from "../context/AiProvider";
1
+ import { useState, useCallback, useMemo } from "react";
2
+ import { useAiContext, type AIModel } from "../context/AiProvider";
3
3
  import {
4
4
  toggleUserModel,
5
- getAvailableModels,
6
- getUserModels,
7
5
  type ModelToggleOptions,
8
6
  } from "../utils/modelManagement";
9
- import {
10
- getCached,
11
- setCache,
12
- isRateLimited,
13
- getRateLimitResetTime,
14
- } from "../utils/cache";
15
-
16
- export interface AIModel {
17
- id: string;
18
- name: string;
19
- description?: string;
20
- provider: string;
21
- category: "text" | "image" | "audio" | "video";
22
- isActive?: boolean;
23
- isPro?: boolean;
24
- costPer1M?: number;
25
- }
26
7
 
27
8
  export interface UseModelManagementOptions extends ModelToggleOptions {
28
9
  autoFetch?: boolean; // Charger automatiquement les données au mount
@@ -49,231 +30,99 @@ export interface UseModelManagementReturn {
49
30
 
50
31
  /**
51
32
  * Hook pour gérer les modèles IA d'un utilisateur
33
+ * Utilise les données du contexte pour éviter les appels API multiples
52
34
  */
53
35
  export function useModelManagement(
54
36
  options: UseModelManagementOptions = {}
55
37
  ): UseModelManagementReturn {
56
- const { autoFetch = false, category, ...apiOptions } = options;
57
- const { baseUrl, apiKeyId } = useAiContext();
38
+ const { category } = options;
39
+ const context = useAiContext();
58
40
 
59
- const [availableModels, setAvailableModels] = useState<AIModel[]>([]);
60
- const [userModels, setUserModels] = useState<string[]>([]);
61
41
  const [loading, setLoading] = useState(false);
62
42
  const [error, setError] = useState<string | null>(null);
63
- const [apiUnavailable, setApiUnavailable] = useState(false);
64
-
65
- // Utiliser baseUrl et apiKey du contexte avec memoization
66
- const effectiveOptions = useMemo(
67
- () => ({
68
- baseUrl: options.baseUrl || baseUrl,
69
- apiKey: options.apiKey || apiKeyId,
70
- }),
71
- [options.baseUrl, options.apiKey, baseUrl, apiKeyId]
72
- );
73
-
74
- const refreshModels = useCallback(async () => {
75
- try {
76
- setLoading(true);
77
- setError(null);
78
-
79
- // Check cache first (5 minutes TTL for models)
80
- const cacheKey = `models_${category || "all"}`;
81
- const cached = getCached<AIModel[]>(cacheKey, 300000);
82
- if (cached) {
83
- console.log("[useModelManagement] Using cached models");
84
- setAvailableModels(cached);
85
- setLoading(false);
86
- return;
87
- }
88
-
89
- // Check rate limit (max 5 calls per minute)
90
- if (isRateLimited("models_fetch", 5, 60000)) {
91
- const resetTime = getRateLimitResetTime("models_fetch");
92
- const resetSeconds = Math.ceil(resetTime / 1000);
93
- throw new Error(
94
- `Rate limit exceeded. Please wait ${resetSeconds}s before trying again.`
95
- );
96
- }
97
-
98
- console.log(
99
- "[useModelManagement] Fetching available models with options:",
100
- {
101
- apiKey: effectiveOptions.apiKey
102
- ? effectiveOptions.apiKey.substring(0, 10) + "..."
103
- : "none",
104
- baseUrl: effectiveOptions.baseUrl || "default",
105
- }
106
- );
107
43
 
108
- const models = await getAvailableModels(effectiveOptions);
109
- console.log("[useModelManagement] Got available models:", models.length);
44
+ // Utiliser les données du contexte si disponibles
45
+ const useContextData =
46
+ (!options.baseUrl || options.baseUrl === context.baseUrl) &&
47
+ (!options.apiKey || options.apiKey === context.apiKeyId);
110
48
 
111
- const filteredModels = category
112
- ? models.filter((m) => m.category === category)
113
- : models;
49
+ // Filtrer par catégorie si nécessaire
50
+ const filteredModels = useMemo(() => {
51
+ if (!useContextData) return [];
52
+ return category
53
+ ? context.availableModels.filter((m) => m.category === category)
54
+ : context.availableModels;
55
+ }, [useContextData, category, context.availableModels]);
114
56
 
115
- setAvailableModels(filteredModels);
116
- // Cache the results
117
- setCache(cacheKey, filteredModels);
118
- console.log(
119
- "[useModelManagement] Set filtered models:",
120
- filteredModels.length
121
- );
122
- } catch (err) {
123
- const errorMessage =
124
- err instanceof Error ? err.message : "Erreur inconnue";
125
- console.error(
126
- "[useModelManagement] Error fetching available models:",
127
- errorMessage
128
- );
129
- setError(errorMessage);
130
- // En cas d'erreur 404 (API non disponible), utiliser un array vide
131
- if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
132
- setAvailableModels([]);
133
- }
134
- } finally {
135
- setLoading(false);
57
+ const refreshModels = useCallback(async () => {
58
+ if (useContextData) {
59
+ await context.refetchProviders();
136
60
  }
137
- }, [category, effectiveOptions]);
61
+ }, [useContextData, context]);
138
62
 
139
63
  const refreshUserModels = useCallback(async () => {
140
- // Si l'API est marquée comme indisponible, ne pas faire l'appel
141
- if (apiUnavailable) {
142
- console.log(
143
- "[useModelManagement] Skipping user models fetch - API marked as unavailable"
144
- );
145
- return;
64
+ if (useContextData) {
65
+ await context.refetchUserModels();
146
66
  }
147
-
148
- try {
149
- setLoading(true);
150
- setError(null);
151
-
152
- console.log("[useModelManagement] Fetching user models with options:", {
153
- apiKey: effectiveOptions.apiKey
154
- ? effectiveOptions.apiKey.substring(0, 10) + "..."
155
- : "none",
156
- baseUrl: effectiveOptions.baseUrl || "default",
157
- });
158
-
159
- const models = await getUserModels(effectiveOptions);
160
- console.log("[useModelManagement] Got user models:", models);
161
- setUserModels(models);
162
- } catch (err) {
163
- const errorMessage =
164
- err instanceof Error ? err.message : "Erreur inconnue";
165
- console.error(
166
- "[useModelManagement] Error fetching user models:",
167
- errorMessage
168
- );
169
- setError(errorMessage);
170
- // En cas d'erreur 404 (API non disponible), utiliser un array vide et ne pas retry
171
- if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
172
- setUserModels([]);
173
- setApiUnavailable(true);
174
- console.warn(
175
- "[useModelManagement] User models API not available (404), disabling further requests"
176
- );
177
- }
178
- } finally {
179
- setLoading(false);
180
- }
181
- }, [effectiveOptions, apiUnavailable]);
67
+ }, [useContextData, context]);
182
68
 
183
69
  const toggleModel = useCallback(
184
70
  async (modelId: string, isActive?: boolean) => {
185
- const currentlyActive = userModels.includes(modelId);
71
+ const currentlyActive = context.userModels.includes(modelId);
186
72
  const targetState = isActive !== undefined ? isActive : !currentlyActive;
187
73
 
188
74
  try {
189
75
  setLoading(true);
190
76
  setError(null);
191
77
 
78
+ const effectiveOptions = {
79
+ baseUrl: options.baseUrl || context.baseUrl,
80
+ apiKey: options.apiKey || context.apiKeyId,
81
+ };
82
+
192
83
  await toggleUserModel(modelId, targetState, effectiveOptions);
193
84
 
194
- // Mise à jour optimiste
195
- if (targetState) {
196
- setUserModels((prev) => [
197
- ...prev.filter((id) => id !== modelId),
198
- modelId,
199
- ]);
200
- } else {
201
- setUserModels((prev) => prev.filter((id) => id !== modelId));
202
- }
85
+ // Rafraîchir les modèles utilisateur depuis le contexte
86
+ await context.refetchUserModels();
203
87
  } catch (err) {
204
- setError(err instanceof Error ? err.message : "Erreur inconnue");
88
+ const errorMessage =
89
+ err instanceof Error ? err.message : "Erreur inconnue";
90
+ console.error(
91
+ "[useModelManagement] Error toggling model:",
92
+ errorMessage
93
+ );
94
+ setError(errorMessage);
205
95
  throw err;
206
96
  } finally {
207
97
  setLoading(false);
208
98
  }
209
99
  },
210
- [userModels, effectiveOptions]
100
+ [context, options.baseUrl, options.apiKey]
211
101
  );
212
102
 
213
- // Helpers
214
103
  const isModelActive = useCallback(
215
104
  (modelId: string) => {
216
- return userModels.includes(modelId);
105
+ return context.userModels.includes(modelId);
217
106
  },
218
- [userModels]
107
+ [context.userModels]
219
108
  );
220
109
 
221
110
  const getActiveModels = useCallback(() => {
222
- return availableModels.filter((model) => userModels.includes(model.id));
223
- }, [availableModels, userModels]);
111
+ return filteredModels.filter((model) =>
112
+ context.userModels.includes(model.id)
113
+ );
114
+ }, [filteredModels, context.userModels]);
224
115
 
225
116
  const getInactiveModels = useCallback(() => {
226
- return availableModels.filter((model) => !userModels.includes(model.id));
227
- }, [availableModels, userModels]);
228
-
229
- // Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe) ET API disponible
230
- useEffect(() => {
231
- const isExternalProxy =
232
- effectiveOptions.baseUrl &&
233
- effectiveOptions.baseUrl.includes("/api/lastbrain");
234
-
235
- console.log("[useModelManagement] useEffect triggered:", {
236
- autoFetch,
237
- hasApiKey: !!effectiveOptions.apiKey,
238
- hasBaseUrl: !!effectiveOptions.baseUrl,
239
- isExternalProxy,
240
- apiUnavailable,
241
- apiKeyPreview: effectiveOptions.apiKey
242
- ? effectiveOptions.apiKey.substring(0, 10) + "..."
243
- : "none",
244
- baseUrl: effectiveOptions.baseUrl || "none",
245
- });
246
-
247
- // Auto-fetch si autoFetch est activé ET qu'on a soit une apiKey soit un proxy externe ET API disponible
248
- if (
249
- autoFetch &&
250
- !apiUnavailable &&
251
- (effectiveOptions.apiKey || isExternalProxy)
252
- ) {
253
- console.log("[useModelManagement] Starting auto-fetch...");
254
- Promise.all([refreshModels(), refreshUserModels()]);
255
- } else if (autoFetch && !effectiveOptions.apiKey && !isExternalProxy) {
256
- console.warn(
257
- "[useModelManagement] autoFetch is enabled but no apiKey or baseUrl provided. Skipping automatic fetch."
258
- );
259
- } else if (autoFetch && apiUnavailable) {
260
- console.log(
261
- "[useModelManagement] autoFetch skipped - API marked as unavailable"
262
- );
263
- }
264
- }, [
265
- autoFetch,
266
- refreshModels,
267
- refreshUserModels,
268
- effectiveOptions.apiKey,
269
- effectiveOptions.baseUrl,
270
- apiUnavailable,
271
- ]);
117
+ return filteredModels.filter(
118
+ (model) => !context.userModels.includes(model.id)
119
+ );
120
+ }, [filteredModels, context.userModels]);
272
121
 
273
122
  return {
274
- availableModels,
275
- userModels,
276
- loading,
123
+ availableModels: filteredModels,
124
+ userModels: context.userModels,
125
+ loading: context.loadingProviders || context.loadingUserModels || loading,
277
126
  error,
278
127
  toggleModel,
279
128
  refreshModels,
@@ -27,12 +27,16 @@ export async function toggleUserModel(
27
27
  }
28
28
 
29
29
  const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
30
+ const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
30
31
 
32
+ // Route mapping pour toggle
31
33
  const endpoint = isPublicApi
32
34
  ? `${baseUrl}/ai/user/models/toggle`
33
- : baseUrl
34
- ? `${baseUrl}/auth/ai-models-toggle`
35
- : `/api/ai/auth/ai-models-toggle`;
35
+ : isExternalProxy
36
+ ? `${baseUrl}/ai/user/models/toggle` // Gateway mappe automatiquement
37
+ : baseUrl
38
+ ? `${baseUrl}/auth/ai-models-toggle`
39
+ : `/api/ai/auth/ai-models-toggle`;
36
40
 
37
41
  const response = await fetch(endpoint, {
38
42
  method: "PUT",