@lastbrain/ai-ui-react 1.0.68 → 1.0.70

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 (76) 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 +23 -70
  4. package/dist/components/AiContextButton.d.ts +10 -2
  5. package/dist/components/AiContextButton.d.ts.map +1 -1
  6. package/dist/components/AiContextButton.js +73 -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 +64 -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 +211 -676
  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/context/LBAuthProvider.d.ts +35 -3
  33. package/dist/context/LBAuthProvider.d.ts.map +1 -1
  34. package/dist/context/LBAuthProvider.js +2 -0
  35. package/dist/examples/AiUiPremiumShowcase.d.ts +2 -0
  36. package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
  37. package/dist/examples/AiUiPremiumShowcase.js +15 -0
  38. package/dist/hooks/useAiModels.d.ts.map +1 -1
  39. package/dist/hooks/useModelManagement.d.ts.map +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +2 -0
  43. package/dist/styles/inline.d.ts +1 -0
  44. package/dist/styles/inline.d.ts.map +1 -1
  45. package/dist/styles/inline.js +25 -129
  46. package/dist/styles.css +1268 -369
  47. package/dist/types.d.ts +3 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/utils/errorHandler.d.ts +2 -2
  50. package/dist/utils/errorHandler.d.ts.map +1 -1
  51. package/dist/utils/errorHandler.js +8 -1
  52. package/dist/utils/modelManagement.d.ts +13 -10
  53. package/dist/utils/modelManagement.d.ts.map +1 -1
  54. package/dist/utils/modelManagement.js +19 -2
  55. package/package.json +2 -2
  56. package/src/components/AiChipLabel.tsx +68 -101
  57. package/src/components/AiContextButton.tsx +142 -413
  58. package/src/components/AiImageButton.tsx +29 -190
  59. package/src/components/AiInput.tsx +49 -74
  60. package/src/components/AiPromptPanel.tsx +81 -260
  61. package/src/components/AiSelect.tsx +61 -69
  62. package/src/components/AiStatusButton.tsx +496 -1327
  63. package/src/components/AiTextarea.tsx +50 -63
  64. package/src/components/LBApiKeySelector.tsx +93 -271
  65. package/src/components/LBConnectButton.tsx +39 -336
  66. package/src/components/LBSigninModal.tsx +141 -472
  67. package/src/context/LBAuthProvider.tsx +45 -6
  68. package/src/examples/AiUiPremiumShowcase.tsx +94 -0
  69. package/src/hooks/useAiModels.ts +2 -1
  70. package/src/hooks/useModelManagement.ts +2 -1
  71. package/src/index.ts +3 -0
  72. package/src/styles/inline.ts +27 -148
  73. package/src/styles.css +1268 -369
  74. package/src/types.ts +3 -0
  75. package/src/utils/errorHandler.ts +16 -3
  76. package/src/utils/modelManagement.ts +53 -15
@@ -10,11 +10,11 @@ import {
10
10
  Lock,
11
11
  } from "lucide-react";
12
12
  import type { BaseAiProps } from "../types";
13
+ import type { AiRadius, AiSize, AiVariant } from "../types";
13
14
  import { useAiCallImage } from "../hooks/useAiCallImage";
14
15
  import { AiPromptPanel } from "./AiPromptPanel";
15
16
  import { useUsageToast } from "./UsageToast";
16
17
  import { useErrorToast, ErrorToast } from "./ErrorToast";
17
- import { aiStyles } from "../styles/inline";
18
18
  import { useAiContext } from "../context/AiProvider";
19
19
  import { handleAIError } from "../utils/errorHandler";
20
20
  import { useLB } from "../context/LBAuthProvider";
@@ -36,6 +36,9 @@ export interface AiImageButtonProps
36
36
  // Props optionnelles pour override du contexte
37
37
  baseUrl?: string;
38
38
  apiKeyId?: string;
39
+ size?: AiSize;
40
+ radius?: AiRadius;
41
+ variant?: AiVariant;
39
42
  }
40
43
 
41
44
  export function AiImageButton({
@@ -54,6 +57,9 @@ export function AiImageButton({
54
57
  onImageSave,
55
58
  storeOutputs,
56
59
  artifactTitle,
60
+ size = "md",
61
+ radius = "full",
62
+ variant = "default",
57
63
  ...buttonProps
58
64
  }: AiImageButtonProps) {
59
65
  const [isOpen, setIsOpen] = useState(false);
@@ -121,45 +127,6 @@ export function AiImageButton({
121
127
  setGeneratedImage(null);
122
128
  };
123
129
 
124
- // Styles selon le thème
125
- const getThemeStyles = () => {
126
- // Détection automatique du thème comme dans les autres composants
127
- const isDark =
128
- typeof document !== "undefined" &&
129
- (document.documentElement.classList.contains("dark") ||
130
- (!document.documentElement.classList.contains("light") &&
131
- window.matchMedia("(prefers-color-scheme: dark)").matches));
132
-
133
- return {
134
- card: {
135
- backgroundColor: isDark ? "#1f2937" : "white",
136
- border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
137
- color: isDark ? "#f3f4f6" : "#374151",
138
- },
139
- header: {
140
- color: isDark ? "#f9fafb" : "#1f2937",
141
- },
142
- closeButton: {
143
- color: isDark ? "#9ca3af" : "#6b7280",
144
- hoverColor: isDark ? "#d1d5db" : "#374151",
145
- },
146
- imageContainer: {
147
- backgroundColor: isDark ? "#111827" : "#f9fafb",
148
- },
149
- actionButton: {
150
- backgroundColor: isDark ? "#374151" : "white",
151
- border: `1px solid ${isDark ? "#4b5563" : "#e5e7eb"}`,
152
- color: isDark ? "#d1d5db" : "#6b7280",
153
- hoverBackground: isDark ? "#4b5563" : "#f3f4f6",
154
- hoverColor: isDark ? "#f3f4f6" : "#1f2937",
155
- },
156
- metadata: {
157
- borderTop: `1px solid ${isDark ? "#374151" : "#f3f4f6"}`,
158
- color: isDark ? "#9ca3af" : "#6b7280",
159
- },
160
- };
161
- };
162
-
163
130
  const handleSubmit = async (
164
131
  selectedModel: string,
165
132
  selectedPrompt: string
@@ -216,107 +183,38 @@ export function AiImageButton({
216
183
  }
217
184
  };
218
185
 
186
+ const sizeClass = `ai-size-${size}`;
187
+ const radiusClass = `ai-radius-${radius}`;
188
+ const variantClass = variant === "light" ? "ai-btn--light" : "";
189
+
219
190
  return (
220
191
  <div className="flex items-start gap-4">
221
- <div style={{ position: "relative", display: "inline-block" }}>
192
+ <div className="relative inline-block ai-glow">
222
193
  <button
223
194
  {...buttonProps}
224
195
  onClick={handleOpenPanel}
225
196
  disabled={disabled || loading || !isAuthReady}
226
- className={className}
227
- style={{
228
- ...aiStyles.button,
229
- display: "flex",
230
- alignItems: "center",
231
- gap: "8px",
232
- cursor:
233
- disabled || loading || !isAuthReady ? "not-allowed" : "pointer",
234
- opacity: disabled || loading || !isAuthReady ? 0.6 : 1,
235
- backgroundColor: loading
236
- ? "#8b5cf6"
237
- : !isAuthReady
238
- ? "#94a3b8"
239
- : "#6366f1",
240
- color: "white",
241
- border: "none",
242
- borderRadius: "12px",
243
- padding: "12px 20px",
244
- fontSize: "14px",
245
- fontWeight: "600",
246
- minWidth: "140px",
247
- height: "44px",
248
- transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
249
- boxShadow: loading
250
- ? "0 4px 12px rgba(139, 92, 246, 0.3)"
251
- : "0 2px 8px rgba(99, 102, 241, 0.2)",
252
- transform: "scale(1)",
253
- ...(loading && {
254
- background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
255
- animation: "pulse 2s ease-in-out infinite",
256
- }),
257
- ...buttonProps.style,
258
- }}
259
- onMouseEnter={(e) => {
260
- if (!disabled && !loading && isAuthReady) {
261
- e.currentTarget.style.transform = "scale(1.02)";
262
- e.currentTarget.style.boxShadow =
263
- "0 6px 16px rgba(99, 102, 241, 0.3)";
264
- }
265
- }}
266
- onMouseLeave={(e) => {
267
- if (!disabled && !loading && isAuthReady) {
268
- e.currentTarget.style.transform = "scale(1)";
269
- e.currentTarget.style.boxShadow = loading
270
- ? "0 4px 12px rgba(139, 92, 246, 0.3)"
271
- : "0 2px 8px rgba(99, 102, 241, 0.2)";
272
- }
273
- }}
274
- onMouseDown={(e) => {
275
- if (!disabled && !loading && isAuthReady) {
276
- e.currentTarget.style.transform = "scale(0.98)";
277
- }
278
- }}
279
- onMouseUp={(e) => {
280
- if (!disabled && !loading && isAuthReady) {
281
- e.currentTarget.style.transform = "scale(1.02)";
282
- }
283
- }}
197
+ className={`ai-btn ai-image-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`}
198
+ style={buttonProps.style}
284
199
  data-ai-image-button
285
200
  title={!isAuthReady ? "Authentication required" : "Générer une image"}
286
201
  >
287
202
  {loading ? (
288
203
  <>
289
- <Loader2
290
- size={18}
291
- className="animate-spin"
292
- style={{
293
- color: "white",
294
- filter: "drop-shadow(0 0 2px rgba(255,255,255,0.3))",
295
- }}
296
- />
297
- <span style={{ letterSpacing: "0.025em" }}>Génération...</span>
204
+ <Loader2 size={18} className="ai-spinner" />
205
+ <span className="ai-text-microtracking">Génération...</span>
298
206
  </>
299
207
  ) : !isAuthReady ? (
300
208
  <>
301
- <Lock
302
- size={18}
303
- style={{
304
- color: "white",
305
- filter: "drop-shadow(0 0 2px rgba(255,255,255,0.2))",
306
- }}
307
- />
209
+ <Lock size={18} />
210
+
308
211
  {children || <span>Connexion requise</span>}
309
212
  </>
310
213
  ) : (
311
214
  <>
312
- <ImageIcon
313
- size={18}
314
- style={{
315
- color: "white",
316
- filter: "drop-shadow(0 0 2px rgba(255,255,255,0.2))",
317
- }}
318
- />
319
- <span style={{ letterSpacing: "0.025em" }}>
215
+ <ImageIcon size={18} />
216
+
217
+ <span className="ai-text-microtracking">
320
218
  {children || "Générer une image"}
321
219
  </span>
322
220
  </>
@@ -339,24 +237,11 @@ export function AiImageButton({
339
237
 
340
238
  {/* Card d'affichage de l'image générée */}
341
239
  {showImageCard && generatedImage && (
342
- <div
343
- className="relative"
344
- style={{
345
- maxWidth: "320px",
346
- borderRadius: "12px",
347
- padding: "16px",
348
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
349
- ...getThemeStyles().card,
350
- }}
351
- >
240
+ <div className="ai-surface ai-image-card relative">
352
241
  {/* Header avec prompt et bouton fermer */}
353
242
  <div className="flex items-start justify-between mb-3">
354
243
  <h3
355
- className="font-medium text-sm leading-tight"
356
- style={{
357
- maxWidth: "calc(100% - 32px)",
358
- ...getThemeStyles().header,
359
- }}
244
+ className="font-medium text-sm leading-tight max-w-[calc(100%-32px)]"
360
245
  title={generatedImage.prompt}
361
246
  >
362
247
  {generatedImage.prompt.length > 60
@@ -365,42 +250,18 @@ export function AiImageButton({
365
250
  </h3>
366
251
  <button
367
252
  onClick={handleCloseImage}
368
- className="transition-colors flex-shrink-0"
369
- style={{
370
- padding: "2px",
371
- borderRadius: "4px",
372
- backgroundColor: "transparent",
373
- border: "none",
374
- cursor: "pointer",
375
- color: getThemeStyles().closeButton.color,
376
- }}
377
- onMouseEnter={(e) => {
378
- e.currentTarget.style.color =
379
- getThemeStyles().closeButton.hoverColor;
380
- }}
381
- onMouseLeave={(e) => {
382
- e.currentTarget.style.color =
383
- getThemeStyles().closeButton.color;
384
- }}
253
+ className="ai-icon-btn flex-shrink-0"
385
254
  >
386
255
  <X size={16} />
387
256
  </button>
388
257
  </div>
389
258
 
390
259
  {/* Image */}
391
- <div
392
- className="mb-4 rounded-lg overflow-hidden"
393
- style={getThemeStyles().imageContainer}
394
- >
260
+ <div className="ai-image-frame">
395
261
  <img
396
262
  src={generatedImage.url}
397
263
  alt={generatedImage.prompt}
398
- className="w-full h-auto"
399
- style={{
400
- maxHeight: "200px",
401
- objectFit: "contain",
402
- display: "block",
403
- }}
264
+ className="ai-image-preview"
404
265
  />
405
266
  </div>
406
267
 
@@ -408,24 +269,7 @@ export function AiImageButton({
408
269
  <div className="flex items-center gap-2 flex-wrap">
409
270
  <button
410
271
  onClick={handleDownload}
411
- className="flex items-center gap-2 px-4 py-2.5 text-sm font-semibold rounded-lg transition-all shadow-sm"
412
- style={{
413
- backgroundColor: "#10b981",
414
- color: "white",
415
- border: "none",
416
- cursor: "pointer",
417
- }}
418
- onMouseEnter={(e) => {
419
- e.currentTarget.style.backgroundColor = "#059669";
420
- e.currentTarget.style.transform = "translateY(-1px)";
421
- e.currentTarget.style.boxShadow =
422
- "0 4px 12px rgba(16, 185, 129, 0.3)";
423
- }}
424
- onMouseLeave={(e) => {
425
- e.currentTarget.style.backgroundColor = "#10b981";
426
- e.currentTarget.style.transform = "translateY(0)";
427
- e.currentTarget.style.boxShadow = "";
428
- }}
272
+ className="ai-btn ai-btn--primary"
429
273
  title="Télécharger l'image"
430
274
  >
431
275
  <Download size={16} />
@@ -435,7 +279,7 @@ export function AiImageButton({
435
279
  {onImageSave && (
436
280
  <button
437
281
  onClick={handleSave}
438
- className="flex items-center gap-1 px-3 py-2 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors"
282
+ className="ai-btn"
439
283
  title="Sauvegarder en base"
440
284
  >
441
285
  <ExternalLink size={14} />
@@ -445,12 +289,7 @@ export function AiImageButton({
445
289
  </div>
446
290
 
447
291
  {/* Metadata */}
448
- <div
449
- className="mt-3 pt-3 text-xs"
450
- style={{
451
- ...getThemeStyles().metadata,
452
- }}
453
- >
292
+ <div className="ai-image-meta">
454
293
  <div className="flex justify-center">
455
294
  <span>ID: {generatedImage.requestId.slice(-8)}</span>
456
295
  </div>
@@ -1,13 +1,12 @@
1
1
  "use client";
2
2
 
3
3
  import React, { useState, useRef, type InputHTMLAttributes } from "react";
4
- import { Sparkles } from "lucide-react";
5
- import type { BaseAiProps } from "../types";
4
+ import { Loader2, Lock, Sparkles } from "lucide-react";
5
+ import type { AiRadius, AiSize, BaseAiProps } from "../types";
6
6
  import { useAiCallText } from "../hooks/useAiCallText";
7
7
  import { useAiModels } from "../hooks/useAiModels";
8
8
  import { AiPromptPanel } from "./AiPromptPanel";
9
9
  import { UsageToast, useUsageToast } from "./UsageToast";
10
- import { aiStyles } from "../styles/inline";
11
10
  import { handleAIError } from "../utils/errorHandler";
12
11
  import { useLB } from "../context/LBAuthProvider";
13
12
  import { LBSigninModal } from "./LBSigninModal";
@@ -16,15 +15,19 @@ import { useAiContext } from "../context/AiProvider";
16
15
  export interface AiInputProps
17
16
  extends
18
17
  Omit<BaseAiProps, "type">,
19
- Omit<InputHTMLAttributes<HTMLInputElement>, "onValue"> {
18
+ Omit<InputHTMLAttributes<HTMLInputElement>, "onValue" | "size"> {
20
19
  uiMode?: "modal" | "drawer";
21
20
  enableModelManagement?: boolean;
21
+ size?: AiSize;
22
+ radius?: AiRadius;
22
23
  }
23
24
 
24
25
  export function AiInput({
25
26
  baseUrl: propBaseUrl,
26
27
  apiKeyId: propApiKeyId,
27
28
  uiMode = "modal",
29
+ size = "md",
30
+ radius = "full",
28
31
  context,
29
32
  model,
30
33
  prompt,
@@ -43,8 +46,6 @@ export function AiInput({
43
46
  const [inputValue, setInputValue] = useState(
44
47
  inputProps.value?.toString() || inputProps.defaultValue?.toString() || ""
45
48
  );
46
- const [isFocused, setIsFocused] = useState(false);
47
- const [isButtonHovered, setIsButtonHovered] = useState(false);
48
49
  const inputRef = useRef<HTMLInputElement>(null);
49
50
  const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
50
51
 
@@ -171,75 +172,49 @@ export function AiInput({
171
172
  inputProps.onChange?.(e);
172
173
  };
173
174
 
175
+ const sizeClass = `ai-size-${size}`;
176
+ const radiusClass = `ai-radius-${radius}`;
177
+
174
178
  return (
175
- <div style={aiStyles.inputWrapper} className={className}>
176
- <input
177
- ref={inputRef}
178
- {...inputProps}
179
- style={{
180
- ...aiStyles.input,
181
- ...(isFocused && aiStyles.inputFocus),
182
- }}
183
- value={inputValue}
184
- onChange={handleInputChange}
185
- onFocus={(e) => {
186
- setIsFocused(true);
187
- inputProps.onFocus?.(e);
188
- }}
189
- onBlur={(e) => {
190
- setIsFocused(false);
191
- inputProps.onBlur?.(e);
192
- }}
193
- disabled={disabled || loading}
194
- />
195
- <button
196
- style={{
197
- ...aiStyles.inputAiButton,
198
- ...(isButtonHovered && aiStyles.inputAiButtonHover),
199
- ...(disabled || loading
200
- ? { opacity: 0.5, cursor: "not-allowed" }
201
- : {}),
202
- }}
203
- onClick={hasConfiguration ? handleQuickGenerate : handleOpenPanel}
204
- onMouseEnter={() => setIsButtonHovered(true)}
205
- onMouseLeave={() => setIsButtonHovered(false)}
206
- disabled={disabled || loading || !isAuthReady}
207
- type="button"
208
- title={
209
- !isAuthReady
210
- ? "Authentication required"
211
- : hasConfiguration
212
- ? "Generate with AI"
213
- : "Setup AI"
214
- }
215
- >
216
- {loading ? (
217
- <svg
218
- style={aiStyles.spinner}
219
- width="16"
220
- height="16"
221
- viewBox="0 0 24 24"
222
- fill="none"
223
- stroke="currentColor"
224
- >
225
- <path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" />
226
- </svg>
227
- ) : shouldShowSparkles ? (
228
- <Sparkles size={16} />
229
- ) : (
230
- <svg
231
- width="16"
232
- height="16"
233
- viewBox="0 0 24 24"
234
- fill="none"
235
- stroke="currentColor"
236
- strokeWidth="2"
237
- >
238
- <rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
239
- <path d="M7 11V7a5 5 0 0 1 10 0v4" />
240
- </svg>
241
- )}
242
- </button>
179
+ <div className={`ai-control-group ai-glow ${className || ""}`}>
180
+ <div className={`ai-shell ${sizeClass} ${radiusClass}`}>
181
+ <input
182
+ ref={inputRef}
183
+ {...inputProps}
184
+ className={`ai-control ai-control-input ${sizeClass} ${radiusClass}`}
185
+ value={inputValue}
186
+ onChange={handleInputChange}
187
+ onFocus={(e) => {
188
+ inputProps.onFocus?.(e);
189
+ }}
190
+ onBlur={(e) => {
191
+ inputProps.onBlur?.(e);
192
+ }}
193
+ aria-invalid={Boolean(inputProps["aria-invalid"])}
194
+ disabled={disabled || loading}
195
+ />
196
+ <button
197
+ className={`ai-control-action ai-spark ${sizeClass} ${radiusClass}`}
198
+ onClick={hasConfiguration ? handleQuickGenerate : handleOpenPanel}
199
+ disabled={disabled || loading}
200
+ type="button"
201
+ title={
202
+ !isAuthReady
203
+ ? "Authentication required"
204
+ : hasConfiguration
205
+ ? "Generate with AI"
206
+ : "Setup AI"
207
+ }
208
+ >
209
+ {loading ? (
210
+ <Loader2 size={16} className="ai-spinner" />
211
+ ) : shouldShowSparkles ? (
212
+ <Sparkles size={16} />
213
+ ) : (
214
+ <Lock size={16} />
215
+ )}
216
+ </button>
217
+ </div>
243
218
  {isOpen && (
244
219
  <AiPromptPanel
245
220
  isOpen={isOpen}