@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.
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +32 -27
- package/dist/components/LBSigninModal.d.ts.map +1 -1
- package/dist/components/LBSigninModal.js +32 -20
- package/dist/context/LBAuthProvider.d.ts +4 -0
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +60 -25
- package/dist/styles/inline.d.ts.map +1 -1
- package/dist/styles/inline.js +5 -0
- package/package.json +2 -2
- package/src/components/AiStatusButton.tsx +35 -40
- package/src/components/LBSigninModal.tsx +31 -22
- package/src/context/LBAuthProvider.tsx +71 -26
- package/src/styles/inline.ts +5 -0
|
@@ -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;
|
|
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,
|
|
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:
|
|
383
|
-
|
|
384
|
-
|
|
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 }) }),
|
|
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 &&
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
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 &&
|
|
85
|
-
return (_jsx(LBApiKeySelector, { apiKeys:
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/styles/inline.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/ai-ui-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.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.
|
|
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
|
-
{
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
{
|
|
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 &&
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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 &&
|
|
114
|
+
if (showKeySelector && currentApiKeys.length > 0) {
|
|
106
115
|
return (
|
|
107
116
|
<LBApiKeySelector
|
|
108
|
-
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>;
|
package/src/styles/inline.ts
CHANGED