@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.
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiChipLabel.js +5 -1
- package/dist/components/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +1 -0
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +15 -2
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +5 -1
- package/dist/components/AiPromptPanel.d.ts +1 -1
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +9 -5
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +5 -1
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +5 -1
- package/dist/context/AiProvider.d.ts +28 -0
- package/dist/context/AiProvider.d.ts.map +1 -1
- package/dist/context/AiProvider.js +159 -1
- package/dist/hooks/useAiModels.d.ts +6 -1
- package/dist/hooks/useAiModels.d.ts.map +1 -1
- package/dist/hooks/useAiModels.js +71 -24
- package/dist/hooks/useModelManagement.d.ts +2 -10
- package/dist/hooks/useModelManagement.d.ts.map +1 -1
- package/dist/hooks/useModelManagement.js +42 -150
- package/dist/utils/modelManagement.d.ts.map +1 -1
- package/dist/utils/modelManagement.js +7 -3
- package/package.json +2 -2
- package/src/components/AiChipLabel.tsx +5 -1
- package/src/components/AiContextButton.tsx +1 -0
- package/src/components/AiImageButton.tsx +16 -2
- package/src/components/AiInput.tsx +5 -1
- package/src/components/AiPromptPanel.tsx +24 -1
- package/src/components/AiSelect.tsx +5 -1
- package/src/components/AiTextarea.tsx +5 -1
- package/src/context/AiProvider.tsx +223 -1
- package/src/hooks/useAiModels.ts +96 -27
- package/src/hooks/useModelManagement.ts +52 -203
- package/src/utils/modelManagement.ts +7 -3
|
@@ -1,181 +1,73 @@
|
|
|
1
|
-
import { useState, useCallback,
|
|
1
|
+
import { useState, useCallback, useMemo } from "react";
|
|
2
2
|
import { useAiContext } from "../context/AiProvider";
|
|
3
|
-
import { toggleUserModel,
|
|
4
|
-
import { getCached, setCache, isRateLimited, getRateLimitResetTime, } from "../utils/cache";
|
|
3
|
+
import { toggleUserModel, } from "../utils/modelManagement";
|
|
5
4
|
/**
|
|
6
5
|
* Hook pour gérer les modèles IA d'un utilisateur
|
|
6
|
+
* Utilise les données du contexte pour éviter les appels API multiples
|
|
7
7
|
*/
|
|
8
8
|
export function useModelManagement(options = {}) {
|
|
9
|
-
const {
|
|
10
|
-
const
|
|
11
|
-
const [availableModels, setAvailableModels] = useState([]);
|
|
12
|
-
const [userModels, setUserModels] = useState([]);
|
|
9
|
+
const { category } = options;
|
|
10
|
+
const context = useAiContext();
|
|
13
11
|
const [loading, setLoading] = useState(false);
|
|
14
12
|
const [error, setError] = useState(null);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
// Utiliser les données du contexte si disponibles
|
|
14
|
+
const useContextData = (!options.baseUrl || options.baseUrl === context.baseUrl) &&
|
|
15
|
+
(!options.apiKey || options.apiKey === context.apiKeyId);
|
|
16
|
+
// Filtrer par catégorie si nécessaire
|
|
17
|
+
const filteredModels = useMemo(() => {
|
|
18
|
+
if (!useContextData)
|
|
19
|
+
return [];
|
|
20
|
+
return category
|
|
21
|
+
? context.availableModels.filter((m) => m.category === category)
|
|
22
|
+
: context.availableModels;
|
|
23
|
+
}, [useContextData, category, context.availableModels]);
|
|
21
24
|
const refreshModels = useCallback(async () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
setError(null);
|
|
25
|
-
// Check cache first (5 minutes TTL for models)
|
|
26
|
-
const cacheKey = `models_${category || "all"}`;
|
|
27
|
-
const cached = getCached(cacheKey, 300000);
|
|
28
|
-
if (cached) {
|
|
29
|
-
console.log("[useModelManagement] Using cached models");
|
|
30
|
-
setAvailableModels(cached);
|
|
31
|
-
setLoading(false);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
// Check rate limit (max 5 calls per minute)
|
|
35
|
-
if (isRateLimited("models_fetch", 5, 60000)) {
|
|
36
|
-
const resetTime = getRateLimitResetTime("models_fetch");
|
|
37
|
-
const resetSeconds = Math.ceil(resetTime / 1000);
|
|
38
|
-
throw new Error(`Rate limit exceeded. Please wait ${resetSeconds}s before trying again.`);
|
|
39
|
-
}
|
|
40
|
-
console.log("[useModelManagement] Fetching available models with options:", {
|
|
41
|
-
apiKey: effectiveOptions.apiKey
|
|
42
|
-
? effectiveOptions.apiKey.substring(0, 10) + "..."
|
|
43
|
-
: "none",
|
|
44
|
-
baseUrl: effectiveOptions.baseUrl || "default",
|
|
45
|
-
});
|
|
46
|
-
const models = await getAvailableModels(effectiveOptions);
|
|
47
|
-
console.log("[useModelManagement] Got available models:", models.length);
|
|
48
|
-
const filteredModels = category
|
|
49
|
-
? models.filter((m) => m.category === category)
|
|
50
|
-
: models;
|
|
51
|
-
setAvailableModels(filteredModels);
|
|
52
|
-
// Cache the results
|
|
53
|
-
setCache(cacheKey, filteredModels);
|
|
54
|
-
console.log("[useModelManagement] Set filtered models:", filteredModels.length);
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
58
|
-
console.error("[useModelManagement] Error fetching available models:", errorMessage);
|
|
59
|
-
setError(errorMessage);
|
|
60
|
-
// En cas d'erreur 404 (API non disponible), utiliser un array vide
|
|
61
|
-
if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
|
|
62
|
-
setAvailableModels([]);
|
|
63
|
-
}
|
|
25
|
+
if (useContextData) {
|
|
26
|
+
await context.refetchProviders();
|
|
64
27
|
}
|
|
65
|
-
|
|
66
|
-
setLoading(false);
|
|
67
|
-
}
|
|
68
|
-
}, [category, effectiveOptions]);
|
|
28
|
+
}, [useContextData, context]);
|
|
69
29
|
const refreshUserModels = useCallback(async () => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
console.log("[useModelManagement] Skipping user models fetch - API marked as unavailable");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
try {
|
|
76
|
-
setLoading(true);
|
|
77
|
-
setError(null);
|
|
78
|
-
console.log("[useModelManagement] Fetching user models with options:", {
|
|
79
|
-
apiKey: effectiveOptions.apiKey
|
|
80
|
-
? effectiveOptions.apiKey.substring(0, 10) + "..."
|
|
81
|
-
: "none",
|
|
82
|
-
baseUrl: effectiveOptions.baseUrl || "default",
|
|
83
|
-
});
|
|
84
|
-
const models = await getUserModels(effectiveOptions);
|
|
85
|
-
console.log("[useModelManagement] Got user models:", models);
|
|
86
|
-
setUserModels(models);
|
|
30
|
+
if (useContextData) {
|
|
31
|
+
await context.refetchUserModels();
|
|
87
32
|
}
|
|
88
|
-
|
|
89
|
-
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
90
|
-
console.error("[useModelManagement] Error fetching user models:", errorMessage);
|
|
91
|
-
setError(errorMessage);
|
|
92
|
-
// En cas d'erreur 404 (API non disponible), utiliser un array vide et ne pas retry
|
|
93
|
-
if (errorMessage.includes("404") || errorMessage.includes("Not Found")) {
|
|
94
|
-
setUserModels([]);
|
|
95
|
-
setApiUnavailable(true);
|
|
96
|
-
console.warn("[useModelManagement] User models API not available (404), disabling further requests");
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
finally {
|
|
100
|
-
setLoading(false);
|
|
101
|
-
}
|
|
102
|
-
}, [effectiveOptions, apiUnavailable]);
|
|
33
|
+
}, [useContextData, context]);
|
|
103
34
|
const toggleModel = useCallback(async (modelId, isActive) => {
|
|
104
|
-
const currentlyActive = userModels.includes(modelId);
|
|
35
|
+
const currentlyActive = context.userModels.includes(modelId);
|
|
105
36
|
const targetState = isActive !== undefined ? isActive : !currentlyActive;
|
|
106
37
|
try {
|
|
107
38
|
setLoading(true);
|
|
108
39
|
setError(null);
|
|
40
|
+
const effectiveOptions = {
|
|
41
|
+
baseUrl: options.baseUrl || context.baseUrl,
|
|
42
|
+
apiKey: options.apiKey || context.apiKeyId,
|
|
43
|
+
};
|
|
109
44
|
await toggleUserModel(modelId, targetState, effectiveOptions);
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
setUserModels((prev) => [
|
|
113
|
-
...prev.filter((id) => id !== modelId),
|
|
114
|
-
modelId,
|
|
115
|
-
]);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
setUserModels((prev) => prev.filter((id) => id !== modelId));
|
|
119
|
-
}
|
|
45
|
+
// Rafraîchir les modèles utilisateur depuis le contexte
|
|
46
|
+
await context.refetchUserModels();
|
|
120
47
|
}
|
|
121
48
|
catch (err) {
|
|
122
|
-
|
|
49
|
+
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
50
|
+
console.error("[useModelManagement] Error toggling model:", errorMessage);
|
|
51
|
+
setError(errorMessage);
|
|
123
52
|
throw err;
|
|
124
53
|
}
|
|
125
54
|
finally {
|
|
126
55
|
setLoading(false);
|
|
127
56
|
}
|
|
128
|
-
}, [
|
|
129
|
-
// Helpers
|
|
57
|
+
}, [context, options.baseUrl, options.apiKey]);
|
|
130
58
|
const isModelActive = useCallback((modelId) => {
|
|
131
|
-
return userModels.includes(modelId);
|
|
132
|
-
}, [userModels]);
|
|
59
|
+
return context.userModels.includes(modelId);
|
|
60
|
+
}, [context.userModels]);
|
|
133
61
|
const getActiveModels = useCallback(() => {
|
|
134
|
-
return
|
|
135
|
-
}, [
|
|
62
|
+
return filteredModels.filter((model) => context.userModels.includes(model.id));
|
|
63
|
+
}, [filteredModels, context.userModels]);
|
|
136
64
|
const getInactiveModels = useCallback(() => {
|
|
137
|
-
return
|
|
138
|
-
}, [
|
|
139
|
-
// Auto-fetch au mount - si autoFetch ET (apiKey OU baseUrl avec proxy externe) ET API disponible
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
const isExternalProxy = effectiveOptions.baseUrl &&
|
|
142
|
-
effectiveOptions.baseUrl.includes("/api/lastbrain");
|
|
143
|
-
console.log("[useModelManagement] useEffect triggered:", {
|
|
144
|
-
autoFetch,
|
|
145
|
-
hasApiKey: !!effectiveOptions.apiKey,
|
|
146
|
-
hasBaseUrl: !!effectiveOptions.baseUrl,
|
|
147
|
-
isExternalProxy,
|
|
148
|
-
apiUnavailable,
|
|
149
|
-
apiKeyPreview: effectiveOptions.apiKey
|
|
150
|
-
? effectiveOptions.apiKey.substring(0, 10) + "..."
|
|
151
|
-
: "none",
|
|
152
|
-
baseUrl: effectiveOptions.baseUrl || "none",
|
|
153
|
-
});
|
|
154
|
-
// Auto-fetch si autoFetch est activé ET qu'on a soit une apiKey soit un proxy externe ET API disponible
|
|
155
|
-
if (autoFetch &&
|
|
156
|
-
!apiUnavailable &&
|
|
157
|
-
(effectiveOptions.apiKey || isExternalProxy)) {
|
|
158
|
-
console.log("[useModelManagement] Starting auto-fetch...");
|
|
159
|
-
Promise.all([refreshModels(), refreshUserModels()]);
|
|
160
|
-
}
|
|
161
|
-
else if (autoFetch && !effectiveOptions.apiKey && !isExternalProxy) {
|
|
162
|
-
console.warn("[useModelManagement] autoFetch is enabled but no apiKey or baseUrl provided. Skipping automatic fetch.");
|
|
163
|
-
}
|
|
164
|
-
else if (autoFetch && apiUnavailable) {
|
|
165
|
-
console.log("[useModelManagement] autoFetch skipped - API marked as unavailable");
|
|
166
|
-
}
|
|
167
|
-
}, [
|
|
168
|
-
autoFetch,
|
|
169
|
-
refreshModels,
|
|
170
|
-
refreshUserModels,
|
|
171
|
-
effectiveOptions.apiKey,
|
|
172
|
-
effectiveOptions.baseUrl,
|
|
173
|
-
apiUnavailable,
|
|
174
|
-
]);
|
|
65
|
+
return filteredModels.filter((model) => !context.userModels.includes(model.id));
|
|
66
|
+
}, [filteredModels, context.userModels]);
|
|
175
67
|
return {
|
|
176
|
-
availableModels,
|
|
177
|
-
userModels,
|
|
178
|
-
loading,
|
|
68
|
+
availableModels: filteredModels,
|
|
69
|
+
userModels: context.userModels,
|
|
70
|
+
loading: context.loadingProviders || context.loadingUserModels || loading,
|
|
179
71
|
error,
|
|
180
72
|
toggleModel,
|
|
181
73
|
refreshModels,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modelManagement.d.ts","sourceRoot":"","sources":["../../src/utils/modelManagement.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"modelManagement.d.ts","sourceRoot":"","sources":["../../src/utils/modelManagement.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,OAAO,EACjB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,IAAI,CAAC,CAqCf;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CACR,KAAK,CAAC;IACJ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC,CACH,CA6CA;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,MAAM,EAAE,CAAC,CA6CnB"}
|
|
@@ -14,11 +14,15 @@ export async function toggleUserModel(modelId, isActive, options = {}) {
|
|
|
14
14
|
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
15
15
|
}
|
|
16
16
|
const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
|
|
17
|
+
const isExternalProxy = baseUrl && baseUrl.includes("/api/lastbrain");
|
|
18
|
+
// Route mapping pour toggle
|
|
17
19
|
const endpoint = isPublicApi
|
|
18
20
|
? `${baseUrl}/ai/user/models/toggle`
|
|
19
|
-
:
|
|
20
|
-
? `${baseUrl}/
|
|
21
|
-
:
|
|
21
|
+
: isExternalProxy
|
|
22
|
+
? `${baseUrl}/ai/user/models/toggle` // Gateway mappe automatiquement
|
|
23
|
+
: baseUrl
|
|
24
|
+
? `${baseUrl}/auth/ai-models-toggle`
|
|
25
|
+
: `/api/ai/auth/ai-models-toggle`;
|
|
22
26
|
const response = await fetch(endpoint, {
|
|
23
27
|
method: "PUT",
|
|
24
28
|
headers,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/ai-ui-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.36",
|
|
4
4
|
"description": "Headless React components for LastBrain AI UI Kit",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"lucide-react": "^0.257.0",
|
|
51
|
-
"@lastbrain/ai-ui-core": "1.0.
|
|
51
|
+
"@lastbrain/ai-ui-core": "1.0.25"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/react": "^19.2.0",
|
|
@@ -93,7 +93,11 @@ export function AiChipInput({
|
|
|
93
93
|
const apiKeyId = propApiKeyId ?? aiContext.apiKeyId;
|
|
94
94
|
|
|
95
95
|
// Hooks pour l'IA avec les valeurs du contexte
|
|
96
|
-
const { models } = useAiModels({
|
|
96
|
+
const { models } = useAiModels({
|
|
97
|
+
baseUrl,
|
|
98
|
+
apiKeyId,
|
|
99
|
+
modelType: "text-or-language",
|
|
100
|
+
});
|
|
97
101
|
const { generateText } = useAiCallText({ baseUrl, apiKeyId });
|
|
98
102
|
|
|
99
103
|
const addChip = (text: string) => {
|
|
@@ -63,9 +63,15 @@ export function AiImageButton({
|
|
|
63
63
|
const baseUrl = propBaseUrl ?? aiContext.baseUrl;
|
|
64
64
|
const apiKeyId = propApiKeyId ?? aiContext.apiKeyId;
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
// Récupérer uniquement les modèles image
|
|
67
|
+
const { models } = useAiModels({ baseUrl, apiKeyId, modelType: "image" });
|
|
67
68
|
const { generateImage, loading } = useAiCallImage({ baseUrl, apiKeyId });
|
|
68
69
|
|
|
70
|
+
console.log("[AiImageButton] Models received:", {
|
|
71
|
+
count: models.length,
|
|
72
|
+
models: models.map((m) => ({ id: m.id, name: m.name, type: m.type })),
|
|
73
|
+
});
|
|
74
|
+
|
|
69
75
|
const handleOpenPanel = () => {
|
|
70
76
|
setIsOpen(true);
|
|
71
77
|
};
|
|
@@ -287,7 +293,15 @@ export function AiImageButton({
|
|
|
287
293
|
onClose={handleClosePanel}
|
|
288
294
|
onSubmit={handleSubmit}
|
|
289
295
|
uiMode={uiMode}
|
|
290
|
-
models={
|
|
296
|
+
models={(() => {
|
|
297
|
+
const filteredModels = models.filter((m) => m.type === "image");
|
|
298
|
+
console.log("[AiImageButton] Passing to AiPromptPanel:", {
|
|
299
|
+
originalCount: models.length,
|
|
300
|
+
filteredCount: filteredModels.length,
|
|
301
|
+
models: filteredModels,
|
|
302
|
+
});
|
|
303
|
+
return filteredModels;
|
|
304
|
+
})()}
|
|
291
305
|
/>
|
|
292
306
|
)}
|
|
293
307
|
</div>
|
|
@@ -44,7 +44,11 @@ export function AiInput({
|
|
|
44
44
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
45
45
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
46
46
|
|
|
47
|
-
const { models } = useAiModels({
|
|
47
|
+
const { models } = useAiModels({
|
|
48
|
+
baseUrl,
|
|
49
|
+
apiKeyId,
|
|
50
|
+
modelType: "text-or-language",
|
|
51
|
+
});
|
|
48
52
|
const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
|
|
49
53
|
|
|
50
54
|
const hasConfiguration = Boolean(model && prompt);
|
|
@@ -17,7 +17,8 @@ import {
|
|
|
17
17
|
type Prompt,
|
|
18
18
|
type PublicPrompt,
|
|
19
19
|
} from "../hooks/usePrompts";
|
|
20
|
-
import { useModelManagement
|
|
20
|
+
import { useModelManagement } from "../hooks/useModelManagement";
|
|
21
|
+
import { type AIModel } from "../context/AiProvider";
|
|
21
22
|
import { AiProvider } from "../context/AiProvider";
|
|
22
23
|
|
|
23
24
|
export interface AiPromptPanelProps {
|
|
@@ -1070,6 +1071,28 @@ function AiPromptPanelInternal({
|
|
|
1070
1071
|
>
|
|
1071
1072
|
Activez ou désactivez les modèles selon vos besoins
|
|
1072
1073
|
</p>
|
|
1074
|
+
<p
|
|
1075
|
+
style={{
|
|
1076
|
+
fontSize: "12px",
|
|
1077
|
+
color: "#9ca3af",
|
|
1078
|
+
margin: "0",
|
|
1079
|
+
}}
|
|
1080
|
+
>
|
|
1081
|
+
{
|
|
1082
|
+
effectiveAvailableModels.filter(
|
|
1083
|
+
(m) => m.category === "text"
|
|
1084
|
+
).length
|
|
1085
|
+
}{" "}
|
|
1086
|
+
modèles disponibles •{" "}
|
|
1087
|
+
{
|
|
1088
|
+
effectiveUserModels.filter((id) =>
|
|
1089
|
+
effectiveAvailableModels.some(
|
|
1090
|
+
(m) => m.id === id && m.category === "text"
|
|
1091
|
+
)
|
|
1092
|
+
).length
|
|
1093
|
+
}{" "}
|
|
1094
|
+
activés
|
|
1095
|
+
</p>
|
|
1073
1096
|
</div>
|
|
1074
1097
|
|
|
1075
1098
|
<div
|
|
@@ -37,7 +37,11 @@ export function AiSelect({
|
|
|
37
37
|
const [isFocused, setIsFocused] = useState(false);
|
|
38
38
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
39
39
|
|
|
40
|
-
const { models } = useAiModels({
|
|
40
|
+
const { models } = useAiModels({
|
|
41
|
+
baseUrl,
|
|
42
|
+
apiKeyId,
|
|
43
|
+
modelType: "text-or-language",
|
|
44
|
+
});
|
|
41
45
|
const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
|
|
42
46
|
|
|
43
47
|
const handleOpenPanel = () => {
|
|
@@ -50,7 +50,11 @@ export function AiTextarea({
|
|
|
50
50
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
51
51
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
52
52
|
|
|
53
|
-
const { models } = useAiModels({
|
|
53
|
+
const { models } = useAiModels({
|
|
54
|
+
baseUrl,
|
|
55
|
+
apiKeyId,
|
|
56
|
+
modelType: "text-or-language",
|
|
57
|
+
});
|
|
54
58
|
const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
|
|
55
59
|
|
|
56
60
|
const hasConfiguration = Boolean(model && prompt);
|
|
@@ -1,12 +1,58 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useState,
|
|
7
|
+
useEffect,
|
|
8
|
+
useCallback,
|
|
9
|
+
useRef,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
} from "react";
|
|
4
12
|
import type { UiMode } from "../types";
|
|
13
|
+
import type { ModelRef } from "@lastbrain/ai-ui-core";
|
|
14
|
+
import { createClient } from "@lastbrain/ai-ui-core";
|
|
15
|
+
import { getAvailableModels, getUserModels } from "../utils/modelManagement";
|
|
16
|
+
|
|
17
|
+
export interface AIModel {
|
|
18
|
+
id: string;
|
|
19
|
+
name: string;
|
|
20
|
+
description?: string;
|
|
21
|
+
provider: string;
|
|
22
|
+
category: "text" | "image" | "audio" | "video";
|
|
23
|
+
isActive?: boolean;
|
|
24
|
+
isPro?: boolean;
|
|
25
|
+
costPer1M?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ProviderData {
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
models: ModelRef[];
|
|
33
|
+
}
|
|
5
34
|
|
|
6
35
|
export interface AiContextValue {
|
|
7
36
|
baseUrl: string;
|
|
8
37
|
apiKeyId: string;
|
|
9
38
|
uiMode: UiMode;
|
|
39
|
+
// Providers et modèles (récupérés une seule fois)
|
|
40
|
+
providers: ProviderData[];
|
|
41
|
+
allModels: ModelRef[]; // Tous les modèles de tous les providers
|
|
42
|
+
availableModels: AIModel[]; // Modèles disponibles avec métadonnées
|
|
43
|
+
userModels: string[]; // IDs des modèles activés par l'utilisateur
|
|
44
|
+
// États de chargement
|
|
45
|
+
loadingProviders: boolean;
|
|
46
|
+
loadingUserModels: boolean;
|
|
47
|
+
// Actions
|
|
48
|
+
refetchProviders: () => Promise<void>;
|
|
49
|
+
refetchUserModels: () => Promise<void>;
|
|
50
|
+
// Helpers pour filtrer les modèles
|
|
51
|
+
getModelsByType: (
|
|
52
|
+
type: "text" | "language" | "image" | "embed"
|
|
53
|
+
) => ModelRef[];
|
|
54
|
+
getTextModels: () => ModelRef[];
|
|
55
|
+
getImageModels: () => ModelRef[];
|
|
10
56
|
}
|
|
11
57
|
|
|
12
58
|
const AiContext = createContext<AiContextValue | undefined>(undefined);
|
|
@@ -24,10 +70,186 @@ export function AiProvider({
|
|
|
24
70
|
uiMode = "modal",
|
|
25
71
|
children,
|
|
26
72
|
}: AiProviderProps) {
|
|
73
|
+
const [providers, setProviders] = useState<ProviderData[]>([]);
|
|
74
|
+
const [allModels, setAllModels] = useState<ModelRef[]>([]);
|
|
75
|
+
const [availableModels, setAvailableModels] = useState<AIModel[]>([]);
|
|
76
|
+
const [userModels, setUserModels] = useState<string[]>([]);
|
|
77
|
+
const [loadingProviders, setLoadingProviders] = useState(false);
|
|
78
|
+
const [loadingUserModels, setLoadingUserModels] = useState(false);
|
|
79
|
+
|
|
80
|
+
// Flags pour éviter les appels multiples et les boucles infinies
|
|
81
|
+
const isFetchingProviders = useRef(false);
|
|
82
|
+
const isFetchingUserModels = useRef(false);
|
|
83
|
+
const hasFetchedProviders = useRef(false);
|
|
84
|
+
const hasFetchedUserModels = useRef(false);
|
|
85
|
+
const providersAvailable = useRef(true); // false si 404
|
|
86
|
+
const userModelsAvailable = useRef(true); // false si 404
|
|
87
|
+
|
|
88
|
+
// Récupérer les providers et leurs modèles + available models en parallèle
|
|
89
|
+
const fetchProviders = useCallback(async () => {
|
|
90
|
+
// Éviter les appels multiples
|
|
91
|
+
if (isFetchingProviders.current) {
|
|
92
|
+
console.log("[AiProvider] Already fetching providers, skipping");
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Si déjà fetch et non disponible, ne pas réessayer
|
|
97
|
+
if (hasFetchedProviders.current && !providersAvailable.current) {
|
|
98
|
+
console.log("[AiProvider] Providers API not available (404), skipping");
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
isFetchingProviders.current = true;
|
|
103
|
+
setLoadingProviders(true);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
console.log("[AiProvider] Fetching providers from:", baseUrl);
|
|
107
|
+
|
|
108
|
+
// Utiliser createClient pour avoir buildUrl qui gère les routes correctement
|
|
109
|
+
const client = createClient({ baseUrl, apiKeyId });
|
|
110
|
+
|
|
111
|
+
// Fetch providers et available models en parallèle
|
|
112
|
+
const [providersData, availableModelsData] = await Promise.all([
|
|
113
|
+
client
|
|
114
|
+
.getModels()
|
|
115
|
+
.then((models) => {
|
|
116
|
+
// getModels retourne directement les modèles, pas les providers
|
|
117
|
+
// On doit reconstruire la structure providers
|
|
118
|
+
return { providers: [{ id: "default", name: "Default", models }] };
|
|
119
|
+
})
|
|
120
|
+
.catch((error) => {
|
|
121
|
+
console.error("[AiProvider] Error fetching models:", error);
|
|
122
|
+
if (error.message?.includes("404")) {
|
|
123
|
+
providersAvailable.current = false;
|
|
124
|
+
}
|
|
125
|
+
return { providers: [] };
|
|
126
|
+
}),
|
|
127
|
+
getAvailableModels({ baseUrl, apiKey: apiKeyId }).catch((error) => {
|
|
128
|
+
console.warn("[AiProvider] Could not fetch available models:", error);
|
|
129
|
+
return [];
|
|
130
|
+
}),
|
|
131
|
+
]);
|
|
132
|
+
console.log("[AiProvider] Providers data received:", providersData);
|
|
133
|
+
|
|
134
|
+
if (providersData.providers && Array.isArray(providersData.providers)) {
|
|
135
|
+
setProviders(providersData.providers);
|
|
136
|
+
|
|
137
|
+
// Extraire tous les modèles
|
|
138
|
+
const models: ModelRef[] = [];
|
|
139
|
+
for (const provider of providersData.providers) {
|
|
140
|
+
if (provider.models && Array.isArray(provider.models)) {
|
|
141
|
+
models.push(...provider.models);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
console.log("[AiProvider] Extracted models:", models.length, models);
|
|
145
|
+
setAllModels(models);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Stocker available models
|
|
149
|
+
console.log("[AiProvider] Available models:", availableModelsData.length);
|
|
150
|
+
setAvailableModels(availableModelsData);
|
|
151
|
+
hasFetchedProviders.current = true;
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error("[AiProvider] Error fetching providers:", error);
|
|
154
|
+
// En cas d'erreur, utiliser des valeurs vides
|
|
155
|
+
setProviders([]);
|
|
156
|
+
setAllModels([]);
|
|
157
|
+
setAvailableModels([]);
|
|
158
|
+
hasFetchedProviders.current = true;
|
|
159
|
+
} finally {
|
|
160
|
+
setLoadingProviders(false);
|
|
161
|
+
isFetchingProviders.current = false;
|
|
162
|
+
}
|
|
163
|
+
}, [baseUrl, apiKeyId]); // Retirer loadingProviders des dépendances
|
|
164
|
+
|
|
165
|
+
// Récupérer les modèles activés par l'utilisateur
|
|
166
|
+
const fetchUserModels = useCallback(async () => {
|
|
167
|
+
// Éviter les appels multiples
|
|
168
|
+
if (isFetchingUserModels.current) {
|
|
169
|
+
console.log("[AiProvider] Already fetching user models, skipping");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Si déjà fetch et non disponible, ne pas réessayer
|
|
174
|
+
if (hasFetchedUserModels.current && !userModelsAvailable.current) {
|
|
175
|
+
console.log("[AiProvider] User models API not available (404), skipping");
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
isFetchingUserModels.current = true;
|
|
180
|
+
setLoadingUserModels(true);
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
const models = await getUserModels({ baseUrl, apiKey: apiKeyId });
|
|
184
|
+
setUserModels(models);
|
|
185
|
+
hasFetchedUserModels.current = true;
|
|
186
|
+
} catch (error) {
|
|
187
|
+
console.error("[AiProvider] Error fetching user models:", error);
|
|
188
|
+
// En cas d'erreur 404, ne pas logger comme erreur critique
|
|
189
|
+
if (error instanceof Error && error.message.includes("404")) {
|
|
190
|
+
console.warn("[AiProvider] User models API not available (404)");
|
|
191
|
+
userModelsAvailable.current = false;
|
|
192
|
+
setUserModels([]);
|
|
193
|
+
}
|
|
194
|
+
hasFetchedUserModels.current = true;
|
|
195
|
+
} finally {
|
|
196
|
+
setLoadingUserModels(false);
|
|
197
|
+
isFetchingUserModels.current = false;
|
|
198
|
+
}
|
|
199
|
+
}, [baseUrl, apiKeyId]); // Retirer loadingUserModels des dépendances
|
|
200
|
+
|
|
201
|
+
// Récupérer les données au montage du provider
|
|
202
|
+
useEffect(() => {
|
|
203
|
+
fetchProviders(); // Fetch providers + available models en même temps
|
|
204
|
+
fetchUserModels();
|
|
205
|
+
}, [fetchProviders, fetchUserModels]);
|
|
206
|
+
|
|
207
|
+
// Helpers pour filtrer les modèles par type
|
|
208
|
+
const getModelsByType = useCallback(
|
|
209
|
+
(type: "text" | "language" | "image" | "embed") => {
|
|
210
|
+
const filtered = allModels.filter((model) => model.type === type);
|
|
211
|
+
console.log(`[AiProvider] getModelsByType(${type}):`, {
|
|
212
|
+
total: allModels.length,
|
|
213
|
+
filtered: filtered.length,
|
|
214
|
+
models: filtered.map((m) => ({ id: m.id, name: m.name, type: m.type })),
|
|
215
|
+
});
|
|
216
|
+
return filtered;
|
|
217
|
+
},
|
|
218
|
+
[allModels]
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const getTextModels = useCallback(() => {
|
|
222
|
+
return allModels.filter(
|
|
223
|
+
(model) => model.type === "text" || model.type === "language"
|
|
224
|
+
);
|
|
225
|
+
}, [allModels]);
|
|
226
|
+
|
|
227
|
+
const getImageModels = useCallback(() => {
|
|
228
|
+
const imageModels = allModels.filter((model) => model.type === "image");
|
|
229
|
+
console.log("[AiProvider] getImageModels:", {
|
|
230
|
+
totalModels: allModels.length,
|
|
231
|
+
imageModels: imageModels.length,
|
|
232
|
+
allTypes: allModels.map((m) => ({ id: m.id, type: m.type })),
|
|
233
|
+
imageModelsList: imageModels.map((m) => ({ id: m.id, name: m.name })),
|
|
234
|
+
});
|
|
235
|
+
return imageModels;
|
|
236
|
+
}, [allModels]);
|
|
237
|
+
|
|
27
238
|
const value: AiContextValue = {
|
|
28
239
|
baseUrl,
|
|
29
240
|
apiKeyId,
|
|
30
241
|
uiMode,
|
|
242
|
+
providers,
|
|
243
|
+
allModels,
|
|
244
|
+
availableModels,
|
|
245
|
+
userModels,
|
|
246
|
+
loadingProviders,
|
|
247
|
+
loadingUserModels,
|
|
248
|
+
refetchProviders: fetchProviders,
|
|
249
|
+
refetchUserModels: fetchUserModels,
|
|
250
|
+
getModelsByType,
|
|
251
|
+
getTextModels,
|
|
252
|
+
getImageModels,
|
|
31
253
|
};
|
|
32
254
|
|
|
33
255
|
return <AiContext.Provider value={value}>{children}</AiContext.Provider>;
|