@lastbrain/ai-ui-react 1.0.37 → 1.0.39

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.
@@ -0,0 +1,269 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Provider d'authentification LastBrain
5
+ * Gère l'état de connexion, la session et les appels IA
6
+ */
7
+
8
+ import {
9
+ createContext,
10
+ useContext,
11
+ useEffect,
12
+ useCallback,
13
+ useState,
14
+ type ReactNode,
15
+ } from "react";
16
+ import type {
17
+ LBAuthState,
18
+ LBApiKey,
19
+ LBSession,
20
+ LBLoginResult,
21
+ LBSessionResult,
22
+ } from "@lastbrain/ai-ui-core";
23
+
24
+ interface LBProviderProps {
25
+ children: ReactNode;
26
+ /** URL de l'API LastBrain (ex: https://api.lastbrain.io) */
27
+ baseUrl?: string;
28
+ /** URL du proxy local (ex: /api/lb) */
29
+ proxyUrl?: string;
30
+ /** Fonction appelée lors des changements d'état */
31
+ onStatusChange?: (status: LBAuthState["status"]) => void;
32
+ }
33
+
34
+ interface LBContextValue extends LBAuthState {
35
+ /** Fonction de connexion */
36
+ login: (email: string, password: string) => Promise<LBLoginResult>;
37
+ /** Fonction de déconnexion */
38
+ logout: () => Promise<void>;
39
+ /** Récupère les clés API de l'utilisateur */
40
+ fetchApiKeys: (accessToken: string) => Promise<LBApiKey[]>;
41
+ /** Sélectionne une clé API et crée une session */
42
+ selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
43
+ /** Recharge l'état de la session */
44
+ refreshSession: () => Promise<void>;
45
+ /** Clés API disponibles */
46
+ apiKeys: LBApiKey[];
47
+ /** Access token temporaire (après login) */
48
+ accessToken?: string;
49
+ }
50
+
51
+ const LBContext = createContext<LBContextValue | undefined>(undefined);
52
+
53
+ export function LBProvider({
54
+ children,
55
+ baseUrl: _baseUrl = "/api/lb",
56
+ proxyUrl = "/api/lb",
57
+ onStatusChange,
58
+ }: LBProviderProps) {
59
+ const [state, setState] = useState<LBAuthState>({
60
+ status: "loading",
61
+ });
62
+ const [apiKeys, setApiKeys] = useState<LBApiKey[]>([]);
63
+ const [accessToken, setAccessToken] = useState<string>();
64
+
65
+ /**
66
+ * Vérifie si une session existe au chargement
67
+ */
68
+ const checkSession = useCallback(async () => {
69
+ try {
70
+ const response = await fetch(`${proxyUrl}/auth/session/verify`, {
71
+ credentials: "include",
72
+ });
73
+
74
+ if (response.ok) {
75
+ const session: LBSession = await response.json();
76
+ setState({
77
+ status: "ready",
78
+ session,
79
+ user: {
80
+ id: session.userId,
81
+ email: "", // Sera rempli par une autre requête si nécessaire
82
+ },
83
+ });
84
+ onStatusChange?.("ready");
85
+ } else {
86
+ setState({ status: "needs_auth" });
87
+ onStatusChange?.("needs_auth");
88
+ }
89
+ } catch (error) {
90
+ console.error("[LBProvider] Session check failed:", error);
91
+ setState({ status: "needs_auth" });
92
+ onStatusChange?.("needs_auth");
93
+ }
94
+ }, [proxyUrl, onStatusChange]);
95
+
96
+ useEffect(() => {
97
+ checkSession();
98
+ }, [checkSession]);
99
+
100
+ /**
101
+ * Connexion utilisateur
102
+ */
103
+ const login = useCallback(
104
+ async (email: string, password: string): Promise<LBLoginResult> => {
105
+ try {
106
+ setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
107
+
108
+ const response = await fetch(`${proxyUrl}/auth/login`, {
109
+ method: "POST",
110
+ headers: { "Content-Type": "application/json" },
111
+ body: JSON.stringify({ email, password }),
112
+ credentials: "include",
113
+ });
114
+
115
+ if (!response.ok) {
116
+ const error = await response.json();
117
+ throw new Error(error.message || "Login failed");
118
+ }
119
+
120
+ const result: LBLoginResult = await response.json();
121
+ setAccessToken(result.accessToken);
122
+
123
+ setState({
124
+ status: "ready",
125
+ user: result.user,
126
+ });
127
+
128
+ return result;
129
+ } catch (error) {
130
+ const message = error instanceof Error ? error.message : "Login failed";
131
+ setState({
132
+ status: "error",
133
+ error: message,
134
+ });
135
+ throw error;
136
+ }
137
+ },
138
+ [proxyUrl]
139
+ );
140
+
141
+ /**
142
+ * Récupère les clés API de l'utilisateur
143
+ */
144
+ const fetchApiKeys = useCallback(
145
+ async (token: string): Promise<LBApiKey[]> => {
146
+ try {
147
+ const response = await fetch(`${proxyUrl}/user/api-keys`, {
148
+ headers: {
149
+ Authorization: `Bearer ${token}`,
150
+ },
151
+ credentials: "include",
152
+ });
153
+
154
+ if (!response.ok) {
155
+ throw new Error("Failed to fetch API keys");
156
+ }
157
+
158
+ const keys: LBApiKey[] = await response.json();
159
+ setApiKeys(keys);
160
+ return keys;
161
+ } catch (error) {
162
+ console.error("[LBProvider] Failed to fetch API keys:", error);
163
+ throw error;
164
+ }
165
+ },
166
+ [proxyUrl]
167
+ );
168
+
169
+ /**
170
+ * Sélectionne une clé API et crée une session
171
+ */
172
+ const selectApiKey = useCallback(
173
+ async (token: string, apiKeyId: string): Promise<void> => {
174
+ try {
175
+ setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
176
+
177
+ const response = await fetch(`${proxyUrl}/auth/session`, {
178
+ method: "POST",
179
+ headers: {
180
+ "Content-Type": "application/json",
181
+ Authorization: `Bearer ${token}`,
182
+ },
183
+ body: JSON.stringify({ api_key_id: apiKeyId }),
184
+ credentials: "include",
185
+ });
186
+
187
+ if (!response.ok) {
188
+ throw new Error("Failed to create session");
189
+ }
190
+
191
+ const sessionResult: LBSessionResult = await response.json();
192
+
193
+ setState({
194
+ status: "ready",
195
+ user: state.user,
196
+ selectedKey: sessionResult.apiKey,
197
+ session: {
198
+ sessionToken: sessionResult.sessionToken,
199
+ userId: state.user?.id || "",
200
+ apiKeyId,
201
+ expiresAt: Date.now() + sessionResult.expiresIn * 1000,
202
+ },
203
+ });
204
+
205
+ setAccessToken(undefined); // Nettoyer l'access token temporaire
206
+ onStatusChange?.("ready");
207
+ } catch (error) {
208
+ const message =
209
+ error instanceof Error ? error.message : "Failed to select API key";
210
+ setState({
211
+ status: "error",
212
+ error: message,
213
+ });
214
+ throw error;
215
+ }
216
+ },
217
+ [proxyUrl, state.user, onStatusChange]
218
+ );
219
+
220
+ /**
221
+ * Déconnexion
222
+ */
223
+ const logout = useCallback(async (): Promise<void> => {
224
+ try {
225
+ await fetch(`${proxyUrl}/auth/session/logout`, {
226
+ method: "POST",
227
+ credentials: "include",
228
+ });
229
+ } catch (error) {
230
+ console.error("[LBProvider] Logout failed:", error);
231
+ } finally {
232
+ setState({ status: "needs_auth" });
233
+ setApiKeys([]);
234
+ setAccessToken(undefined);
235
+ onStatusChange?.("needs_auth");
236
+ }
237
+ }, [proxyUrl, onStatusChange]);
238
+
239
+ /**
240
+ * Recharge la session
241
+ */
242
+ const refreshSession = useCallback(async (): Promise<void> => {
243
+ await checkSession();
244
+ }, [checkSession]);
245
+
246
+ const value: LBContextValue = {
247
+ ...state,
248
+ login,
249
+ logout,
250
+ fetchApiKeys,
251
+ selectApiKey,
252
+ refreshSession,
253
+ apiKeys,
254
+ accessToken,
255
+ };
256
+
257
+ return <LBContext.Provider value={value}>{children}</LBContext.Provider>;
258
+ }
259
+
260
+ /**
261
+ * Hook pour accéder au contexte LastBrain
262
+ */
263
+ export function useLB(): LBContextValue {
264
+ const context = useContext(LBContext);
265
+ if (!context) {
266
+ throw new Error("useLB must be used within LBProvider");
267
+ }
268
+ return context;
269
+ }
@@ -87,8 +87,7 @@ export function useAiModels(options?: UseAiModelsOptions): UseAiModelsResult {
87
87
  filtered.length
88
88
  );
89
89
  return filtered;
90
- return filtered;
91
- }, [useContextData, context, options?.modelType]);
90
+ }, [useContextData, context, options]);
92
91
 
93
92
  const refetch = useCallback(() => {
94
93
  if (useContextData) {
@@ -0,0 +1,7 @@
1
+ "use client";
2
+
3
+ /**
4
+ * Hook useLB - Export du hook depuis LBAuthProvider
5
+ */
6
+
7
+ export { useLB } from "../context/LBAuthProvider";
@@ -79,7 +79,10 @@ export function usePrompts(): UsePromptsReturn {
79
79
  // Check cache first (60 seconds TTL)
80
80
  const cached = getCached<Prompt[] | PublicPrompt[]>(cacheKey, 60000);
81
81
  if (cached) {
82
- console.log("[usePrompts] Using cached data", cached.length);
82
+ console.log("[usePrompts] Using cached data:", {
83
+ count: cached.length,
84
+ prompts: cached,
85
+ });
83
86
  setPrompts(cached);
84
87
  setLoading(false);
85
88
  return;
@@ -141,6 +144,13 @@ export function usePrompts(): UsePromptsReturn {
141
144
 
142
145
  const data = await response.json();
143
146
 
147
+ console.log("[usePrompts] Server response:", {
148
+ ok: response.ok,
149
+ status: response.status,
150
+ promptsCount: data.prompts?.length || 0,
151
+ data,
152
+ });
153
+
144
154
  if (response.ok) {
145
155
  const promptsData = data.prompts || [];
146
156
  setPrompts(promptsData);
@@ -155,7 +165,7 @@ export function usePrompts(): UsePromptsReturn {
155
165
  setLoading(false);
156
166
  }
157
167
  },
158
- [baseUrl]
168
+ [baseUrl, apiKeyId]
159
169
  );
160
170
 
161
171
  const createPrompt = useCallback(
@@ -314,7 +324,7 @@ export function usePrompts(): UsePromptsReturn {
314
324
  body: JSON.stringify({ prompt_id: promptId, stat_type: statType }),
315
325
  credentials: isExternalProxy ? "include" : "same-origin",
316
326
  });
317
- } catch (err) {
327
+ } catch {
318
328
  // Silent fail for stats
319
329
  }
320
330
  },
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./types";
3
3
 
4
4
  // Context
5
5
  export * from "./context/AiProvider";
6
+ export * from "./context/LBAuthProvider";
6
7
 
7
8
  // Hooks
8
9
  export * from "./hooks/useAiClient";
@@ -12,6 +13,7 @@ export * from "./hooks/useAiCallText";
12
13
  export * from "./hooks/useAiCallImage";
13
14
  export * from "./hooks/usePrompts";
14
15
  export * from "./hooks/useModelManagement";
16
+ export * from "./hooks/useLB";
15
17
 
16
18
  // Components
17
19
  export * from "./components/AiPromptPanel";
@@ -24,6 +26,8 @@ export * from "./components/AiImageButton";
24
26
  export * from "./components/AiContextButton";
25
27
  export * from "./components/AiSettingsButton";
26
28
  export * from "./components/AiStatusButton";
29
+ export * from "./components/LBConnectButton";
30
+ export * from "./components/LBKeyPicker";
27
31
 
28
32
  // Toast system
29
33
  export * from "./components/ErrorToast";