@lastbrain/ai-ui-react 1.0.52 → 1.0.55

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.
@@ -1 +1 @@
1
- {"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAoBtD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,GACf,EAAE,mBAAmB,2CA8iCrB"}
1
+ {"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAmBtD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,GACf,EAAE,mBAAmB,2CA0iCrB"}
@@ -2,7 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useRef, useLayoutEffect } from "react";
4
4
  import { createPortal } from "react-dom";
5
- import { BarChart3, Settings, FileText, History as HistoryIcon, FolderPlus, Power, LogOut, Key, ArrowRightLeft, } from "lucide-react";
5
+ import { BarChart3, Settings, FileText, History as HistoryIcon, FolderPlus, Power, LogOut, ArrowRightLeft, } from "lucide-react";
6
6
  import { aiStyles, calculateTooltipPosition } from "../styles/inline";
7
7
  import { useLB } from "../context/LBAuthProvider";
8
8
  import { useAiContext } from "../context/AiProvider";
@@ -16,7 +16,9 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
16
16
  let apiKeys = [];
17
17
  let accessToken;
18
18
  let selectApiKeyWithToken;
19
+ let switchApiKey;
19
20
  let lbApiStatus = null;
21
+ let lbIsLoadingStatus = false;
20
22
  try {
21
23
  const lbContext = useLB();
22
24
  lbStatus = lbContext.status;
@@ -25,7 +27,9 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
25
27
  apiKeys = lbContext.apiKeys || [];
26
28
  accessToken = lbContext.accessToken;
27
29
  selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
30
+ switchApiKey = lbContext.switchApiKey;
28
31
  lbApiStatus = lbContext.apiStatus;
32
+ lbIsLoadingStatus = lbContext.isLoadingStatus || false;
29
33
  }
30
34
  catch {
31
35
  // LBProvider n'est pas disponible, ignorer
@@ -47,6 +51,8 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
47
51
  }
48
52
  const [showSigninModal, setShowSigninModal] = useState(false);
49
53
  const [showApiKeySelector, setShowApiKeySelector] = useState(false);
54
+ const [isSelectingApiKey, setIsSelectingApiKey] = useState(false);
55
+ const [isLoadingStatus, setIsLoadingStatus] = useState(false);
50
56
  const formatNumber = (value) => typeof value === "number" ? value.toLocaleString() : "0";
51
57
  const formatFixed = (value, digits) => typeof value === "number" ? value.toFixed(digits) : "0.00";
52
58
  const formatStorage = (valueMb) => {
@@ -174,7 +180,7 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
174
180
  }
175
181
  }, 100);
176
182
  };
177
- if (loading) {
183
+ if (loading || isSelectingApiKey || isLoadingStatus || lbIsLoadingStatus) {
178
184
  return (_jsx("button", { ref: buttonRef, style: {
179
185
  ...aiStyles.statusButton,
180
186
  ...aiStyles.statusButtonDisabled,
@@ -379,9 +385,15 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
379
385
  display: "flex",
380
386
  alignItems: "center",
381
387
  gap: "8px",
382
- }, children: [_jsx("span", { style: aiStyles.tooltipValue, children: effectiveStatus.apiKey?.name ||
383
- effectiveStatus.api_key?.name ||
384
- "Unknown" }), selectApiKeyWithToken && (_jsx("button", { onClick: (e) => {
388
+ }, children: [_jsx("span", { style: aiStyles.tooltipValue, children: lbIsLoadingStatus ? (_jsx("div", { style: {
389
+ height: "16px",
390
+ width: "100px",
391
+ background: "rgba(139, 92, 246, 0.1)",
392
+ borderRadius: "4px",
393
+ animation: "pulse 2s ease-in-out infinite",
394
+ } })) : (effectiveStatus?.apiKey?.name ||
395
+ effectiveStatus?.api_key?.name ||
396
+ "Unknown") }), switchApiKey && (_jsx("button", { onClick: (e) => {
385
397
  e.stopPropagation();
386
398
  setShowTooltip(false);
387
399
  setShowApiKeySelector(true);
@@ -509,26 +521,7 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
509
521
  Object.assign(e.currentTarget.style, {
510
522
  background: "transparent",
511
523
  });
512
- }, title: "New Folder", children: _jsx(FolderPlus, { size: 18 }) }), selectApiKeyWithToken && apiKeys.length > 1 && (_jsx("button", { onClick: () => setShowApiKeySelector(true), style: {
513
- background: "transparent",
514
- border: "none",
515
- padding: "14px",
516
- cursor: "pointer",
517
- display: "flex",
518
- alignItems: "center",
519
- justifyContent: "center",
520
- color: "#8b5cf6",
521
- transition: "all 0.2s ease",
522
- borderRadius: "4px",
523
- }, onMouseEnter: (e) => {
524
- Object.assign(e.currentTarget.style, {
525
- background: "rgba(139, 92, 246, 0.1)",
526
- });
527
- }, onMouseLeave: (e) => {
528
- Object.assign(e.currentTarget.style, {
529
- background: "transparent",
530
- });
531
- }, title: "Change API Key", children: _jsx(Key, { size: 18 }) })), logout && (_jsx("button", { onClick: async () => {
524
+ }, title: "New Folder", children: _jsx(FolderPlus, { size: 18 }) }), logout && (_jsx("button", { onClick: async () => {
532
525
  try {
533
526
  await logout();
534
527
  setShowTooltip(false);
@@ -559,11 +552,19 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
559
552
  Object.assign(e.currentTarget.style, {
560
553
  background: "transparent",
561
554
  });
562
- }, title: "Logout", children: _jsx(LogOut, { size: 18 }) }))] })] }), document.body), showApiKeySelector && selectApiKeyWithToken && (_jsx(LBApiKeySelector, { isOpen: showApiKeySelector, apiKeys: apiKeys, onSelect: async (keyId) => {
555
+ }, title: "Logout", children: _jsx(LogOut, { size: 18 }) }))] })] }), document.body), showApiKeySelector && apiKeys.length > 0 && (_jsx(LBApiKeySelector, { isOpen: showApiKeySelector, apiKeys: apiKeys, onSelect: async (keyId) => {
556
+ setIsSelectingApiKey(true);
563
557
  try {
564
- await selectApiKeyWithToken(keyId);
558
+ // Utiliser la nouvelle fonction switchApiKey qui gère automatiquement le contexte
559
+ if (switchApiKey) {
560
+ await switchApiKey(keyId);
561
+ }
562
+ else {
563
+ throw new Error("Switch API key function not available");
564
+ }
565
565
  setShowApiKeySelector(false);
566
566
  setShowTooltip(false);
567
+ setIsLoadingStatus(true);
567
568
  // Refresh provider data after API key selection
568
569
  if (refetchProviders) {
569
570
  await refetchProviders();
@@ -572,5 +573,9 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
572
573
  catch (error) {
573
574
  console.error("Failed to select API key:", error);
574
575
  }
576
+ finally {
577
+ setIsSelectingApiKey(false);
578
+ setIsLoadingStatus(false);
579
+ }
575
580
  }, onCancel: () => setShowApiKeySelector(false) }))] }));
576
581
  }
@@ -1 +1 @@
1
- {"version":3,"file":"LBSigninModal.d.ts","sourceRoot":"","sources":["../../src/components/LBSigninModal.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,kDAojBpE"}
1
+ {"version":3,"file":"LBSigninModal.d.ts","sourceRoot":"","sources":["../../src/components/LBSigninModal.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,kBAAkB,kDA6jBpE"}
@@ -1,19 +1,29 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useState, useEffect } from "react";
3
+ import { useState } from "react";
4
4
  import { useLB } from "../context/LBAuthProvider";
5
5
  import { LBApiKeySelector } from "./LBApiKeySelector";
6
6
  import { Mail, Lock, Sparkles, X, Loader2, AlertCircle } from "lucide-react";
7
7
  export function LBSigninModal({ isOpen, onClose }) {
8
+ // React Hooks doivent être appelés avant toute condition ou return
9
+ const [email, setEmail] = useState("");
10
+ const [password, setPassword] = useState("");
11
+ const [loading, setLoading] = useState(false);
12
+ const [error, setError] = useState("");
13
+ const [showKeySelector, setShowKeySelector] = useState(false);
14
+ const [currentApiKeys, setCurrentApiKeys] = useState([]); // Stocker les clés API localement
8
15
  // Vérifier si LBProvider est disponible
9
16
  let login;
10
17
  let selectApiKeyWithToken;
18
+ let fetchApiKeys;
11
19
  let apiKeys = [];
12
20
  let lbStatus;
21
+ let lbContext;
13
22
  try {
14
- const lbContext = useLB();
23
+ lbContext = useLB();
15
24
  login = lbContext.login;
16
25
  selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
26
+ fetchApiKeys = lbContext.fetchApiKeys;
17
27
  apiKeys = lbContext.apiKeys || [];
18
28
  lbStatus = lbContext.status;
19
29
  }
@@ -21,17 +31,6 @@ export function LBSigninModal({ isOpen, onClose }) {
21
31
  // LBProvider n'est pas disponible, ne pas rendre le modal
22
32
  return null;
23
33
  }
24
- const [email, setEmail] = useState("");
25
- const [password, setPassword] = useState("");
26
- const [loading, setLoading] = useState(false);
27
- const [error, setError] = useState("");
28
- const [showKeySelector, setShowKeySelector] = useState(false);
29
- // Si le status est "needs_key_selection", afficher le sélecteur
30
- useEffect(() => {
31
- if (lbStatus === "needs_key_selection" && apiKeys.length > 0) {
32
- setShowKeySelector(true);
33
- }
34
- }, [lbStatus, apiKeys.length]);
35
34
  if (!isOpen || !login)
36
35
  return null;
37
36
  const handleSubmit = async (e) => {
@@ -43,7 +42,21 @@ export function LBSigninModal({ isOpen, onClose }) {
43
42
  if (result.success) {
44
43
  if (result.needsKeySelection) {
45
44
  // L'utilisateur doit choisir une clé API
46
- setShowKeySelector(true);
45
+ // Récupérer les clés API avec l'access token temporaire
46
+ if (fetchApiKeys && lbContext.accessToken) {
47
+ try {
48
+ const keys = await fetchApiKeys(lbContext.accessToken);
49
+ setCurrentApiKeys(keys);
50
+ setShowKeySelector(true);
51
+ }
52
+ catch (keyError) {
53
+ setError("Impossible de récupérer les clés API");
54
+ console.error("Failed to fetch API keys:", keyError);
55
+ }
56
+ }
57
+ else {
58
+ setError("Accès aux clés API indisponible");
59
+ }
47
60
  }
48
61
  else {
49
62
  // Connexion réussie, fermer le modal
@@ -76,13 +89,12 @@ export function LBSigninModal({ isOpen, onClose }) {
76
89
  };
77
90
  const handleCancelKeySelection = () => {
78
91
  setShowKeySelector(false);
79
- setEmail("");
80
- setPassword("");
81
- setError("");
92
+ // Fermer complètement le modal au lieu de rester dans l'état de sélection
93
+ onClose();
82
94
  };
83
95
  // Si on doit afficher le sélecteur de clés
84
- if (showKeySelector && apiKeys.length > 0) {
85
- return (_jsx(LBApiKeySelector, { apiKeys: apiKeys, onSelect: handleKeySelect, onCancel: handleCancelKeySelection, isOpen: true }));
96
+ if (showKeySelector && currentApiKeys.length > 0) {
97
+ return (_jsx(LBApiKeySelector, { apiKeys: currentApiKeys, onSelect: handleKeySelect, onCancel: handleCancelKeySelection, isOpen: true }));
86
98
  }
87
99
  const handleKeyDown = (e) => {
88
100
  if (e.key === "Escape") {
@@ -307,7 +319,7 @@ export function LBSigninModal({ isOpen, onClose }) {
307
319
  fontSize: "14px",
308
320
  color: "light-dark(#64748b, #94a3b8)",
309
321
  fontWeight: 500,
310
- }, children: "Pas encore de compte ?" }), _jsxs("a", { href: "https://lastbrain.io/signup", target: "_blank", rel: "noopener noreferrer", style: {
322
+ }, children: "Pas encore de compte ?" }), _jsxs("a", { href: "https://prompt.lastbrain.io/signup", target: "_blank", rel: "noopener noreferrer", style: {
311
323
  display: "inline-flex",
312
324
  alignItems: "center",
313
325
  gap: "8px",
@@ -30,6 +30,8 @@ interface LBContextValue extends LBAuthState {
30
30
  selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
31
31
  /** Sélectionne une clé API avec le token stocké */
32
32
  selectApiKeyWithToken: (apiKeyId: string) => Promise<void>;
33
+ /** Change l'API key active (fonctionne avec session ou token) */
34
+ switchApiKey: (apiKeyId: string) => Promise<void>;
33
35
  /** Recharge l'état de la session */
34
36
  refreshSession: () => Promise<void>;
35
37
  /** Clés API disponibles */
@@ -40,6 +42,8 @@ interface LBContextValue extends LBAuthState {
40
42
  apiStatus: AiStatus | null;
41
43
  /** Fonction pour rafraîchir le status */
42
44
  refreshStatus: () => Promise<void>;
45
+ /** Indique si le status est en cours de chargement */
46
+ isLoadingStatus: boolean;
43
47
  }
44
48
  export declare function LBProvider({ children, baseUrl: _baseUrl, proxyUrl, onStatusChange, onAuthChange, }: LBProviderProps): import("react/jsx-runtime").JSX.Element;
45
49
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAIR,QAAQ,EACT,MAAM,uBAAuB,CAAC;AAE/B,UAAU,eAAe;IACvB,QAAQ,EAAE,SAAS,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACzD,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,UAAU,cAAe,SAAQ,WAAW;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,CACL,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC,CAAC;IACH,8BAA8B;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,6CAA6C;IAC7C,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3D,gEAAgE;IAChE,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,mDAAmD;IACnD,qBAAqB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,oCAAoC;IACpC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,2BAA2B;IAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,yCAAyC;IACzC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACpC;AAID,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,EAAE,QAA2B,EACpC,QAA2B,EAC3B,cAAc,EACd,YAAY,GACb,EAAE,eAAe,2CAkbjB;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,cAAc,CAMtC"}
1
+ {"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAIR,QAAQ,EACT,MAAM,uBAAuB,CAAC;AAE/B,UAAU,eAAe;IACvB,QAAQ,EAAE,SAAS,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACzD,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,UAAU,cAAe,SAAQ,WAAW;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,CACL,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC,CAAC;IACH,8BAA8B;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,6CAA6C;IAC7C,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3D,gEAAgE;IAChE,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,mDAAmD;IACnD,qBAAqB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,iEAAiE;IACjE,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,oCAAoC;IACpC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,2BAA2B;IAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC3B,yCAAyC;IACzC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,sDAAsD;IACtD,eAAe,EAAE,OAAO,CAAC;CAC1B;AAID,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,EAAE,QAA2B,EACpC,QAA2B,EAC3B,cAAc,EACd,YAAY,GACb,EAAE,eAAe,2CA2djB;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,cAAc,CAMtC"}
@@ -13,6 +13,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
13
13
  const [apiKeys, setApiKeys] = useState([]);
14
14
  const [accessToken, setAccessToken] = useState();
15
15
  const [apiStatus, setApiStatus] = useState(null);
16
+ const [isLoadingStatus, setIsLoadingStatus] = useState(false);
16
17
  /**
17
18
  * Vérifie si une session existe au chargement
18
19
  */
@@ -255,6 +256,36 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
255
256
  return { success: false, error: message };
256
257
  }
257
258
  }, [proxyUrl, fetchApiKeys, selectApiKey]);
259
+ /**
260
+ * Récupère le status API (balance, storage, API key info)
261
+ */
262
+ const refreshStatus = useCallback(async () => {
263
+ if (state.status !== "ready") {
264
+ setApiStatus(null);
265
+ setIsLoadingStatus(false);
266
+ return;
267
+ }
268
+ setIsLoadingStatus(true);
269
+ try {
270
+ const response = await fetch(`${proxyUrl}/auth/status`, {
271
+ credentials: "include",
272
+ });
273
+ if (response.ok) {
274
+ const data = await response.json();
275
+ setApiStatus(data);
276
+ }
277
+ else {
278
+ setApiStatus(null);
279
+ }
280
+ }
281
+ catch (error) {
282
+ console.error("[LBProvider] Failed to fetch status:", error);
283
+ setApiStatus(null);
284
+ }
285
+ finally {
286
+ setIsLoadingStatus(false);
287
+ }
288
+ }, [proxyUrl, state.status]);
258
289
  /**
259
290
  * Sélectionne une clé API avec le token déjà stocké (après login)
260
291
  */
@@ -264,6 +295,33 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
264
295
  }
265
296
  await selectApiKey(accessToken, apiKeyId);
266
297
  }, [accessToken, selectApiKey]);
298
+ /**
299
+ * Change l'API key active - utilise la bonne méthode selon le contexte
300
+ */
301
+ const switchApiKey = useCallback(async (apiKeyId) => {
302
+ if (state.status === "ready") {
303
+ // Utiliser la route de switch avec session
304
+ const response = await fetch(`${proxyUrl}/auth/session/switch-api-key`, {
305
+ method: "POST",
306
+ credentials: "include",
307
+ headers: { "Content-Type": "application/json" },
308
+ body: JSON.stringify({ api_key_id: apiKeyId }),
309
+ });
310
+ if (!response.ok) {
311
+ const errorData = await response.json().catch(() => ({}));
312
+ throw new Error(errorData.error || "Failed to switch API key");
313
+ }
314
+ // Refresh le status après le changement
315
+ await refreshStatus();
316
+ }
317
+ else if (accessToken) {
318
+ // Utiliser la méthode avec access token
319
+ await selectApiKey(accessToken, apiKeyId);
320
+ }
321
+ else {
322
+ throw new Error("No valid authentication method available");
323
+ }
324
+ }, [state.status, proxyUrl, accessToken, selectApiKey, refreshStatus]);
267
325
  /**
268
326
  * Déconnexion
269
327
  */
@@ -291,31 +349,6 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
291
349
  const refreshSession = useCallback(async () => {
292
350
  await checkSession();
293
351
  }, [checkSession]);
294
- /**
295
- * Récupère le status API (balance, storage, API key info)
296
- */
297
- const refreshStatus = useCallback(async () => {
298
- if (state.status !== "ready") {
299
- setApiStatus(null);
300
- return;
301
- }
302
- try {
303
- const response = await fetch(`${proxyUrl}/auth/status`, {
304
- credentials: "include",
305
- });
306
- if (response.ok) {
307
- const data = await response.json();
308
- setApiStatus(data);
309
- }
310
- else {
311
- setApiStatus(null);
312
- }
313
- }
314
- catch (error) {
315
- console.error("[LBProvider] Failed to fetch status:", error);
316
- setApiStatus(null);
317
- }
318
- }, [proxyUrl, state.status]);
319
352
  /**
320
353
  * Récupère les clés API avec la session active
321
354
  */
@@ -362,11 +395,13 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
362
395
  fetchApiKeys,
363
396
  selectApiKey,
364
397
  selectApiKeyWithToken,
398
+ switchApiKey,
365
399
  refreshSession,
366
400
  apiKeys,
367
401
  accessToken,
368
402
  apiStatus,
369
403
  refreshStatus,
404
+ isLoadingStatus,
370
405
  };
371
406
  return _jsx(LBContext.Provider, { value: value, children: children });
372
407
  }
@@ -1 +1 @@
1
- {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/styles/inline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA6HH,eAAO,MAAM,QAAQ;WAgBd,KAAK,CAAC,aAAa;gBAKnB,KAAK,CAAC,aAAa;gBAInB,KAAK,CAAC,aAAa;;;;;mBAyBnB,KAAK,CAAC,aAAa;wBAMnB,KAAK,CAAC,aAAa;cAmBnB,KAAK,CAAC,aAAa;mBAKnB,KAAK,CAAC,aAAa;;;;;sBAwBnB,KAAK,CAAC,aAAa;2BAMnB,KAAK,CAAC,aAAa;YAoBnB,KAAK,CAAC,aAAa;iBAMnB,KAAK,CAAC,aAAa;oBAMnB,KAAK,CAAC,aAAa;YA2BnB,KAAK,CAAC,aAAa;iBAKnB,KAAK,CAAC,aAAa;kBAgBnB,KAAK,CAAC,aAAa;uBAMnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAgBnB,KAAK,CAAC,aAAa;mBASnB,KAAK,CAAC,aAAa;oBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;qBASnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;kBAKnB,KAAK,CAAC,aAAa;kBAQnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;oBASnB,KAAK,CAAC,aAAa;iBAcnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;UAcnB,KAAK,CAAC,aAAa;WAenB,KAAK,CAAC,aAAa;kBAWnB,KAAK,CAAC,aAAa;kBAanB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAOnB,KAAK,CAAC,aAAa;sBAiBnB,KAAK,CAAC,aAAa;2BAKnB,KAAK,CAAC,aAAa;eAMnB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;qBAInB,KAAK,CAAC,aAAa;qBAOnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAUnB,KAAK,CAAC,aAAa;CACzB,CAAC;AA+BF,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,OAAO,EACnB,YAAY,GAAE,MAAY,EAC1B,aAAa,GAAE,MAAY,GAC1B;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAoDlE"}
1
+ {"version":3,"file":"inline.d.ts","sourceRoot":"","sources":["../../src/styles/inline.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA6HH,eAAO,MAAM,QAAQ;WAgBd,KAAK,CAAC,aAAa;gBAKnB,KAAK,CAAC,aAAa;gBAInB,KAAK,CAAC,aAAa;;;;;mBAyBnB,KAAK,CAAC,aAAa;wBAMnB,KAAK,CAAC,aAAa;cAmBnB,KAAK,CAAC,aAAa;mBAKnB,KAAK,CAAC,aAAa;;;;;sBAwBnB,KAAK,CAAC,aAAa;2BAMnB,KAAK,CAAC,aAAa;YAoBnB,KAAK,CAAC,aAAa;iBAMnB,KAAK,CAAC,aAAa;oBAMnB,KAAK,CAAC,aAAa;YA2BnB,KAAK,CAAC,aAAa;iBAKnB,KAAK,CAAC,aAAa;kBAgBnB,KAAK,CAAC,aAAa;uBAMnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAgBnB,KAAK,CAAC,aAAa;mBASnB,KAAK,CAAC,aAAa;oBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;qBASnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;kBAKnB,KAAK,CAAC,aAAa;kBAQnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;yBAKnB,KAAK,CAAC,aAAa;oBASnB,KAAK,CAAC,aAAa;iBAcnB,KAAK,CAAC,aAAa;sBAKnB,KAAK,CAAC,aAAa;UAcnB,KAAK,CAAC,aAAa;WAenB,KAAK,CAAC,aAAa;kBAWnB,KAAK,CAAC,aAAa;kBAanB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAOnB,KAAK,CAAC,aAAa;sBAiBnB,KAAK,CAAC,aAAa;2BAKnB,KAAK,CAAC,aAAa;eAMnB,KAAK,CAAC,aAAa;iBAQnB,KAAK,CAAC,aAAa;gBAQnB,KAAK,CAAC,aAAa;qBAInB,KAAK,CAAC,aAAa;qBAOnB,KAAK,CAAC,aAAa;0BAKnB,KAAK,CAAC,aAAa;aAUnB,KAAK,CAAC,aAAa;CACzB,CAAC;AAoCF,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,OAAO,EACnB,YAAY,GAAE,MAAY,EAC1B,aAAa,GAAE,MAAY,GAC1B;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAoDlE"}
@@ -529,6 +529,11 @@ if (typeof document !== "undefined") {
529
529
  transform: translateY(0);
530
530
  }
531
531
  }
532
+
533
+ @keyframes pulse {
534
+ 0%, 100% { opacity: 1; }
535
+ 50% { opacity: 0.6; }
536
+ }
532
537
  `;
533
538
  document.head.appendChild(styleSheet);
534
539
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/ai-ui-react",
3
- "version": "1.0.52",
3
+ "version": "1.0.55",
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.41"
51
+ "@lastbrain/ai-ui-core": "1.0.42"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/react": "^19.2.0",
@@ -11,7 +11,6 @@ import {
11
11
  FolderPlus,
12
12
  Power,
13
13
  LogOut,
14
- Key,
15
14
  ArrowRightLeft,
16
15
  } from "lucide-react";
17
16
  import { aiStyles, calculateTooltipPosition } from "../styles/inline";
@@ -38,7 +37,9 @@ export function AiStatusButton({
38
37
  let apiKeys: any[] = [];
39
38
  let accessToken: string | undefined;
40
39
  let selectApiKeyWithToken: ((apiKeyId: string) => Promise<void>) | undefined;
40
+ let switchApiKey: ((apiKeyId: string) => Promise<void>) | undefined;
41
41
  let lbApiStatus: any = null;
42
+ let lbIsLoadingStatus: boolean = false;
42
43
 
43
44
  try {
44
45
  const lbContext = useLB();
@@ -48,7 +49,9 @@ export function AiStatusButton({
48
49
  apiKeys = lbContext.apiKeys || [];
49
50
  accessToken = lbContext.accessToken;
50
51
  selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
52
+ switchApiKey = lbContext.switchApiKey;
51
53
  lbApiStatus = lbContext.apiStatus;
54
+ lbIsLoadingStatus = lbContext.isLoadingStatus || false;
52
55
  } catch {
53
56
  // LBProvider n'est pas disponible, ignorer
54
57
  lbStatus = undefined;
@@ -71,6 +74,8 @@ export function AiStatusButton({
71
74
 
72
75
  const [showSigninModal, setShowSigninModal] = useState(false);
73
76
  const [showApiKeySelector, setShowApiKeySelector] = useState(false);
77
+ const [isSelectingApiKey, setIsSelectingApiKey] = useState(false);
78
+ const [isLoadingStatus, setIsLoadingStatus] = useState(false);
74
79
 
75
80
  type BalanceUsage = {
76
81
  used?: number;
@@ -280,7 +285,7 @@ export function AiStatusButton({
280
285
  }, 100);
281
286
  };
282
287
 
283
- if (loading) {
288
+ if (loading || isSelectingApiKey || isLoadingStatus || lbIsLoadingStatus) {
284
289
  return (
285
290
  <button
286
291
  ref={buttonRef}
@@ -737,11 +742,23 @@ export function AiStatusButton({
737
742
  }}
738
743
  >
739
744
  <span style={aiStyles.tooltipValue}>
740
- {effectiveStatus.apiKey?.name ||
741
- effectiveStatus.api_key?.name ||
742
- "Unknown"}
745
+ {lbIsLoadingStatus ? (
746
+ <div
747
+ style={{
748
+ height: "16px",
749
+ width: "100px",
750
+ background: "rgba(139, 92, 246, 0.1)",
751
+ borderRadius: "4px",
752
+ animation: "pulse 2s ease-in-out infinite",
753
+ }}
754
+ />
755
+ ) : (
756
+ effectiveStatus?.apiKey?.name ||
757
+ effectiveStatus?.api_key?.name ||
758
+ "Unknown"
759
+ )}
743
760
  </span>
744
- {selectApiKeyWithToken && (
761
+ {switchApiKey && (
745
762
  <button
746
763
  onClick={(e) => {
747
764
  e.stopPropagation();
@@ -998,38 +1015,6 @@ export function AiStatusButton({
998
1015
  <FolderPlus size={18} />
999
1016
  </button>
1000
1017
 
1001
- {/* Change API Key Button */}
1002
- {selectApiKeyWithToken && apiKeys.length > 1 && (
1003
- <button
1004
- onClick={() => setShowApiKeySelector(true)}
1005
- style={{
1006
- background: "transparent",
1007
- border: "none",
1008
- padding: "14px",
1009
- cursor: "pointer",
1010
- display: "flex",
1011
- alignItems: "center",
1012
- justifyContent: "center",
1013
- color: "#8b5cf6",
1014
- transition: "all 0.2s ease",
1015
- borderRadius: "4px",
1016
- }}
1017
- onMouseEnter={(e) => {
1018
- Object.assign(e.currentTarget.style, {
1019
- background: "rgba(139, 92, 246, 0.1)",
1020
- });
1021
- }}
1022
- onMouseLeave={(e) => {
1023
- Object.assign(e.currentTarget.style, {
1024
- background: "transparent",
1025
- });
1026
- }}
1027
- title="Change API Key"
1028
- >
1029
- <Key size={18} />
1030
- </button>
1031
- )}
1032
-
1033
1018
  {/* Logout Button */}
1034
1019
  {logout && (
1035
1020
  <button
@@ -1078,21 +1063,31 @@ export function AiStatusButton({
1078
1063
  )}
1079
1064
 
1080
1065
  {/* API Key Selector Modal */}
1081
- {showApiKeySelector && selectApiKeyWithToken && (
1066
+ {showApiKeySelector && apiKeys.length > 0 && (
1082
1067
  <LBApiKeySelector
1083
1068
  isOpen={showApiKeySelector}
1084
1069
  apiKeys={apiKeys}
1085
1070
  onSelect={async (keyId) => {
1071
+ setIsSelectingApiKey(true);
1086
1072
  try {
1087
- await selectApiKeyWithToken(keyId);
1073
+ // Utiliser la nouvelle fonction switchApiKey qui gère automatiquement le contexte
1074
+ if (switchApiKey) {
1075
+ await switchApiKey(keyId);
1076
+ } else {
1077
+ throw new Error("Switch API key function not available");
1078
+ }
1088
1079
  setShowApiKeySelector(false);
1089
1080
  setShowTooltip(false);
1081
+ setIsLoadingStatus(true);
1090
1082
  // Refresh provider data after API key selection
1091
1083
  if (refetchProviders) {
1092
1084
  await refetchProviders();
1093
1085
  }
1094
1086
  } catch (error) {
1095
1087
  console.error("Failed to select API key:", error);
1088
+ } finally {
1089
+ setIsSelectingApiKey(false);
1090
+ setIsLoadingStatus(false);
1096
1091
  }
1097
1092
  }}
1098
1093
  onCancel={() => setShowApiKeySelector(false)}
@@ -1,6 +1,6 @@
1
1
  "use client";
2
2
 
3
- import { useState, useEffect } from "react";
3
+ import { useState } from "react";
4
4
  import { useLB } from "../context/LBAuthProvider";
5
5
  import { LBApiKeySelector } from "./LBApiKeySelector";
6
6
  import { Mail, Lock, Sparkles, X, Loader2, AlertCircle } from "lucide-react";
@@ -11,6 +11,14 @@ export interface LBSigninModalProps {
11
11
  }
12
12
 
13
13
  export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
14
+ // React Hooks doivent être appelés avant toute condition ou return
15
+ const [email, setEmail] = useState("");
16
+ const [password, setPassword] = useState("");
17
+ const [loading, setLoading] = useState(false);
18
+ const [error, setError] = useState("");
19
+ const [showKeySelector, setShowKeySelector] = useState(false);
20
+ const [currentApiKeys, setCurrentApiKeys] = useState<any[]>([]); // Stocker les clés API localement
21
+
14
22
  // Vérifier si LBProvider est disponible
15
23
  let login:
16
24
  | ((
@@ -23,13 +31,16 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
23
31
  }>)
24
32
  | undefined;
25
33
  let selectApiKeyWithToken: ((apiKeyId: string) => Promise<void>) | undefined;
34
+ let fetchApiKeys: ((accessToken: string) => Promise<any[]>) | undefined;
26
35
  let apiKeys: any[] = [];
27
36
  let lbStatus: string | undefined;
28
37
 
38
+ let lbContext: any;
29
39
  try {
30
- const lbContext = useLB();
40
+ lbContext = useLB();
31
41
  login = lbContext.login;
32
42
  selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
43
+ fetchApiKeys = lbContext.fetchApiKeys;
33
44
  apiKeys = lbContext.apiKeys || [];
34
45
  lbStatus = lbContext.status;
35
46
  } catch {
@@ -37,19 +48,6 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
37
48
  return null;
38
49
  }
39
50
 
40
- const [email, setEmail] = useState("");
41
- const [password, setPassword] = useState("");
42
- const [loading, setLoading] = useState(false);
43
- const [error, setError] = useState("");
44
- const [showKeySelector, setShowKeySelector] = useState(false);
45
-
46
- // Si le status est "needs_key_selection", afficher le sélecteur
47
- useEffect(() => {
48
- if (lbStatus === "needs_key_selection" && apiKeys.length > 0) {
49
- setShowKeySelector(true);
50
- }
51
- }, [lbStatus, apiKeys.length]);
52
-
53
51
  if (!isOpen || !login) return null;
54
52
 
55
53
  const handleSubmit = async (e: React.FormEvent) => {
@@ -62,7 +60,19 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
62
60
  if (result.success) {
63
61
  if (result.needsKeySelection) {
64
62
  // L'utilisateur doit choisir une clé API
65
- setShowKeySelector(true);
63
+ // Récupérer les clés API avec l'access token temporaire
64
+ if (fetchApiKeys && lbContext.accessToken) {
65
+ try {
66
+ const keys = await fetchApiKeys(lbContext.accessToken);
67
+ setCurrentApiKeys(keys);
68
+ setShowKeySelector(true);
69
+ } catch (keyError) {
70
+ setError("Impossible de récupérer les clés API");
71
+ console.error("Failed to fetch API keys:", keyError);
72
+ }
73
+ } else {
74
+ setError("Accès aux clés API indisponible");
75
+ }
66
76
  } else {
67
77
  // Connexion réussie, fermer le modal
68
78
  onClose();
@@ -96,16 +106,15 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
96
106
 
97
107
  const handleCancelKeySelection = () => {
98
108
  setShowKeySelector(false);
99
- setEmail("");
100
- setPassword("");
101
- setError("");
109
+ // Fermer complètement le modal au lieu de rester dans l'état de sélection
110
+ onClose();
102
111
  };
103
112
 
104
113
  // Si on doit afficher le sélecteur de clés
105
- if (showKeySelector && apiKeys.length > 0) {
114
+ if (showKeySelector && currentApiKeys.length > 0) {
106
115
  return (
107
116
  <LBApiKeySelector
108
- apiKeys={apiKeys}
117
+ apiKeys={currentApiKeys}
109
118
  onSelect={handleKeySelect}
110
119
  onCancel={handleCancelKeySelection}
111
120
  isOpen={true}
@@ -498,7 +507,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
498
507
  Pas encore de compte ?
499
508
  </p>
500
509
  <a
501
- href="https://lastbrain.io/signup"
510
+ href="https://prompt.lastbrain.io/signup"
502
511
  target="_blank"
503
512
  rel="noopener noreferrer"
504
513
  style={{
@@ -52,6 +52,8 @@ interface LBContextValue extends LBAuthState {
52
52
  selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
53
53
  /** Sélectionne une clé API avec le token stocké */
54
54
  selectApiKeyWithToken: (apiKeyId: string) => Promise<void>;
55
+ /** Change l'API key active (fonctionne avec session ou token) */
56
+ switchApiKey: (apiKeyId: string) => Promise<void>;
55
57
  /** Recharge l'état de la session */
56
58
  refreshSession: () => Promise<void>;
57
59
  /** Clés API disponibles */
@@ -62,6 +64,8 @@ interface LBContextValue extends LBAuthState {
62
64
  apiStatus: AiStatus | null;
63
65
  /** Fonction pour rafraîchir le status */
64
66
  refreshStatus: () => Promise<void>;
67
+ /** Indique si le status est en cours de chargement */
68
+ isLoadingStatus: boolean;
65
69
  }
66
70
 
67
71
  const LBContext = createContext<LBContextValue | undefined>(undefined);
@@ -79,6 +83,7 @@ export function LBProvider({
79
83
  const [apiKeys, setApiKeys] = useState<LBApiKey[]>([]);
80
84
  const [accessToken, setAccessToken] = useState<string>();
81
85
  const [apiStatus, setApiStatus] = useState<AiStatus | null>(null);
86
+ const [isLoadingStatus, setIsLoadingStatus] = useState(false);
82
87
 
83
88
  /**
84
89
  * Vérifie si une session existe au chargement
@@ -385,6 +390,36 @@ export function LBProvider({
385
390
  [proxyUrl, fetchApiKeys, selectApiKey]
386
391
  );
387
392
 
393
+ /**
394
+ * Récupère le status API (balance, storage, API key info)
395
+ */
396
+ const refreshStatus = useCallback(async (): Promise<void> => {
397
+ if (state.status !== "ready") {
398
+ setApiStatus(null);
399
+ setIsLoadingStatus(false);
400
+ return;
401
+ }
402
+
403
+ setIsLoadingStatus(true);
404
+ try {
405
+ const response = await fetch(`${proxyUrl}/auth/status`, {
406
+ credentials: "include",
407
+ });
408
+
409
+ if (response.ok) {
410
+ const data = await response.json();
411
+ setApiStatus(data);
412
+ } else {
413
+ setApiStatus(null);
414
+ }
415
+ } catch (error) {
416
+ console.error("[LBProvider] Failed to fetch status:", error);
417
+ setApiStatus(null);
418
+ } finally {
419
+ setIsLoadingStatus(false);
420
+ }
421
+ }, [proxyUrl, state.status]);
422
+
388
423
  /**
389
424
  * Sélectionne une clé API avec le token déjà stocké (après login)
390
425
  */
@@ -398,6 +433,40 @@ export function LBProvider({
398
433
  [accessToken, selectApiKey]
399
434
  );
400
435
 
436
+ /**
437
+ * Change l'API key active - utilise la bonne méthode selon le contexte
438
+ */
439
+ const switchApiKey = useCallback(
440
+ async (apiKeyId: string): Promise<void> => {
441
+ if (state.status === "ready") {
442
+ // Utiliser la route de switch avec session
443
+ const response = await fetch(
444
+ `${proxyUrl}/auth/session/switch-api-key`,
445
+ {
446
+ method: "POST",
447
+ credentials: "include",
448
+ headers: { "Content-Type": "application/json" },
449
+ body: JSON.stringify({ api_key_id: apiKeyId }),
450
+ }
451
+ );
452
+
453
+ if (!response.ok) {
454
+ const errorData = await response.json().catch(() => ({}));
455
+ throw new Error(errorData.error || "Failed to switch API key");
456
+ }
457
+
458
+ // Refresh le status après le changement
459
+ await refreshStatus();
460
+ } else if (accessToken) {
461
+ // Utiliser la méthode avec access token
462
+ await selectApiKey(accessToken, apiKeyId);
463
+ } else {
464
+ throw new Error("No valid authentication method available");
465
+ }
466
+ },
467
+ [state.status, proxyUrl, accessToken, selectApiKey, refreshStatus]
468
+ );
469
+
401
470
  /**
402
471
  * Déconnexion
403
472
  */
@@ -425,32 +494,6 @@ export function LBProvider({
425
494
  await checkSession();
426
495
  }, [checkSession]);
427
496
 
428
- /**
429
- * Récupère le status API (balance, storage, API key info)
430
- */
431
- const refreshStatus = useCallback(async (): Promise<void> => {
432
- if (state.status !== "ready") {
433
- setApiStatus(null);
434
- return;
435
- }
436
-
437
- try {
438
- const response = await fetch(`${proxyUrl}/auth/status`, {
439
- credentials: "include",
440
- });
441
-
442
- if (response.ok) {
443
- const data = await response.json();
444
- setApiStatus(data);
445
- } else {
446
- setApiStatus(null);
447
- }
448
- } catch (error) {
449
- console.error("[LBProvider] Failed to fetch status:", error);
450
- setApiStatus(null);
451
- }
452
- }, [proxyUrl, state.status]);
453
-
454
497
  /**
455
498
  * Récupère les clés API avec la session active
456
499
  */
@@ -498,11 +541,13 @@ export function LBProvider({
498
541
  fetchApiKeys,
499
542
  selectApiKey,
500
543
  selectApiKeyWithToken,
544
+ switchApiKey,
501
545
  refreshSession,
502
546
  apiKeys,
503
547
  accessToken,
504
548
  apiStatus,
505
549
  refreshStatus,
550
+ isLoadingStatus,
506
551
  };
507
552
 
508
553
  return <LBContext.Provider value={value}>{children}</LBContext.Provider>;
@@ -600,6 +600,11 @@ if (typeof document !== "undefined") {
600
600
  transform: translateY(0);
601
601
  }
602
602
  }
603
+
604
+ @keyframes pulse {
605
+ 0%, 100% { opacity: 1; }
606
+ 50% { opacity: 0.6; }
607
+ }
603
608
  `;
604
609
  document.head.appendChild(styleSheet);
605
610
  }