@lastbrain/ai-ui-react 1.0.43 → 1.0.44
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 +2 -2
- package/dist/components/LBApiKeySelector.d.ts +10 -0
- package/dist/components/LBApiKeySelector.d.ts.map +1 -0
- package/dist/components/LBApiKeySelector.js +193 -0
- package/dist/components/LBSigninModal.d.ts.map +1 -1
- package/dist/components/LBSigninModal.js +46 -2
- package/dist/context/LBAuthProvider.d.ts +4 -1
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +55 -17
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +2 -2
- package/src/components/AiStatusButton.tsx +8 -2
- package/src/components/LBApiKeySelector.tsx +331 -0
- package/src/components/LBSigninModal.tsx +58 -3
- package/src/context/LBAuthProvider.tsx +72 -19
- package/src/index.ts +1 -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;AAetD,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,
|
|
1
|
+
{"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAetD,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,2CAk2BrB"}
|
|
@@ -194,7 +194,7 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
|
194
194
|
gap: "8px",
|
|
195
195
|
borderTop: "1px solid var(--ai-border-primary, #374151)",
|
|
196
196
|
paddingTop: "12px",
|
|
197
|
-
}, children: [_jsx("button", { onClick: () => window.open("https://lastbrain.io/metrics", "_blank"), style: {
|
|
197
|
+
}, children: [_jsx("button", { onClick: () => window.open("https://prompt.lastbrain.io/metrics", "_blank"), style: {
|
|
198
198
|
flex: 1,
|
|
199
199
|
background: "transparent",
|
|
200
200
|
border: "none",
|
|
@@ -213,7 +213,7 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
|
213
213
|
Object.assign(e.currentTarget.style, {
|
|
214
214
|
background: "transparent",
|
|
215
215
|
});
|
|
216
|
-
}, title: "View Metrics", children: _jsx(BarChart3, { size: 18 }) }), _jsx("button", { onClick: () => window.open("https://lastbrain.io/settings", "_blank"), style: {
|
|
216
|
+
}, title: "View Metrics", children: _jsx(BarChart3, { size: 18 }) }), _jsx("button", { onClick: () => window.open("https://prompt.lastbrain.io/settings", "_blank"), style: {
|
|
217
217
|
flex: 1,
|
|
218
218
|
background: "transparent",
|
|
219
219
|
border: "none",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { LBApiKey } from "@lastbrain/ai-ui-core";
|
|
2
|
+
interface LBApiKeySelectorProps {
|
|
3
|
+
apiKeys: LBApiKey[];
|
|
4
|
+
onSelect: (apiKeyId: string) => Promise<void>;
|
|
5
|
+
onCancel: () => void;
|
|
6
|
+
isOpen: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function LBApiKeySelector({ apiKeys, onSelect, onCancel, isOpen, }: LBApiKeySelectorProps): import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=LBApiKeySelector.d.ts.map
|
|
@@ -0,0 +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,kDA2TvB"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
export function LBApiKeySelector({ apiKeys, onSelect, onCancel, isOpen, }) {
|
|
4
|
+
const [selectedKeyId, setSelectedKeyId] = useState(apiKeys.find((k) => k.isActive)?.id || apiKeys[0]?.id || "");
|
|
5
|
+
const [loading, setLoading] = useState(false);
|
|
6
|
+
const [error, setError] = useState("");
|
|
7
|
+
if (!isOpen)
|
|
8
|
+
return null;
|
|
9
|
+
const handleSubmit = async (e) => {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
if (!selectedKeyId) {
|
|
12
|
+
setError("Veuillez sélectionner une clé API");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
setLoading(true);
|
|
16
|
+
setError("");
|
|
17
|
+
try {
|
|
18
|
+
await onSelect(selectedKeyId);
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
setError(err instanceof Error ? err.message : "Erreur lors de la sélection");
|
|
22
|
+
setLoading(false);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
return (_jsxs("div", { style: {
|
|
26
|
+
position: "fixed",
|
|
27
|
+
top: 0,
|
|
28
|
+
left: 0,
|
|
29
|
+
right: 0,
|
|
30
|
+
bottom: 0,
|
|
31
|
+
zIndex: 10000,
|
|
32
|
+
display: "flex",
|
|
33
|
+
alignItems: "center",
|
|
34
|
+
justifyContent: "center",
|
|
35
|
+
padding: "16px",
|
|
36
|
+
}, onClick: onCancel, children: [_jsx("div", { style: {
|
|
37
|
+
position: "absolute",
|
|
38
|
+
top: 0,
|
|
39
|
+
left: 0,
|
|
40
|
+
right: 0,
|
|
41
|
+
bottom: 0,
|
|
42
|
+
background: "var(--ai-overlay-bg, rgba(0, 0, 0, 0.75))",
|
|
43
|
+
backdropFilter: "blur(8px)",
|
|
44
|
+
} }), _jsxs("div", { style: {
|
|
45
|
+
position: "relative",
|
|
46
|
+
background: "var(--ai-modal-bg, #1f2937)",
|
|
47
|
+
border: "1px solid var(--ai-border-primary, #374151)",
|
|
48
|
+
borderRadius: "12px",
|
|
49
|
+
padding: "24px",
|
|
50
|
+
maxWidth: "500px",
|
|
51
|
+
width: "100%",
|
|
52
|
+
boxShadow: "0 20px 50px rgba(0, 0, 0, 0.5)",
|
|
53
|
+
}, onClick: (e) => e.stopPropagation(), children: [_jsx("h2", { style: {
|
|
54
|
+
margin: "0 0 8px 0",
|
|
55
|
+
fontSize: "20px",
|
|
56
|
+
fontWeight: 600,
|
|
57
|
+
color: "var(--ai-text-primary, #f9fafb)",
|
|
58
|
+
textAlign: "center",
|
|
59
|
+
}, children: "S\u00E9lectionnez une cl\u00E9 API" }), _jsx("p", { style: {
|
|
60
|
+
margin: "0 0 24px 0",
|
|
61
|
+
fontSize: "14px",
|
|
62
|
+
color: "var(--ai-text-secondary, #9ca3af)",
|
|
63
|
+
textAlign: "center",
|
|
64
|
+
lineHeight: "1.5",
|
|
65
|
+
}, children: "Choisissez la cl\u00E9 API \u00E0 utiliser pour vos requ\u00EAtes IA" }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsx("div", { style: {
|
|
66
|
+
display: "flex",
|
|
67
|
+
flexDirection: "column",
|
|
68
|
+
gap: "8px",
|
|
69
|
+
marginBottom: "24px",
|
|
70
|
+
maxHeight: "300px",
|
|
71
|
+
overflowY: "auto",
|
|
72
|
+
}, children: apiKeys.map((key) => {
|
|
73
|
+
const isSelected = key.id === selectedKeyId;
|
|
74
|
+
const isActive = key.isActive;
|
|
75
|
+
return (_jsxs("label", { style: {
|
|
76
|
+
display: "flex",
|
|
77
|
+
alignItems: "center",
|
|
78
|
+
padding: "12px 16px",
|
|
79
|
+
background: isSelected
|
|
80
|
+
? "var(--ai-input-bg-focus, #374151)"
|
|
81
|
+
: "var(--ai-input-bg, #111827)",
|
|
82
|
+
border: `2px solid ${isSelected
|
|
83
|
+
? "var(--ai-accent-primary, #8b5cf6)"
|
|
84
|
+
: "var(--ai-border-primary, #374151)"}`,
|
|
85
|
+
borderRadius: "8px",
|
|
86
|
+
cursor: isActive ? "pointer" : "not-allowed",
|
|
87
|
+
opacity: isActive ? 1 : 0.5,
|
|
88
|
+
transition: "all 0.2s ease",
|
|
89
|
+
}, onMouseEnter: (e) => {
|
|
90
|
+
if (isActive && !isSelected) {
|
|
91
|
+
e.currentTarget.style.borderColor =
|
|
92
|
+
"var(--ai-border-hover, #4b5563)";
|
|
93
|
+
e.currentTarget.style.background =
|
|
94
|
+
"var(--ai-input-bg-focus, #374151)";
|
|
95
|
+
}
|
|
96
|
+
}, onMouseLeave: (e) => {
|
|
97
|
+
if (isActive && !isSelected) {
|
|
98
|
+
e.currentTarget.style.borderColor =
|
|
99
|
+
"var(--ai-border-primary, #374151)";
|
|
100
|
+
e.currentTarget.style.background =
|
|
101
|
+
"var(--ai-input-bg, #111827)";
|
|
102
|
+
}
|
|
103
|
+
}, children: [_jsx("input", { type: "radio", name: "apiKey", value: key.id, checked: isSelected, disabled: !isActive, onChange: (e) => setSelectedKeyId(e.target.value), style: {
|
|
104
|
+
marginRight: "12px",
|
|
105
|
+
accentColor: "var(--ai-accent-primary, #8b5cf6)",
|
|
106
|
+
cursor: isActive ? "pointer" : "not-allowed",
|
|
107
|
+
} }), _jsxs("div", { style: { flex: 1 }, children: [_jsx("div", { style: {
|
|
108
|
+
fontSize: "14px",
|
|
109
|
+
fontWeight: 500,
|
|
110
|
+
color: "var(--ai-text-primary, #f9fafb)",
|
|
111
|
+
marginBottom: "4px",
|
|
112
|
+
}, children: key.name }), _jsx("div", { style: {
|
|
113
|
+
fontSize: "12px",
|
|
114
|
+
color: "var(--ai-text-secondary, #9ca3af)",
|
|
115
|
+
fontFamily: "monospace",
|
|
116
|
+
}, children: key.keyPrefix || key.id.substring(0, 12) + "..." })] }), isActive ? (_jsx("div", { style: {
|
|
117
|
+
fontSize: "11px",
|
|
118
|
+
padding: "4px 8px",
|
|
119
|
+
borderRadius: "4px",
|
|
120
|
+
background: "rgba(16, 185, 129, 0.1)",
|
|
121
|
+
color: "#10b981",
|
|
122
|
+
fontWeight: 600,
|
|
123
|
+
}, children: "Active" })) : (_jsx("div", { style: {
|
|
124
|
+
fontSize: "11px",
|
|
125
|
+
padding: "4px 8px",
|
|
126
|
+
borderRadius: "4px",
|
|
127
|
+
background: "rgba(239, 68, 68, 0.1)",
|
|
128
|
+
color: "#ef4444",
|
|
129
|
+
fontWeight: 600,
|
|
130
|
+
}, children: "Inactive" }))] }, key.id));
|
|
131
|
+
}) }), error && (_jsx("div", { style: {
|
|
132
|
+
padding: "12px",
|
|
133
|
+
background: "rgba(239, 68, 68, 0.1)",
|
|
134
|
+
border: "1px solid rgba(239, 68, 68, 0.3)",
|
|
135
|
+
borderRadius: "6px",
|
|
136
|
+
marginBottom: "16px",
|
|
137
|
+
}, children: _jsx("p", { style: {
|
|
138
|
+
margin: 0,
|
|
139
|
+
fontSize: "13px",
|
|
140
|
+
color: "#ef4444",
|
|
141
|
+
lineHeight: "1.5",
|
|
142
|
+
}, children: error }) })), _jsxs("div", { style: { display: "flex", gap: "12px" }, children: [_jsx("button", { type: "button", onClick: onCancel, disabled: loading, style: {
|
|
143
|
+
flex: 1,
|
|
144
|
+
padding: "12px",
|
|
145
|
+
background: "transparent",
|
|
146
|
+
border: "1px solid var(--ai-border-primary, #374151)",
|
|
147
|
+
borderRadius: "8px",
|
|
148
|
+
color: "var(--ai-text-secondary, #9ca3af)",
|
|
149
|
+
fontSize: "14px",
|
|
150
|
+
fontWeight: 600,
|
|
151
|
+
cursor: loading ? "not-allowed" : "pointer",
|
|
152
|
+
opacity: loading ? 0.5 : 1,
|
|
153
|
+
transition: "all 0.2s ease",
|
|
154
|
+
}, onMouseEnter: (e) => {
|
|
155
|
+
if (!loading) {
|
|
156
|
+
e.currentTarget.style.background =
|
|
157
|
+
"var(--ai-input-bg, #111827)";
|
|
158
|
+
e.currentTarget.style.borderColor =
|
|
159
|
+
"var(--ai-border-hover, #4b5563)";
|
|
160
|
+
}
|
|
161
|
+
}, onMouseLeave: (e) => {
|
|
162
|
+
if (!loading) {
|
|
163
|
+
e.currentTarget.style.background = "transparent";
|
|
164
|
+
e.currentTarget.style.borderColor =
|
|
165
|
+
"var(--ai-border-primary, #374151)";
|
|
166
|
+
}
|
|
167
|
+
}, children: "Annuler" }), _jsx("button", { type: "submit", disabled: loading || !selectedKeyId, style: {
|
|
168
|
+
flex: 1,
|
|
169
|
+
padding: "12px",
|
|
170
|
+
background: loading
|
|
171
|
+
? "var(--ai-input-bg, #111827)"
|
|
172
|
+
: "linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)",
|
|
173
|
+
border: "none",
|
|
174
|
+
borderRadius: "8px",
|
|
175
|
+
color: "#ffffff",
|
|
176
|
+
fontSize: "14px",
|
|
177
|
+
fontWeight: 600,
|
|
178
|
+
cursor: loading || !selectedKeyId ? "not-allowed" : "pointer",
|
|
179
|
+
opacity: loading || !selectedKeyId ? 0.5 : 1,
|
|
180
|
+
transition: "all 0.2s ease",
|
|
181
|
+
}, onMouseEnter: (e) => {
|
|
182
|
+
if (!loading && selectedKeyId) {
|
|
183
|
+
e.currentTarget.style.transform = "translateY(-1px)";
|
|
184
|
+
e.currentTarget.style.boxShadow =
|
|
185
|
+
"0 8px 20px rgba(139, 92, 246, 0.4)";
|
|
186
|
+
}
|
|
187
|
+
}, onMouseLeave: (e) => {
|
|
188
|
+
if (!loading && selectedKeyId) {
|
|
189
|
+
e.currentTarget.style.transform = "none";
|
|
190
|
+
e.currentTarget.style.boxShadow = "none";
|
|
191
|
+
}
|
|
192
|
+
}, children: loading ? "Connexion..." : "Continuer" })] })] })] })] }));
|
|
193
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBSigninModal.d.ts","sourceRoot":"","sources":["../../src/components/LBSigninModal.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LBSigninModal.d.ts","sourceRoot":"","sources":["../../src/components/LBSigninModal.tsx"],"names":[],"mappings":"AAMA,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,kDA0bpE"}
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
4
|
import { useLB } from "../context/LBAuthProvider";
|
|
5
|
+
import { LBApiKeySelector } from "./LBApiKeySelector";
|
|
5
6
|
export function LBSigninModal({ isOpen, onClose }) {
|
|
6
7
|
// Vérifier si LBProvider est disponible
|
|
7
8
|
let login;
|
|
9
|
+
let selectApiKeyWithToken;
|
|
10
|
+
let apiKeys = [];
|
|
11
|
+
let lbStatus;
|
|
8
12
|
try {
|
|
9
13
|
const lbContext = useLB();
|
|
10
14
|
login = lbContext.login;
|
|
15
|
+
selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
|
|
16
|
+
apiKeys = lbContext.apiKeys || [];
|
|
17
|
+
lbStatus = lbContext.status;
|
|
11
18
|
}
|
|
12
19
|
catch {
|
|
13
20
|
// LBProvider n'est pas disponible, ne pas rendre le modal
|
|
@@ -17,6 +24,13 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
17
24
|
const [password, setPassword] = useState("");
|
|
18
25
|
const [loading, setLoading] = useState(false);
|
|
19
26
|
const [error, setError] = useState("");
|
|
27
|
+
const [showKeySelector, setShowKeySelector] = useState(false);
|
|
28
|
+
// Si le status est "needs_key_selection", afficher le sélecteur
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (lbStatus === "needs_key_selection" && apiKeys.length > 0) {
|
|
31
|
+
setShowKeySelector(true);
|
|
32
|
+
}
|
|
33
|
+
}, [lbStatus, apiKeys.length]);
|
|
20
34
|
if (!isOpen || !login)
|
|
21
35
|
return null;
|
|
22
36
|
const handleSubmit = async (e) => {
|
|
@@ -26,7 +40,14 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
26
40
|
try {
|
|
27
41
|
const result = await login(email, password);
|
|
28
42
|
if (result.success) {
|
|
29
|
-
|
|
43
|
+
if (result.needsKeySelection) {
|
|
44
|
+
// L'utilisateur doit choisir une clé API
|
|
45
|
+
setShowKeySelector(true);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
// Connexion réussie, fermer le modal
|
|
49
|
+
onClose();
|
|
50
|
+
}
|
|
30
51
|
}
|
|
31
52
|
else {
|
|
32
53
|
setError(result.error || "Échec de la connexion");
|
|
@@ -39,6 +60,29 @@ export function LBSigninModal({ isOpen, onClose }) {
|
|
|
39
60
|
setLoading(false);
|
|
40
61
|
}
|
|
41
62
|
};
|
|
63
|
+
const handleKeySelect = async (apiKeyId) => {
|
|
64
|
+
if (!selectApiKeyWithToken)
|
|
65
|
+
return;
|
|
66
|
+
try {
|
|
67
|
+
await selectApiKeyWithToken(apiKeyId);
|
|
68
|
+
setShowKeySelector(false);
|
|
69
|
+
onClose();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
setError(err instanceof Error ? err.message : "Erreur lors de la sélection");
|
|
73
|
+
setShowKeySelector(false);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const handleCancelKeySelection = () => {
|
|
77
|
+
setShowKeySelector(false);
|
|
78
|
+
setEmail("");
|
|
79
|
+
setPassword("");
|
|
80
|
+
setError("");
|
|
81
|
+
};
|
|
82
|
+
// Si on doit afficher le sélecteur de clés
|
|
83
|
+
if (showKeySelector && apiKeys.length > 0) {
|
|
84
|
+
return (_jsx(LBApiKeySelector, { apiKeys: apiKeys, onSelect: handleKeySelect, onCancel: handleCancelKeySelection, isOpen: true }));
|
|
85
|
+
}
|
|
42
86
|
const handleKeyDown = (e) => {
|
|
43
87
|
if (e.key === "Escape") {
|
|
44
88
|
onClose();
|
|
@@ -18,13 +18,16 @@ interface LBContextValue extends LBAuthState {
|
|
|
18
18
|
login: (email: string, password: string) => Promise<{
|
|
19
19
|
success: boolean;
|
|
20
20
|
error?: string;
|
|
21
|
+
needsKeySelection?: boolean;
|
|
21
22
|
}>;
|
|
22
23
|
/** Fonction de déconnexion */
|
|
23
24
|
logout: () => Promise<void>;
|
|
24
25
|
/** Récupère les clés API de l'utilisateur */
|
|
25
26
|
fetchApiKeys: (accessToken: string) => Promise<LBApiKey[]>;
|
|
26
|
-
/** Sélectionne une clé API et crée une session */
|
|
27
|
+
/** Sélectionne une clé API et crée une session (après login) */
|
|
27
28
|
selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
|
|
29
|
+
/** Sélectionne une clé API avec le token stocké */
|
|
30
|
+
selectApiKeyWithToken: (apiKeyId: string) => Promise<void>;
|
|
28
31
|
/** Recharge l'état de la session */
|
|
29
32
|
refreshSession: () => Promise<void>;
|
|
30
33
|
/** Clés API disponibles */
|
|
@@ -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,EAIT,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;CAC1D;AAED,UAAU,cAAe,SAAQ,WAAW;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,CACL,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,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,EAIT,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;CAC1D;AAED,UAAU,cAAe,SAAQ,WAAW;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,CACL,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAChF,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;CACtB;AAID,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,EAAE,QAA2B,EACpC,QAA2B,EAC3B,cAAc,GACf,EAAE,eAAe,2CAkSjB;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,cAAc,CAMtC"}
|
|
@@ -51,16 +51,22 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
51
51
|
*/
|
|
52
52
|
const fetchApiKeys = useCallback(async (token) => {
|
|
53
53
|
try {
|
|
54
|
+
console.log("[LBProvider] Fetching API keys with token:", token.substring(0, 20) + "...");
|
|
54
55
|
const response = await fetch(`${proxyUrl}/public/user/api-keys`, {
|
|
55
56
|
headers: {
|
|
56
57
|
Authorization: `Bearer ${token}`,
|
|
58
|
+
"Content-Type": "application/json",
|
|
57
59
|
},
|
|
58
60
|
credentials: "include",
|
|
59
61
|
});
|
|
62
|
+
console.log("[LBProvider] API keys response status:", response.status);
|
|
60
63
|
if (!response.ok) {
|
|
61
|
-
|
|
64
|
+
const errorData = await response.json().catch(() => ({}));
|
|
65
|
+
console.error("[LBProvider] Failed to fetch API keys:", errorData);
|
|
66
|
+
throw new Error(errorData.message || "Failed to fetch API keys");
|
|
62
67
|
}
|
|
63
68
|
const data = await response.json();
|
|
69
|
+
console.log("[LBProvider] API keys received:", data);
|
|
64
70
|
const keys = data.apiKeys || data;
|
|
65
71
|
setApiKeys(keys);
|
|
66
72
|
return keys;
|
|
@@ -75,6 +81,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
75
81
|
*/
|
|
76
82
|
const selectApiKey = useCallback(async (token, apiKeyId) => {
|
|
77
83
|
try {
|
|
84
|
+
console.log("[LBProvider] Selecting API key:", apiKeyId);
|
|
78
85
|
setState((prev) => ({ ...prev, status: "loading" }));
|
|
79
86
|
const response = await fetch(`${proxyUrl}/auth/session`, {
|
|
80
87
|
method: "POST",
|
|
@@ -85,10 +92,14 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
85
92
|
body: JSON.stringify({ api_key_id: apiKeyId }),
|
|
86
93
|
credentials: "include",
|
|
87
94
|
});
|
|
95
|
+
console.log("[LBProvider] Session response status:", response.status);
|
|
88
96
|
if (!response.ok) {
|
|
89
|
-
|
|
97
|
+
const errorData = await response.json().catch(() => ({}));
|
|
98
|
+
console.error("[LBProvider] Failed to create session:", errorData);
|
|
99
|
+
throw new Error(errorData.message || "Failed to create session");
|
|
90
100
|
}
|
|
91
101
|
const sessionResult = await response.json();
|
|
102
|
+
console.log("[LBProvider] Session created successfully:", sessionResult);
|
|
92
103
|
setState({
|
|
93
104
|
status: "ready",
|
|
94
105
|
user: state.user,
|
|
@@ -101,6 +112,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
101
112
|
},
|
|
102
113
|
});
|
|
103
114
|
setAccessToken(undefined); // Nettoyer l'access token temporaire
|
|
115
|
+
setApiKeys([]); // Nettoyer les clés API temporaires
|
|
104
116
|
onStatusChange?.("ready");
|
|
105
117
|
}
|
|
106
118
|
catch (error) {
|
|
@@ -113,10 +125,12 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
113
125
|
}
|
|
114
126
|
}, [proxyUrl, state.user, onStatusChange]);
|
|
115
127
|
/**
|
|
116
|
-
* Connexion utilisateur
|
|
128
|
+
* Connexion utilisateur (étape 1 : login)
|
|
129
|
+
* Retourne le token et les clés API sans créer de session
|
|
117
130
|
*/
|
|
118
131
|
const login = useCallback(async (email, password) => {
|
|
119
132
|
try {
|
|
133
|
+
console.log("[LBProvider] Login attempt:", email);
|
|
120
134
|
setState((prev) => ({ ...prev, status: "loading" }));
|
|
121
135
|
const response = await fetch(`${proxyUrl}/auth/login`, {
|
|
122
136
|
method: "POST",
|
|
@@ -124,9 +138,11 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
124
138
|
body: JSON.stringify({ email, password }),
|
|
125
139
|
credentials: "include",
|
|
126
140
|
});
|
|
141
|
+
console.log("[LBProvider] Login response status:", response.status);
|
|
127
142
|
if (!response.ok) {
|
|
128
143
|
const error = await response.json();
|
|
129
144
|
const errorMessage = error.message || "Login failed";
|
|
145
|
+
console.error("[LBProvider] Login failed:", errorMessage);
|
|
130
146
|
setState({
|
|
131
147
|
status: "needs_auth",
|
|
132
148
|
error: errorMessage,
|
|
@@ -134,41 +150,53 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
134
150
|
return { success: false, error: errorMessage };
|
|
135
151
|
}
|
|
136
152
|
const result = await response.json();
|
|
153
|
+
console.log("[LBProvider] Login successful:", result.user?.email);
|
|
137
154
|
const token = result.accessToken;
|
|
138
155
|
setAccessToken(token);
|
|
139
156
|
setState({
|
|
140
157
|
status: "loading",
|
|
141
158
|
user: result.user,
|
|
142
159
|
});
|
|
143
|
-
// Récupérer les clés API
|
|
160
|
+
// Récupérer les clés API
|
|
144
161
|
try {
|
|
145
162
|
const keys = await fetchApiKeys(token);
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
await selectApiKey(token, activeKey.id);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
// Aucune clé active trouvée
|
|
163
|
+
console.log("[LBProvider] Fetched keys:", keys.length);
|
|
164
|
+
if (keys.length === 0) {
|
|
165
|
+
console.warn("[LBProvider] No API keys found for user");
|
|
153
166
|
setState({
|
|
154
167
|
status: "ready",
|
|
155
168
|
user: result.user,
|
|
169
|
+
error: "Aucune clé API disponible",
|
|
156
170
|
});
|
|
171
|
+
return { success: true, needsKeySelection: false };
|
|
172
|
+
}
|
|
173
|
+
// Si une seule clé active, la sélectionner automatiquement
|
|
174
|
+
const activeKeys = keys.filter((k) => k.isActive);
|
|
175
|
+
if (activeKeys.length === 1) {
|
|
176
|
+
console.log("[LBProvider] Auto-selecting single active key");
|
|
177
|
+
await selectApiKey(token, activeKeys[0].id);
|
|
178
|
+
return { success: true, needsKeySelection: false };
|
|
157
179
|
}
|
|
158
|
-
|
|
180
|
+
// Sinon, laisser l'utilisateur choisir
|
|
181
|
+
console.log("[LBProvider] Multiple keys available, user needs to select");
|
|
182
|
+
setState({
|
|
183
|
+
status: "needs_key_selection",
|
|
184
|
+
user: result.user,
|
|
185
|
+
});
|
|
186
|
+
return { success: true, needsKeySelection: true };
|
|
159
187
|
}
|
|
160
188
|
catch (keyError) {
|
|
161
|
-
console.error("[LBProvider] Failed to fetch
|
|
162
|
-
// Login réussi mais impossible de récupérer les clés
|
|
189
|
+
console.error("[LBProvider] Failed to fetch API keys:", keyError);
|
|
163
190
|
setState({
|
|
164
|
-
status: "
|
|
165
|
-
|
|
191
|
+
status: "needs_auth",
|
|
192
|
+
error: "Impossible de récupérer les clés API",
|
|
166
193
|
});
|
|
167
|
-
return { success:
|
|
194
|
+
return { success: false, error: "Impossible de récupérer les clés API" };
|
|
168
195
|
}
|
|
169
196
|
}
|
|
170
197
|
catch (error) {
|
|
171
198
|
const message = error instanceof Error ? error.message : "Une erreur s'est produite";
|
|
199
|
+
console.error("[LBProvider] Login error:", message);
|
|
172
200
|
setState({
|
|
173
201
|
status: "error",
|
|
174
202
|
error: message,
|
|
@@ -176,6 +204,15 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
176
204
|
return { success: false, error: message };
|
|
177
205
|
}
|
|
178
206
|
}, [proxyUrl, fetchApiKeys, selectApiKey]);
|
|
207
|
+
/**
|
|
208
|
+
* Sélectionne une clé API avec le token déjà stocké (après login)
|
|
209
|
+
*/
|
|
210
|
+
const selectApiKeyWithToken = useCallback(async (apiKeyId) => {
|
|
211
|
+
if (!accessToken) {
|
|
212
|
+
throw new Error("No access token available");
|
|
213
|
+
}
|
|
214
|
+
await selectApiKey(accessToken, apiKeyId);
|
|
215
|
+
}, [accessToken, selectApiKey]);
|
|
179
216
|
/**
|
|
180
217
|
* Déconnexion
|
|
181
218
|
*/
|
|
@@ -208,6 +245,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
208
245
|
logout,
|
|
209
246
|
fetchApiKeys,
|
|
210
247
|
selectApiKey,
|
|
248
|
+
selectApiKeyWithToken,
|
|
211
249
|
refreshSession,
|
|
212
250
|
apiKeys,
|
|
213
251
|
accessToken,
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export * from "./components/AiSettingsButton";
|
|
|
21
21
|
export * from "./components/AiStatusButton";
|
|
22
22
|
export * from "./components/LBConnectButton";
|
|
23
23
|
export * from "./components/LBSigninModal";
|
|
24
|
+
export * from "./components/LBApiKeySelector";
|
|
24
25
|
export * from "./components/LBKeyPicker";
|
|
25
26
|
export * from "./components/ErrorToast";
|
|
26
27
|
export * from "./components/UsageToast";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AAGxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AAGzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,eAAe,CAAC;AAG9B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,0BAA0B,CAAC;AAGzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAG9B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,SAAS,CAAC;AAGxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,0BAA0B,CAAC;AAGzC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,wBAAwB,CAAC;AACvC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,eAAe,CAAC;AAG9B,cAAc,4BAA4B,CAAC;AAC3C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,0BAA0B,CAAC;AAGzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,yBAAyB,CAAC;AAGxC,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAG9B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kCAAkC,CAAC;AACjD,cAAc,+BAA+B,CAAC;AAC9C,cAAc,iCAAiC,CAAC;AAChD,cAAc,mCAAmC,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export * from "./components/AiSettingsButton";
|
|
|
25
25
|
export * from "./components/AiStatusButton";
|
|
26
26
|
export * from "./components/LBConnectButton";
|
|
27
27
|
export * from "./components/LBSigninModal";
|
|
28
|
+
export * from "./components/LBApiKeySelector";
|
|
28
29
|
export * from "./components/LBKeyPicker";
|
|
29
30
|
// Toast system
|
|
30
31
|
export * from "./components/ErrorToast";
|
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.44",
|
|
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.32"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/react": "^19.2.0",
|
|
@@ -394,7 +394,10 @@ export function AiStatusButton({
|
|
|
394
394
|
>
|
|
395
395
|
<button
|
|
396
396
|
onClick={() =>
|
|
397
|
-
window.open(
|
|
397
|
+
window.open(
|
|
398
|
+
"https://prompt.lastbrain.io/metrics",
|
|
399
|
+
"_blank"
|
|
400
|
+
)
|
|
398
401
|
}
|
|
399
402
|
style={{
|
|
400
403
|
flex: 1,
|
|
@@ -424,7 +427,10 @@ export function AiStatusButton({
|
|
|
424
427
|
</button>
|
|
425
428
|
<button
|
|
426
429
|
onClick={() =>
|
|
427
|
-
window.open(
|
|
430
|
+
window.open(
|
|
431
|
+
"https://prompt.lastbrain.io/settings",
|
|
432
|
+
"_blank"
|
|
433
|
+
)
|
|
428
434
|
}
|
|
429
435
|
style={{
|
|
430
436
|
flex: 1,
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import type { LBApiKey } from "@lastbrain/ai-ui-core";
|
|
3
|
+
|
|
4
|
+
interface LBApiKeySelectorProps {
|
|
5
|
+
apiKeys: LBApiKey[];
|
|
6
|
+
onSelect: (apiKeyId: string) => Promise<void>;
|
|
7
|
+
onCancel: () => void;
|
|
8
|
+
isOpen: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function LBApiKeySelector({
|
|
12
|
+
apiKeys,
|
|
13
|
+
onSelect,
|
|
14
|
+
onCancel,
|
|
15
|
+
isOpen,
|
|
16
|
+
}: LBApiKeySelectorProps) {
|
|
17
|
+
const [selectedKeyId, setSelectedKeyId] = useState<string>(
|
|
18
|
+
apiKeys.find((k) => k.isActive)?.id || apiKeys[0]?.id || ""
|
|
19
|
+
);
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [error, setError] = useState("");
|
|
22
|
+
|
|
23
|
+
if (!isOpen) return null;
|
|
24
|
+
|
|
25
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
if (!selectedKeyId) {
|
|
28
|
+
setError("Veuillez sélectionner une clé API");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setLoading(true);
|
|
33
|
+
setError("");
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await onSelect(selectedKeyId);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
setError(
|
|
39
|
+
err instanceof Error ? err.message : "Erreur lors de la sélection"
|
|
40
|
+
);
|
|
41
|
+
setLoading(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
style={{
|
|
48
|
+
position: "fixed",
|
|
49
|
+
top: 0,
|
|
50
|
+
left: 0,
|
|
51
|
+
right: 0,
|
|
52
|
+
bottom: 0,
|
|
53
|
+
zIndex: 10000,
|
|
54
|
+
display: "flex",
|
|
55
|
+
alignItems: "center",
|
|
56
|
+
justifyContent: "center",
|
|
57
|
+
padding: "16px",
|
|
58
|
+
}}
|
|
59
|
+
onClick={onCancel}
|
|
60
|
+
>
|
|
61
|
+
{/* Backdrop */}
|
|
62
|
+
<div
|
|
63
|
+
style={{
|
|
64
|
+
position: "absolute",
|
|
65
|
+
top: 0,
|
|
66
|
+
left: 0,
|
|
67
|
+
right: 0,
|
|
68
|
+
bottom: 0,
|
|
69
|
+
background: "var(--ai-overlay-bg, rgba(0, 0, 0, 0.75))",
|
|
70
|
+
backdropFilter: "blur(8px)",
|
|
71
|
+
}}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
{/* Modal */}
|
|
75
|
+
<div
|
|
76
|
+
style={{
|
|
77
|
+
position: "relative",
|
|
78
|
+
background: "var(--ai-modal-bg, #1f2937)",
|
|
79
|
+
border: "1px solid var(--ai-border-primary, #374151)",
|
|
80
|
+
borderRadius: "12px",
|
|
81
|
+
padding: "24px",
|
|
82
|
+
maxWidth: "500px",
|
|
83
|
+
width: "100%",
|
|
84
|
+
boxShadow: "0 20px 50px rgba(0, 0, 0, 0.5)",
|
|
85
|
+
}}
|
|
86
|
+
onClick={(e) => e.stopPropagation()}
|
|
87
|
+
>
|
|
88
|
+
<h2
|
|
89
|
+
style={{
|
|
90
|
+
margin: "0 0 8px 0",
|
|
91
|
+
fontSize: "20px",
|
|
92
|
+
fontWeight: 600,
|
|
93
|
+
color: "var(--ai-text-primary, #f9fafb)",
|
|
94
|
+
textAlign: "center",
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
Sélectionnez une clé API
|
|
98
|
+
</h2>
|
|
99
|
+
|
|
100
|
+
<p
|
|
101
|
+
style={{
|
|
102
|
+
margin: "0 0 24px 0",
|
|
103
|
+
fontSize: "14px",
|
|
104
|
+
color: "var(--ai-text-secondary, #9ca3af)",
|
|
105
|
+
textAlign: "center",
|
|
106
|
+
lineHeight: "1.5",
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
Choisissez la clé API à utiliser pour vos requêtes IA
|
|
110
|
+
</p>
|
|
111
|
+
|
|
112
|
+
<form onSubmit={handleSubmit}>
|
|
113
|
+
{/* Liste des clés API */}
|
|
114
|
+
<div
|
|
115
|
+
style={{
|
|
116
|
+
display: "flex",
|
|
117
|
+
flexDirection: "column",
|
|
118
|
+
gap: "8px",
|
|
119
|
+
marginBottom: "24px",
|
|
120
|
+
maxHeight: "300px",
|
|
121
|
+
overflowY: "auto",
|
|
122
|
+
}}
|
|
123
|
+
>
|
|
124
|
+
{apiKeys.map((key) => {
|
|
125
|
+
const isSelected = key.id === selectedKeyId;
|
|
126
|
+
const isActive = key.isActive;
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<label
|
|
130
|
+
key={key.id}
|
|
131
|
+
style={{
|
|
132
|
+
display: "flex",
|
|
133
|
+
alignItems: "center",
|
|
134
|
+
padding: "12px 16px",
|
|
135
|
+
background: isSelected
|
|
136
|
+
? "var(--ai-input-bg-focus, #374151)"
|
|
137
|
+
: "var(--ai-input-bg, #111827)",
|
|
138
|
+
border: `2px solid ${
|
|
139
|
+
isSelected
|
|
140
|
+
? "var(--ai-accent-primary, #8b5cf6)"
|
|
141
|
+
: "var(--ai-border-primary, #374151)"
|
|
142
|
+
}`,
|
|
143
|
+
borderRadius: "8px",
|
|
144
|
+
cursor: isActive ? "pointer" : "not-allowed",
|
|
145
|
+
opacity: isActive ? 1 : 0.5,
|
|
146
|
+
transition: "all 0.2s ease",
|
|
147
|
+
}}
|
|
148
|
+
onMouseEnter={(e) => {
|
|
149
|
+
if (isActive && !isSelected) {
|
|
150
|
+
e.currentTarget.style.borderColor =
|
|
151
|
+
"var(--ai-border-hover, #4b5563)";
|
|
152
|
+
e.currentTarget.style.background =
|
|
153
|
+
"var(--ai-input-bg-focus, #374151)";
|
|
154
|
+
}
|
|
155
|
+
}}
|
|
156
|
+
onMouseLeave={(e) => {
|
|
157
|
+
if (isActive && !isSelected) {
|
|
158
|
+
e.currentTarget.style.borderColor =
|
|
159
|
+
"var(--ai-border-primary, #374151)";
|
|
160
|
+
e.currentTarget.style.background =
|
|
161
|
+
"var(--ai-input-bg, #111827)";
|
|
162
|
+
}
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<input
|
|
166
|
+
type="radio"
|
|
167
|
+
name="apiKey"
|
|
168
|
+
value={key.id}
|
|
169
|
+
checked={isSelected}
|
|
170
|
+
disabled={!isActive}
|
|
171
|
+
onChange={(e) => setSelectedKeyId(e.target.value)}
|
|
172
|
+
style={{
|
|
173
|
+
marginRight: "12px",
|
|
174
|
+
accentColor: "var(--ai-accent-primary, #8b5cf6)",
|
|
175
|
+
cursor: isActive ? "pointer" : "not-allowed",
|
|
176
|
+
}}
|
|
177
|
+
/>
|
|
178
|
+
<div style={{ flex: 1 }}>
|
|
179
|
+
<div
|
|
180
|
+
style={{
|
|
181
|
+
fontSize: "14px",
|
|
182
|
+
fontWeight: 500,
|
|
183
|
+
color: "var(--ai-text-primary, #f9fafb)",
|
|
184
|
+
marginBottom: "4px",
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
{key.name}
|
|
188
|
+
</div>
|
|
189
|
+
<div
|
|
190
|
+
style={{
|
|
191
|
+
fontSize: "12px",
|
|
192
|
+
color: "var(--ai-text-secondary, #9ca3af)",
|
|
193
|
+
fontFamily: "monospace",
|
|
194
|
+
}}
|
|
195
|
+
>
|
|
196
|
+
{key.keyPrefix || key.id.substring(0, 12) + "..."}
|
|
197
|
+
</div>
|
|
198
|
+
</div>
|
|
199
|
+
{isActive ? (
|
|
200
|
+
<div
|
|
201
|
+
style={{
|
|
202
|
+
fontSize: "11px",
|
|
203
|
+
padding: "4px 8px",
|
|
204
|
+
borderRadius: "4px",
|
|
205
|
+
background: "rgba(16, 185, 129, 0.1)",
|
|
206
|
+
color: "#10b981",
|
|
207
|
+
fontWeight: 600,
|
|
208
|
+
}}
|
|
209
|
+
>
|
|
210
|
+
Active
|
|
211
|
+
</div>
|
|
212
|
+
) : (
|
|
213
|
+
<div
|
|
214
|
+
style={{
|
|
215
|
+
fontSize: "11px",
|
|
216
|
+
padding: "4px 8px",
|
|
217
|
+
borderRadius: "4px",
|
|
218
|
+
background: "rgba(239, 68, 68, 0.1)",
|
|
219
|
+
color: "#ef4444",
|
|
220
|
+
fontWeight: 600,
|
|
221
|
+
}}
|
|
222
|
+
>
|
|
223
|
+
Inactive
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
</label>
|
|
227
|
+
);
|
|
228
|
+
})}
|
|
229
|
+
</div>
|
|
230
|
+
|
|
231
|
+
{error && (
|
|
232
|
+
<div
|
|
233
|
+
style={{
|
|
234
|
+
padding: "12px",
|
|
235
|
+
background: "rgba(239, 68, 68, 0.1)",
|
|
236
|
+
border: "1px solid rgba(239, 68, 68, 0.3)",
|
|
237
|
+
borderRadius: "6px",
|
|
238
|
+
marginBottom: "16px",
|
|
239
|
+
}}
|
|
240
|
+
>
|
|
241
|
+
<p
|
|
242
|
+
style={{
|
|
243
|
+
margin: 0,
|
|
244
|
+
fontSize: "13px",
|
|
245
|
+
color: "#ef4444",
|
|
246
|
+
lineHeight: "1.5",
|
|
247
|
+
}}
|
|
248
|
+
>
|
|
249
|
+
{error}
|
|
250
|
+
</p>
|
|
251
|
+
</div>
|
|
252
|
+
)}
|
|
253
|
+
|
|
254
|
+
{/* Boutons */}
|
|
255
|
+
<div style={{ display: "flex", gap: "12px" }}>
|
|
256
|
+
<button
|
|
257
|
+
type="button"
|
|
258
|
+
onClick={onCancel}
|
|
259
|
+
disabled={loading}
|
|
260
|
+
style={{
|
|
261
|
+
flex: 1,
|
|
262
|
+
padding: "12px",
|
|
263
|
+
background: "transparent",
|
|
264
|
+
border: "1px solid var(--ai-border-primary, #374151)",
|
|
265
|
+
borderRadius: "8px",
|
|
266
|
+
color: "var(--ai-text-secondary, #9ca3af)",
|
|
267
|
+
fontSize: "14px",
|
|
268
|
+
fontWeight: 600,
|
|
269
|
+
cursor: loading ? "not-allowed" : "pointer",
|
|
270
|
+
opacity: loading ? 0.5 : 1,
|
|
271
|
+
transition: "all 0.2s ease",
|
|
272
|
+
}}
|
|
273
|
+
onMouseEnter={(e) => {
|
|
274
|
+
if (!loading) {
|
|
275
|
+
e.currentTarget.style.background =
|
|
276
|
+
"var(--ai-input-bg, #111827)";
|
|
277
|
+
e.currentTarget.style.borderColor =
|
|
278
|
+
"var(--ai-border-hover, #4b5563)";
|
|
279
|
+
}
|
|
280
|
+
}}
|
|
281
|
+
onMouseLeave={(e) => {
|
|
282
|
+
if (!loading) {
|
|
283
|
+
e.currentTarget.style.background = "transparent";
|
|
284
|
+
e.currentTarget.style.borderColor =
|
|
285
|
+
"var(--ai-border-primary, #374151)";
|
|
286
|
+
}
|
|
287
|
+
}}
|
|
288
|
+
>
|
|
289
|
+
Annuler
|
|
290
|
+
</button>
|
|
291
|
+
<button
|
|
292
|
+
type="submit"
|
|
293
|
+
disabled={loading || !selectedKeyId}
|
|
294
|
+
style={{
|
|
295
|
+
flex: 1,
|
|
296
|
+
padding: "12px",
|
|
297
|
+
background: loading
|
|
298
|
+
? "var(--ai-input-bg, #111827)"
|
|
299
|
+
: "linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)",
|
|
300
|
+
border: "none",
|
|
301
|
+
borderRadius: "8px",
|
|
302
|
+
color: "#ffffff",
|
|
303
|
+
fontSize: "14px",
|
|
304
|
+
fontWeight: 600,
|
|
305
|
+
cursor:
|
|
306
|
+
loading || !selectedKeyId ? "not-allowed" : "pointer",
|
|
307
|
+
opacity: loading || !selectedKeyId ? 0.5 : 1,
|
|
308
|
+
transition: "all 0.2s ease",
|
|
309
|
+
}}
|
|
310
|
+
onMouseEnter={(e) => {
|
|
311
|
+
if (!loading && selectedKeyId) {
|
|
312
|
+
e.currentTarget.style.transform = "translateY(-1px)";
|
|
313
|
+
e.currentTarget.style.boxShadow =
|
|
314
|
+
"0 8px 20px rgba(139, 92, 246, 0.4)";
|
|
315
|
+
}
|
|
316
|
+
}}
|
|
317
|
+
onMouseLeave={(e) => {
|
|
318
|
+
if (!loading && selectedKeyId) {
|
|
319
|
+
e.currentTarget.style.transform = "none";
|
|
320
|
+
e.currentTarget.style.boxShadow = "none";
|
|
321
|
+
}
|
|
322
|
+
}}
|
|
323
|
+
>
|
|
324
|
+
{loading ? "Connexion..." : "Continuer"}
|
|
325
|
+
</button>
|
|
326
|
+
</div>
|
|
327
|
+
</form>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { useState } from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
4
|
import { useLB } from "../context/LBAuthProvider";
|
|
5
|
+
import { LBApiKeySelector } from "./LBApiKeySelector";
|
|
5
6
|
|
|
6
7
|
export interface LBSigninModalProps {
|
|
7
8
|
isOpen: boolean;
|
|
@@ -14,12 +15,18 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
14
15
|
| ((
|
|
15
16
|
email: string,
|
|
16
17
|
password: string
|
|
17
|
-
) => Promise<{ success: boolean; error?: string }>)
|
|
18
|
+
) => Promise<{ success: boolean; error?: string; needsKeySelection?: boolean }>)
|
|
18
19
|
| undefined;
|
|
20
|
+
let selectApiKeyWithToken: ((apiKeyId: string) => Promise<void>) | undefined;
|
|
21
|
+
let apiKeys: any[] = [];
|
|
22
|
+
let lbStatus: string | undefined;
|
|
19
23
|
|
|
20
24
|
try {
|
|
21
25
|
const lbContext = useLB();
|
|
22
26
|
login = lbContext.login;
|
|
27
|
+
selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
|
|
28
|
+
apiKeys = lbContext.apiKeys || [];
|
|
29
|
+
lbStatus = lbContext.status;
|
|
23
30
|
} catch {
|
|
24
31
|
// LBProvider n'est pas disponible, ne pas rendre le modal
|
|
25
32
|
return null;
|
|
@@ -29,6 +36,14 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
29
36
|
const [password, setPassword] = useState("");
|
|
30
37
|
const [loading, setLoading] = useState(false);
|
|
31
38
|
const [error, setError] = useState("");
|
|
39
|
+
const [showKeySelector, setShowKeySelector] = useState(false);
|
|
40
|
+
|
|
41
|
+
// Si le status est "needs_key_selection", afficher le sélecteur
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (lbStatus === "needs_key_selection" && apiKeys.length > 0) {
|
|
44
|
+
setShowKeySelector(true);
|
|
45
|
+
}
|
|
46
|
+
}, [lbStatus, apiKeys.length]);
|
|
32
47
|
|
|
33
48
|
if (!isOpen || !login) return null;
|
|
34
49
|
|
|
@@ -40,7 +55,13 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
40
55
|
try {
|
|
41
56
|
const result = await login(email, password);
|
|
42
57
|
if (result.success) {
|
|
43
|
-
|
|
58
|
+
if (result.needsKeySelection) {
|
|
59
|
+
// L'utilisateur doit choisir une clé API
|
|
60
|
+
setShowKeySelector(true);
|
|
61
|
+
} else {
|
|
62
|
+
// Connexion réussie, fermer le modal
|
|
63
|
+
onClose();
|
|
64
|
+
}
|
|
44
65
|
} else {
|
|
45
66
|
setError(result.error || "Échec de la connexion");
|
|
46
67
|
}
|
|
@@ -53,6 +74,40 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
53
74
|
}
|
|
54
75
|
};
|
|
55
76
|
|
|
77
|
+
const handleKeySelect = async (apiKeyId: string) => {
|
|
78
|
+
if (!selectApiKeyWithToken) return;
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await selectApiKeyWithToken(apiKeyId);
|
|
82
|
+
setShowKeySelector(false);
|
|
83
|
+
onClose();
|
|
84
|
+
} catch (err) {
|
|
85
|
+
setError(
|
|
86
|
+
err instanceof Error ? err.message : "Erreur lors de la sélection"
|
|
87
|
+
);
|
|
88
|
+
setShowKeySelector(false);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleCancelKeySelection = () => {
|
|
93
|
+
setShowKeySelector(false);
|
|
94
|
+
setEmail("");
|
|
95
|
+
setPassword("");
|
|
96
|
+
setError("");
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Si on doit afficher le sélecteur de clés
|
|
100
|
+
if (showKeySelector && apiKeys.length > 0) {
|
|
101
|
+
return (
|
|
102
|
+
<LBApiKeySelector
|
|
103
|
+
apiKeys={apiKeys}
|
|
104
|
+
onSelect={handleKeySelect}
|
|
105
|
+
onCancel={handleCancelKeySelection}
|
|
106
|
+
isOpen={true}
|
|
107
|
+
/>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
56
111
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
57
112
|
if (e.key === "Escape") {
|
|
58
113
|
onClose();
|
|
@@ -36,13 +36,15 @@ interface LBContextValue extends LBAuthState {
|
|
|
36
36
|
login: (
|
|
37
37
|
email: string,
|
|
38
38
|
password: string
|
|
39
|
-
) => Promise<{ success: boolean; error?: string }>;
|
|
39
|
+
) => Promise<{ success: boolean; error?: string; needsKeySelection?: boolean }>;
|
|
40
40
|
/** Fonction de déconnexion */
|
|
41
41
|
logout: () => Promise<void>;
|
|
42
42
|
/** Récupère les clés API de l'utilisateur */
|
|
43
43
|
fetchApiKeys: (accessToken: string) => Promise<LBApiKey[]>;
|
|
44
|
-
/** Sélectionne une clé API et crée une session */
|
|
44
|
+
/** Sélectionne une clé API et crée une session (après login) */
|
|
45
45
|
selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
|
|
46
|
+
/** Sélectionne une clé API avec le token stocké */
|
|
47
|
+
selectApiKeyWithToken: (apiKeyId: string) => Promise<void>;
|
|
46
48
|
/** Recharge l'état de la session */
|
|
47
49
|
refreshSession: () => Promise<void>;
|
|
48
50
|
/** Clés API disponibles */
|
|
@@ -106,18 +108,27 @@ export function LBProvider({
|
|
|
106
108
|
const fetchApiKeys = useCallback(
|
|
107
109
|
async (token: string): Promise<LBApiKey[]> => {
|
|
108
110
|
try {
|
|
111
|
+
console.log("[LBProvider] Fetching API keys with token:", token.substring(0, 20) + "...");
|
|
112
|
+
|
|
109
113
|
const response = await fetch(`${proxyUrl}/public/user/api-keys`, {
|
|
110
114
|
headers: {
|
|
111
115
|
Authorization: `Bearer ${token}`,
|
|
116
|
+
"Content-Type": "application/json",
|
|
112
117
|
},
|
|
113
118
|
credentials: "include",
|
|
114
119
|
});
|
|
115
120
|
|
|
121
|
+
console.log("[LBProvider] API keys response status:", response.status);
|
|
122
|
+
|
|
116
123
|
if (!response.ok) {
|
|
117
|
-
|
|
124
|
+
const errorData = await response.json().catch(() => ({}));
|
|
125
|
+
console.error("[LBProvider] Failed to fetch API keys:", errorData);
|
|
126
|
+
throw new Error(errorData.message || "Failed to fetch API keys");
|
|
118
127
|
}
|
|
119
128
|
|
|
120
129
|
const data = await response.json();
|
|
130
|
+
console.log("[LBProvider] API keys received:", data);
|
|
131
|
+
|
|
121
132
|
const keys: LBApiKey[] = data.apiKeys || data;
|
|
122
133
|
setApiKeys(keys);
|
|
123
134
|
return keys;
|
|
@@ -135,6 +146,7 @@ export function LBProvider({
|
|
|
135
146
|
const selectApiKey = useCallback(
|
|
136
147
|
async (token: string, apiKeyId: string): Promise<void> => {
|
|
137
148
|
try {
|
|
149
|
+
console.log("[LBProvider] Selecting API key:", apiKeyId);
|
|
138
150
|
setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
|
|
139
151
|
|
|
140
152
|
const response = await fetch(`${proxyUrl}/auth/session`, {
|
|
@@ -147,11 +159,16 @@ export function LBProvider({
|
|
|
147
159
|
credentials: "include",
|
|
148
160
|
});
|
|
149
161
|
|
|
162
|
+
console.log("[LBProvider] Session response status:", response.status);
|
|
163
|
+
|
|
150
164
|
if (!response.ok) {
|
|
151
|
-
|
|
165
|
+
const errorData = await response.json().catch(() => ({}));
|
|
166
|
+
console.error("[LBProvider] Failed to create session:", errorData);
|
|
167
|
+
throw new Error(errorData.message || "Failed to create session");
|
|
152
168
|
}
|
|
153
169
|
|
|
154
170
|
const sessionResult: LBSessionResult = await response.json();
|
|
171
|
+
console.log("[LBProvider] Session created successfully:", sessionResult);
|
|
155
172
|
|
|
156
173
|
setState({
|
|
157
174
|
status: "ready",
|
|
@@ -166,6 +183,7 @@ export function LBProvider({
|
|
|
166
183
|
});
|
|
167
184
|
|
|
168
185
|
setAccessToken(undefined); // Nettoyer l'access token temporaire
|
|
186
|
+
setApiKeys([]); // Nettoyer les clés API temporaires
|
|
169
187
|
onStatusChange?.("ready");
|
|
170
188
|
} catch (error) {
|
|
171
189
|
const message =
|
|
@@ -181,14 +199,16 @@ export function LBProvider({
|
|
|
181
199
|
);
|
|
182
200
|
|
|
183
201
|
/**
|
|
184
|
-
* Connexion utilisateur
|
|
202
|
+
* Connexion utilisateur (étape 1 : login)
|
|
203
|
+
* Retourne le token et les clés API sans créer de session
|
|
185
204
|
*/
|
|
186
205
|
const login = useCallback(
|
|
187
206
|
async (
|
|
188
207
|
email: string,
|
|
189
208
|
password: string
|
|
190
|
-
): Promise<{ success: boolean; error?: string }> => {
|
|
209
|
+
): Promise<{ success: boolean; error?: string; needsKeySelection?: boolean }> => {
|
|
191
210
|
try {
|
|
211
|
+
console.log("[LBProvider] Login attempt:", email);
|
|
192
212
|
setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
|
|
193
213
|
|
|
194
214
|
const response = await fetch(`${proxyUrl}/auth/login`, {
|
|
@@ -198,9 +218,12 @@ export function LBProvider({
|
|
|
198
218
|
credentials: "include",
|
|
199
219
|
});
|
|
200
220
|
|
|
221
|
+
console.log("[LBProvider] Login response status:", response.status);
|
|
222
|
+
|
|
201
223
|
if (!response.ok) {
|
|
202
224
|
const error = await response.json();
|
|
203
225
|
const errorMessage = error.message || "Login failed";
|
|
226
|
+
console.error("[LBProvider] Login failed:", errorMessage);
|
|
204
227
|
setState({
|
|
205
228
|
status: "needs_auth",
|
|
206
229
|
error: errorMessage,
|
|
@@ -209,6 +232,8 @@ export function LBProvider({
|
|
|
209
232
|
}
|
|
210
233
|
|
|
211
234
|
const result: LBLoginResult = await response.json();
|
|
235
|
+
console.log("[LBProvider] Login successful:", result.user?.email);
|
|
236
|
+
|
|
212
237
|
const token = result.accessToken;
|
|
213
238
|
setAccessToken(token);
|
|
214
239
|
|
|
@@ -217,38 +242,52 @@ export function LBProvider({
|
|
|
217
242
|
user: result.user,
|
|
218
243
|
});
|
|
219
244
|
|
|
220
|
-
// Récupérer les clés API
|
|
245
|
+
// Récupérer les clés API
|
|
221
246
|
try {
|
|
222
247
|
const keys = await fetchApiKeys(token);
|
|
223
|
-
|
|
248
|
+
console.log("[LBProvider] Fetched keys:", keys.length);
|
|
224
249
|
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
await selectApiKey(token, activeKey.id);
|
|
228
|
-
} else {
|
|
229
|
-
// Aucune clé active trouvée
|
|
250
|
+
if (keys.length === 0) {
|
|
251
|
+
console.warn("[LBProvider] No API keys found for user");
|
|
230
252
|
setState({
|
|
231
253
|
status: "ready",
|
|
232
254
|
user: result.user,
|
|
255
|
+
error: "Aucune clé API disponible",
|
|
233
256
|
});
|
|
257
|
+
return { success: true, needsKeySelection: false };
|
|
234
258
|
}
|
|
235
259
|
|
|
236
|
-
|
|
260
|
+
// Si une seule clé active, la sélectionner automatiquement
|
|
261
|
+
const activeKeys = keys.filter((k) => k.isActive);
|
|
262
|
+
if (activeKeys.length === 1) {
|
|
263
|
+
console.log("[LBProvider] Auto-selecting single active key");
|
|
264
|
+
await selectApiKey(token, activeKeys[0].id);
|
|
265
|
+
return { success: true, needsKeySelection: false };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Sinon, laisser l'utilisateur choisir
|
|
269
|
+
console.log("[LBProvider] Multiple keys available, user needs to select");
|
|
270
|
+
setState({
|
|
271
|
+
status: "needs_key_selection",
|
|
272
|
+
user: result.user,
|
|
273
|
+
});
|
|
274
|
+
return { success: true, needsKeySelection: true };
|
|
275
|
+
|
|
237
276
|
} catch (keyError) {
|
|
238
277
|
console.error(
|
|
239
|
-
"[LBProvider] Failed to fetch
|
|
278
|
+
"[LBProvider] Failed to fetch API keys:",
|
|
240
279
|
keyError
|
|
241
280
|
);
|
|
242
|
-
// Login réussi mais impossible de récupérer les clés
|
|
243
281
|
setState({
|
|
244
|
-
status: "
|
|
245
|
-
|
|
282
|
+
status: "needs_auth",
|
|
283
|
+
error: "Impossible de récupérer les clés API",
|
|
246
284
|
});
|
|
247
|
-
return { success:
|
|
285
|
+
return { success: false, error: "Impossible de récupérer les clés API" };
|
|
248
286
|
}
|
|
249
287
|
} catch (error) {
|
|
250
288
|
const message =
|
|
251
289
|
error instanceof Error ? error.message : "Une erreur s'est produite";
|
|
290
|
+
console.error("[LBProvider] Login error:", message);
|
|
252
291
|
setState({
|
|
253
292
|
status: "error",
|
|
254
293
|
error: message,
|
|
@@ -259,6 +298,19 @@ export function LBProvider({
|
|
|
259
298
|
[proxyUrl, fetchApiKeys, selectApiKey]
|
|
260
299
|
);
|
|
261
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Sélectionne une clé API avec le token déjà stocké (après login)
|
|
303
|
+
*/
|
|
304
|
+
const selectApiKeyWithToken = useCallback(
|
|
305
|
+
async (apiKeyId: string): Promise<void> => {
|
|
306
|
+
if (!accessToken) {
|
|
307
|
+
throw new Error("No access token available");
|
|
308
|
+
}
|
|
309
|
+
await selectApiKey(accessToken, apiKeyId);
|
|
310
|
+
},
|
|
311
|
+
[accessToken, selectApiKey]
|
|
312
|
+
);
|
|
313
|
+
|
|
262
314
|
/**
|
|
263
315
|
* Déconnexion
|
|
264
316
|
*/
|
|
@@ -291,6 +343,7 @@ export function LBProvider({
|
|
|
291
343
|
logout,
|
|
292
344
|
fetchApiKeys,
|
|
293
345
|
selectApiKey,
|
|
346
|
+
selectApiKeyWithToken,
|
|
294
347
|
refreshSession,
|
|
295
348
|
apiKeys,
|
|
296
349
|
accessToken,
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,7 @@ export * from "./components/AiSettingsButton";
|
|
|
28
28
|
export * from "./components/AiStatusButton";
|
|
29
29
|
export * from "./components/LBConnectButton";
|
|
30
30
|
export * from "./components/LBSigninModal";
|
|
31
|
+
export * from "./components/LBApiKeySelector";
|
|
31
32
|
export * from "./components/LBKeyPicker";
|
|
32
33
|
|
|
33
34
|
// Toast system
|