@lastbrain/ai-ui-react 1.0.63 → 1.0.64
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/AiPromptPanel.js +19 -6
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +78 -11
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +129 -182
- package/dist/utils/modelManagement.d.ts.map +1 -1
- package/dist/utils/modelManagement.js +11 -5
- package/package.json +2 -2
- package/src/components/AiPromptPanel.tsx +25 -6
- package/src/components/AiStatusButton.tsx +122 -12
- package/src/context/LBAuthProvider.tsx +137 -209
- package/src/utils/modelManagement.ts +14 -5
|
@@ -43,6 +43,9 @@ export function AiStatusButton({
|
|
|
43
43
|
let lbStorageStatus: any = null;
|
|
44
44
|
let lbIsLoadingStatus: boolean = false;
|
|
45
45
|
let lbIsLoadingStorage: boolean = false;
|
|
46
|
+
let lbSelectedKey: any = null;
|
|
47
|
+
let lbRefreshBasicStatus: (() => Promise<void>) | undefined;
|
|
48
|
+
let lbRefreshStorageStatus: ((force?: boolean) => Promise<void>) | undefined;
|
|
46
49
|
|
|
47
50
|
try {
|
|
48
51
|
const lbContext = useLB();
|
|
@@ -58,6 +61,9 @@ export function AiStatusButton({
|
|
|
58
61
|
lbStorageStatus = lbContext.storageStatus;
|
|
59
62
|
lbIsLoadingStatus = lbContext.isLoadingStatus || false;
|
|
60
63
|
lbIsLoadingStorage = lbContext.isLoadingStorage || false;
|
|
64
|
+
lbSelectedKey = lbContext.selectedKey || null;
|
|
65
|
+
lbRefreshBasicStatus = lbContext.refreshBasicStatus;
|
|
66
|
+
lbRefreshStorageStatus = lbContext.refreshStorageStatus;
|
|
61
67
|
} catch {
|
|
62
68
|
// LBProvider n'est pas disponible, ignorer
|
|
63
69
|
lbStatus = undefined;
|
|
@@ -65,13 +71,17 @@ export function AiStatusButton({
|
|
|
65
71
|
logout = undefined;
|
|
66
72
|
}
|
|
67
73
|
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
// Toujours prioriser les données du contexte LB quand disponibles
|
|
75
|
+
// pour éviter d'afficher un status externe obsolète (Unknown/0).
|
|
76
|
+
const lbEffectiveStatus =
|
|
77
|
+
lbStatus === "ready"
|
|
78
|
+
? {
|
|
79
|
+
...(lbApiStatus || {}),
|
|
80
|
+
...(lbBasicStatus || {}),
|
|
81
|
+
storage: lbStorageStatus?.storage || lbApiStatus?.storage,
|
|
82
|
+
}
|
|
83
|
+
: null;
|
|
84
|
+
const effectiveStatus = lbEffectiveStatus || status || null;
|
|
75
85
|
|
|
76
86
|
// Récupérer refetchProviders depuis AiProvider si disponible
|
|
77
87
|
let refetchProviders: (() => Promise<void>) | undefined;
|
|
@@ -94,6 +104,7 @@ export function AiStatusButton({
|
|
|
94
104
|
percentage?: number;
|
|
95
105
|
purchased?: number;
|
|
96
106
|
quota?: number;
|
|
107
|
+
remaining?: number;
|
|
97
108
|
};
|
|
98
109
|
type StorageUsage = {
|
|
99
110
|
used_mb?: number;
|
|
@@ -104,8 +115,6 @@ export function AiStatusButton({
|
|
|
104
115
|
total_mb?: number;
|
|
105
116
|
};
|
|
106
117
|
|
|
107
|
-
const formatNumber = (value: number | null | undefined) =>
|
|
108
|
-
typeof value === "number" ? value.toLocaleString() : "0";
|
|
109
118
|
const formatFixed = (value: number | null | undefined, digits: number) =>
|
|
110
119
|
typeof value === "number" ? value.toFixed(digits) : "0.00";
|
|
111
120
|
const formatStorage = (valueMb: number | null | undefined) => {
|
|
@@ -179,10 +188,13 @@ export function AiStatusButton({
|
|
|
179
188
|
const balanceUsage = effectiveStatus?.balance as BalanceUsage | undefined;
|
|
180
189
|
const storageUsage = effectiveStatus?.storage as StorageUsage | undefined;
|
|
181
190
|
|
|
182
|
-
const
|
|
191
|
+
const balanceUsed = safeNumber(balanceUsage?.used);
|
|
192
|
+
const balanceRemaining = safeNumber(balanceUsage?.remaining);
|
|
193
|
+
const rawBalanceTotal =
|
|
183
194
|
balanceUsage?.total ??
|
|
184
195
|
safeNumber(balanceUsage?.purchased) + safeNumber(balanceUsage?.quota);
|
|
185
|
-
const
|
|
196
|
+
const balanceTotal =
|
|
197
|
+
rawBalanceTotal > 0 ? rawBalanceTotal : balanceUsed + balanceRemaining;
|
|
186
198
|
const balancePercentage =
|
|
187
199
|
balanceUsage?.percentage ??
|
|
188
200
|
(balanceTotal > 0 ? Math.round((balanceUsed / balanceTotal) * 100) : 0);
|
|
@@ -202,6 +214,11 @@ export function AiStatusButton({
|
|
|
202
214
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
203
215
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
204
216
|
const canPortal = typeof document !== "undefined";
|
|
217
|
+
const hasApiKeySelected = Boolean(
|
|
218
|
+
effectiveStatus?.apiKey?.id || effectiveStatus?.api_key?.id || lbSelectedKey?.id
|
|
219
|
+
);
|
|
220
|
+
const requiresApiKeySelection =
|
|
221
|
+
lbStatus === "ready" && !hasApiKeySelected && apiKeys.length > 0;
|
|
205
222
|
|
|
206
223
|
useLayoutEffect(() => {
|
|
207
224
|
if (!showTooltip || !buttonRef.current) {
|
|
@@ -279,11 +296,17 @@ export function AiStatusButton({
|
|
|
279
296
|
}, [showTooltip, canPortal]);
|
|
280
297
|
|
|
281
298
|
const handleMouseEnter = () => {
|
|
299
|
+
if (requiresApiKeySelection) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
282
302
|
setShowTooltip(true);
|
|
283
303
|
setIsHovered(true);
|
|
284
304
|
};
|
|
285
305
|
|
|
286
306
|
const handleMouseLeave = () => {
|
|
307
|
+
if (requiresApiKeySelection) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
287
310
|
// Keep tooltip visible if hovering over it
|
|
288
311
|
setTimeout(() => {
|
|
289
312
|
if (
|
|
@@ -296,6 +319,32 @@ export function AiStatusButton({
|
|
|
296
319
|
}, 100);
|
|
297
320
|
};
|
|
298
321
|
|
|
322
|
+
useLayoutEffect(() => {
|
|
323
|
+
if (!showTooltip || requiresApiKeySelection || lbStatus !== "ready") {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (!lbBasicStatus && !lbIsLoadingStatus && lbRefreshBasicStatus) {
|
|
327
|
+
lbRefreshBasicStatus().catch(() => undefined);
|
|
328
|
+
}
|
|
329
|
+
if (
|
|
330
|
+
!lbStorageStatus?.storage &&
|
|
331
|
+
!lbIsLoadingStorage &&
|
|
332
|
+
lbRefreshStorageStatus
|
|
333
|
+
) {
|
|
334
|
+
lbRefreshStorageStatus().catch(() => undefined);
|
|
335
|
+
}
|
|
336
|
+
}, [
|
|
337
|
+
showTooltip,
|
|
338
|
+
requiresApiKeySelection,
|
|
339
|
+
lbStatus,
|
|
340
|
+
lbBasicStatus,
|
|
341
|
+
lbStorageStatus,
|
|
342
|
+
lbIsLoadingStatus,
|
|
343
|
+
lbIsLoadingStorage,
|
|
344
|
+
lbRefreshBasicStatus,
|
|
345
|
+
lbRefreshStorageStatus,
|
|
346
|
+
]);
|
|
347
|
+
|
|
299
348
|
if (loading || isSelectingApiKey || isLoadingStatus || lbIsLoadingStatus) {
|
|
300
349
|
return (
|
|
301
350
|
<button
|
|
@@ -321,6 +370,59 @@ export function AiStatusButton({
|
|
|
321
370
|
);
|
|
322
371
|
}
|
|
323
372
|
|
|
373
|
+
if (requiresApiKeySelection) {
|
|
374
|
+
return (
|
|
375
|
+
<div style={{ position: "relative", display: "inline-block" }}>
|
|
376
|
+
<button
|
|
377
|
+
ref={buttonRef}
|
|
378
|
+
style={{
|
|
379
|
+
...aiStyles.statusButton,
|
|
380
|
+
color: "#f59e0b",
|
|
381
|
+
...(isHovered && aiStyles.statusButtonHover),
|
|
382
|
+
}}
|
|
383
|
+
className={className}
|
|
384
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
385
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
386
|
+
onClick={() => setShowApiKeySelector(true)}
|
|
387
|
+
title="Select an API key to enable AI status and generation"
|
|
388
|
+
>
|
|
389
|
+
<svg
|
|
390
|
+
width="16"
|
|
391
|
+
height="16"
|
|
392
|
+
viewBox="0 0 24 24"
|
|
393
|
+
fill="none"
|
|
394
|
+
stroke="currentColor"
|
|
395
|
+
>
|
|
396
|
+
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
|
397
|
+
</svg>
|
|
398
|
+
</button>
|
|
399
|
+
{showApiKeySelector && apiKeys.length > 0 && (
|
|
400
|
+
<LBApiKeySelector
|
|
401
|
+
isOpen={showApiKeySelector}
|
|
402
|
+
apiKeys={apiKeys}
|
|
403
|
+
onSelect={async (keyId) => {
|
|
404
|
+
setIsSelectingApiKey(true);
|
|
405
|
+
try {
|
|
406
|
+
if (switchApiKey) {
|
|
407
|
+
await switchApiKey(keyId);
|
|
408
|
+
}
|
|
409
|
+
setShowApiKeySelector(false);
|
|
410
|
+
if (refetchProviders) {
|
|
411
|
+
await refetchProviders();
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error("Failed to select API key:", error);
|
|
415
|
+
} finally {
|
|
416
|
+
setIsSelectingApiKey(false);
|
|
417
|
+
}
|
|
418
|
+
}}
|
|
419
|
+
onCancel={() => setShowApiKeySelector(false)}
|
|
420
|
+
/>
|
|
421
|
+
)}
|
|
422
|
+
</div>
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
324
426
|
if (!effectiveStatus) {
|
|
325
427
|
// Si pas de statut API et pas de LBProvider, afficher message simple
|
|
326
428
|
if (!lbStatus && lbStatus !== "ready") {
|
|
@@ -828,6 +930,14 @@ export function AiStatusButton({
|
|
|
828
930
|
req/min
|
|
829
931
|
</span>
|
|
830
932
|
</div>
|
|
933
|
+
<div style={aiStyles.tooltipRow}>
|
|
934
|
+
<span style={aiStyles.tooltipLabel}>Auth:</span>
|
|
935
|
+
<span style={aiStyles.tooltipValue}>
|
|
936
|
+
{lbIsLoadingStatus
|
|
937
|
+
? "..."
|
|
938
|
+
: effectiveStatus?.authType || lbStatus || "unknown"}
|
|
939
|
+
</span>
|
|
940
|
+
</div>
|
|
831
941
|
</div>
|
|
832
942
|
|
|
833
943
|
<div style={aiStyles.tooltipSection}>
|
|
@@ -835,7 +945,7 @@ export function AiStatusButton({
|
|
|
835
945
|
<div style={aiStyles.tooltipRow}>
|
|
836
946
|
<span style={aiStyles.tooltipLabel}>Total:</span>
|
|
837
947
|
<span style={aiStyles.tooltipValue}>
|
|
838
|
-
${formatFixed(balanceUsed,
|
|
948
|
+
${formatFixed(balanceUsed, 2)} / ${formatFixed(balanceTotal, 2)}
|
|
839
949
|
</span>
|
|
840
950
|
{renderUsageCircle(balancePercentage)}
|
|
841
951
|
</div>
|