@lastbrain/ai-ui-react 1.0.78 → 1.0.80
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/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +12 -6
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +11 -5
- package/dist/components/AiInput.js +4 -4
- package/dist/components/AiPromptPanel.d.ts +2 -0
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +138 -5
- package/dist/components/AiSelect.js +4 -4
- package/dist/components/AiStatusButton.js +3 -3
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +15 -4
- package/dist/components/LBApiKeySelector.d.ts.map +1 -1
- package/dist/components/LBApiKeySelector.js +12 -2
- package/dist/components/UsageToast.js +1 -1
- package/dist/context/LBAuthProvider.d.ts +3 -1
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +65 -29
- package/dist/styles.css +6 -4
- package/package.json +1 -1
- package/src/components/AiContextButton.tsx +14 -6
- package/src/components/AiImageButton.tsx +12 -6
- package/src/components/AiInput.tsx +4 -4
- package/src/components/AiPromptPanel.tsx +279 -12
- package/src/components/AiSelect.tsx +4 -4
- package/src/components/AiStatusButton.tsx +3 -3
- package/src/components/AiTextarea.tsx +16 -5
- package/src/components/LBApiKeySelector.tsx +14 -2
- package/src/components/UsageToast.tsx +1 -1
- package/src/context/LBAuthProvider.tsx +84 -30
- package/src/styles.css +6 -4
|
@@ -46,6 +46,8 @@ export interface BasicStatus {
|
|
|
46
46
|
used?: number;
|
|
47
47
|
total?: number;
|
|
48
48
|
percentage?: number;
|
|
49
|
+
remaining?: number;
|
|
50
|
+
providerBudget?: number;
|
|
49
51
|
};
|
|
50
52
|
storage?: StorageStatus["storage"];
|
|
51
53
|
}
|
|
@@ -58,6 +60,20 @@ export interface StorageStatus {
|
|
|
58
60
|
} | null;
|
|
59
61
|
}
|
|
60
62
|
|
|
63
|
+
interface WalletStatusResponse {
|
|
64
|
+
walletSellValueUsd?: number;
|
|
65
|
+
totalAdded?: number;
|
|
66
|
+
totalUsed?: number;
|
|
67
|
+
percentUsed?: number;
|
|
68
|
+
percentage?: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasServerSelectedApiKeyCookie(userData: unknown): boolean {
|
|
72
|
+
if (!userData || typeof userData !== "object") return false;
|
|
73
|
+
const value = (userData as Record<string, unknown>).hasApiKeySelectedCookie;
|
|
74
|
+
return value === true;
|
|
75
|
+
}
|
|
76
|
+
|
|
61
77
|
interface LBProviderProps {
|
|
62
78
|
children: ReactNode;
|
|
63
79
|
/** URL de l'API LastBrain (ex: https://api.lastbrain.io) */
|
|
@@ -115,7 +131,7 @@ interface LBContextValue extends LBAuthState {
|
|
|
115
131
|
isLoadingStatus: boolean;
|
|
116
132
|
/** Indique si le storage est en cours de chargement */
|
|
117
133
|
isLoadingStorage: boolean;
|
|
118
|
-
/** True si le cookie api_key_selected est présent */
|
|
134
|
+
/** True si le cookie api_key_selected est présent (info backend) */
|
|
119
135
|
hasSelectedApiKeyCookie: boolean;
|
|
120
136
|
}
|
|
121
137
|
|
|
@@ -145,18 +161,10 @@ export function LBProvider({
|
|
|
145
161
|
const [isLoadingStatus, setIsLoadingStatus] = useState(false);
|
|
146
162
|
const [isLoadingStorage, setIsLoadingStorage] = useState(false);
|
|
147
163
|
const [storageLastFetch, setStorageLastFetch] = useState<number>(0);
|
|
148
|
-
const [hasSelectedApiKeyCookie, setHasSelectedApiKeyCookie] =
|
|
164
|
+
const [hasSelectedApiKeyCookie, setHasSelectedApiKeyCookie] =
|
|
165
|
+
useState(false);
|
|
149
166
|
const previousStatusRef = useRef<LBAuthState["status"]>("loading");
|
|
150
167
|
|
|
151
|
-
const syncSelectedApiKeyCookie = useCallback(() => {
|
|
152
|
-
if (typeof document === "undefined") return;
|
|
153
|
-
const hasCookie = document.cookie
|
|
154
|
-
.split(";")
|
|
155
|
-
.map((part) => part.trim())
|
|
156
|
-
.some((part) => part.startsWith("api_key_selected=") && part.length > 17);
|
|
157
|
-
setHasSelectedApiKeyCookie(hasCookie);
|
|
158
|
-
}, []);
|
|
159
|
-
|
|
160
168
|
const lbClient = useMemo(
|
|
161
169
|
() =>
|
|
162
170
|
createLBClient({
|
|
@@ -175,6 +183,7 @@ export function LBProvider({
|
|
|
175
183
|
const session = await lbClient.verifySession();
|
|
176
184
|
if (session) {
|
|
177
185
|
const userData = await lbClient.getUser().catch(() => null);
|
|
186
|
+
const hasCookie = hasServerSelectedApiKeyCookie(userData);
|
|
178
187
|
const activeKey = userData?.apiKeyActive
|
|
179
188
|
? {
|
|
180
189
|
id: userData.apiKeyActive.id,
|
|
@@ -193,13 +202,15 @@ export function LBProvider({
|
|
|
193
202
|
id: session.userId,
|
|
194
203
|
email: userData?.user?.email || "",
|
|
195
204
|
},
|
|
196
|
-
selectedKey: activeKey,
|
|
205
|
+
selectedKey: hasCookie ? activeKey : undefined,
|
|
197
206
|
});
|
|
207
|
+
setHasSelectedApiKeyCookie(hasCookie);
|
|
198
208
|
onStatusChange?.("ready");
|
|
199
209
|
} else {
|
|
200
210
|
// Supabase session mode (no lb_session cookie): try user endpoint directly
|
|
201
211
|
const userData = await lbClient.getUser().catch(() => null);
|
|
202
212
|
if (userData?.user?.id) {
|
|
213
|
+
const hasCookie = hasServerSelectedApiKeyCookie(userData);
|
|
203
214
|
const activeKey = userData?.apiKeyActive
|
|
204
215
|
? {
|
|
205
216
|
id: userData.apiKeyActive.id,
|
|
@@ -218,17 +229,20 @@ export function LBProvider({
|
|
|
218
229
|
id: userData.user.id,
|
|
219
230
|
email: userData.user.email || "",
|
|
220
231
|
},
|
|
221
|
-
selectedKey: activeKey,
|
|
232
|
+
selectedKey: hasCookie ? activeKey : undefined,
|
|
222
233
|
});
|
|
234
|
+
setHasSelectedApiKeyCookie(hasCookie);
|
|
223
235
|
onStatusChange?.("ready");
|
|
224
236
|
} else {
|
|
225
237
|
setState({ status: "needs_auth" });
|
|
238
|
+
setHasSelectedApiKeyCookie(false);
|
|
226
239
|
onStatusChange?.("needs_auth");
|
|
227
240
|
}
|
|
228
241
|
}
|
|
229
242
|
} catch (error) {
|
|
230
243
|
console.error("[LBProvider] Session check failed:", error);
|
|
231
244
|
setState({ status: "needs_auth" });
|
|
245
|
+
setHasSelectedApiKeyCookie(false);
|
|
232
246
|
onStatusChange?.("needs_auth");
|
|
233
247
|
}
|
|
234
248
|
}, [lbClient, onStatusChange]);
|
|
@@ -237,12 +251,42 @@ export function LBProvider({
|
|
|
237
251
|
checkSession();
|
|
238
252
|
}, [checkSession]);
|
|
239
253
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
254
|
+
const fetchWalletSummary = useCallback(async (): Promise<{
|
|
255
|
+
used?: number;
|
|
256
|
+
total?: number;
|
|
257
|
+
percentage?: number;
|
|
258
|
+
remaining?: number;
|
|
259
|
+
} | null> => {
|
|
260
|
+
const headers = lbClient.getAuthHeaders(state.session?.sessionToken);
|
|
261
|
+
const walletUrls = Array.from(
|
|
262
|
+
new Set([
|
|
263
|
+
`${proxyUrl}/auth/wallet`,
|
|
264
|
+
`${proxyUrl.replace("/api/lastbrain", "/api/ai")}/auth/wallet`,
|
|
265
|
+
])
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
for (const url of walletUrls) {
|
|
269
|
+
try {
|
|
270
|
+
const response = await fetch(url, {
|
|
271
|
+
method: "GET",
|
|
272
|
+
credentials: "include",
|
|
273
|
+
headers,
|
|
274
|
+
});
|
|
275
|
+
if (!response.ok) continue;
|
|
276
|
+
const data = (await response.json()) as WalletStatusResponse;
|
|
277
|
+
return {
|
|
278
|
+
used: Number(data.totalUsed || 0),
|
|
279
|
+
total: Number(data.totalAdded || 0),
|
|
280
|
+
percentage: Number(data.percentUsed ?? data.percentage ?? 0),
|
|
281
|
+
remaining: Number(data.walletSellValueUsd || 0),
|
|
282
|
+
};
|
|
283
|
+
} catch {
|
|
284
|
+
// try next endpoint
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return null;
|
|
289
|
+
}, [lbClient, proxyUrl, state.session?.sessionToken]);
|
|
246
290
|
|
|
247
291
|
/**
|
|
248
292
|
* Récupère les clés API de l'utilisateur
|
|
@@ -294,7 +338,6 @@ export function LBProvider({
|
|
|
294
338
|
setAccessToken(undefined); // Nettoyer l'access token temporaire
|
|
295
339
|
setApiKeys([]); // Nettoyer les clés API temporaires
|
|
296
340
|
setHasSelectedApiKeyCookie(true);
|
|
297
|
-
setTimeout(() => syncSelectedApiKeyCookie(), 100);
|
|
298
341
|
onStatusChange?.("ready");
|
|
299
342
|
onAuthChange?.(); // Refresh provider after signin
|
|
300
343
|
} catch (error) {
|
|
@@ -307,7 +350,7 @@ export function LBProvider({
|
|
|
307
350
|
throw error;
|
|
308
351
|
}
|
|
309
352
|
},
|
|
310
|
-
[lbClient, onAuthChange, onStatusChange, state.user
|
|
353
|
+
[lbClient, onAuthChange, onStatusChange, state.user]
|
|
311
354
|
);
|
|
312
355
|
|
|
313
356
|
/**
|
|
@@ -452,16 +495,20 @@ export function LBProvider({
|
|
|
452
495
|
used: 0,
|
|
453
496
|
total: userData.balance?.sellValueUsd || 0,
|
|
454
497
|
percentage: 0,
|
|
498
|
+
remaining: userData.balance?.sellValueUsd || 0,
|
|
455
499
|
},
|
|
456
500
|
};
|
|
457
501
|
}
|
|
502
|
+
const userData = await lbClient.getUser().catch(() => null);
|
|
503
|
+
setHasSelectedApiKeyCookie(hasServerSelectedApiKeyCookie(userData));
|
|
504
|
+
const walletSummary = await fetchWalletSummary().catch(() => null);
|
|
458
505
|
const normalizedStatus = {
|
|
459
506
|
...data,
|
|
460
507
|
authType: data?.authType,
|
|
461
|
-
user: data?.user,
|
|
462
|
-
apiKey: data?.apiKey || data?.api_key,
|
|
463
|
-
api_key: data?.api_key || data?.apiKey,
|
|
464
|
-
balance: data?.balance || {
|
|
508
|
+
user: data?.user || userData?.user,
|
|
509
|
+
apiKey: data?.apiKey || data?.api_key || userData?.apiKeyActive,
|
|
510
|
+
api_key: data?.api_key || data?.apiKey || userData?.apiKeyActive,
|
|
511
|
+
balance: walletSummary || data?.balance || {
|
|
465
512
|
used: 0,
|
|
466
513
|
total: 0,
|
|
467
514
|
percentage: 0,
|
|
@@ -480,7 +527,7 @@ export function LBProvider({
|
|
|
480
527
|
} finally {
|
|
481
528
|
setIsLoadingStatus(false);
|
|
482
529
|
}
|
|
483
|
-
}, [lbClient, state.status, storageStatus]);
|
|
530
|
+
}, [fetchWalletSummary, lbClient, state.status, storageStatus]);
|
|
484
531
|
|
|
485
532
|
/**
|
|
486
533
|
* Récupère le storage - LENT avec cache (5 minutes)
|
|
@@ -572,9 +619,11 @@ export function LBProvider({
|
|
|
572
619
|
}));
|
|
573
620
|
}
|
|
574
621
|
setHasSelectedApiKeyCookie(true);
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
setTimeout(() =>
|
|
622
|
+
// Ne pas bloquer la validation UI de sélection sur des requêtes status/wallet/storage.
|
|
623
|
+
void refreshBasicStatus();
|
|
624
|
+
setTimeout(() => {
|
|
625
|
+
void refreshStorageStatus();
|
|
626
|
+
}, 100);
|
|
578
627
|
} else {
|
|
579
628
|
throw new Error("No valid authentication method available");
|
|
580
629
|
}
|
|
@@ -587,7 +636,6 @@ export function LBProvider({
|
|
|
587
636
|
lbClient,
|
|
588
637
|
refreshBasicStatus,
|
|
589
638
|
refreshStorageStatus,
|
|
590
|
-
syncSelectedApiKeyCookie,
|
|
591
639
|
]
|
|
592
640
|
);
|
|
593
641
|
|
|
@@ -639,6 +687,12 @@ export function LBProvider({
|
|
|
639
687
|
}
|
|
640
688
|
}, [lbClient, state.status]);
|
|
641
689
|
|
|
690
|
+
useEffect(() => {
|
|
691
|
+
if (state.status !== "ready" || !state.session) return;
|
|
692
|
+
if (hasSelectedApiKeyCookie) return;
|
|
693
|
+
setState((prev) => ({ ...prev, selectedKey: undefined }));
|
|
694
|
+
}, [hasSelectedApiKeyCookie, state.session, state.status]);
|
|
695
|
+
|
|
642
696
|
// Refresh status uniquement lors de la transition vers "ready"
|
|
643
697
|
// (évite les boucles status/user causées par des callbacks recréés)
|
|
644
698
|
useEffect(() => {
|
package/src/styles.css
CHANGED
|
@@ -310,7 +310,7 @@
|
|
|
310
310
|
min-height: 96px;
|
|
311
311
|
resize: none;
|
|
312
312
|
overflow: hidden;
|
|
313
|
-
padding: 6px 2px
|
|
313
|
+
padding: 6px 2px 32px;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
316
|
.ai-control[aria-invalid="true"],
|
|
@@ -641,7 +641,7 @@
|
|
|
641
641
|
|
|
642
642
|
.ai-status-tooltip {
|
|
643
643
|
width: min(370px, calc(100vw - 16px));
|
|
644
|
-
z-index:
|
|
644
|
+
z-index: 2147483646;
|
|
645
645
|
}
|
|
646
646
|
|
|
647
647
|
.ai-status-actions {
|
|
@@ -685,7 +685,7 @@
|
|
|
685
685
|
white-space: nowrap;
|
|
686
686
|
opacity: 0;
|
|
687
687
|
pointer-events: none;
|
|
688
|
-
z-index:
|
|
688
|
+
z-index: 2147483646;
|
|
689
689
|
padding: 4px 8px;
|
|
690
690
|
border-radius: 8px;
|
|
691
691
|
border: 1px solid var(--ai-border);
|
|
@@ -772,7 +772,7 @@
|
|
|
772
772
|
[data-ai-settings-panel] {
|
|
773
773
|
position: fixed;
|
|
774
774
|
inset: 0;
|
|
775
|
-
z-index:
|
|
775
|
+
z-index: 2147483647;
|
|
776
776
|
display: flex;
|
|
777
777
|
align-items: center;
|
|
778
778
|
justify-content: center;
|
|
@@ -1109,6 +1109,8 @@
|
|
|
1109
1109
|
|
|
1110
1110
|
.ai-key-modal-panel {
|
|
1111
1111
|
max-width: 520px;
|
|
1112
|
+
position: relative;
|
|
1113
|
+
z-index: 2147483647;
|
|
1112
1114
|
}
|
|
1113
1115
|
|
|
1114
1116
|
.ai-key-radio {
|