@lastbrain/ai-ui-react 1.0.43 → 1.0.45
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 +72 -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 +330 -0
- package/src/components/LBSigninModal.tsx +62 -3
- package/src/context/LBAuthProvider.tsx +117 -20
- 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,kDA0TvB"}
|
|
@@ -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,kDA8bpE"}
|
|
@@ -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;
|
|
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;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;CACtB;AAID,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,EAAE,QAA2B,EACpC,QAA2B,EAC3B,cAAc,GACf,EAAE,eAAe,2CA0UjB;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,70 @@ 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);
|
|
154
|
+
console.log("[LBProvider] Access token received:", result.accessToken ? "YES" : "NO");
|
|
155
|
+
console.log("[LBProvider] Token length:", result.accessToken?.length || 0);
|
|
137
156
|
const token = result.accessToken;
|
|
157
|
+
if (!token) {
|
|
158
|
+
console.error("[LBProvider] No access token in login response!");
|
|
159
|
+
setState({
|
|
160
|
+
status: "needs_auth",
|
|
161
|
+
error: "Token manquant dans la réponse",
|
|
162
|
+
});
|
|
163
|
+
return { success: false, error: "Token manquant dans la réponse" };
|
|
164
|
+
}
|
|
138
165
|
setAccessToken(token);
|
|
139
166
|
setState({
|
|
140
167
|
status: "loading",
|
|
141
168
|
user: result.user,
|
|
142
169
|
});
|
|
143
|
-
// Récupérer les clés API
|
|
170
|
+
// Récupérer les clés API
|
|
171
|
+
console.log("[LBProvider] About to fetch API keys...");
|
|
144
172
|
try {
|
|
145
173
|
const keys = await fetchApiKeys(token);
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
await selectApiKey(token, activeKey.id);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
// Aucune clé active trouvée
|
|
174
|
+
console.log("[LBProvider] Fetched keys:", keys.length);
|
|
175
|
+
if (keys.length === 0) {
|
|
176
|
+
console.warn("[LBProvider] No API keys found for user");
|
|
153
177
|
setState({
|
|
154
178
|
status: "ready",
|
|
155
179
|
user: result.user,
|
|
180
|
+
error: "Aucune clé API disponible",
|
|
156
181
|
});
|
|
182
|
+
return { success: true, needsKeySelection: false };
|
|
157
183
|
}
|
|
158
|
-
|
|
184
|
+
// Si une seule clé active, la sélectionner automatiquement
|
|
185
|
+
const activeKeys = keys.filter((k) => k.isActive);
|
|
186
|
+
if (activeKeys.length === 1) {
|
|
187
|
+
console.log("[LBProvider] Auto-selecting single active key");
|
|
188
|
+
await selectApiKey(token, activeKeys[0].id);
|
|
189
|
+
return { success: true, needsKeySelection: false };
|
|
190
|
+
}
|
|
191
|
+
// Sinon, laisser l'utilisateur choisir
|
|
192
|
+
console.log("[LBProvider] Multiple keys available, user needs to select");
|
|
193
|
+
setState({
|
|
194
|
+
status: "needs_key_selection",
|
|
195
|
+
user: result.user,
|
|
196
|
+
});
|
|
197
|
+
return { success: true, needsKeySelection: true };
|
|
159
198
|
}
|
|
160
199
|
catch (keyError) {
|
|
161
|
-
console.error("[LBProvider] Failed to fetch
|
|
162
|
-
|
|
200
|
+
console.error("[LBProvider] Failed to fetch API keys:");
|
|
201
|
+
console.error("[LBProvider] Error details:", keyError);
|
|
202
|
+
console.error("[LBProvider] Error message:", keyError instanceof Error ? keyError.message : String(keyError));
|
|
203
|
+
console.error("[LBProvider] Error stack:", keyError instanceof Error ? keyError.stack : "No stack");
|
|
163
204
|
setState({
|
|
164
|
-
status: "
|
|
165
|
-
|
|
205
|
+
status: "needs_auth",
|
|
206
|
+
error: "Impossible de récupérer les clés API",
|
|
166
207
|
});
|
|
167
|
-
return {
|
|
208
|
+
return {
|
|
209
|
+
success: false,
|
|
210
|
+
error: "Impossible de récupérer les clés API",
|
|
211
|
+
};
|
|
168
212
|
}
|
|
169
213
|
}
|
|
170
214
|
catch (error) {
|
|
171
215
|
const message = error instanceof Error ? error.message : "Une erreur s'est produite";
|
|
216
|
+
console.error("[LBProvider] Login error:", message);
|
|
172
217
|
setState({
|
|
173
218
|
status: "error",
|
|
174
219
|
error: message,
|
|
@@ -176,6 +221,15 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
176
221
|
return { success: false, error: message };
|
|
177
222
|
}
|
|
178
223
|
}, [proxyUrl, fetchApiKeys, selectApiKey]);
|
|
224
|
+
/**
|
|
225
|
+
* Sélectionne une clé API avec le token déjà stocké (après login)
|
|
226
|
+
*/
|
|
227
|
+
const selectApiKeyWithToken = useCallback(async (apiKeyId) => {
|
|
228
|
+
if (!accessToken) {
|
|
229
|
+
throw new Error("No access token available");
|
|
230
|
+
}
|
|
231
|
+
await selectApiKey(accessToken, apiKeyId);
|
|
232
|
+
}, [accessToken, selectApiKey]);
|
|
179
233
|
/**
|
|
180
234
|
* Déconnexion
|
|
181
235
|
*/
|
|
@@ -208,6 +262,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
208
262
|
logout,
|
|
209
263
|
fetchApiKeys,
|
|
210
264
|
selectApiKey,
|
|
265
|
+
selectApiKeyWithToken,
|
|
211
266
|
refreshSession,
|
|
212
267
|
apiKeys,
|
|
213
268
|
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.45",
|
|
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.33"
|
|
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,330 @@
|
|
|
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: loading || !selectedKeyId ? "not-allowed" : "pointer",
|
|
306
|
+
opacity: loading || !selectedKeyId ? 0.5 : 1,
|
|
307
|
+
transition: "all 0.2s ease",
|
|
308
|
+
}}
|
|
309
|
+
onMouseEnter={(e) => {
|
|
310
|
+
if (!loading && selectedKeyId) {
|
|
311
|
+
e.currentTarget.style.transform = "translateY(-1px)";
|
|
312
|
+
e.currentTarget.style.boxShadow =
|
|
313
|
+
"0 8px 20px rgba(139, 92, 246, 0.4)";
|
|
314
|
+
}
|
|
315
|
+
}}
|
|
316
|
+
onMouseLeave={(e) => {
|
|
317
|
+
if (!loading && selectedKeyId) {
|
|
318
|
+
e.currentTarget.style.transform = "none";
|
|
319
|
+
e.currentTarget.style.boxShadow = "none";
|
|
320
|
+
}
|
|
321
|
+
}}
|
|
322
|
+
>
|
|
323
|
+
{loading ? "Connexion..." : "Continuer"}
|
|
324
|
+
</button>
|
|
325
|
+
</div>
|
|
326
|
+
</form>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
@@ -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,22 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
14
15
|
| ((
|
|
15
16
|
email: string,
|
|
16
17
|
password: string
|
|
17
|
-
) => Promise<{
|
|
18
|
+
) => Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
needsKeySelection?: boolean;
|
|
22
|
+
}>)
|
|
18
23
|
| undefined;
|
|
24
|
+
let selectApiKeyWithToken: ((apiKeyId: string) => Promise<void>) | undefined;
|
|
25
|
+
let apiKeys: any[] = [];
|
|
26
|
+
let lbStatus: string | undefined;
|
|
19
27
|
|
|
20
28
|
try {
|
|
21
29
|
const lbContext = useLB();
|
|
22
30
|
login = lbContext.login;
|
|
31
|
+
selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
|
|
32
|
+
apiKeys = lbContext.apiKeys || [];
|
|
33
|
+
lbStatus = lbContext.status;
|
|
23
34
|
} catch {
|
|
24
35
|
// LBProvider n'est pas disponible, ne pas rendre le modal
|
|
25
36
|
return null;
|
|
@@ -29,6 +40,14 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
29
40
|
const [password, setPassword] = useState("");
|
|
30
41
|
const [loading, setLoading] = useState(false);
|
|
31
42
|
const [error, setError] = useState("");
|
|
43
|
+
const [showKeySelector, setShowKeySelector] = useState(false);
|
|
44
|
+
|
|
45
|
+
// Si le status est "needs_key_selection", afficher le sélecteur
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (lbStatus === "needs_key_selection" && apiKeys.length > 0) {
|
|
48
|
+
setShowKeySelector(true);
|
|
49
|
+
}
|
|
50
|
+
}, [lbStatus, apiKeys.length]);
|
|
32
51
|
|
|
33
52
|
if (!isOpen || !login) return null;
|
|
34
53
|
|
|
@@ -40,7 +59,13 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
40
59
|
try {
|
|
41
60
|
const result = await login(email, password);
|
|
42
61
|
if (result.success) {
|
|
43
|
-
|
|
62
|
+
if (result.needsKeySelection) {
|
|
63
|
+
// L'utilisateur doit choisir une clé API
|
|
64
|
+
setShowKeySelector(true);
|
|
65
|
+
} else {
|
|
66
|
+
// Connexion réussie, fermer le modal
|
|
67
|
+
onClose();
|
|
68
|
+
}
|
|
44
69
|
} else {
|
|
45
70
|
setError(result.error || "Échec de la connexion");
|
|
46
71
|
}
|
|
@@ -53,6 +78,40 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
|
|
|
53
78
|
}
|
|
54
79
|
};
|
|
55
80
|
|
|
81
|
+
const handleKeySelect = async (apiKeyId: string) => {
|
|
82
|
+
if (!selectApiKeyWithToken) return;
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
await selectApiKeyWithToken(apiKeyId);
|
|
86
|
+
setShowKeySelector(false);
|
|
87
|
+
onClose();
|
|
88
|
+
} catch (err) {
|
|
89
|
+
setError(
|
|
90
|
+
err instanceof Error ? err.message : "Erreur lors de la sélection"
|
|
91
|
+
);
|
|
92
|
+
setShowKeySelector(false);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const handleCancelKeySelection = () => {
|
|
97
|
+
setShowKeySelector(false);
|
|
98
|
+
setEmail("");
|
|
99
|
+
setPassword("");
|
|
100
|
+
setError("");
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Si on doit afficher le sélecteur de clés
|
|
104
|
+
if (showKeySelector && apiKeys.length > 0) {
|
|
105
|
+
return (
|
|
106
|
+
<LBApiKeySelector
|
|
107
|
+
apiKeys={apiKeys}
|
|
108
|
+
onSelect={handleKeySelect}
|
|
109
|
+
onCancel={handleCancelKeySelection}
|
|
110
|
+
isOpen={true}
|
|
111
|
+
/>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
56
115
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
57
116
|
if (e.key === "Escape") {
|
|
58
117
|
onClose();
|
|
@@ -36,13 +36,19 @@ interface LBContextValue extends LBAuthState {
|
|
|
36
36
|
login: (
|
|
37
37
|
email: string,
|
|
38
38
|
password: string
|
|
39
|
-
) => Promise<{
|
|
39
|
+
) => Promise<{
|
|
40
|
+
success: boolean;
|
|
41
|
+
error?: string;
|
|
42
|
+
needsKeySelection?: boolean;
|
|
43
|
+
}>;
|
|
40
44
|
/** Fonction de déconnexion */
|
|
41
45
|
logout: () => Promise<void>;
|
|
42
46
|
/** Récupère les clés API de l'utilisateur */
|
|
43
47
|
fetchApiKeys: (accessToken: string) => Promise<LBApiKey[]>;
|
|
44
|
-
/** Sélectionne une clé API et crée une session */
|
|
48
|
+
/** Sélectionne une clé API et crée une session (après login) */
|
|
45
49
|
selectApiKey: (accessToken: string, apiKeyId: string) => Promise<void>;
|
|
50
|
+
/** Sélectionne une clé API avec le token stocké */
|
|
51
|
+
selectApiKeyWithToken: (apiKeyId: string) => Promise<void>;
|
|
46
52
|
/** Recharge l'état de la session */
|
|
47
53
|
refreshSession: () => Promise<void>;
|
|
48
54
|
/** Clés API disponibles */
|
|
@@ -106,18 +112,30 @@ export function LBProvider({
|
|
|
106
112
|
const fetchApiKeys = useCallback(
|
|
107
113
|
async (token: string): Promise<LBApiKey[]> => {
|
|
108
114
|
try {
|
|
115
|
+
console.log(
|
|
116
|
+
"[LBProvider] Fetching API keys with token:",
|
|
117
|
+
token.substring(0, 20) + "..."
|
|
118
|
+
);
|
|
119
|
+
|
|
109
120
|
const response = await fetch(`${proxyUrl}/public/user/api-keys`, {
|
|
110
121
|
headers: {
|
|
111
122
|
Authorization: `Bearer ${token}`,
|
|
123
|
+
"Content-Type": "application/json",
|
|
112
124
|
},
|
|
113
125
|
credentials: "include",
|
|
114
126
|
});
|
|
115
127
|
|
|
128
|
+
console.log("[LBProvider] API keys response status:", response.status);
|
|
129
|
+
|
|
116
130
|
if (!response.ok) {
|
|
117
|
-
|
|
131
|
+
const errorData = await response.json().catch(() => ({}));
|
|
132
|
+
console.error("[LBProvider] Failed to fetch API keys:", errorData);
|
|
133
|
+
throw new Error(errorData.message || "Failed to fetch API keys");
|
|
118
134
|
}
|
|
119
135
|
|
|
120
136
|
const data = await response.json();
|
|
137
|
+
console.log("[LBProvider] API keys received:", data);
|
|
138
|
+
|
|
121
139
|
const keys: LBApiKey[] = data.apiKeys || data;
|
|
122
140
|
setApiKeys(keys);
|
|
123
141
|
return keys;
|
|
@@ -135,6 +153,7 @@ export function LBProvider({
|
|
|
135
153
|
const selectApiKey = useCallback(
|
|
136
154
|
async (token: string, apiKeyId: string): Promise<void> => {
|
|
137
155
|
try {
|
|
156
|
+
console.log("[LBProvider] Selecting API key:", apiKeyId);
|
|
138
157
|
setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
|
|
139
158
|
|
|
140
159
|
const response = await fetch(`${proxyUrl}/auth/session`, {
|
|
@@ -147,11 +166,19 @@ export function LBProvider({
|
|
|
147
166
|
credentials: "include",
|
|
148
167
|
});
|
|
149
168
|
|
|
169
|
+
console.log("[LBProvider] Session response status:", response.status);
|
|
170
|
+
|
|
150
171
|
if (!response.ok) {
|
|
151
|
-
|
|
172
|
+
const errorData = await response.json().catch(() => ({}));
|
|
173
|
+
console.error("[LBProvider] Failed to create session:", errorData);
|
|
174
|
+
throw new Error(errorData.message || "Failed to create session");
|
|
152
175
|
}
|
|
153
176
|
|
|
154
177
|
const sessionResult: LBSessionResult = await response.json();
|
|
178
|
+
console.log(
|
|
179
|
+
"[LBProvider] Session created successfully:",
|
|
180
|
+
sessionResult
|
|
181
|
+
);
|
|
155
182
|
|
|
156
183
|
setState({
|
|
157
184
|
status: "ready",
|
|
@@ -166,6 +193,7 @@ export function LBProvider({
|
|
|
166
193
|
});
|
|
167
194
|
|
|
168
195
|
setAccessToken(undefined); // Nettoyer l'access token temporaire
|
|
196
|
+
setApiKeys([]); // Nettoyer les clés API temporaires
|
|
169
197
|
onStatusChange?.("ready");
|
|
170
198
|
} catch (error) {
|
|
171
199
|
const message =
|
|
@@ -181,14 +209,20 @@ export function LBProvider({
|
|
|
181
209
|
);
|
|
182
210
|
|
|
183
211
|
/**
|
|
184
|
-
* Connexion utilisateur
|
|
212
|
+
* Connexion utilisateur (étape 1 : login)
|
|
213
|
+
* Retourne le token et les clés API sans créer de session
|
|
185
214
|
*/
|
|
186
215
|
const login = useCallback(
|
|
187
216
|
async (
|
|
188
217
|
email: string,
|
|
189
218
|
password: string
|
|
190
|
-
): Promise<{
|
|
219
|
+
): Promise<{
|
|
220
|
+
success: boolean;
|
|
221
|
+
error?: string;
|
|
222
|
+
needsKeySelection?: boolean;
|
|
223
|
+
}> => {
|
|
191
224
|
try {
|
|
225
|
+
console.log("[LBProvider] Login attempt:", email);
|
|
192
226
|
setState((prev: LBAuthState) => ({ ...prev, status: "loading" }));
|
|
193
227
|
|
|
194
228
|
const response = await fetch(`${proxyUrl}/auth/login`, {
|
|
@@ -198,9 +232,12 @@ export function LBProvider({
|
|
|
198
232
|
credentials: "include",
|
|
199
233
|
});
|
|
200
234
|
|
|
235
|
+
console.log("[LBProvider] Login response status:", response.status);
|
|
236
|
+
|
|
201
237
|
if (!response.ok) {
|
|
202
238
|
const error = await response.json();
|
|
203
239
|
const errorMessage = error.message || "Login failed";
|
|
240
|
+
console.error("[LBProvider] Login failed:", errorMessage);
|
|
204
241
|
setState({
|
|
205
242
|
status: "needs_auth",
|
|
206
243
|
error: errorMessage,
|
|
@@ -209,7 +246,27 @@ export function LBProvider({
|
|
|
209
246
|
}
|
|
210
247
|
|
|
211
248
|
const result: LBLoginResult = await response.json();
|
|
249
|
+
console.log("[LBProvider] Login successful:", result.user?.email);
|
|
250
|
+
console.log(
|
|
251
|
+
"[LBProvider] Access token received:",
|
|
252
|
+
result.accessToken ? "YES" : "NO"
|
|
253
|
+
);
|
|
254
|
+
console.log(
|
|
255
|
+
"[LBProvider] Token length:",
|
|
256
|
+
result.accessToken?.length || 0
|
|
257
|
+
);
|
|
258
|
+
|
|
212
259
|
const token = result.accessToken;
|
|
260
|
+
|
|
261
|
+
if (!token) {
|
|
262
|
+
console.error("[LBProvider] No access token in login response!");
|
|
263
|
+
setState({
|
|
264
|
+
status: "needs_auth",
|
|
265
|
+
error: "Token manquant dans la réponse",
|
|
266
|
+
});
|
|
267
|
+
return { success: false, error: "Token manquant dans la réponse" };
|
|
268
|
+
}
|
|
269
|
+
|
|
213
270
|
setAccessToken(token);
|
|
214
271
|
|
|
215
272
|
setState({
|
|
@@ -217,38 +274,64 @@ export function LBProvider({
|
|
|
217
274
|
user: result.user,
|
|
218
275
|
});
|
|
219
276
|
|
|
220
|
-
// Récupérer les clés API
|
|
277
|
+
// Récupérer les clés API
|
|
278
|
+
console.log("[LBProvider] About to fetch API keys...");
|
|
221
279
|
try {
|
|
222
280
|
const keys = await fetchApiKeys(token);
|
|
223
|
-
|
|
281
|
+
console.log("[LBProvider] Fetched keys:", keys.length);
|
|
224
282
|
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
await selectApiKey(token, activeKey.id);
|
|
228
|
-
} else {
|
|
229
|
-
// Aucune clé active trouvée
|
|
283
|
+
if (keys.length === 0) {
|
|
284
|
+
console.warn("[LBProvider] No API keys found for user");
|
|
230
285
|
setState({
|
|
231
286
|
status: "ready",
|
|
232
287
|
user: result.user,
|
|
288
|
+
error: "Aucune clé API disponible",
|
|
233
289
|
});
|
|
290
|
+
return { success: true, needsKeySelection: false };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Si une seule clé active, la sélectionner automatiquement
|
|
294
|
+
const activeKeys = keys.filter((k) => k.isActive);
|
|
295
|
+
if (activeKeys.length === 1) {
|
|
296
|
+
console.log("[LBProvider] Auto-selecting single active key");
|
|
297
|
+
await selectApiKey(token, activeKeys[0].id);
|
|
298
|
+
return { success: true, needsKeySelection: false };
|
|
234
299
|
}
|
|
235
300
|
|
|
236
|
-
|
|
301
|
+
// Sinon, laisser l'utilisateur choisir
|
|
302
|
+
console.log(
|
|
303
|
+
"[LBProvider] Multiple keys available, user needs to select"
|
|
304
|
+
);
|
|
305
|
+
setState({
|
|
306
|
+
status: "needs_key_selection",
|
|
307
|
+
user: result.user,
|
|
308
|
+
});
|
|
309
|
+
return { success: true, needsKeySelection: true };
|
|
237
310
|
} catch (keyError) {
|
|
311
|
+
console.error("[LBProvider] Failed to fetch API keys:");
|
|
312
|
+
console.error("[LBProvider] Error details:", keyError);
|
|
238
313
|
console.error(
|
|
239
|
-
"[LBProvider]
|
|
240
|
-
keyError
|
|
314
|
+
"[LBProvider] Error message:",
|
|
315
|
+
keyError instanceof Error ? keyError.message : String(keyError)
|
|
241
316
|
);
|
|
242
|
-
|
|
317
|
+
console.error(
|
|
318
|
+
"[LBProvider] Error stack:",
|
|
319
|
+
keyError instanceof Error ? keyError.stack : "No stack"
|
|
320
|
+
);
|
|
321
|
+
|
|
243
322
|
setState({
|
|
244
|
-
status: "
|
|
245
|
-
|
|
323
|
+
status: "needs_auth",
|
|
324
|
+
error: "Impossible de récupérer les clés API",
|
|
246
325
|
});
|
|
247
|
-
return {
|
|
326
|
+
return {
|
|
327
|
+
success: false,
|
|
328
|
+
error: "Impossible de récupérer les clés API",
|
|
329
|
+
};
|
|
248
330
|
}
|
|
249
331
|
} catch (error) {
|
|
250
332
|
const message =
|
|
251
333
|
error instanceof Error ? error.message : "Une erreur s'est produite";
|
|
334
|
+
console.error("[LBProvider] Login error:", message);
|
|
252
335
|
setState({
|
|
253
336
|
status: "error",
|
|
254
337
|
error: message,
|
|
@@ -259,6 +342,19 @@ export function LBProvider({
|
|
|
259
342
|
[proxyUrl, fetchApiKeys, selectApiKey]
|
|
260
343
|
);
|
|
261
344
|
|
|
345
|
+
/**
|
|
346
|
+
* Sélectionne une clé API avec le token déjà stocké (après login)
|
|
347
|
+
*/
|
|
348
|
+
const selectApiKeyWithToken = useCallback(
|
|
349
|
+
async (apiKeyId: string): Promise<void> => {
|
|
350
|
+
if (!accessToken) {
|
|
351
|
+
throw new Error("No access token available");
|
|
352
|
+
}
|
|
353
|
+
await selectApiKey(accessToken, apiKeyId);
|
|
354
|
+
},
|
|
355
|
+
[accessToken, selectApiKey]
|
|
356
|
+
);
|
|
357
|
+
|
|
262
358
|
/**
|
|
263
359
|
* Déconnexion
|
|
264
360
|
*/
|
|
@@ -291,6 +387,7 @@ export function LBProvider({
|
|
|
291
387
|
logout,
|
|
292
388
|
fetchApiKeys,
|
|
293
389
|
selectApiKey,
|
|
390
|
+
selectApiKeyWithToken,
|
|
294
391
|
refreshSession,
|
|
295
392
|
apiKeys,
|
|
296
393
|
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
|