@lastbrain/ai-ui-react 1.0.68 → 1.0.69

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.
Files changed (60) hide show
  1. package/dist/components/AiChipLabel.d.ts +8 -3
  2. package/dist/components/AiChipLabel.d.ts.map +1 -1
  3. package/dist/components/AiChipLabel.js +21 -70
  4. package/dist/components/AiContextButton.d.ts +5 -1
  5. package/dist/components/AiContextButton.d.ts.map +1 -1
  6. package/dist/components/AiContextButton.js +67 -291
  7. package/dist/components/AiImageButton.d.ts +5 -1
  8. package/dist/components/AiImageButton.d.ts.map +1 -1
  9. package/dist/components/AiImageButton.js +6 -142
  10. package/dist/components/AiInput.d.ts +5 -3
  11. package/dist/components/AiInput.d.ts.map +1 -1
  12. package/dist/components/AiInput.js +13 -25
  13. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  14. package/dist/components/AiPromptPanel.js +58 -212
  15. package/dist/components/AiSelect.d.ts +5 -3
  16. package/dist/components/AiSelect.d.ts.map +1 -1
  17. package/dist/components/AiSelect.js +21 -30
  18. package/dist/components/AiStatusButton.d.ts +4 -1
  19. package/dist/components/AiStatusButton.d.ts.map +1 -1
  20. package/dist/components/AiStatusButton.js +198 -668
  21. package/dist/components/AiTextarea.d.ts +4 -2
  22. package/dist/components/AiTextarea.d.ts.map +1 -1
  23. package/dist/components/AiTextarea.js +14 -26
  24. package/dist/components/LBApiKeySelector.d.ts.map +1 -1
  25. package/dist/components/LBApiKeySelector.js +5 -166
  26. package/dist/components/LBConnectButton.d.ts +4 -7
  27. package/dist/components/LBConnectButton.d.ts.map +1 -1
  28. package/dist/components/LBConnectButton.js +17 -86
  29. package/dist/components/LBSigninModal.d.ts +1 -1
  30. package/dist/components/LBSigninModal.d.ts.map +1 -1
  31. package/dist/components/LBSigninModal.js +42 -320
  32. package/dist/examples/AiUiPremiumShowcase.d.ts +2 -0
  33. package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
  34. package/dist/examples/AiUiPremiumShowcase.js +15 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +2 -0
  38. package/dist/styles/inline.d.ts +1 -0
  39. package/dist/styles/inline.d.ts.map +1 -1
  40. package/dist/styles/inline.js +25 -129
  41. package/dist/styles.css +1268 -369
  42. package/dist/types.d.ts +3 -0
  43. package/dist/types.d.ts.map +1 -1
  44. package/package.json +2 -2
  45. package/src/components/AiChipLabel.tsx +64 -101
  46. package/src/components/AiContextButton.tsx +138 -430
  47. package/src/components/AiImageButton.tsx +29 -190
  48. package/src/components/AiInput.tsx +49 -74
  49. package/src/components/AiPromptPanel.tsx +71 -254
  50. package/src/components/AiSelect.tsx +61 -69
  51. package/src/components/AiStatusButton.tsx +477 -1313
  52. package/src/components/AiTextarea.tsx +49 -64
  53. package/src/components/LBApiKeySelector.tsx +86 -274
  54. package/src/components/LBConnectButton.tsx +46 -334
  55. package/src/components/LBSigninModal.tsx +140 -481
  56. package/src/examples/AiUiPremiumShowcase.tsx +91 -0
  57. package/src/index.ts +3 -0
  58. package/src/styles/inline.ts +27 -148
  59. package/src/styles.css +1268 -369
  60. package/src/types.ts +3 -0
@@ -1,27 +1,19 @@
1
1
  "use client";
2
2
 
3
- /**
4
- * Bouton de connexion LastBrain
5
- * Ouvre la modal d'authentification
6
- */
7
-
8
3
  import React from "react";
4
+ import { Loader2, LogIn, LogOut } from "lucide-react";
9
5
  import { useLB } from "../context/LBAuthProvider";
10
- import type { LBApiKey } from "@lastbrain/ai-ui-core";
6
+ import { LBSigninModal } from "./LBSigninModal";
11
7
 
12
8
  interface LBConnectButtonProps {
13
- /** Texte du bouton */
14
9
  label?: string;
15
- /** Classe CSS personnalisée */
16
10
  className?: string;
17
- /** Callback après connexion réussie */
18
11
  onConnected?: () => void;
19
- /** Callback à l'ouverture de la modal */
20
12
  onOpenModal?: () => void;
21
13
  }
22
14
 
23
15
  export function LBConnectButton({
24
- label = "Se connecter à LastBrain",
16
+ label = "Se connecter",
25
17
  className = "",
26
18
  onConnected,
27
19
  onOpenModal,
@@ -29,353 +21,73 @@ export function LBConnectButton({
29
21
  const { status, user, logout } = useLB();
30
22
  const [showModal, setShowModal] = React.useState(false);
31
23
 
24
+ React.useEffect(() => {
25
+ if (status === "ready" && user && showModal) {
26
+ setShowModal(false);
27
+ onConnected?.();
28
+ }
29
+ }, [status, user, showModal, onConnected]);
30
+
32
31
  const handleClick = () => {
33
32
  if (status === "ready" && user) {
34
- // Déjà connecté, proposer de se déconnecter
35
- logout().then(() => {
36
- // Redirection propre après logout
37
- if (typeof window !== "undefined") {
38
- // Garder la langue actuelle de l'URL
39
- const currentPath = window.location.pathname;
40
- const langMatch = currentPath.match(/^\/([a-z]{2})\//);
41
- const currentLang = langMatch ? langMatch[1] : "en";
42
-
43
- // Rediriger vers la page d'accueil dans la langue actuelle
44
- window.location.href = `/${currentLang}`;
45
- }
46
- });
47
- } else {
48
- // Pas connecté, ouvrir la modal
49
- setShowModal(true);
50
- onOpenModal?.();
33
+ void logout();
34
+ return;
51
35
  }
52
- };
53
36
 
54
- const handleModalClose = (success: boolean) => {
55
- setShowModal(false);
56
- if (success) {
57
- onConnected?.();
58
- }
37
+ setShowModal(true);
38
+ onOpenModal?.();
59
39
  };
60
40
 
41
+ const buttonLabel =
42
+ status === "ready" && user ? "Déconnexion" : label;
43
+
61
44
  return (
62
45
  <>
63
46
  <button
47
+ type="button"
64
48
  onClick={handleClick}
65
- className={
66
- className ||
67
- (status === "ready" && user
68
- ? "px-4 py-2 bg-gradient-to-r from-emerald-500 to-teal-600 text-white rounded-lg hover:from-emerald-600 hover:to-teal-700 transition-all duration-200 shadow-md hover:shadow-lg"
69
- : "px-4 py-2 bg-gradient-to-r from-violet-500 to-purple-600 text-white rounded-lg hover:from-violet-600 hover:to-purple-700 transition-all duration-200 shadow-md hover:shadow-lg")
70
- }
49
+ className={className || "ai-btn ai-btn--auth"}
71
50
  disabled={status === "loading"}
72
51
  >
73
- {status === "loading"
74
- ? "⏳ Chargement..."
75
- : status === "ready" && user
76
- ? `✓ ${user.email}`
77
- : `🔐 ${label}`}
52
+ {status === "loading" ? (
53
+ <>
54
+ <Loader2 size={16} className="animate-spin" />
55
+ Chargement...
56
+ </>
57
+ ) : status === "ready" && user ? (
58
+ <>
59
+ <LogOut size={16} />
60
+ {buttonLabel}
61
+ </>
62
+ ) : (
63
+ <>
64
+ <LogIn size={16} />
65
+ {buttonLabel}
66
+ </>
67
+ )}
78
68
  </button>
79
69
 
80
- {showModal && <LBAuthModal onClose={handleModalClose} />}
70
+ <LBSigninModal
71
+ isOpen={showModal}
72
+ onClose={() => setShowModal(false)}
73
+ />
81
74
  </>
82
75
  );
83
76
  }
84
77
 
85
78
  /**
86
- * Modal d'authentification LastBrain
79
+ * Compat export (ancien API): on garde un wrapper minimal
80
+ * pour éviter de casser les imports existants.
87
81
  */
88
82
  interface LBAuthModalProps {
89
83
  onClose: (success: boolean) => void;
90
84
  }
91
85
 
92
- function LBAuthModal({ onClose }: LBAuthModalProps) {
93
- const { login, fetchApiKeys, selectApiKey } = useLB();
94
- const [step, setStep] = React.useState<"login" | "select-key">("login");
95
- const [email, setEmail] = React.useState("");
96
- const [password, setPassword] = React.useState("");
97
- const [error, setError] = React.useState("");
98
- const [loading, setLoading] = React.useState(false);
99
- const [accessToken, setAccessToken] = React.useState("");
100
- const [apiKeys, setApiKeys] = React.useState<LBApiKey[]>([]);
101
-
102
- const handleLogin = async (e: React.FormEvent) => {
103
- e.preventDefault();
104
- setError("");
105
- setLoading(true);
106
-
107
- try {
108
- const result = await login(email, password);
109
- if (!result.success) {
110
- setError(result.error || "Échec de la connexion");
111
- return;
112
- }
113
-
114
- // Note: avec le nouveau système, login gère automatiquement
115
- // la récupération et sélection de clé. Ce modal n'est plus nécessaire.
116
- onClose(true);
117
- } catch (err) {
118
- setError(err instanceof Error ? err.message : "Échec de la connexion");
119
- } finally {
120
- setLoading(false);
121
- }
122
- };
123
-
124
- const handleSelectKey = async (apiKeyId: string) => {
125
- setError("");
126
- setLoading(true);
127
-
128
- try {
129
- await selectApiKey(accessToken, apiKeyId);
130
- onClose(true); // Succès
131
- } catch (err) {
132
- setError(err instanceof Error ? err.message : "Échec de la sélection");
133
- } finally {
134
- setLoading(false);
135
- }
136
- };
137
-
86
+ export function LBAuthModal({ onClose }: LBAuthModalProps) {
138
87
  return (
139
- <div className="fixed inset-0 bg-black/60 dark:bg-black/80 backdrop-blur-md flex items-center justify-center z-50 p-4 animate-in fade-in duration-200">
140
- <div className="bg-white/95 dark:bg-slate-900/95 backdrop-blur-xl rounded-3xl shadow-2xl max-w-md w-full border border-slate-200/50 dark:border-slate-700/50 overflow-hidden animate-in zoom-in-95 duration-200">
141
- {/* Header avec gradient */}
142
- <div className="relative bg-gradient-to-r from-violet-600 via-purple-600 to-fuchsia-600 dark:from-violet-500 dark:via-purple-500 dark:to-fuchsia-500 px-6 py-8 text-white overflow-hidden">
143
- {/* Pattern background */}
144
- <div className="absolute inset-0 bg-[url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjAiIGhlaWdodD0iNjAiIHZpZXdCb3g9IjAgMCA2MCA2MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIGZpbGw9IiNmZmYiIGZpbGwtb3BhY2l0eT0iMC4wNSI+PHBhdGggZD0iTTM2IDE0YzAtMTEuMDUgOC45NS0yMCAyMC0yMHMyMCA4Ljk1IDIwIDIwLTguOTUgMjAtMjAgMjAtMjAtOC45NS0yMC0yMHoiLz48L2c+PC9nPjwvc3ZnPg==')] opacity-30"></div>
145
-
146
- <div className="relative flex justify-between items-start">
147
- <div className="flex-1">
148
- <div className="flex items-center gap-3 mb-2">
149
- <div className="w-10 h-10 rounded-xl bg-white/20 backdrop-blur-sm flex items-center justify-center">
150
- {step === "login" ? "🔐" : "🔑"}
151
- </div>
152
- <h2 className="text-2xl font-bold tracking-tight">
153
- {step === "login" ? "Connexion" : "Sélection clé"}
154
- </h2>
155
- </div>
156
- <p className="text-white/90 text-sm font-medium mt-1 ml-13">
157
- {step === "login"
158
- ? "Accédez à vos outils IA"
159
- : "Session sécurisée 72h"}
160
- </p>
161
- </div>
162
- <button
163
- onClick={() => onClose(false)}
164
- className="text-white/80 hover:text-white hover:bg-white/20 rounded-lg p-2 transition-all duration-200 backdrop-blur-sm"
165
- >
166
- <svg
167
- className="w-5 h-5"
168
- fill="none"
169
- stroke="currentColor"
170
- viewBox="0 0 24 24"
171
- >
172
- <path
173
- strokeLinecap="round"
174
- strokeLinejoin="round"
175
- strokeWidth={2.5}
176
- d="M6 18L18 6M6 6l12 12"
177
- />
178
- </svg>
179
- </button>
180
- </div>
181
- </div>
182
-
183
- <div className="p-8">
184
- {step === "login" ? (
185
- <form onSubmit={handleLogin} className="space-y-5">
186
- <div className="space-y-2">
187
- <label className="block text-sm font-semibold text-slate-700 dark:text-slate-200 mb-2 tracking-wide">
188
- 📧 Adresse email
189
- </label>
190
- <input
191
- type="email"
192
- value={email}
193
- onChange={(e) => setEmail(e.target.value)}
194
- className="w-full px-4 py-3.5 text-base border-2 border-slate-200 dark:border-slate-600 rounded-xl bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:border-violet-500 dark:focus:border-violet-400 focus:ring-4 focus:ring-violet-100 dark:focus:ring-violet-900/30 transition-all outline-none font-medium tracking-wide"
195
- placeholder="votre@email.com"
196
- required
197
- autoFocus
198
- autoComplete="email"
199
- style={{ letterSpacing: "0.01em" }}
200
- />
201
- </div>
202
-
203
- <div className="space-y-2">
204
- <label className="block text-sm font-semibold text-slate-700 dark:text-slate-200 mb-2 tracking-wide">
205
- 🔒 Mot de passe
206
- </label>
207
- <input
208
- type="password"
209
- value={password}
210
- onChange={(e) => setPassword(e.target.value)}
211
- className="w-full px-4 py-3.5 text-base border-2 border-slate-200 dark:border-slate-600 rounded-xl bg-white dark:bg-slate-800 text-slate-900 dark:text-white placeholder:text-slate-400 dark:placeholder:text-slate-500 focus:border-violet-500 dark:focus:border-violet-400 focus:ring-4 focus:ring-violet-100 dark:focus:ring-violet-900/30 transition-all outline-none font-medium tracking-wide"
212
- placeholder="••••••••"
213
- required
214
- autoComplete="current-password"
215
- style={{ letterSpacing: "0.15em" }}
216
- />
217
- </div>
218
-
219
- {error && (
220
- <div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 dark:border-red-400 px-4 py-3.5 rounded-lg animate-in slide-in-from-top-2 duration-200">
221
- <div className="flex items-start gap-3">
222
- <span className="text-red-500 dark:text-red-400 text-xl flex-shrink-0 mt-0.5">
223
- ⚠️
224
- </span>
225
- <p className="text-red-700 dark:text-red-300 text-sm font-medium leading-relaxed">
226
- {error}
227
- </p>
228
- </div>
229
- </div>
230
- )}
231
-
232
- <button
233
- type="submit"
234
- disabled={loading}
235
- className="w-full mt-6 px-6 py-4 bg-gradient-to-r from-violet-600 to-purple-600 hover:from-violet-700 hover:to-purple-700 disabled:from-slate-400 disabled:to-slate-500 text-white font-bold text-base rounded-xl disabled:cursor-not-allowed transition-all duration-200 shadow-lg shadow-violet-500/30 hover:shadow-xl hover:shadow-violet-500/40 disabled:shadow-none transform hover:translate-y-[-2px] active:translate-y-0 disabled:translate-y-0 tracking-wide"
236
- >
237
- {loading ? (
238
- <span className="flex items-center justify-center gap-3">
239
- <svg
240
- className="animate-spin h-5 w-5"
241
- xmlns="http://www.w3.org/2000/svg"
242
- fill="none"
243
- viewBox="0 0 24 24"
244
- >
245
- <circle
246
- className="opacity-25"
247
- cx="12"
248
- cy="12"
249
- r="10"
250
- stroke="currentColor"
251
- strokeWidth="4"
252
- ></circle>
253
- <path
254
- className="opacity-75"
255
- fill="currentColor"
256
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
257
- ></path>
258
- </svg>
259
- <span className="font-semibold">Connexion en cours...</span>
260
- </span>
261
- ) : (
262
- <span className="flex items-center justify-center gap-2">
263
- 🚀 <span>Se connecter</span>
264
- </span>
265
- )}
266
- </button>
267
-
268
- <div className="relative my-8">
269
- <div className="absolute inset-0 flex items-center">
270
- <div className="w-full border-t border-slate-200 dark:border-slate-700"></div>
271
- </div>
272
- <div className="relative flex justify-center text-sm">
273
- <span className="px-4 bg-white dark:bg-slate-900 text-slate-500 dark:text-slate-400 font-medium">
274
- ou
275
- </span>
276
- </div>
277
- </div>
278
-
279
- <div className="bg-gradient-to-br from-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-900/20 rounded-xl p-5 border border-violet-200 dark:border-violet-800/50 backdrop-blur-sm">
280
- <p className="text-sm text-slate-700 dark:text-slate-300 text-center font-medium mb-3">
281
- Pas encore de compte ?
282
- </p>
283
- <a
284
- href="https://prompt.lastbrain.io/signup"
285
- target="_blank"
286
- rel="noopener noreferrer"
287
- className="block text-center px-4 py-3 bg-white dark:bg-slate-800 text-violet-600 dark:text-violet-400 hover:text-violet-700 dark:hover:text-violet-300 font-bold text-sm rounded-lg hover:shadow-md transition-all duration-200 border-2 border-violet-200 dark:border-violet-700"
288
- >
289
- ✨ Créer un compte gratuitement
290
- </a>
291
- </div>
292
- </form>
293
- ) : (
294
- <div className="space-y-4">
295
- <p className="text-sm text-slate-600 dark:text-slate-400 bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg border border-blue-200 dark:border-blue-800">
296
- ℹ️ Sélectionnez une clé API pour créer une session sécurisée de
297
- 72h
298
- </p>
299
-
300
- <div className="space-y-3 max-h-96 overflow-y-auto">
301
- {apiKeys.map((key) => (
302
- <button
303
- key={key.id}
304
- onClick={() => handleSelectKey(key.id)}
305
- disabled={!key.isActive || loading}
306
- className={`w-full text-left px-5 py-4 border-2 rounded-lg transition-all transform hover:scale-[1.02] ${
307
- key.isActive
308
- ? "border-slate-200 dark:border-slate-600 hover:border-violet-400 dark:hover:border-violet-500 hover:bg-gradient-to-r hover:from-violet-50 hover:to-purple-50 dark:hover:from-violet-900/20 dark:hover:to-purple-900/20"
309
- : "border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50 opacity-50 cursor-not-allowed"
310
- }`}
311
- >
312
- <div className="flex items-start justify-between gap-2">
313
- <div className="flex-1">
314
- <div className="font-semibold text-slate-900 dark:text-white flex items-center gap-2">
315
- {key.isActive ? "🔑" : "🔒"}
316
- {key.name}
317
- </div>
318
- <div className="text-sm text-slate-500 dark:text-slate-400 mt-1 font-mono">
319
- {key.keyPrefix}...
320
- </div>
321
- {key.scopes && (
322
- <div className="flex gap-1 mt-2 flex-wrap">
323
- {key.scopes.slice(0, 3).map((scope: string) => (
324
- <span
325
- key={scope}
326
- className="text-xs px-2 py-1 bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 rounded"
327
- >
328
- {scope}
329
- </span>
330
- ))}
331
- {key.scopes.length > 3 && (
332
- <span className="text-xs px-2 py-1 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-400 rounded">
333
- +{key.scopes.length - 3}
334
- </span>
335
- )}
336
- </div>
337
- )}
338
- </div>
339
- {key.isActive ? (
340
- <svg
341
- className="w-5 h-5 text-violet-500 flex-shrink-0 mt-1"
342
- fill="none"
343
- stroke="currentColor"
344
- viewBox="0 0 24 24"
345
- >
346
- <path
347
- strokeLinecap="round"
348
- strokeLinejoin="round"
349
- strokeWidth={2}
350
- d="M9 5l7 7-7 7"
351
- />
352
- </svg>
353
- ) : (
354
- <span className="text-xs text-red-600 dark:text-red-400 font-semibold bg-red-100 dark:bg-red-900/30 px-2 py-1 rounded">
355
- Inactive
356
- </span>
357
- )}
358
- </div>
359
- </button>
360
- ))}
361
- </div>
362
-
363
- {error && (
364
- <div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 p-4 rounded">
365
- <div className="flex items-start gap-2">
366
- <span className="text-red-500 text-xl">⚠️</span>
367
- <p className="text-red-700 dark:text-red-400 text-sm font-medium">
368
- {error}
369
- </p>
370
- </div>
371
- </div>
372
- )}
373
- </div>
374
- )}
375
- </div>
376
- </div>
377
- </div>
88
+ <LBSigninModal
89
+ isOpen
90
+ onClose={() => onClose(false)}
91
+ />
378
92
  );
379
93
  }
380
-
381
- export { LBAuthModal };