@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.
@@ -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
- // Utiliser le status du contexte LB si pas de prop status
69
- // Combinaison du basic status et storage status pour backward compatibility
70
- const effectiveStatus = status || {
71
- ...lbApiStatus,
72
- ...lbBasicStatus,
73
- storage: lbStorageStatus?.storage,
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 balanceTotal =
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 balanceUsed = balanceUsage?.used ?? balanceUsage?.total ?? 0;
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, 6)} / ${formatNumber(balanceTotal)}
948
+ ${formatFixed(balanceUsed, 2)} / ${formatFixed(balanceTotal, 2)}
839
949
  </span>
840
950
  {renderUsageCircle(balancePercentage)}
841
951
  </div>