@lastbrain/ai-ui-react 1.0.69 → 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 (41) hide show
  1. package/dist/components/AiChipLabel.d.ts.map +1 -1
  2. package/dist/components/AiChipLabel.js +3 -1
  3. package/dist/components/AiContextButton.d.ts +5 -1
  4. package/dist/components/AiContextButton.d.ts.map +1 -1
  5. package/dist/components/AiContextButton.js +10 -4
  6. package/dist/components/AiPromptPanel.js +12 -6
  7. package/dist/components/AiStatusButton.d.ts.map +1 -1
  8. package/dist/components/AiStatusButton.js +18 -13
  9. package/dist/components/AiTextarea.d.ts.map +1 -1
  10. package/dist/components/LBApiKeySelector.d.ts.map +1 -1
  11. package/dist/components/LBConnectButton.d.ts.map +1 -1
  12. package/dist/components/LBConnectButton.js +1 -1
  13. package/dist/components/LBSigninModal.d.ts.map +1 -1
  14. package/dist/components/LBSigninModal.js +1 -1
  15. package/dist/context/LBAuthProvider.d.ts +35 -3
  16. package/dist/context/LBAuthProvider.d.ts.map +1 -1
  17. package/dist/context/LBAuthProvider.js +2 -0
  18. package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -1
  19. package/dist/hooks/useAiModels.d.ts.map +1 -1
  20. package/dist/hooks/useModelManagement.d.ts.map +1 -1
  21. package/dist/utils/errorHandler.d.ts +2 -2
  22. package/dist/utils/errorHandler.d.ts.map +1 -1
  23. package/dist/utils/errorHandler.js +8 -1
  24. package/dist/utils/modelManagement.d.ts +13 -10
  25. package/dist/utils/modelManagement.d.ts.map +1 -1
  26. package/dist/utils/modelManagement.js +19 -2
  27. package/package.json +1 -1
  28. package/src/components/AiChipLabel.tsx +5 -1
  29. package/src/components/AiContextButton.tsx +44 -23
  30. package/src/components/AiPromptPanel.tsx +19 -15
  31. package/src/components/AiStatusButton.tsx +24 -19
  32. package/src/components/AiTextarea.tsx +3 -1
  33. package/src/components/LBApiKeySelector.tsx +13 -3
  34. package/src/components/LBConnectButton.tsx +3 -12
  35. package/src/components/LBSigninModal.tsx +20 -10
  36. package/src/context/LBAuthProvider.tsx +45 -6
  37. package/src/examples/AiUiPremiumShowcase.tsx +4 -1
  38. package/src/hooks/useAiModels.ts +2 -1
  39. package/src/hooks/useModelManagement.ts +2 -1
  40. package/src/utils/errorHandler.ts +16 -3
  41. package/src/utils/modelManagement.ts +53 -15
@@ -1,14 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, type ButtonHTMLAttributes } from "react";
4
- import {
5
- Download,
6
- FileText,
7
- Loader2,
8
- Lock,
9
- Sparkles,
10
- X,
11
- } from "lucide-react";
4
+ import { Download, FileText, Loader2, Lock, Sparkles, X } from "lucide-react";
12
5
  import type { BaseAiProps } from "../types";
13
6
  import type { AiRadius, AiSize, AiVariant } from "../types";
14
7
  import { useAiCallText } from "../hooks/useAiCallText";
@@ -20,10 +13,14 @@ import { handleAIError } from "../utils/errorHandler";
20
13
  import { useLB } from "../context/LBAuthProvider";
21
14
  import { LBSigninModal } from "./LBSigninModal";
22
15
 
16
+ // Types pour les données de contexte
17
+ type ContextData = string | number | boolean | object | unknown[] | { [key: string]: unknown };
18
+
23
19
  export interface AiContextButtonProps
24
- extends Omit<BaseAiProps, "onValue" | "type">,
20
+ extends
21
+ Omit<BaseAiProps, "onValue" | "type">,
25
22
  Omit<ButtonHTMLAttributes<HTMLButtonElement>, "baseUrl" | "apiKeyId"> {
26
- contextData: any;
23
+ contextData: ContextData;
27
24
  contextDescription?: string;
28
25
  onResult?: (
29
26
  result: string,
@@ -99,7 +96,7 @@ export function AiContextButton({
99
96
  setIsOpen(true);
100
97
  };
101
98
 
102
- const formatContextData = (data: any): string => {
99
+ const formatContextData = (data: ContextData): string => {
103
100
  if (typeof data === "string") {
104
101
  return data;
105
102
  }
@@ -109,7 +106,10 @@ export function AiContextButton({
109
106
  return String(data);
110
107
  };
111
108
 
112
- const handleSubmit = async (selectedModel: string, selectedPrompt: string) => {
109
+ const handleSubmit = async (
110
+ selectedModel: string,
111
+ selectedPrompt: string
112
+ ) => {
113
113
  try {
114
114
  const contextString = formatContextData(contextData);
115
115
  const fullPrompt = `${selectedPrompt}\n\nCONTEXTE (${contextDescription}):\n${contextString}\n\nAnalyse ces données et réponds de manière structurée et claire.`;
@@ -175,12 +175,17 @@ export function AiContextButton({
175
175
  return;
176
176
  }
177
177
 
178
- const currentDate = new Date().toLocaleDateString("fr-FR").replace(/\//g, "-");
178
+ const currentDate = new Date()
179
+ .toLocaleDateString("fr-FR")
180
+ .replace(/\//g, "-");
179
181
  const defaultName = `analyse-${currentDate}.txt`;
180
182
  const fileName = prompt("Nom du fichier :", defaultName) || defaultName;
181
183
 
182
- const content = `ANALYSE DES DONNÉES - ${new Date().toLocaleString("fr-FR")}\n\nPROMPT UTILISÉ :\n${analysisResult.prompt}\n\nRÉSULTAT DE L'ANALYSE :\n${analysisResult.content}\n\n--- MÉTADONNÉES ---\nTokens utilisés: ${analysisResult.tokens.toLocaleString()}\nCoût: $${(
183
- apiKeyId?.includes("dev") ? 0 : analysisResult.cost
184
+ const content = `ANALYSE DES DONNÉES - ${new Date().toLocaleString("fr-FR")}\n\nPROMPT UTILISÉ :\n${analysisResult.prompt}\n\nRÉSULTAT DE L'ANALYSE :\n${analysisResult.content}\n\n--- MÉTADONNÉES ---\nTokens utilisés: ${analysisResult.tokens.toLocaleString()}\nCoût: $${(apiKeyId?.includes(
185
+ "dev"
186
+ )
187
+ ? 0
188
+ : analysisResult.cost
184
189
  ).toFixed(6)}\nID de requête: ${analysisResult.requestId || "N/A"}`;
185
190
 
186
191
  const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
@@ -206,7 +211,9 @@ export function AiContextButton({
206
211
  onClick={handleOpenPanel}
207
212
  disabled={disabled || loading || !isAuthReady}
208
213
  className={`ai-btn ai-context-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`}
209
- title={!isAuthReady ? "Authentication required" : "Analyser avec l'IA"}
214
+ title={
215
+ !isAuthReady ? "Authentication required" : "Analyser avec l'IA"
216
+ }
210
217
  >
211
218
  {loading ? (
212
219
  <>
@@ -251,14 +258,21 @@ export function AiContextButton({
251
258
  }
252
259
  }}
253
260
  >
254
- <div className="ai-popover ai-result-modal" onClick={(e) => e.stopPropagation()}>
261
+ <div
262
+ className="ai-popover ai-result-modal"
263
+ onClick={(e) => e.stopPropagation()}
264
+ >
255
265
  <div className="ai-result-header">
256
266
  <div className="ai-row">
257
267
  <FileText size={18} />
258
268
  <h2 className="ai-result-title">{resultModalTitle}</h2>
259
269
  </div>
260
270
  <div className="ai-row">
261
- <button type="button" className={`ai-btn ai-btn--ghost ai-btn--compact ${sizeClass} ${radiusClass}`} onClick={saveToFile}>
271
+ <button
272
+ type="button"
273
+ className={`ai-btn ai-btn--ghost ai-btn--compact ${sizeClass} ${radiusClass}`}
274
+ onClick={saveToFile}
275
+ >
262
276
  <Download size={14} />
263
277
  Sauvegarder
264
278
  </button>
@@ -284,14 +298,18 @@ export function AiContextButton({
284
298
 
285
299
  <div className="ai-result-block">
286
300
  <h3 className="ai-result-subtitle">Résultat</h3>
287
- <div className="ai-result-content">{analysisResult.content}</div>
301
+ <div className="ai-result-content">
302
+ {analysisResult.content}
303
+ </div>
288
304
  </div>
289
305
 
290
306
  <div className="ai-result-meta ai-between">
291
307
  <span>
292
- Coût: ${
293
- (apiKeyId?.includes("dev") ? 0 : analysisResult.cost).toFixed(6)
294
- }
308
+ Coût: $
309
+ {(apiKeyId?.includes("dev")
310
+ ? 0
311
+ : analysisResult.cost
312
+ ).toFixed(6)}
295
313
  </span>
296
314
  <span>ID: {analysisResult.requestId?.slice(-8) || "N/A"}</span>
297
315
  </div>
@@ -300,7 +318,10 @@ export function AiContextButton({
300
318
  </div>
301
319
  ) : null}
302
320
 
303
- <LBSigninModal isOpen={showAuthModal} onClose={() => setShowAuthModal(false)} />
321
+ <LBSigninModal
322
+ isOpen={showAuthModal}
323
+ onClose={() => setShowAuthModal(false)}
324
+ />
304
325
  <ErrorToast key={errorKey} error={errorData} onComplete={clearError} />
305
326
  </>
306
327
  );
@@ -284,7 +284,9 @@ function AiPromptPanelInternal({
284
284
  setSelectedModel("");
285
285
  return;
286
286
  }
287
- const hasSelected = modelOptions.some((model) => model.id === selectedModel);
287
+ const hasSelected = modelOptions.some(
288
+ (model) => model.id === selectedModel
289
+ );
288
290
  if (!hasSelected) {
289
291
  setSelectedModel(modelOptions[0].id);
290
292
  }
@@ -441,7 +443,8 @@ function AiPromptPanelInternal({
441
443
  width: "64px",
442
444
  height: "64px",
443
445
  borderRadius: "999px",
444
- border: "2px solid color-mix(in srgb, var(--ai-primary) 20%, transparent)",
446
+ border:
447
+ "2px solid color-mix(in srgb, var(--ai-primary) 20%, transparent)",
445
448
  borderTopColor: "var(--ai-primary)",
446
449
  animation: "ai-spin 1.1s linear infinite",
447
450
  display: "flex",
@@ -873,12 +876,15 @@ function AiPromptPanelInternal({
873
876
  transition: "all 0.2s",
874
877
  }}
875
878
  onMouseEnter={(e) => {
876
- e.currentTarget.style.background = "color-mix(in srgb, var(--ai-primary) 10%, transparent)";
877
- e.currentTarget.style.borderColor = "var(--ai-primary)";
879
+ e.currentTarget.style.background =
880
+ "color-mix(in srgb, var(--ai-primary) 10%, transparent)";
881
+ e.currentTarget.style.borderColor =
882
+ "var(--ai-primary)";
878
883
  }}
879
884
  onMouseLeave={(e) => {
880
885
  e.currentTarget.style.background = "transparent";
881
- e.currentTarget.style.borderColor = "var(--ai-border)";
886
+ e.currentTarget.style.borderColor =
887
+ "var(--ai-border)";
882
888
  }}
883
889
  >
884
890
  <div
@@ -958,12 +964,15 @@ function AiPromptPanelInternal({
958
964
  transition: "all 0.2s",
959
965
  }}
960
966
  onMouseEnter={(e) => {
961
- e.currentTarget.style.background = "color-mix(in srgb, var(--ai-primary) 10%, transparent)";
962
- e.currentTarget.style.borderColor = "var(--ai-primary)";
967
+ e.currentTarget.style.background =
968
+ "color-mix(in srgb, var(--ai-primary) 10%, transparent)";
969
+ e.currentTarget.style.borderColor =
970
+ "var(--ai-primary)";
963
971
  }}
964
972
  onMouseLeave={(e) => {
965
973
  e.currentTarget.style.background = "transparent";
966
- e.currentTarget.style.borderColor = "var(--ai-border)";
974
+ e.currentTarget.style.borderColor =
975
+ "var(--ai-border)";
967
976
  }}
968
977
  >
969
978
  <div
@@ -1020,10 +1029,7 @@ function AiPromptPanelInternal({
1020
1029
  backdropFilter: "blur(8px)",
1021
1030
  }}
1022
1031
  >
1023
- <button
1024
- onClick={handleClose}
1025
- className="ai-btn ai-btn--ghost"
1026
- >
1032
+ <button onClick={handleClose} className="ai-btn ai-btn--ghost">
1027
1033
  Cancel
1028
1034
  </button>
1029
1035
  <button
@@ -1179,9 +1185,7 @@ function AiPromptPanelInternal({
1179
1185
  />
1180
1186
  <div style={{ flex: 1 }}>
1181
1187
  <div className="ai-model-item-title">
1182
- <span>
1183
- {modelData.name}
1184
- </span>
1188
+ <span>{modelData.name}</span>
1185
1189
  {modelData.isPro && (
1186
1190
  <span className="ai-pill ai-pill--pro">
1187
1191
  PRO
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
- import type { AiStatus } from "@lastbrain/ai-ui-core";
4
- import { useLayoutEffect, useMemo, useRef, useState } from "react";
3
+ import type { AiStatus, LBUser } from "@lastbrain/ai-ui-core";
4
+ import { useLayoutEffect, useMemo, useRef, useState, useContext } from "react";
5
5
  import { createPortal } from "react-dom";
6
6
  import {
7
7
  ArrowRightLeft,
@@ -14,8 +14,8 @@ import {
14
14
  Settings,
15
15
  Shield,
16
16
  } from "lucide-react";
17
- import { useLB } from "../context/LBAuthProvider";
18
- import { useAiContext } from "../context/AiProvider";
17
+ import { LBContext, type LBApiKey, type BasicStatus, type StorageStatus } from "../context/LBAuthProvider";
18
+ import { AiContext } from "../context/AiProvider";
19
19
  import { LBApiKeySelector } from "./LBApiKeySelector";
20
20
  import { LBSigninModal } from "./LBSigninModal";
21
21
  import type { AiRadius, AiSize } from "../types";
@@ -149,23 +149,23 @@ export function AiStatusButton({
149
149
  radius = "full",
150
150
  }: AiStatusButtonProps) {
151
151
  let lbStatus: string | undefined;
152
- let user: any;
152
+ let user: LBUser | null = null;
153
153
  let logout: (() => Promise<void>) | undefined;
154
- let apiKeys: any[] = [];
154
+ let apiKeys: LBApiKey[] = [];
155
155
  let switchApiKey: ((apiKeyId: string) => Promise<void>) | undefined;
156
- let lbApiStatus: any = null;
157
- let lbBasicStatus: any = null;
158
- let lbStorageStatus: any = null;
156
+ let lbApiStatus: AiStatus | null = null;
157
+ let lbBasicStatus: BasicStatus | null = null;
158
+ let lbStorageStatus: StorageStatus | null = null;
159
159
  let lbIsLoadingStatus = false;
160
160
  let lbIsLoadingStorage = false;
161
- let lbSelectedKey: any = null;
161
+ let lbSelectedKey: LBApiKey | null = null;
162
162
  let lbRefreshBasicStatus: (() => Promise<void>) | undefined;
163
163
  let lbRefreshStorageStatus: ((force?: boolean) => Promise<void>) | undefined;
164
164
 
165
- try {
166
- const lbContext = useLB();
165
+ const lbContext = useContext(LBContext);
166
+ if (lbContext) {
167
167
  lbStatus = lbContext.status;
168
- user = lbContext.user;
168
+ user = lbContext.user ?? null;
169
169
  logout = lbContext.logout;
170
170
  apiKeys = lbContext.apiKeys || [];
171
171
  switchApiKey = lbContext.switchApiKey;
@@ -177,14 +177,15 @@ export function AiStatusButton({
177
177
  lbSelectedKey = lbContext.selectedKey || null;
178
178
  lbRefreshBasicStatus = lbContext.refreshBasicStatus;
179
179
  lbRefreshStorageStatus = lbContext.refreshStorageStatus;
180
- } catch {
180
+ } else {
181
181
  lbStatus = undefined;
182
182
  }
183
183
 
184
+ const aiContext = useContext(AiContext);
184
185
  let refetchProviders: (() => Promise<void>) | undefined;
185
- try {
186
- refetchProviders = useAiContext().refetchProviders;
187
- } catch {
186
+ if (aiContext) {
187
+ refetchProviders = aiContext.refetchProviders;
188
+ } else {
188
189
  refetchProviders = undefined;
189
190
  }
190
191
 
@@ -214,7 +215,7 @@ export function AiStatusButton({
214
215
  );
215
216
  const requiresApiKeySelection =
216
217
  lbStatus === "ready" && !hasApiKeySelected && apiKeys.length > 0;
217
- const isApiKeyAuthMode = effectiveStatus?.authType === "api_key";
218
+ const isApiKeyAuthMode = effectiveStatus && 'authType' in effectiveStatus && effectiveStatus.authType === "api_key";
218
219
 
219
220
  const [tooltipStyle, setTooltipStyle] = useState<Record<string, string>>({});
220
221
 
@@ -453,7 +454,11 @@ export function AiStatusButton({
453
454
  <span className="ai-popover-value">
454
455
  {lbIsLoadingStatus
455
456
  ? "..."
456
- : effectiveStatus?.authType || lbStatus || "unknown"}
457
+ : (effectiveStatus &&
458
+ "authType" in effectiveStatus &&
459
+ effectiveStatus.authType) ||
460
+ lbStatus ||
461
+ "unknown"}
457
462
  </span>
458
463
  </div>
459
464
  </div>
@@ -197,7 +197,9 @@ export function AiTextarea({
197
197
 
198
198
  return (
199
199
  <div className={`ai-control-group ai-glow ${className || ""}`}>
200
- <div className={`ai-shell ai-shell--textarea ${sizeClass} ${radiusClass}`}>
200
+ <div
201
+ className={`ai-shell ai-shell--textarea ${sizeClass} ${radiusClass}`}
202
+ >
201
203
  <textarea
202
204
  ref={textareaRef}
203
205
  {...textareaProps}
@@ -45,7 +45,10 @@ export function LBApiKeySelector({
45
45
 
46
46
  return (
47
47
  <div className="ai-signin-overlay" onClick={onCancel}>
48
- <div className="ai-signin-panel ai-key-modal-panel" onClick={(e) => e.stopPropagation()}>
48
+ <div
49
+ className="ai-signin-panel ai-key-modal-panel"
50
+ onClick={(e) => e.stopPropagation()}
51
+ >
49
52
  <div className="ai-signin-header">
50
53
  <div className="ai-center mb-3">
51
54
  <span className="ai-icon-badge">
@@ -87,7 +90,9 @@ export function LBApiKeySelector({
87
90
  <div>
88
91
  <div className="ai-model-item-title">{key.name}</div>
89
92
  <div className="ai-model-item-meta">
90
- <span>{key.keyPrefix || key.id.substring(0, 12) + "..."}</span>
93
+ <span>
94
+ {key.keyPrefix || key.id.substring(0, 12) + "..."}
95
+ </span>
91
96
  </div>
92
97
  </div>
93
98
  </div>
@@ -115,7 +120,12 @@ export function LBApiKeySelector({
115
120
  ) : null}
116
121
 
117
122
  <div className="ai-signin-actions">
118
- <button type="button" onClick={onCancel} disabled={loading} className="ai-btn ai-btn--ghost">
123
+ <button
124
+ type="button"
125
+ onClick={onCancel}
126
+ disabled={loading}
127
+ className="ai-btn ai-btn--ghost"
128
+ >
119
129
  Annuler
120
130
  </button>
121
131
  <button
@@ -38,8 +38,7 @@ export function LBConnectButton({
38
38
  onOpenModal?.();
39
39
  };
40
40
 
41
- const buttonLabel =
42
- status === "ready" && user ? "Déconnexion" : label;
41
+ const buttonLabel = status === "ready" && user ? "Déconnexion" : label;
43
42
 
44
43
  return (
45
44
  <>
@@ -67,10 +66,7 @@ export function LBConnectButton({
67
66
  )}
68
67
  </button>
69
68
 
70
- <LBSigninModal
71
- isOpen={showModal}
72
- onClose={() => setShowModal(false)}
73
- />
69
+ <LBSigninModal isOpen={showModal} onClose={() => setShowModal(false)} />
74
70
  </>
75
71
  );
76
72
  }
@@ -84,10 +80,5 @@ interface LBAuthModalProps {
84
80
  }
85
81
 
86
82
  export function LBAuthModal({ onClose }: LBAuthModalProps) {
87
- return (
88
- <LBSigninModal
89
- isOpen
90
- onClose={() => onClose(false)}
91
- />
92
- );
83
+ return <LBSigninModal isOpen onClose={() => onClose(false)} />;
93
84
  }
@@ -22,11 +22,7 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
22
22
  const [showKeySelector, setShowKeySelector] = useState(false);
23
23
  const [currentApiKeys, setCurrentApiKeys] = useState<any[]>([]);
24
24
 
25
- const {
26
- login,
27
- selectApiKeyWithToken,
28
- fetchApiKeys,
29
- } = lbContext || {};
25
+ const { login, selectApiKeyWithToken, fetchApiKeys } = lbContext || {};
30
26
 
31
27
  const canRender = Boolean(isOpen && lbContext && login);
32
28
 
@@ -75,7 +71,9 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
75
71
  setError("Erreur lors de la récupération des clés API");
76
72
  }
77
73
  } catch (err) {
78
- setError(err instanceof Error ? err.message : "Une erreur s'est produite");
74
+ setError(
75
+ err instanceof Error ? err.message : "Une erreur s'est produite"
76
+ );
79
77
  } finally {
80
78
  setLoading(false);
81
79
  }
@@ -91,7 +89,9 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
91
89
  setShowKeySelector(false);
92
90
  onClose();
93
91
  } catch (err) {
94
- setError(err instanceof Error ? err.message : "Erreur lors de la sélection");
92
+ setError(
93
+ err instanceof Error ? err.message : "Erreur lors de la sélection"
94
+ );
95
95
  setShowKeySelector(false);
96
96
  }
97
97
  };
@@ -151,7 +151,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
151
151
  <div className="ai-signin-content">
152
152
  <form onSubmit={handleSubmit}>
153
153
  <div className="ai-input-row">
154
- <label htmlFor="lb-signin-email" className="ai-input-label ai-row">
154
+ <label
155
+ htmlFor="lb-signin-email"
156
+ className="ai-input-label ai-row"
157
+ >
155
158
  <Mail size={14} className="ai-inline-icon" />
156
159
  Email
157
160
  </label>
@@ -173,7 +176,10 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
173
176
  </div>
174
177
 
175
178
  <div className="ai-input-row">
176
- <label htmlFor="lb-signin-password" className="ai-input-label ai-row">
179
+ <label
180
+ htmlFor="lb-signin-password"
181
+ className="ai-input-label ai-row"
182
+ >
177
183
  <Lock size={14} className="ai-inline-icon" />
178
184
  Mot de passe
179
185
  </label>
@@ -201,7 +207,11 @@ export function LBSigninModal({ isOpen, onClose }: LBSigninModalProps) {
201
207
  ) : null}
202
208
 
203
209
  <div className="ai-signin-actions">
204
- <button type="submit" className="ai-btn ai-btn--auth" disabled={loading}>
210
+ <button
211
+ type="submit"
212
+ className="ai-btn ai-btn--auth"
213
+ disabled={loading}
214
+ >
205
215
  {loading ? (
206
216
  <>
207
217
  <Loader2 size={16} className="ai-spinner" />
@@ -24,6 +24,39 @@ import type {
24
24
  } from "@lastbrain/ai-ui-core";
25
25
  import { createLBClient } from "@lastbrain/ai-ui-core";
26
26
 
27
+ export interface ApiKeyUser {
28
+ id?: string;
29
+ name?: string;
30
+ prefix?: string;
31
+ env?: string;
32
+ rate_limit_rpm?: number;
33
+ scopes?: string[];
34
+ }
35
+
36
+ export interface BasicStatus {
37
+ authType?: string;
38
+ user?: {
39
+ id?: string;
40
+ email?: string;
41
+ } | null;
42
+ apiKey?: ApiKeyUser | null;
43
+ api_key?: ApiKeyUser | null;
44
+ balance?: {
45
+ used?: number;
46
+ total?: number;
47
+ percentage?: number;
48
+ };
49
+ storage?: StorageStatus["storage"];
50
+ }
51
+
52
+ export interface StorageStatus {
53
+ storage?: {
54
+ used_mb?: number;
55
+ allocated_mb?: number;
56
+ percentage?: number;
57
+ } | null;
58
+ }
59
+
27
60
  interface LBProviderProps {
28
61
  children: ReactNode;
29
62
  /** URL de l'API LastBrain (ex: https://api.lastbrain.io) */
@@ -66,9 +99,9 @@ interface LBContextValue extends LBAuthState {
66
99
  /** Status API (balance, storage, API key info) */
67
100
  apiStatus: AiStatus | null;
68
101
  /** Status basique (rapide) - balance, API key info sans storage */
69
- basicStatus: any;
102
+ basicStatus: BasicStatus | null;
70
103
  /** Status storage (lent) avec cache */
71
- storageStatus: any;
104
+ storageStatus: StorageStatus | null;
72
105
  /** Fonction pour rafraîchir le status rapide */
73
106
  refreshBasicStatus: () => Promise<void>;
74
107
  /** Fonction pour rafraîchir le storage (avec cache optionnel) */
@@ -83,6 +116,9 @@ interface LBContextValue extends LBAuthState {
83
116
 
84
117
  const LBContext = createContext<LBContextValue | undefined>(undefined);
85
118
 
119
+ // Export pour usage dans d'autres composants
120
+ export { LBContext };
121
+
86
122
  export function LBProvider({
87
123
  children,
88
124
  baseUrl: _baseUrl = "/api/lastbrain",
@@ -96,8 +132,8 @@ export function LBProvider({
96
132
  const [apiKeys, setApiKeys] = useState<LBApiKey[]>([]);
97
133
  const [accessToken, setAccessToken] = useState<string>();
98
134
  const [apiStatus, setApiStatus] = useState<AiStatus | null>(null);
99
- const [basicStatus, setBasicStatus] = useState<any>(null);
100
- const [storageStatus, setStorageStatus] = useState<any>(null);
135
+ const [basicStatus, setBasicStatus] = useState<BasicStatus | null>(null);
136
+ const [storageStatus, setStorageStatus] = useState<StorageStatus | null>(null);
101
137
  const [isLoadingStatus, setIsLoadingStatus] = useState(false);
102
138
  const [isLoadingStorage, setIsLoadingStorage] = useState(false);
103
139
  const [storageLastFetch, setStorageLastFetch] = useState<number>(0);
@@ -373,7 +409,7 @@ export function LBProvider({
373
409
 
374
410
  setIsLoadingStatus(true);
375
411
  try {
376
- let data: any;
412
+ let data: BasicStatus;
377
413
  try {
378
414
  data = await lbClient.getStatus();
379
415
  } catch {
@@ -452,7 +488,7 @@ export function LBProvider({
452
488
  ...basicStatus,
453
489
  storage: storageData?.storage,
454
490
  };
455
- setApiStatus(combinedStatus);
491
+ setApiStatus(combinedStatus as AiStatus);
456
492
  } catch (error) {
457
493
  console.error("[LBProvider] Failed to fetch storage status:", error);
458
494
  // Arrêter les tentatives répétées si erreur persistante
@@ -619,3 +655,6 @@ export function useLB(): LBContextValue {
619
655
  }
620
656
  return context;
621
657
  }
658
+
659
+ // Re-export des types pour usage externe
660
+ export type { LBApiKey };
@@ -56,7 +56,10 @@ function ShowcasePanel({
56
56
  <Sparkles size={14} className="text-[var(--ai-primary)]" />
57
57
  </div>
58
58
  <div className="mt-3 space-y-2">
59
- <input className="ai-control ai-control-input" placeholder="Default state" />
59
+ <input
60
+ className="ai-control ai-control-input"
61
+ placeholder="Default state"
62
+ />
60
63
  <input
61
64
  className="ai-control ai-control-input ai-control--focused"
62
65
  placeholder="Focused style"
@@ -26,7 +26,8 @@ export function useAiModels(options?: UseAiModelsOptions): UseAiModelsResult {
26
26
 
27
27
  // Utiliser le contexte dès que la base URL correspond.
28
28
  // L'apiKeyId peut varier (session/api-key sélectionnée) sans invalider le contexte.
29
- const useContextData = !options?.baseUrl || options.baseUrl === context.baseUrl;
29
+ const useContextData =
30
+ !options?.baseUrl || options.baseUrl === context.baseUrl;
30
31
 
31
32
  // Filtrer les modèles selon le type demandé
32
33
  const filteredModels = useMemo(() => {
@@ -44,7 +44,8 @@ export function useModelManagement(
44
44
  // Utiliser les données du contexte si la base URL correspond.
45
45
  // L'apiKey peut changer selon le mode d'auth (lb_session/supabase/api_key) sans
46
46
  // devoir casser le cache/provider de modèles déjà présent.
47
- const useContextData = !options.baseUrl || options.baseUrl === context.baseUrl;
47
+ const useContextData =
48
+ !options.baseUrl || options.baseUrl === context.baseUrl;
48
49
 
49
50
  // Filtrer par catégorie si nécessaire
50
51
  const filteredModels = useMemo(() => {
@@ -8,12 +8,25 @@ export interface ParsedError {
8
8
  isUserFriendly: boolean;
9
9
  }
10
10
 
11
+ type NormalizedErrorLike = {
12
+ code: string;
13
+ message: string;
14
+ };
15
+
16
+ function isNormalizedErrorLike(value: unknown): value is NormalizedErrorLike {
17
+ if (!value || typeof value !== "object") {
18
+ return false;
19
+ }
20
+ const v = value as Record<string, unknown>;
21
+ return typeof v.code === "string" && typeof v.message === "string";
22
+ }
23
+
11
24
  /**
12
25
  * Parse et uniformise la gestion des erreurs des composants AI
13
26
  */
14
- export function parseAIError(error: any): ParsedError {
27
+ export function parseAIError(error: unknown): ParsedError {
15
28
  // Si l'erreur est déjà un objet normalisé
16
- if (error?.code && error?.message) {
29
+ if (isNormalizedErrorLike(error)) {
17
30
  return {
18
31
  message: getUserFriendlyMessage(error.code, error.message),
19
32
  code: error.code,
@@ -160,7 +173,7 @@ export interface ErrorToastCallback {
160
173
  * @param showInternalToast Callback interne optionnelle pour afficher un toast dans le composant
161
174
  */
162
175
  export function handleAIError(
163
- error: any,
176
+ error: unknown,
164
177
  onToast?: ErrorToastCallback,
165
178
  showInternalToast?: (error: { message: string; code?: string }) => void
166
179
  ): void {