@lastbrain/ai-ui-react 1.0.53 → 1.0.56

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.
@@ -279,7 +279,6 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
279
279
  position: "sticky",
280
280
  top: 0,
281
281
  zIndex: 5,
282
- borderBottom: "1px solid var(--ai-border-primary, #374151)",
283
282
  backdropFilter: "blur(8px)",
284
283
  }, children: [_jsx("h2", { style: aiStyles.modalTitle, children: showPromptLibrary ? "Select a Prompt" : "AI Prompt Configuration" }), _jsx("button", { style: {
285
284
  ...aiStyles.modalCloseButton,
@@ -529,7 +528,6 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
529
528
  position: "sticky",
530
529
  bottom: 0,
531
530
  zIndex: 5,
532
- borderTop: "1px solid var(--ai-border-primary, #374151)",
533
531
  backdropFilter: "blur(8px)",
534
532
  }, children: [_jsx("button", { onClick: handleClose, onMouseEnter: () => setIsCancelHovered(true), onMouseLeave: () => setIsCancelHovered(false), style: {
535
533
  ...aiStyles.button,
@@ -611,30 +609,30 @@ function AiPromptPanelInternal({ isOpen, onClose, onSubmit, uiMode: _uiMode = "m
611
609
  alignItems: "center",
612
610
  justifyContent: "space-between",
613
611
  padding: "16px 18px",
614
- border: "2px solid",
615
- borderColor: isActive
616
- ? "#10b981"
617
- : "var(--ai-border-primary, #374151)",
612
+ // border: "2px solid",
613
+ // borderColor: isActive
614
+ // ? "#10b981"
615
+ // : "var(--ai-border-primary, #374151)",
618
616
  borderRadius: "10px",
619
617
  backgroundColor: isActive
620
- ? "rgba(16, 185, 129, 0.08)"
621
- : "var(--ai-bg-secondary, rgba(31, 41, 55, 0.5))",
618
+ ? "rgba(16, 185, 129, 0.03)"
619
+ : "var(--ai-bg-secondary, rgba(31, 41, 55, 0.2))",
622
620
  transition: "all 0.2s",
623
621
  cursor: "pointer",
624
622
  backdropFilter: "blur(8px)",
625
623
  }, onClick: () => !isLoading &&
626
624
  handleModelToggle(modelData.id, !isActive), onMouseEnter: (e) => {
627
- e.currentTarget.style.borderColor = isActive
628
- ? "#059669"
629
- : "#4b5563";
625
+ // e.currentTarget.style.borderColor = isActive
626
+ // ? "#059669"
627
+ // : "#4b5563";
630
628
  e.currentTarget.style.transform = "translateY(-2px)";
631
629
  e.currentTarget.style.boxShadow = isActive
632
630
  ? "0 8px 16px rgba(16, 185, 129, 0.2)"
633
631
  : "0 8px 16px rgba(0, 0, 0, 0.15)";
634
632
  }, onMouseLeave: (e) => {
635
- e.currentTarget.style.borderColor = isActive
636
- ? "#10b981"
637
- : "var(--ai-border-primary, #374151)";
633
+ // e.currentTarget.style.borderColor = isActive
634
+ // ? "#10b981"
635
+ // : "var(--ai-border-primary, #374151)";
638
636
  e.currentTarget.style.transform = "translateY(0)";
639
637
  e.currentTarget.style.boxShadow = "none";
640
638
  }, children: [_jsxs("div", { style: {
@@ -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":"LBApiKeySelector.d.ts","sourceRoot":"","sources":["../../src/components/LBApiKeySelector.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,MAAM,GACP,EAAE,qBAAqB,kDAwTvB"}
1
+ {"version":3,"file":"LBApiKeySelector.d.ts","sourceRoot":"","sources":["../../src/components/LBApiKeySelector.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,UAAU,qBAAqB;IAC7B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,MAAM,GACP,EAAE,qBAAqB,kDAyTvB"}
@@ -41,6 +41,7 @@ export function LBApiKeySelector({ apiKeys, onSelect, onCancel, isOpen, }) {
41
41
  bottom: 0,
42
42
  background: "rgba(0, 0, 0, 0.75)",
43
43
  backdropFilter: "blur(8px)",
44
+ height: "110%",
44
45
  } }), _jsxs("div", { style: {
45
46
  position: "relative",
46
47
  background: "light-dark(#ffffff, #1e293b)",
@@ -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,kDA8jBpE"}
@@ -1,6 +1,6 @@
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";
@@ -11,15 +11,19 @@ export function LBSigninModal({ isOpen, onClose }) {
11
11
  const [loading, setLoading] = useState(false);
12
12
  const [error, setError] = useState("");
13
13
  const [showKeySelector, setShowKeySelector] = useState(false);
14
+ const [currentApiKeys, setCurrentApiKeys] = useState([]); // Stocker les clés API localement
14
15
  // Vérifier si LBProvider est disponible
15
16
  let login;
16
17
  let selectApiKeyWithToken;
18
+ let fetchApiKeys;
17
19
  let apiKeys = [];
18
20
  let lbStatus;
21
+ let lbContext;
19
22
  try {
20
- const lbContext = useLB();
23
+ lbContext = useLB();
21
24
  login = lbContext.login;
22
25
  selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
26
+ fetchApiKeys = lbContext.fetchApiKeys;
23
27
  apiKeys = lbContext.apiKeys || [];
24
28
  lbStatus = lbContext.status;
25
29
  }
@@ -27,12 +31,6 @@ export function LBSigninModal({ isOpen, onClose }) {
27
31
  // LBProvider n'est pas disponible, ne pas rendre le modal
28
32
  return null;
29
33
  }
30
- // Si le status est "needs_key_selection", afficher le sélecteur
31
- useEffect(() => {
32
- if (lbStatus === "needs_key_selection" && apiKeys.length > 0 && !showKeySelector) {
33
- setShowKeySelector(true);
34
- }
35
- }, [lbStatus, apiKeys.length, showKeySelector]);
36
34
  if (!isOpen || !login)
37
35
  return null;
38
36
  const handleSubmit = async (e) => {
@@ -44,7 +42,21 @@ export function LBSigninModal({ isOpen, onClose }) {
44
42
  if (result.success) {
45
43
  if (result.needsKeySelection) {
46
44
  // L'utilisateur doit choisir une clé API
47
- setShowKeySelector(true);
45
+ // Utiliser l'access token retourné directement par login
46
+ if (fetchApiKeys && result.accessToken) {
47
+ try {
48
+ const keys = await fetchApiKeys(result.accessToken);
49
+ setCurrentApiKeys(keys);
50
+ setShowKeySelector(true);
51
+ }
52
+ catch (keyError) {
53
+ setError("Erreur lors de la récupération des clés API");
54
+ console.error("Failed to fetch API keys:", keyError);
55
+ }
56
+ }
57
+ else {
58
+ setError("Token d'accès non disponible");
59
+ }
48
60
  }
49
61
  else {
50
62
  // Connexion réussie, fermer le modal
@@ -81,8 +93,8 @@ export function LBSigninModal({ isOpen, onClose }) {
81
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",
@@ -21,6 +21,7 @@ interface LBContextValue extends LBAuthState {
21
21
  success: boolean;
22
22
  error?: string;
23
23
  needsKeySelection?: boolean;
24
+ accessToken?: string;
24
25
  }>;
25
26
  /** Fonction de déconnexion */
26
27
  logout: () => Promise<void>;
@@ -30,6 +31,8 @@ interface LBContextValue extends LBAuthState {
30
31
  selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
31
32
  /** Sélectionne une clé API avec le token stocké */
32
33
  selectApiKeyWithToken: (apiKeyId: string) => Promise<void>;
34
+ /** Change l'API key active (fonctionne avec session ou token) */
35
+ switchApiKey: (apiKeyId: string) => Promise<void>;
33
36
  /** Recharge l'état de la session */
34
37
  refreshSession: () => Promise<void>;
35
38
  /** Clés API disponibles */
@@ -40,6 +43,8 @@ interface LBContextValue extends LBAuthState {
40
43
  apiStatus: AiStatus | null;
41
44
  /** Fonction pour rafraîchir le status */
42
45
  refreshStatus: () => Promise<void>;
46
+ /** Indique si le status est en cours de chargement */
47
+ isLoadingStatus: boolean;
43
48
  }
44
49
  export declare function LBProvider({ children, baseUrl: _baseUrl, proxyUrl, onStatusChange, onAuthChange, }: LBProviderProps): import("react/jsx-runtime").JSX.Element;
45
50
  /**
@@ -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;QAC5B,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,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,2CA4djB;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
  */
@@ -228,7 +229,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
228
229
  status: "needs_key_selection",
229
230
  user: result.user,
230
231
  });
231
- return { success: true, needsKeySelection: true };
232
+ return { success: true, needsKeySelection: true, accessToken: token };
232
233
  }
233
234
  catch (keyError) {
234
235
  console.error("[LBProvider] Failed to fetch API keys:");
@@ -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;qBASnB,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"}
@@ -489,7 +489,9 @@ export const aiStyles = {
489
489
  buttonSecondary: {
490
490
  background: themeVars.bgSecondary,
491
491
  color: themeVars.text,
492
- border: `1px solid ${themeVars.border}`,
492
+ borderWidth: "1px",
493
+ borderStyle: "solid",
494
+ borderColor: themeVars.border,
493
495
  },
494
496
  buttonSecondaryHover: {
495
497
  background: themeVars.bgTertiary,
@@ -529,6 +531,11 @@ if (typeof document !== "undefined") {
529
531
  transform: translateY(0);
530
532
  }
531
533
  }
534
+
535
+ @keyframes pulse {
536
+ 0%, 100% { opacity: 1; }
537
+ 50% { opacity: 0.6; }
538
+ }
532
539
  `;
533
540
  document.head.appendChild(styleSheet);
534
541
  }
@@ -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,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"}
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,CAkDnB"}
@@ -85,9 +85,15 @@ export async function getUserModels(options = {}) {
85
85
  : `/api/ai/auth/user-models`; // fallback
86
86
  console.log("[getUserModels] isExternalProxy:", isExternalProxy, "isPublicApi:", isPublicApi, "endpoint:", endpoint);
87
87
  const headers = {};
88
- // Ajouter la clé API pour les appels publics directs
89
- if (isPublicApi && apiKey) {
90
- headers["Authorization"] = `Bearer ${apiKey}`;
88
+ // Ajouter la clé API pour tous les types d'appels si disponible
89
+ if (apiKey) {
90
+ if (isPublicApi) {
91
+ headers["Authorization"] = `Bearer ${apiKey}`;
92
+ }
93
+ else {
94
+ // Pour les routes auth internes, utiliser aussi le Bearer token si pas de session
95
+ headers["Authorization"] = `Bearer ${apiKey}`;
96
+ }
91
97
  }
92
98
  const response = await fetch(endpoint, {
93
99
  method: "GET",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/ai-ui-react",
3
- "version": "1.0.53",
3
+ "version": "1.0.56",
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.43"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/react": "^19.2.0",
@@ -425,7 +425,6 @@ function AiPromptPanelInternal({
425
425
  top: 0,
426
426
  zIndex: 5,
427
427
 
428
- borderBottom: "1px solid var(--ai-border-primary, #374151)",
429
428
  backdropFilter: "blur(8px)",
430
429
  }}
431
430
  >
@@ -1004,7 +1003,6 @@ function AiPromptPanelInternal({
1004
1003
  bottom: 0,
1005
1004
  zIndex: 5,
1006
1005
 
1007
- borderTop: "1px solid var(--ai-border-primary, #374151)",
1008
1006
  backdropFilter: "blur(8px)",
1009
1007
  }}
1010
1008
  >
@@ -1183,14 +1181,14 @@ function AiPromptPanelInternal({
1183
1181
  alignItems: "center",
1184
1182
  justifyContent: "space-between",
1185
1183
  padding: "16px 18px",
1186
- border: "2px solid",
1187
- borderColor: isActive
1188
- ? "#10b981"
1189
- : "var(--ai-border-primary, #374151)",
1184
+ // border: "2px solid",
1185
+ // borderColor: isActive
1186
+ // ? "#10b981"
1187
+ // : "var(--ai-border-primary, #374151)",
1190
1188
  borderRadius: "10px",
1191
1189
  backgroundColor: isActive
1192
- ? "rgba(16, 185, 129, 0.08)"
1193
- : "var(--ai-bg-secondary, rgba(31, 41, 55, 0.5))",
1190
+ ? "rgba(16, 185, 129, 0.03)"
1191
+ : "var(--ai-bg-secondary, rgba(31, 41, 55, 0.2))",
1194
1192
  transition: "all 0.2s",
1195
1193
  cursor: "pointer",
1196
1194
  backdropFilter: "blur(8px)",
@@ -1200,18 +1198,18 @@ function AiPromptPanelInternal({
1200
1198
  handleModelToggle(modelData.id, !isActive)
1201
1199
  }
1202
1200
  onMouseEnter={(e) => {
1203
- e.currentTarget.style.borderColor = isActive
1204
- ? "#059669"
1205
- : "#4b5563";
1201
+ // e.currentTarget.style.borderColor = isActive
1202
+ // ? "#059669"
1203
+ // : "#4b5563";
1206
1204
  e.currentTarget.style.transform = "translateY(-2px)";
1207
1205
  e.currentTarget.style.boxShadow = isActive
1208
1206
  ? "0 8px 16px rgba(16, 185, 129, 0.2)"
1209
1207
  : "0 8px 16px rgba(0, 0, 0, 0.15)";
1210
1208
  }}
1211
1209
  onMouseLeave={(e) => {
1212
- e.currentTarget.style.borderColor = isActive
1213
- ? "#10b981"
1214
- : "var(--ai-border-primary, #374151)";
1210
+ // e.currentTarget.style.borderColor = isActive
1211
+ // ? "#10b981"
1212
+ // : "var(--ai-border-primary, #374151)";
1215
1213
  e.currentTarget.style.transform = "translateY(0)";
1216
1214
  e.currentTarget.style.boxShadow = "none";
1217
1215
  }}
@@ -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)}
@@ -68,6 +68,7 @@ export function LBApiKeySelector({
68
68
  bottom: 0,
69
69
  background: "rgba(0, 0, 0, 0.75)",
70
70
  backdropFilter: "blur(8px)",
71
+ height: "110%",
71
72
  }}
72
73
  />
73
74
 
@@ -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";
@@ -17,6 +17,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
17
17
  const [loading, setLoading] = useState(false);
18
18
  const [error, setError] = useState("");
19
19
  const [showKeySelector, setShowKeySelector] = useState(false);
20
+ const [currentApiKeys, setCurrentApiKeys] = useState<any[]>([]); // Stocker les clés API localement
20
21
 
21
22
  // Vérifier si LBProvider est disponible
22
23
  let login:
@@ -27,16 +28,20 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
27
28
  success: boolean;
28
29
  error?: string;
29
30
  needsKeySelection?: boolean;
31
+ accessToken?: string;
30
32
  }>)
31
33
  | undefined;
32
34
  let selectApiKeyWithToken: ((apiKeyId: string) => Promise<void>) | undefined;
35
+ let fetchApiKeys: ((accessToken: string) => Promise<any[]>) | undefined;
33
36
  let apiKeys: any[] = [];
34
37
  let lbStatus: string | undefined;
35
38
 
39
+ let lbContext: any;
36
40
  try {
37
- const lbContext = useLB();
41
+ lbContext = useLB();
38
42
  login = lbContext.login;
39
43
  selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
44
+ fetchApiKeys = lbContext.fetchApiKeys;
40
45
  apiKeys = lbContext.apiKeys || [];
41
46
  lbStatus = lbContext.status;
42
47
  } catch {
@@ -44,13 +49,6 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
44
49
  return null;
45
50
  }
46
51
 
47
- // Si le status est "needs_key_selection", afficher le sélecteur
48
- useEffect(() => {
49
- if (lbStatus === "needs_key_selection" && apiKeys.length > 0 && !showKeySelector) {
50
- setShowKeySelector(true);
51
- }
52
- }, [lbStatus, apiKeys.length, showKeySelector]);
53
-
54
52
  if (!isOpen || !login) return null;
55
53
 
56
54
  const handleSubmit = async (e: React.FormEvent) => {
@@ -63,7 +61,19 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
63
61
  if (result.success) {
64
62
  if (result.needsKeySelection) {
65
63
  // L'utilisateur doit choisir une clé API
66
- setShowKeySelector(true);
64
+ // Utiliser l'access token retourné directement par login
65
+ if (fetchApiKeys && result.accessToken) {
66
+ try {
67
+ const keys = await fetchApiKeys(result.accessToken);
68
+ setCurrentApiKeys(keys);
69
+ setShowKeySelector(true);
70
+ } catch (keyError) {
71
+ setError("Erreur lors de la récupération des clés API");
72
+ console.error("Failed to fetch API keys:", keyError);
73
+ }
74
+ } else {
75
+ setError("Token d'accès non disponible");
76
+ }
67
77
  } else {
68
78
  // Connexion réussie, fermer le modal
69
79
  onClose();
@@ -102,10 +112,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
102
112
  };
103
113
 
104
114
  // Si on doit afficher le sélecteur de clés
105
- if (showKeySelector && apiKeys.length > 0) {
115
+ if (showKeySelector && currentApiKeys.length > 0) {
106
116
  return (
107
117
  <LBApiKeySelector
108
- apiKeys={apiKeys}
118
+ apiKeys={currentApiKeys}
109
119
  onSelect={handleKeySelect}
110
120
  onCancel={handleCancelKeySelection}
111
121
  isOpen={true}
@@ -498,7 +508,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
498
508
  Pas encore de compte ?
499
509
  </p>
500
510
  <a
501
- href="https://lastbrain.io/signup"
511
+ href="https://prompt.lastbrain.io/signup"
502
512
  target="_blank"
503
513
  rel="noopener noreferrer"
504
514
  style={{
@@ -43,6 +43,7 @@ interface LBContextValue extends LBAuthState {
43
43
  success: boolean;
44
44
  error?: string;
45
45
  needsKeySelection?: boolean;
46
+ accessToken?: string;
46
47
  }>;
47
48
  /** Fonction de déconnexion */
48
49
  logout: () => Promise<void>;
@@ -52,6 +53,8 @@ interface LBContextValue extends LBAuthState {
52
53
  selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
53
54
  /** Sélectionne une clé API avec le token stocké */
54
55
  selectApiKeyWithToken: (apiKeyId: string) => Promise<void>;
56
+ /** Change l'API key active (fonctionne avec session ou token) */
57
+ switchApiKey: (apiKeyId: string) => Promise<void>;
55
58
  /** Recharge l'état de la session */
56
59
  refreshSession: () => Promise<void>;
57
60
  /** Clés API disponibles */
@@ -62,6 +65,8 @@ interface LBContextValue extends LBAuthState {
62
65
  apiStatus: AiStatus | null;
63
66
  /** Fonction pour rafraîchir le status */
64
67
  refreshStatus: () => Promise<void>;
68
+ /** Indique si le status est en cours de chargement */
69
+ isLoadingStatus: boolean;
65
70
  }
66
71
 
67
72
  const LBContext = createContext<LBContextValue | undefined>(undefined);
@@ -79,6 +84,7 @@ export function LBProvider({
79
84
  const [apiKeys, setApiKeys] = useState<LBApiKey[]>([]);
80
85
  const [accessToken, setAccessToken] = useState<string>();
81
86
  const [apiStatus, setApiStatus] = useState<AiStatus | null>(null);
87
+ const [isLoadingStatus, setIsLoadingStatus] = useState(false);
82
88
 
83
89
  /**
84
90
  * Vérifie si une session existe au chargement
@@ -263,6 +269,7 @@ export function LBProvider({
263
269
  success: boolean;
264
270
  error?: string;
265
271
  needsKeySelection?: boolean;
272
+ accessToken?: string;
266
273
  }> => {
267
274
  try {
268
275
  console.log("[LBProvider] Login attempt:", email);
@@ -349,7 +356,7 @@ export function LBProvider({
349
356
  status: "needs_key_selection",
350
357
  user: result.user,
351
358
  });
352
- return { success: true, needsKeySelection: true };
359
+ return { success: true, needsKeySelection: true, accessToken: token };
353
360
  } catch (keyError) {
354
361
  console.error("[LBProvider] Failed to fetch API keys:");
355
362
  console.error("[LBProvider] Error details:", keyError);
@@ -385,6 +392,36 @@ export function LBProvider({
385
392
  [proxyUrl, fetchApiKeys, selectApiKey]
386
393
  );
387
394
 
395
+ /**
396
+ * Récupère le status API (balance, storage, API key info)
397
+ */
398
+ const refreshStatus = useCallback(async (): Promise<void> => {
399
+ if (state.status !== "ready") {
400
+ setApiStatus(null);
401
+ setIsLoadingStatus(false);
402
+ return;
403
+ }
404
+
405
+ setIsLoadingStatus(true);
406
+ try {
407
+ const response = await fetch(`${proxyUrl}/auth/status`, {
408
+ credentials: "include",
409
+ });
410
+
411
+ if (response.ok) {
412
+ const data = await response.json();
413
+ setApiStatus(data);
414
+ } else {
415
+ setApiStatus(null);
416
+ }
417
+ } catch (error) {
418
+ console.error("[LBProvider] Failed to fetch status:", error);
419
+ setApiStatus(null);
420
+ } finally {
421
+ setIsLoadingStatus(false);
422
+ }
423
+ }, [proxyUrl, state.status]);
424
+
388
425
  /**
389
426
  * Sélectionne une clé API avec le token déjà stocké (après login)
390
427
  */
@@ -398,6 +435,40 @@ export function LBProvider({
398
435
  [accessToken, selectApiKey]
399
436
  );
400
437
 
438
+ /**
439
+ * Change l'API key active - utilise la bonne méthode selon le contexte
440
+ */
441
+ const switchApiKey = useCallback(
442
+ async (apiKeyId: string): Promise<void> => {
443
+ if (state.status === "ready") {
444
+ // Utiliser la route de switch avec session
445
+ const response = await fetch(
446
+ `${proxyUrl}/auth/session/switch-api-key`,
447
+ {
448
+ method: "POST",
449
+ credentials: "include",
450
+ headers: { "Content-Type": "application/json" },
451
+ body: JSON.stringify({ api_key_id: apiKeyId }),
452
+ }
453
+ );
454
+
455
+ if (!response.ok) {
456
+ const errorData = await response.json().catch(() => ({}));
457
+ throw new Error(errorData.error || "Failed to switch API key");
458
+ }
459
+
460
+ // Refresh le status après le changement
461
+ await refreshStatus();
462
+ } else if (accessToken) {
463
+ // Utiliser la méthode avec access token
464
+ await selectApiKey(accessToken, apiKeyId);
465
+ } else {
466
+ throw new Error("No valid authentication method available");
467
+ }
468
+ },
469
+ [state.status, proxyUrl, accessToken, selectApiKey, refreshStatus]
470
+ );
471
+
401
472
  /**
402
473
  * Déconnexion
403
474
  */
@@ -425,32 +496,6 @@ export function LBProvider({
425
496
  await checkSession();
426
497
  }, [checkSession]);
427
498
 
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
499
  /**
455
500
  * Récupère les clés API avec la session active
456
501
  */
@@ -498,11 +543,13 @@ export function LBProvider({
498
543
  fetchApiKeys,
499
544
  selectApiKey,
500
545
  selectApiKeyWithToken,
546
+ switchApiKey,
501
547
  refreshSession,
502
548
  apiKeys,
503
549
  accessToken,
504
550
  apiStatus,
505
551
  refreshStatus,
552
+ isLoadingStatus,
506
553
  };
507
554
 
508
555
  return <LBContext.Provider value={value}>{children}</LBContext.Provider>;
@@ -557,7 +557,9 @@ export const aiStyles = {
557
557
  buttonSecondary: {
558
558
  background: themeVars.bgSecondary,
559
559
  color: themeVars.text,
560
- border: `1px solid ${themeVars.border}`,
560
+ borderWidth: "1px",
561
+ borderStyle: "solid",
562
+ borderColor: themeVars.border,
561
563
  } as React.CSSProperties,
562
564
 
563
565
  buttonSecondaryHover: {
@@ -600,6 +602,11 @@ if (typeof document !== "undefined") {
600
602
  transform: translateY(0);
601
603
  }
602
604
  }
605
+
606
+ @keyframes pulse {
607
+ 0%, 100% { opacity: 1; }
608
+ 50% { opacity: 0.6; }
609
+ }
603
610
  `;
604
611
  document.head.appendChild(styleSheet);
605
612
  }
@@ -147,9 +147,14 @@ export async function getUserModels(
147
147
 
148
148
  const headers: Record<string, string> = {};
149
149
 
150
- // Ajouter la clé API pour les appels publics directs
151
- if (isPublicApi && apiKey) {
152
- headers["Authorization"] = `Bearer ${apiKey}`;
150
+ // Ajouter la clé API pour tous les types d'appels si disponible
151
+ if (apiKey) {
152
+ if (isPublicApi) {
153
+ headers["Authorization"] = `Bearer ${apiKey}`;
154
+ } else {
155
+ // Pour les routes auth internes, utiliser aussi le Bearer token si pas de session
156
+ headers["Authorization"] = `Bearer ${apiKey}`;
157
+ }
153
158
  }
154
159
 
155
160
  const response = await fetch(endpoint, {