@lastbrain/ai-ui-react 1.0.62 → 1.0.64

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.
@@ -10,6 +10,7 @@ import {
10
10
  useContext,
11
11
  useEffect,
12
12
  useCallback,
13
+ useMemo,
13
14
  useState,
14
15
  type ReactNode,
15
16
  } from "react";
@@ -21,6 +22,7 @@ import type {
21
22
  LBSessionResult,
22
23
  AiStatus,
23
24
  } from "@lastbrain/ai-ui-core";
25
+ import { createLBClient } from "@lastbrain/ai-ui-core";
24
26
 
25
27
  interface LBProviderProps {
26
28
  children: ReactNode;
@@ -100,69 +102,81 @@ export function LBProvider({
100
102
  const [isLoadingStorage, setIsLoadingStorage] = useState(false);
101
103
  const [storageLastFetch, setStorageLastFetch] = useState<number>(0);
102
104
 
105
+ const lbClient = useMemo(
106
+ () =>
107
+ createLBClient({
108
+ baseUrl: proxyUrl,
109
+ mode: process.env.LB_API_KEY ? "env-key" : "auto",
110
+ selectedApiKeyId: state.selectedKey?.id,
111
+ }),
112
+ [proxyUrl, state.selectedKey?.id]
113
+ );
114
+
103
115
  /**
104
116
  * Vérifie si une session existe au chargement
105
117
  */
106
118
  const checkSession = useCallback(async () => {
107
119
  try {
108
- const response = await fetch(`${proxyUrl}/auth/session/verify`, {
109
- credentials: "include",
110
- });
111
-
112
- if (response.ok) {
113
- const session: LBSession = await response.json();
114
-
115
- // Récupérer les infos utilisateur depuis /auth/status
116
- try {
117
- const statusResponse = await fetch(`${proxyUrl}/auth/status`, {
118
- credentials: "include",
119
- });
120
-
121
- if (statusResponse.ok) {
122
- const statusData = await statusResponse.json();
123
- setState({
124
- status: "ready",
125
- session,
126
- user: {
127
- id: session.userId,
128
- email: statusData.user?.email || "",
129
- },
130
- });
131
- } else {
132
- // Fallback sans email
133
- setState({
134
- status: "ready",
135
- session,
136
- user: {
137
- id: session.userId,
138
- email: "",
139
- },
140
- });
141
- }
142
- } catch (statusError) {
143
- console.error("[LBProvider] Failed to fetch status:", statusError);
144
- // Fallback sans email
120
+ const session = await lbClient.verifySession();
121
+ if (session) {
122
+ const userData = await lbClient.getUser().catch(() => null);
123
+ const activeKey = userData?.apiKeyActive
124
+ ? {
125
+ id: userData.apiKeyActive.id,
126
+ name: userData.apiKeyActive.name,
127
+ keyPrefix: userData.apiKeyActive.prefix,
128
+ scopes: userData.apiKeyActive.scopes || [],
129
+ isActive: true,
130
+ createdAt: "",
131
+ }
132
+ : undefined;
133
+ setApiKeys(userData?.apiKeys || []);
134
+ setState({
135
+ status: "ready",
136
+ session,
137
+ user: {
138
+ id: session.userId,
139
+ email: userData?.user?.email || "",
140
+ },
141
+ selectedKey: activeKey,
142
+ });
143
+ onStatusChange?.("ready");
144
+ } else {
145
+ // Supabase session mode (no lb_session cookie): try user endpoint directly
146
+ const userData = await lbClient.getUser().catch(() => null);
147
+ if (userData?.user?.id) {
148
+ const activeKey = userData?.apiKeyActive
149
+ ? {
150
+ id: userData.apiKeyActive.id,
151
+ name: userData.apiKeyActive.name,
152
+ keyPrefix: userData.apiKeyActive.prefix,
153
+ scopes: userData.apiKeyActive.scopes || [],
154
+ isActive: true,
155
+ createdAt: "",
156
+ }
157
+ : undefined;
158
+
159
+ setApiKeys(userData.apiKeys || []);
145
160
  setState({
146
161
  status: "ready",
147
- session,
148
162
  user: {
149
- id: session.userId,
150
- email: "",
163
+ id: userData.user.id,
164
+ email: userData.user.email || "",
151
165
  },
166
+ selectedKey: activeKey,
152
167
  });
168
+ onStatusChange?.("ready");
169
+ } else {
170
+ setState({ status: "needs_auth" });
171
+ onStatusChange?.("needs_auth");
153
172
  }
154
-
155
- onStatusChange?.("ready");
156
- } else {
157
- setState({ status: "needs_auth" });
158
- onStatusChange?.("needs_auth");
159
173
  }
160
174
  } catch (error) {
161
175
  console.error("[LBProvider] Session check failed:", error);
162
176
  setState({ status: "needs_auth" });
163
177
  onStatusChange?.("needs_auth");
164
178
  }
165
- }, [proxyUrl, onStatusChange]);
179
+ }, [lbClient, onStatusChange]);
166
180
 
167
181
  useEffect(() => {
168
182
  checkSession();
@@ -174,31 +188,8 @@ export function LBProvider({
174
188
  const fetchApiKeys = useCallback(
175
189
  async (token: string): Promise<LBApiKey[]> => {
176
190
  try {
177
- console.log(
178
- "[LBProvider] Fetching API keys with token:",
179
- token.substring(0, 20) + "..."
180
- );
181
-
182
- const response = await fetch(`${proxyUrl}/public/user/api-keys`, {
183
- headers: {
184
- Authorization: `Bearer ${token}`,
185
- "Content-Type": "application/json",
186
- },
187
- credentials: "include",
188
- });
189
-
190
- console.log("[LBProvider] API keys response status:", response.status);
191
-
192
- if (!response.ok) {
193
- const errorData = await response.json().catch(() => ({}));
194
- console.error("[LBProvider] Failed to fetch API keys:", errorData);
195
- throw new Error(errorData.message || "Failed to fetch API keys");
196
- }
197
-
198
- const data = await response.json();
199
- console.log("[LBProvider] API keys received:", data);
200
-
201
- const keys: LBApiKey[] = data.apiKeys || data;
191
+ const data = await lbClient.getUser(token);
192
+ const keys: LBApiKey[] = data.apiKeys || [];
202
193
  setApiKeys(keys);
203
194
  return keys;
204
195
  } catch (error) {
@@ -206,7 +197,7 @@ export function LBProvider({
206
197
  throw error;
207
198
  }
208
199
  },
209
- [proxyUrl]
200
+ [lbClient]
210
201
  );
211
202
 
212
203
  /**
@@ -217,26 +208,10 @@ export function LBProvider({
217
208
  try {
218
209
  console.log("[LBProvider] Selecting API key:", apiKeyId);
219
210
  setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
220
-
221
- const response = await fetch(`${proxyUrl}/auth/session`, {
222
- method: "POST",
223
- headers: {
224
- "Content-Type": "application/json",
225
- Authorization: `Bearer ${token}`,
226
- },
227
- body: JSON.stringify({ api_key_id: apiKeyId }),
228
- credentials: "include",
229
- });
230
-
231
- console.log("[LBProvider] Session response status:", response.status);
232
-
233
- if (!response.ok) {
234
- const errorData = await response.json().catch(() => ({}));
235
- console.error("[LBProvider] Failed to create session:", errorData);
236
- throw new Error(errorData.message || "Failed to create session");
237
- }
238
-
239
- const sessionResult: LBSessionResult = await response.json();
211
+ const sessionResult: LBSessionResult = await lbClient.selectApiKey(
212
+ apiKeyId,
213
+ token
214
+ );
240
215
  console.log(
241
216
  "[LBProvider] Session created successfully:",
242
217
  sessionResult
@@ -268,7 +243,7 @@ export function LBProvider({
268
243
  throw error;
269
244
  }
270
245
  },
271
- [proxyUrl, state.user, onStatusChange, onAuthChange]
246
+ [lbClient, state.user, onStatusChange, onAuthChange]
272
247
  );
273
248
 
274
249
  /**
@@ -289,27 +264,7 @@ export function LBProvider({
289
264
  console.log("[LBProvider] Login attempt:", email);
290
265
  setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
291
266
 
292
- const response = await fetch(`${proxyUrl}/auth/login`, {
293
- method: "POST",
294
- headers: { "Content-Type": "application/json" },
295
- body: JSON.stringify({ email, password }),
296
- credentials: "include",
297
- });
298
-
299
- console.log("[LBProvider] Login response status:", response.status);
300
-
301
- if (!response.ok) {
302
- const error = await response.json();
303
- const errorMessage = error.message || "Login failed";
304
- console.error("[LBProvider] Login failed:", errorMessage);
305
- setState({
306
- status: "needs_auth",
307
- error: errorMessage,
308
- });
309
- return { success: false, error: errorMessage };
310
- }
311
-
312
- const result: LBLoginResult = await response.json();
267
+ const result: LBLoginResult = await lbClient.login(email, password);
313
268
  console.log("[LBProvider] Login successful:", result.user?.email);
314
269
  console.log(
315
270
  "[LBProvider] Access token received:",
@@ -403,7 +358,7 @@ export function LBProvider({
403
358
  return { success: false, error: message };
404
359
  }
405
360
  },
406
- [proxyUrl, fetchApiKeys, selectApiKey]
361
+ [lbClient, fetchApiKeys, selectApiKey]
407
362
  );
408
363
 
409
364
  /**
@@ -418,30 +373,50 @@ export function LBProvider({
418
373
 
419
374
  setIsLoadingStatus(true);
420
375
  try {
421
- const response = await fetch(`${proxyUrl}/auth/status?fast=true`, {
422
- credentials: "include",
423
- });
424
-
425
- if (response.ok) {
426
- const data = await response.json();
427
- setBasicStatus(data);
428
-
429
- // Combiner avec le storage existant si disponible
430
- const combinedStatus = {
431
- ...data,
432
- storage: storageStatus?.storage || data.storage,
376
+ let data: any;
377
+ try {
378
+ data = await lbClient.getStatus();
379
+ } catch {
380
+ // Backward compatibility: older backends may not expose /auth/status
381
+ const userData = await lbClient.getUser();
382
+ data = {
383
+ authType: userData.authType,
384
+ user: userData.user,
385
+ apiKey: userData.apiKeyActive,
386
+ api_key: userData.apiKeyActive,
387
+ balance: {
388
+ used: 0,
389
+ total: userData.balance?.sellValueUsd || 0,
390
+ percentage: 0,
391
+ },
433
392
  };
434
- setApiStatus(combinedStatus);
435
- } else {
436
- setBasicStatus(null);
437
393
  }
394
+ const normalizedStatus = {
395
+ ...data,
396
+ authType: data?.authType,
397
+ user: data?.user,
398
+ apiKey: data?.apiKey || data?.api_key,
399
+ api_key: data?.api_key || data?.apiKey,
400
+ balance: data?.balance || {
401
+ used: 0,
402
+ total: 0,
403
+ percentage: 0,
404
+ },
405
+ };
406
+ setBasicStatus(normalizedStatus);
407
+
408
+ const combinedStatus = {
409
+ ...normalizedStatus,
410
+ storage: storageStatus?.storage,
411
+ };
412
+ setApiStatus(combinedStatus as any);
438
413
  } catch (error) {
439
414
  console.error("[LBProvider] Failed to fetch basic status:", error);
440
415
  setBasicStatus(null);
441
416
  } finally {
442
417
  setIsLoadingStatus(false);
443
418
  }
444
- }, [proxyUrl, state.status, storageStatus]);
419
+ }, [lbClient, state.status, storageStatus]);
445
420
 
446
421
  /**
447
422
  * Récupère le storage - LENT avec cache (5 minutes)
@@ -467,42 +442,17 @@ export function LBProvider({
467
442
 
468
443
  setIsLoadingStorage(true);
469
444
  try {
470
- // Essayer l'endpoint spécialisé storage d'abord
471
- let response = await fetch(`${proxyUrl}/auth/status/storage`, {
472
- credentials: "include",
473
- });
445
+ const data = await lbClient.getStorageStatus();
446
+ const storageData = data?.storage ? { storage: data.storage } : data;
447
+ setStorageStatus(storageData);
448
+ setStorageLastFetch(now);
474
449
 
475
- // Si 404, faire un fallback vers l'endpoint normal (backward compatibility)
476
- if (!response.ok && response.status === 404) {
477
- console.log("[LBProvider] Storage endpoint not available, using fallback");
478
- response = await fetch(`${proxyUrl}/auth/status`, {
479
- credentials: "include",
480
- });
481
- }
482
-
483
- if (response.ok) {
484
- const data = await response.json();
485
-
486
- // Si c'est la réponse complète (fallback), extraire juste le storage
487
- const storageData = data.storage ? { storage: data.storage } : data;
488
-
489
- setStorageStatus(storageData);
490
- setStorageLastFetch(now);
491
-
492
- // Combiner avec le basic status
493
- const combinedStatus = {
494
- ...basicStatus,
495
- storage: storageData.storage,
496
- };
497
- setApiStatus(combinedStatus);
498
- } else {
499
- console.warn(
500
- "[LBProvider] Failed to fetch storage status:",
501
- response.status
502
- );
503
- // Arrêter les tentatives répétées si échec persistant
504
- setStorageLastFetch(now); // Marquer comme essayé pour éviter la boucle
505
- }
450
+ // Combiner avec le basic status
451
+ const combinedStatus = {
452
+ ...basicStatus,
453
+ storage: storageData?.storage,
454
+ };
455
+ setApiStatus(combinedStatus);
506
456
  } catch (error) {
507
457
  console.error("[LBProvider] Failed to fetch storage status:", error);
508
458
  // Arrêter les tentatives répétées si erreur persistante
@@ -511,7 +461,7 @@ export function LBProvider({
511
461
  setIsLoadingStorage(false);
512
462
  }
513
463
  },
514
- [proxyUrl, state.status, basicStatus, storageLastFetch]
464
+ [lbClient, state.status, basicStatus, storageLastFetch]
515
465
  );
516
466
 
517
467
  /**
@@ -532,39 +482,26 @@ export function LBProvider({
532
482
  */
533
483
  const switchApiKey = useCallback(
534
484
  async (apiKeyId: string): Promise<void> => {
535
- if (state.status === "ready") {
536
- // Utiliser la route de switch avec session
537
- const response = await fetch(
538
- `${proxyUrl}/auth/session/switch-api-key`,
539
- {
540
- method: "POST",
541
- credentials: "include",
542
- headers: { "Content-Type": "application/json" },
543
- body: JSON.stringify({ api_key_id: apiKeyId }),
544
- }
545
- );
546
-
547
- if (!response.ok) {
548
- const errorData = await response.json().catch(() => ({}));
549
- throw new Error(errorData.error || "Failed to switch API key");
485
+ if (state.status === "ready" || accessToken) {
486
+ await lbClient.selectApiKey(apiKeyId, accessToken);
487
+ const selectedKey = apiKeys.find((key) => key.id === apiKeyId);
488
+ if (selectedKey) {
489
+ setState((prev) => ({
490
+ ...prev,
491
+ selectedKey,
492
+ }));
550
493
  }
551
-
552
- // Refresh le status après le changement
553
494
  await refreshBasicStatus();
554
- // Refresh storage en arrière-plan
555
495
  setTimeout(() => refreshStorageStatus(), 100);
556
- } else if (accessToken) {
557
- // Utiliser la méthode avec access token
558
- await selectApiKey(accessToken, apiKeyId);
559
496
  } else {
560
497
  throw new Error("No valid authentication method available");
561
498
  }
562
499
  },
563
500
  [
564
501
  state.status,
565
- proxyUrl,
566
502
  accessToken,
567
- selectApiKey,
503
+ apiKeys,
504
+ lbClient,
568
505
  refreshBasicStatus,
569
506
  refreshStorageStatus,
570
507
  ]
@@ -575,20 +512,22 @@ export function LBProvider({
575
512
  */
576
513
  const logout = useCallback(async (): Promise<void> => {
577
514
  try {
578
- await fetch(`${proxyUrl}/auth/session/logout`, {
579
- method: "POST",
580
- credentials: "include",
581
- });
515
+ await lbClient.logout();
582
516
  } catch (error) {
583
517
  console.error("[LBProvider] Logout failed:", error);
584
518
  } finally {
585
519
  setState({ status: "needs_auth" });
586
520
  setApiKeys([]);
587
521
  setAccessToken(undefined);
522
+ // Reset tous les statuts après logout
523
+ setApiStatus(null);
524
+ setBasicStatus(null);
525
+ setStorageStatus(null);
526
+ setStorageLastFetch(0);
588
527
  onStatusChange?.("needs_auth");
589
528
  onAuthChange?.(); // Refresh provider after logout
590
529
  }
591
- }, [proxyUrl, onStatusChange, onAuthChange]);
530
+ }, [lbClient, onStatusChange, onAuthChange]);
592
531
 
593
532
  /**
594
533
  * Recharge la session
@@ -607,24 +546,13 @@ export function LBProvider({
607
546
  }
608
547
 
609
548
  try {
610
- console.log("[LBProvider] Fetching API keys with session...");
611
- const response = await fetch(`${proxyUrl}/auth/api-keys`, {
612
- credentials: "include",
613
- });
614
-
615
- if (response.ok) {
616
- const data = await response.json();
617
- console.log("[LBProvider] API keys received:", data);
618
- setApiKeys(data.apiKeys || []);
619
- } else {
620
- console.warn("[LBProvider] Failed to fetch API keys:", response.status);
621
- setApiKeys([]);
622
- }
549
+ const data = await lbClient.getUser();
550
+ setApiKeys(data.apiKeys || []);
623
551
  } catch (error) {
624
552
  console.error("[LBProvider] Failed to fetch API keys:", error);
625
553
  setApiKeys([]);
626
554
  }
627
- }, [proxyUrl, state.status]);
555
+ }, [lbClient, state.status]);
628
556
 
629
557
  // Refresh status quand la session devient ready
630
558
  useEffect(() => {
@@ -77,12 +77,12 @@ export async function getAvailableModels(
77
77
  const isPublicApi = baseUrl && baseUrl.includes("/api/public/v1");
78
78
 
79
79
  const endpoint = isExternalProxy
80
- ? `${baseUrl}/ai/models/available` // Proxy routes to public API
80
+ ? `${baseUrl}/auth/models` // Proxy routes to /api/ai/auth/models
81
81
  : isPublicApi
82
- ? `${baseUrl}/ai/models/available` // → /api/public/v1/ai/models/available
82
+ ? `${baseUrl}/gateway-models` // → /api/public/v1/gateway-models
83
83
  : baseUrl
84
- ? `${baseUrl}/auth/ai-models-available` // → /api/ai/auth/ai-models-available
85
- : `/api/ai/auth/ai-models-available`; // fallback
84
+ ? `${baseUrl}/auth/models` // → /api/ai/auth/models
85
+ : `/api/ai/auth/models`; // fallback
86
86
 
87
87
  console.log(
88
88
  "[getAvailableModels] isExternalProxy:",
@@ -113,7 +113,16 @@ export async function getAvailableModels(
113
113
  }
114
114
 
115
115
  const data = await response.json();
116
- return data.models || [];
116
+
117
+ if (Array.isArray(data?.models)) {
118
+ return data.models;
119
+ }
120
+ if (Array.isArray(data?.providers)) {
121
+ return data.providers.flatMap((provider: any) =>
122
+ Array.isArray(provider.models) ? provider.models : []
123
+ );
124
+ }
125
+ return [];
117
126
  }
118
127
 
119
128
  /**