@lastbrain/ai-ui-react 1.0.11 → 1.0.12

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 (44) hide show
  1. package/dist/components/AiInput.d.ts.map +1 -1
  2. package/dist/components/AiInput.js +1 -1
  3. package/dist/components/AiPromptPanel.d.ts +14 -1
  4. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  5. package/dist/components/AiPromptPanel.js +255 -7
  6. package/dist/components/AiSettingsButton.d.ts.map +1 -1
  7. package/dist/components/AiSettingsButton.js +1 -1
  8. package/dist/components/AiStatusButton.d.ts.map +1 -1
  9. package/dist/components/AiStatusButton.js +106 -11
  10. package/dist/examples/AiImageGenerator.d.ts +34 -0
  11. package/dist/examples/AiImageGenerator.d.ts.map +1 -0
  12. package/dist/examples/AiImageGenerator.js +85 -0
  13. package/dist/examples/AiPromptPanelAdvanced.d.ts +20 -0
  14. package/dist/examples/AiPromptPanelAdvanced.d.ts.map +1 -0
  15. package/dist/examples/AiPromptPanelAdvanced.js +222 -0
  16. package/dist/examples/ExternalIntegration.d.ts +2 -0
  17. package/dist/examples/ExternalIntegration.d.ts.map +1 -0
  18. package/dist/examples/ExternalIntegration.js +2 -0
  19. package/dist/hooks/useAiStatus.d.ts.map +1 -1
  20. package/dist/hooks/useAiStatus.js +3 -0
  21. package/dist/hooks/useModelManagement.d.ts +32 -0
  22. package/dist/hooks/useModelManagement.d.ts.map +1 -0
  23. package/dist/hooks/useModelManagement.js +135 -0
  24. package/dist/index.d.ts +4 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +6 -0
  27. package/dist/styles/inline.d.ts.map +1 -1
  28. package/dist/styles/inline.js +10 -1
  29. package/dist/utils/modelManagement.d.ts +29 -0
  30. package/dist/utils/modelManagement.d.ts.map +1 -0
  31. package/dist/utils/modelManagement.js +80 -0
  32. package/package.json +2 -2
  33. package/src/components/AiInput.tsx +3 -0
  34. package/src/components/AiPromptPanel.tsx +502 -24
  35. package/src/components/AiSettingsButton.tsx +4 -2
  36. package/src/components/AiStatusButton.tsx +185 -39
  37. package/src/examples/AiImageGenerator.tsx +214 -0
  38. package/src/examples/AiPromptPanelAdvanced.tsx +381 -0
  39. package/src/examples/ExternalIntegration.ts +55 -0
  40. package/src/hooks/useAiStatus.ts +4 -0
  41. package/src/hooks/useModelManagement.ts +210 -0
  42. package/src/index.ts +8 -0
  43. package/src/styles/inline.ts +10 -1
  44. package/src/utils/modelManagement.ts +130 -0
@@ -3,6 +3,13 @@
3
3
  import type { AiStatus } from "@lastbrain/ai-ui-core";
4
4
  import { useState, useRef, useLayoutEffect, type CSSProperties } from "react";
5
5
  import { createPortal } from "react-dom";
6
+ import {
7
+ BarChart3,
8
+ Settings,
9
+ FileText,
10
+ History as HistoryIcon,
11
+ FolderPlus,
12
+ } from "lucide-react";
6
13
  import { aiStyles, calculateTooltipPosition } from "../styles/inline";
7
14
 
8
15
  export interface AiStatusButtonProps {
@@ -36,6 +43,14 @@ export function AiStatusButton({
36
43
  typeof value === "number" ? value.toLocaleString() : "0";
37
44
  const formatFixed = (value: number | null | undefined, digits: number) =>
38
45
  typeof value === "number" ? value.toFixed(digits) : "0.00";
46
+ const formatStorage = (valueMb: number | null | undefined) => {
47
+ const mb = typeof valueMb === "number" ? valueMb : 0;
48
+ if (mb >= 1024) {
49
+ const gb = mb / 1024;
50
+ return `${gb.toFixed(2)} GB`;
51
+ }
52
+ return `${mb.toFixed(2)} MB`;
53
+ };
39
54
  const safeNumber = (value: number | null | undefined) =>
40
55
  typeof value === "number" ? value : 0;
41
56
  const clampPercentage = (value: number | null | undefined) =>
@@ -81,6 +96,17 @@ export function AiStatusButton({
81
96
  strokeLinecap="round"
82
97
  transform={`rotate(-90 ${size / 2} ${size / 2})`}
83
98
  />
99
+ <text
100
+ x={size / 2}
101
+ y={size / 2}
102
+ textAnchor="middle"
103
+ dominantBaseline="central"
104
+ fontSize="7px"
105
+ fill={color}
106
+ fontWeight="600"
107
+ >
108
+ {percentage.toFixed(0)}%
109
+ </text>
84
110
  </svg>
85
111
  );
86
112
  };
@@ -352,65 +378,185 @@ export function AiStatusButton({
352
378
  <div style={aiStyles.tooltipRow}>
353
379
  <span style={aiStyles.tooltipLabel}>Total:</span>
354
380
  <span style={aiStyles.tooltipValue}>
355
- {formatFixed(storageUsed, 2)} MB /{" "}
356
- {formatFixed(storageAllocated, 2)} MB
381
+ {formatStorage(storageUsed)} /{" "}
382
+ {formatStorage(storageAllocated)}
357
383
  </span>
358
384
  {renderUsageCircle(storagePercentage)}
359
385
  </div>
360
386
  </div>
361
387
 
362
- <div style={aiStyles.tooltipActions}>
363
- <a
364
- href="https://prompt.lastbrain.io/fr/auth/dashboard"
365
- target="_blank"
366
- rel="noopener noreferrer"
367
- style={aiStyles.tooltipLink}
388
+ <div
389
+ style={{
390
+ ...aiStyles.tooltipActions,
391
+ width: "100%",
392
+
393
+ flexDirection: "row",
394
+ }}
395
+ >
396
+ <button
397
+ onClick={() =>
398
+ window.open(
399
+ "https://prompt.lastbrain.io/auth/ai/tokens",
400
+ "_blank"
401
+ )
402
+ }
403
+ style={{
404
+ background: "transparent",
405
+ border: "none",
406
+ borderRadius: "4px",
407
+ padding: "14px",
408
+ cursor: "pointer",
409
+ display: "flex",
410
+ alignItems: "center",
411
+ justifyContent: "center",
412
+ color: "#8b5cf6",
413
+ transition: "all 0.2s ease",
414
+ }}
368
415
  onMouseEnter={(e) => {
369
- Object.assign(
370
- e.currentTarget.style,
371
- aiStyles.tooltipLinkHover
372
- );
416
+ Object.assign(e.currentTarget.style, {
417
+ background: "rgba(139, 92, 246, 0.1)",
418
+ });
373
419
  }}
374
420
  onMouseLeave={(e) => {
375
- Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
421
+ Object.assign(e.currentTarget.style, {
422
+ background: "transparent",
423
+ });
376
424
  }}
425
+ title="Dashboard"
377
426
  >
378
- Dashboard
379
- </a>
380
- <a
381
- href="https://prompt.lastbrain.io/fr/auth/billing"
382
- target="_blank"
383
- rel="noopener noreferrer"
384
- style={aiStyles.tooltipLink}
427
+ <BarChart3 size={18} />
428
+ </button>
429
+ <button
430
+ onClick={() =>
431
+ window.open(
432
+ "https://prompt.lastbrain.io/auth/ai/history",
433
+ "_blank"
434
+ )
435
+ }
436
+ style={{
437
+ background: "transparent",
438
+ border: "none",
439
+ borderRadius: "4px",
440
+ padding: "14px",
441
+ cursor: "pointer",
442
+ display: "flex",
443
+ alignItems: "center",
444
+ justifyContent: "center",
445
+ color: "#8b5cf6",
446
+ transition: "all 0.2s ease",
447
+ }}
385
448
  onMouseEnter={(e) => {
386
- Object.assign(
387
- e.currentTarget.style,
388
- aiStyles.tooltipLinkHover
389
- );
449
+ Object.assign(e.currentTarget.style, {
450
+ background: "rgba(139, 92, 246, 0.1)",
451
+ });
390
452
  }}
391
453
  onMouseLeave={(e) => {
392
- Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
454
+ Object.assign(e.currentTarget.style, {
455
+ background: "transparent",
456
+ });
393
457
  }}
458
+ title="History"
394
459
  >
395
- History
396
- </a>
397
- <a
398
- href="https://prompt.lastbrain.io/fr/auth/billing"
399
- target="_blank"
400
- rel="noopener noreferrer"
401
- style={aiStyles.tooltipLink}
460
+ <HistoryIcon size={18} />
461
+ </button>
462
+ <button
463
+ onClick={() =>
464
+ window.open(
465
+ "https://prompt.lastbrain.io/auth/ai/settings",
466
+ "_blank"
467
+ )
468
+ }
469
+ style={{
470
+ background: "transparent",
471
+ border: "none",
472
+ borderRadius: "4px",
473
+ padding: "14px",
474
+ cursor: "pointer",
475
+ display: "flex",
476
+ alignItems: "center",
477
+ justifyContent: "center",
478
+ color: "#8b5cf6",
479
+ transition: "all 0.2s ease",
480
+ }}
481
+ onMouseEnter={(e) => {
482
+ Object.assign(e.currentTarget.style, {
483
+ background: "rgba(139, 92, 246, 0.1)",
484
+ });
485
+ }}
486
+ onMouseLeave={(e) => {
487
+ Object.assign(e.currentTarget.style, {
488
+ background: "transparent",
489
+ });
490
+ }}
491
+ title="Settings"
492
+ >
493
+ <Settings size={18} />
494
+ </button>
495
+ <button
496
+ onClick={() =>
497
+ window.open(
498
+ "https://prompt.lastbrain.io/auth/ai/prompts",
499
+ "_blank"
500
+ )
501
+ }
502
+ style={{
503
+ background: "transparent",
504
+ border: "none",
505
+ borderRadius: "4px",
506
+ padding: "14px",
507
+ cursor: "pointer",
508
+ display: "flex",
509
+ alignItems: "center",
510
+ justifyContent: "center",
511
+ color: "#8b5cf6",
512
+ transition: "all 0.2s ease",
513
+ }}
514
+ onMouseEnter={(e) => {
515
+ Object.assign(e.currentTarget.style, {
516
+ background: "rgba(139, 92, 246, 0.1)",
517
+ });
518
+ }}
519
+ onMouseLeave={(e) => {
520
+ Object.assign(e.currentTarget.style, {
521
+ background: "transparent",
522
+ });
523
+ }}
524
+ title="New Prompt"
525
+ >
526
+ <FileText size={18} />
527
+ </button>
528
+ <button
529
+ onClick={() =>
530
+ window.open(
531
+ "https://prompt.lastbrain.io/auth/folder",
532
+ "_blank"
533
+ )
534
+ }
535
+ style={{
536
+ background: "transparent",
537
+ border: "none",
538
+ padding: "14px",
539
+ cursor: "pointer",
540
+ display: "flex",
541
+ alignItems: "center",
542
+ justifyContent: "center",
543
+ color: "#8b5cf6",
544
+ transition: "all 0.2s ease",
545
+ }}
402
546
  onMouseEnter={(e) => {
403
- Object.assign(
404
- e.currentTarget.style,
405
- aiStyles.tooltipLinkHover
406
- );
547
+ Object.assign(e.currentTarget.style, {
548
+ background: "rgba(139, 92, 246, 0.1)",
549
+ });
407
550
  }}
408
551
  onMouseLeave={(e) => {
409
- Object.assign(e.currentTarget.style, aiStyles.tooltipLink);
552
+ Object.assign(e.currentTarget.style, {
553
+ background: "transparent",
554
+ });
410
555
  }}
556
+ title="New Folder"
411
557
  >
412
- Settings
413
- </a>
558
+ <FolderPlus size={18} />
559
+ </button>
414
560
  </div>
415
561
  </div>,
416
562
  document.body
@@ -0,0 +1,214 @@
1
+ import { useModelManagement } from "../hooks/useModelManagement";
2
+
3
+ interface ModelManagementExampleProps {
4
+ /** Clé API LastBrain pour l'authentification */
5
+ apiKey?: string;
6
+ /** URL de base de l'API */
7
+ baseUrl?: string;
8
+ /** Catégorie de modèles à gérer */
9
+ category?: "text" | "image" | "audio" | "video";
10
+ }
11
+
12
+ /**
13
+ * Composant exemple montrant comment utiliser le hook useModelManagement
14
+ * Retourne les données et handlers pour les passer à d'autres composants
15
+ */
16
+ export function useImageModelManagement({
17
+ apiKey,
18
+ baseUrl,
19
+ category = "image",
20
+ }: ModelManagementExampleProps = {}) {
21
+ const {
22
+ availableModels,
23
+ userModels,
24
+ loading,
25
+ error,
26
+ toggleModel,
27
+ isModelActive,
28
+ getActiveModels,
29
+ getInactiveModels,
30
+ } = useModelManagement({
31
+ apiKey,
32
+ baseUrl,
33
+ category,
34
+ autoFetch: true,
35
+ });
36
+
37
+ const handleModelToggle = async (modelId: string, isActive: boolean) => {
38
+ try {
39
+ await toggleModel(modelId, isActive);
40
+ } catch (error) {
41
+ console.error("Erreur lors de la modification du modèle:", error);
42
+ throw error;
43
+ }
44
+ };
45
+
46
+ return {
47
+ availableModels,
48
+ userModels,
49
+ loading,
50
+ error,
51
+ toggleModel: handleModelToggle,
52
+ isModelActive,
53
+ getActiveModels,
54
+ getInactiveModels,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Composant exemple affichant les statistiques des modèles
60
+ */
61
+ export function ModelManagementStats({
62
+ apiKey,
63
+ baseUrl,
64
+ category = "image",
65
+ }: ModelManagementExampleProps = {}) {
66
+ const {
67
+ availableModels,
68
+ loading,
69
+ error,
70
+ getActiveModels,
71
+ getInactiveModels,
72
+ } = useModelManagement({
73
+ apiKey,
74
+ baseUrl,
75
+ category,
76
+ autoFetch: true,
77
+ });
78
+
79
+ if (loading) {
80
+ return <div className="p-4 text-center">Chargement des modèles...</div>;
81
+ }
82
+
83
+ if (error) {
84
+ return (
85
+ <div className="p-4 border border-red-200 rounded-lg bg-red-50">
86
+ <p className="text-red-600">Erreur: {error}</p>
87
+ </div>
88
+ );
89
+ }
90
+
91
+ return (
92
+ <div className="grid grid-cols-3 gap-4">
93
+ <div className="p-3 bg-green-100 rounded-lg">
94
+ <div className="text-sm text-green-700">Modèles activés</div>
95
+ <div className="text-2xl font-bold text-green-800">
96
+ {getActiveModels().length}
97
+ </div>
98
+ </div>
99
+ <div className="p-3 bg-blue-100 rounded-lg">
100
+ <div className="text-sm text-blue-700">Modèles disponibles</div>
101
+ <div className="text-2xl font-bold text-blue-800">
102
+ {availableModels.length}
103
+ </div>
104
+ </div>
105
+ <div className="p-3 bg-orange-100 rounded-lg">
106
+ <div className="text-sm text-orange-700">Non utilisés</div>
107
+ <div className="text-2xl font-bold text-orange-800">
108
+ {getInactiveModels().length}
109
+ </div>
110
+ </div>
111
+ </div>
112
+ );
113
+ }
114
+
115
+ /**
116
+ * Composant exemple pour afficher et gérer une liste de modèles
117
+ */
118
+ export function ModelManagementList({
119
+ apiKey,
120
+ baseUrl,
121
+ category = "image",
122
+ onModelToggle,
123
+ }: ModelManagementExampleProps & {
124
+ onModelToggle?: (modelId: string, isActive: boolean) => void;
125
+ }) {
126
+ const {
127
+ availableModels,
128
+ userModels,
129
+ loading,
130
+ error,
131
+ toggleModel,
132
+ isModelActive,
133
+ } = useModelManagement({
134
+ apiKey,
135
+ baseUrl,
136
+ category,
137
+ autoFetch: true,
138
+ });
139
+
140
+ const handleToggle = async (modelId: string) => {
141
+ const isCurrentlyActive = isModelActive(modelId);
142
+ const newState = !isCurrentlyActive;
143
+
144
+ try {
145
+ await toggleModel(modelId, newState);
146
+ onModelToggle?.(modelId, newState);
147
+ } catch (error) {
148
+ console.error("Erreur lors de la modification du modèle:", error);
149
+ }
150
+ };
151
+
152
+ if (loading) {
153
+ return <div className="p-4 text-center">Chargement des modèles...</div>;
154
+ }
155
+
156
+ if (error) {
157
+ return (
158
+ <div className="p-4 border border-red-200 rounded-lg bg-red-50">
159
+ <p className="text-red-600">Erreur: {error}</p>
160
+ </div>
161
+ );
162
+ }
163
+
164
+ return (
165
+ <div className="space-y-3">
166
+ <h3 className="text-lg font-semibold">Modèles {category}</h3>
167
+ {availableModels.map((model) => {
168
+ const isActive = isModelActive(model.id);
169
+
170
+ return (
171
+ <div
172
+ key={model.id}
173
+ className="flex items-center justify-between p-3 border rounded-lg hover:bg-gray-50"
174
+ >
175
+ <div className="flex items-center gap-3">
176
+ <div
177
+ className={`w-3 h-3 rounded-full ${isActive ? "bg-green-500" : "bg-gray-300"}`}
178
+ />
179
+ <div>
180
+ <div className="flex items-center gap-2">
181
+ <span className="font-medium">{model.name}</span>
182
+ {model.isPro && (
183
+ <span className="px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded">
184
+ PRO
185
+ </span>
186
+ )}
187
+ </div>
188
+ {model.description && (
189
+ <p className="text-sm text-gray-500">{model.description}</p>
190
+ )}
191
+ <div className="flex items-center gap-4 mt-1 text-xs text-gray-400">
192
+ <span>Fournisseur: {model.provider}</span>
193
+ {model.costPer1M && (
194
+ <span>Coût: ${model.costPer1M}/1M tokens</span>
195
+ )}
196
+ </div>
197
+ </div>
198
+ </div>
199
+ <button
200
+ onClick={() => handleToggle(model.id)}
201
+ className={`px-3 py-1 text-sm rounded ${
202
+ isActive
203
+ ? "bg-red-100 text-red-700 hover:bg-red-200"
204
+ : "bg-green-100 text-green-700 hover:bg-green-200"
205
+ }`}
206
+ >
207
+ {isActive ? "Désactiver" : "Activer"}
208
+ </button>
209
+ </div>
210
+ );
211
+ })}
212
+ </div>
213
+ );
214
+ }