@lastbrain/ai-ui-react 1.0.46 → 1.0.48
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/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +128 -4
- package/dist/context/LBAuthProvider.d.ts +3 -1
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +5 -3
- package/package.json +2 -2
- package/src/components/AiStatusButton.tsx +202 -6
- package/src/context/LBAuthProvider.tsx +7 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAoBtD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,GACf,EAAE,mBAAmB,2CAiiCrB"}
|
|
@@ -2,20 +2,28 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useRef, useLayoutEffect } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
|
-
import { BarChart3, Settings, FileText, History as HistoryIcon, FolderPlus, Power, } from "lucide-react";
|
|
5
|
+
import { BarChart3, Settings, FileText, History as HistoryIcon, FolderPlus, Power, LogOut, Key, RefreshCw, } from "lucide-react";
|
|
6
6
|
import { aiStyles, calculateTooltipPosition } from "../styles/inline";
|
|
7
7
|
import { useLB } from "../context/LBAuthProvider";
|
|
8
|
+
import { useAiContext } from "../context/AiProvider";
|
|
8
9
|
import { LBSigninModal } from "./LBSigninModal";
|
|
10
|
+
import { LBApiKeySelector } from "./LBApiKeySelector";
|
|
9
11
|
export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
10
12
|
// Rendre l'authentification optionnelle
|
|
11
13
|
let lbStatus;
|
|
12
14
|
let user;
|
|
13
15
|
let logout;
|
|
16
|
+
let apiKeys = [];
|
|
17
|
+
let accessToken;
|
|
18
|
+
let selectApiKeyWithToken;
|
|
14
19
|
try {
|
|
15
20
|
const lbContext = useLB();
|
|
16
21
|
lbStatus = lbContext.status;
|
|
17
22
|
user = lbContext.user;
|
|
18
23
|
logout = lbContext.logout;
|
|
24
|
+
apiKeys = lbContext.apiKeys || [];
|
|
25
|
+
accessToken = lbContext.accessToken;
|
|
26
|
+
selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
|
|
19
27
|
}
|
|
20
28
|
catch {
|
|
21
29
|
// LBProvider n'est pas disponible, ignorer
|
|
@@ -23,7 +31,18 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
|
23
31
|
user = undefined;
|
|
24
32
|
logout = undefined;
|
|
25
33
|
}
|
|
34
|
+
// Récupérer refetchProviders depuis AiProvider si disponible
|
|
35
|
+
let refetchProviders;
|
|
36
|
+
try {
|
|
37
|
+
const aiContext = useAiContext();
|
|
38
|
+
refetchProviders = aiContext.refetchProviders;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// AiProvider n'est pas disponible, ignorer
|
|
42
|
+
refetchProviders = undefined;
|
|
43
|
+
}
|
|
26
44
|
const [showSigninModal, setShowSigninModal] = useState(false);
|
|
45
|
+
const [showApiKeySelector, setShowApiKeySelector] = useState(false);
|
|
27
46
|
const formatNumber = (value) => typeof value === "number" ? value.toLocaleString() : "0";
|
|
28
47
|
const formatFixed = (value, digits) => typeof value === "number" ? value.toFixed(digits) : "0.00";
|
|
29
48
|
const formatStorage = (valueMb) => {
|
|
@@ -273,6 +292,10 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
|
273
292
|
}, title: "New Folder", children: _jsx(FolderPlus, { size: 18 }) }), _jsx("button", { onClick: async () => {
|
|
274
293
|
if (logout) {
|
|
275
294
|
await logout();
|
|
295
|
+
// Refresh provider data after logout
|
|
296
|
+
if (refetchProviders) {
|
|
297
|
+
await refetchProviders();
|
|
298
|
+
}
|
|
276
299
|
}
|
|
277
300
|
setShowTooltip(false);
|
|
278
301
|
}, style: {
|
|
@@ -335,10 +358,50 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
|
335
358
|
...aiStyles.tooltip,
|
|
336
359
|
...tooltipPosition,
|
|
337
360
|
zIndex: 50,
|
|
338
|
-
}, onMouseEnter: () => setShowTooltip(true), onMouseLeave: handleMouseLeave, children: [_jsx("div", { style: aiStyles.tooltipHeader, children: "API Status" }),
|
|
361
|
+
}, onMouseEnter: () => setShowTooltip(true), onMouseLeave: handleMouseLeave, children: [_jsx("div", { style: aiStyles.tooltipHeader, children: "API Status" }), status.user?.email && (_jsx("div", { style: {
|
|
339
362
|
...aiStyles.tooltipSection,
|
|
340
363
|
...aiStyles.tooltipSectionFirst,
|
|
341
|
-
}, children:
|
|
364
|
+
}, children: _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "User:" }), _jsx("span", { style: {
|
|
365
|
+
...aiStyles.tooltipValue,
|
|
366
|
+
fontSize: "12px",
|
|
367
|
+
maxWidth: "200px",
|
|
368
|
+
overflow: "hidden",
|
|
369
|
+
textOverflow: "ellipsis",
|
|
370
|
+
}, children: status.user.email })] }) })), _jsxs("div", { style: {
|
|
371
|
+
...aiStyles.tooltipSection,
|
|
372
|
+
...(status.user?.email ? {} : aiStyles.tooltipSectionFirst),
|
|
373
|
+
}, children: [_jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "API Key:" }), _jsxs("div", { style: {
|
|
374
|
+
display: "flex",
|
|
375
|
+
alignItems: "center",
|
|
376
|
+
gap: "8px",
|
|
377
|
+
}, children: [_jsx("span", { style: aiStyles.tooltipValue, children: status.apiKey?.name || status.api_key?.name || "Unknown" }), apiKeys.length > 1 && selectApiKeyWithToken && (_jsx("button", { onClick: (e) => {
|
|
378
|
+
e.stopPropagation();
|
|
379
|
+
setShowApiKeySelector(true);
|
|
380
|
+
}, style: {
|
|
381
|
+
background: "rgba(139, 92, 246, 0.1)",
|
|
382
|
+
border: "1px solid rgba(139, 92, 246, 0.3)",
|
|
383
|
+
borderRadius: "50%",
|
|
384
|
+
width: "24px",
|
|
385
|
+
height: "24px",
|
|
386
|
+
display: "flex",
|
|
387
|
+
alignItems: "center",
|
|
388
|
+
justifyContent: "center",
|
|
389
|
+
cursor: "pointer",
|
|
390
|
+
padding: 0,
|
|
391
|
+
transition: "all 0.2s ease",
|
|
392
|
+
}, onMouseEnter: (e) => {
|
|
393
|
+
e.currentTarget.style.background =
|
|
394
|
+
"rgba(139, 92, 246, 0.2)";
|
|
395
|
+
e.currentTarget.style.borderColor =
|
|
396
|
+
"rgba(139, 92, 246, 0.5)";
|
|
397
|
+
}, onMouseLeave: (e) => {
|
|
398
|
+
e.currentTarget.style.background =
|
|
399
|
+
"rgba(139, 92, 246, 0.1)";
|
|
400
|
+
e.currentTarget.style.borderColor =
|
|
401
|
+
"rgba(139, 92, 246, 0.3)";
|
|
402
|
+
}, title: "Change API Key", children: _jsx(RefreshCw, { size: 12, style: { color: "rgba(139, 92, 246, 1)" } }) }))] })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Env:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.apiKey?.env || status.api_key?.env || "N/A" })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Rate Limit:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.apiKey?.rate_limit_rpm ||
|
|
403
|
+
status.api_key?.rate_limit_rpm ||
|
|
404
|
+
0, " ", "req/min"] })] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Balance" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: ["$", formatFixed(balanceUsed, 6), " / $", formatNumber(balanceTotal)] }), renderUsageCircle(balancePercentage)] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Storage" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [formatStorage(storageUsed), " /", " ", formatStorage(storageAllocated)] }), renderUsageCircle(storagePercentage)] })] }), _jsxs("div", { style: {
|
|
342
405
|
...aiStyles.tooltipActions,
|
|
343
406
|
width: "100%",
|
|
344
407
|
flexDirection: "row",
|
|
@@ -436,5 +499,66 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
|
436
499
|
Object.assign(e.currentTarget.style, {
|
|
437
500
|
background: "transparent",
|
|
438
501
|
});
|
|
439
|
-
}, title: "New Folder", children: _jsx(FolderPlus, { size: 18 }) })
|
|
502
|
+
}, title: "New Folder", children: _jsx(FolderPlus, { size: 18 }) }), selectApiKeyWithToken && apiKeys.length > 1 && (_jsx("button", { onClick: () => setShowApiKeySelector(true), style: {
|
|
503
|
+
background: "transparent",
|
|
504
|
+
border: "none",
|
|
505
|
+
padding: "14px",
|
|
506
|
+
cursor: "pointer",
|
|
507
|
+
display: "flex",
|
|
508
|
+
alignItems: "center",
|
|
509
|
+
justifyContent: "center",
|
|
510
|
+
color: "#8b5cf6",
|
|
511
|
+
transition: "all 0.2s ease",
|
|
512
|
+
}, onMouseEnter: (e) => {
|
|
513
|
+
Object.assign(e.currentTarget.style, {
|
|
514
|
+
background: "rgba(139, 92, 246, 0.1)",
|
|
515
|
+
});
|
|
516
|
+
}, onMouseLeave: (e) => {
|
|
517
|
+
Object.assign(e.currentTarget.style, {
|
|
518
|
+
background: "transparent",
|
|
519
|
+
});
|
|
520
|
+
}, title: "Change API Key", children: _jsx(Key, { size: 18 }) })), logout && (_jsx("button", { onClick: async () => {
|
|
521
|
+
try {
|
|
522
|
+
await logout();
|
|
523
|
+
setShowTooltip(false);
|
|
524
|
+
// Refresh provider data after logout
|
|
525
|
+
if (refetchProviders) {
|
|
526
|
+
await refetchProviders();
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
console.error("Logout failed:", error);
|
|
531
|
+
}
|
|
532
|
+
}, style: {
|
|
533
|
+
background: "transparent",
|
|
534
|
+
border: "none",
|
|
535
|
+
padding: "14px",
|
|
536
|
+
cursor: "pointer",
|
|
537
|
+
display: "flex",
|
|
538
|
+
alignItems: "center",
|
|
539
|
+
justifyContent: "center",
|
|
540
|
+
color: "#ef4444",
|
|
541
|
+
transition: "all 0.2s ease",
|
|
542
|
+
}, onMouseEnter: (e) => {
|
|
543
|
+
Object.assign(e.currentTarget.style, {
|
|
544
|
+
background: "rgba(239, 68, 68, 0.1)",
|
|
545
|
+
});
|
|
546
|
+
}, onMouseLeave: (e) => {
|
|
547
|
+
Object.assign(e.currentTarget.style, {
|
|
548
|
+
background: "transparent",
|
|
549
|
+
});
|
|
550
|
+
}, title: "Logout", children: _jsx(LogOut, { size: 18 }) }))] })] }), document.body), showApiKeySelector && selectApiKeyWithToken && (_jsx(LBApiKeySelector, { isOpen: showApiKeySelector, apiKeys: apiKeys, onSelect: async (keyId) => {
|
|
551
|
+
try {
|
|
552
|
+
await selectApiKeyWithToken(keyId);
|
|
553
|
+
setShowApiKeySelector(false);
|
|
554
|
+
setShowTooltip(false);
|
|
555
|
+
// Refresh provider data after API key selection
|
|
556
|
+
if (refetchProviders) {
|
|
557
|
+
await refetchProviders();
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
catch (error) {
|
|
561
|
+
console.error("Failed to select API key:", error);
|
|
562
|
+
}
|
|
563
|
+
}, onCancel: () => setShowApiKeySelector(false) }))] }));
|
|
440
564
|
}
|
|
@@ -12,6 +12,8 @@ interface LBProviderProps {
|
|
|
12
12
|
proxyUrl?: string;
|
|
13
13
|
/** Fonction appelée lors des changements d'état */
|
|
14
14
|
onStatusChange?: (status: LBAuthState["status"]) => void;
|
|
15
|
+
/** Fonction appelée après signin/logout pour refresh les providers */
|
|
16
|
+
onAuthChange?: () => void;
|
|
15
17
|
}
|
|
16
18
|
interface LBContextValue extends LBAuthState {
|
|
17
19
|
/** Fonction de connexion */
|
|
@@ -35,7 +37,7 @@ interface LBContextValue extends LBAuthState {
|
|
|
35
37
|
/** Access token temporaire (après login) */
|
|
36
38
|
accessToken?: string;
|
|
37
39
|
}
|
|
38
|
-
export declare function LBProvider({ children, baseUrl: _baseUrl, proxyUrl, onStatusChange, }: LBProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
export declare function LBProvider({ children, baseUrl: _baseUrl, proxyUrl, onStatusChange, onAuthChange, }: LBProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
39
41
|
/**
|
|
40
42
|
* Hook pour accéder au contexte LastBrain
|
|
41
43
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAIT,MAAM,uBAAuB,CAAC;AAE/B,UAAU,eAAe;IACvB,QAAQ,EAAE,SAAS,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"LBAuthProvider.d.ts","sourceRoot":"","sources":["../../src/context/LBAuthProvider.tsx"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,OAAO,EAML,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EACV,WAAW,EACX,QAAQ,EAIT,MAAM,uBAAuB,CAAC;AAE/B,UAAU,eAAe;IACvB,QAAQ,EAAE,SAAS,CAAC;IACpB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,KAAK,IAAI,CAAC;IACzD,sEAAsE;IACtE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,UAAU,cAAe,SAAQ,WAAW;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,CACL,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC,CAAC;IACH,8BAA8B;IAC9B,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,6CAA6C;IAC7C,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC3D,gEAAgE;IAChE,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,mDAAmD;IACnD,qBAAqB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,oCAAoC;IACpC,cAAc,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,2BAA2B;IAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC;IACpB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,wBAAgB,UAAU,CAAC,EACzB,QAAQ,EACR,OAAO,EAAE,QAA2B,EACpC,QAA2B,EAC3B,cAAc,EACd,YAAY,GACb,EAAE,eAAe,2CA4UjB;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,cAAc,CAMtC"}
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { createContext, useContext, useEffect, useCallback, useState, } from "react";
|
|
8
8
|
const LBContext = createContext(undefined);
|
|
9
|
-
export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", proxyUrl = "/api/lastbrain", onStatusChange, }) {
|
|
9
|
+
export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", proxyUrl = "/api/lastbrain", onStatusChange, onAuthChange, }) {
|
|
10
10
|
const [state, setState] = useState({
|
|
11
11
|
status: "loading",
|
|
12
12
|
});
|
|
@@ -114,6 +114,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
114
114
|
setAccessToken(undefined); // Nettoyer l'access token temporaire
|
|
115
115
|
setApiKeys([]); // Nettoyer les clés API temporaires
|
|
116
116
|
onStatusChange?.("ready");
|
|
117
|
+
onAuthChange?.(); // Refresh provider after signin
|
|
117
118
|
}
|
|
118
119
|
catch (error) {
|
|
119
120
|
const message = error instanceof Error ? error.message : "Failed to select API key";
|
|
@@ -123,7 +124,7 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
123
124
|
});
|
|
124
125
|
throw error;
|
|
125
126
|
}
|
|
126
|
-
}, [proxyUrl, state.user, onStatusChange]);
|
|
127
|
+
}, [proxyUrl, state.user, onStatusChange, onAuthChange]);
|
|
127
128
|
/**
|
|
128
129
|
* Connexion utilisateur (étape 1 : login)
|
|
129
130
|
* Retourne le token et les clés API sans créer de session
|
|
@@ -248,8 +249,9 @@ export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", pro
|
|
|
248
249
|
setApiKeys([]);
|
|
249
250
|
setAccessToken(undefined);
|
|
250
251
|
onStatusChange?.("needs_auth");
|
|
252
|
+
onAuthChange?.(); // Refresh provider after logout
|
|
251
253
|
}
|
|
252
|
-
}, [proxyUrl, onStatusChange]);
|
|
254
|
+
}, [proxyUrl, onStatusChange, onAuthChange]);
|
|
253
255
|
/**
|
|
254
256
|
* Recharge la session
|
|
255
257
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/ai-ui-react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
4
4
|
"description": "Headless React components for LastBrain AI UI Kit",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"lucide-react": "^0.257.0",
|
|
51
|
-
"@lastbrain/ai-ui-core": "1.0.
|
|
51
|
+
"@lastbrain/ai-ui-core": "1.0.39"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/react": "^19.2.0",
|
|
@@ -10,10 +10,15 @@ import {
|
|
|
10
10
|
History as HistoryIcon,
|
|
11
11
|
FolderPlus,
|
|
12
12
|
Power,
|
|
13
|
+
LogOut,
|
|
14
|
+
Key,
|
|
15
|
+
RefreshCw,
|
|
13
16
|
} from "lucide-react";
|
|
14
17
|
import { aiStyles, calculateTooltipPosition } from "../styles/inline";
|
|
15
18
|
import { useLB } from "../context/LBAuthProvider";
|
|
19
|
+
import { useAiContext } from "../context/AiProvider";
|
|
16
20
|
import { LBSigninModal } from "./LBSigninModal";
|
|
21
|
+
import { LBApiKeySelector } from "./LBApiKeySelector";
|
|
17
22
|
|
|
18
23
|
export interface AiStatusButtonProps {
|
|
19
24
|
status: AiStatus | null;
|
|
@@ -30,12 +35,18 @@ export function AiStatusButton({
|
|
|
30
35
|
let lbStatus: string | undefined;
|
|
31
36
|
let user: any;
|
|
32
37
|
let logout: (() => Promise<void>) | undefined;
|
|
38
|
+
let apiKeys: any[] = [];
|
|
39
|
+
let accessToken: string | undefined;
|
|
40
|
+
let selectApiKeyWithToken: ((apiKeyId: string) => Promise<void>) | undefined;
|
|
33
41
|
|
|
34
42
|
try {
|
|
35
43
|
const lbContext = useLB();
|
|
36
44
|
lbStatus = lbContext.status;
|
|
37
45
|
user = lbContext.user;
|
|
38
46
|
logout = lbContext.logout;
|
|
47
|
+
apiKeys = lbContext.apiKeys || [];
|
|
48
|
+
accessToken = lbContext.accessToken;
|
|
49
|
+
selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
|
|
39
50
|
} catch {
|
|
40
51
|
// LBProvider n'est pas disponible, ignorer
|
|
41
52
|
lbStatus = undefined;
|
|
@@ -43,7 +54,18 @@ export function AiStatusButton({
|
|
|
43
54
|
logout = undefined;
|
|
44
55
|
}
|
|
45
56
|
|
|
57
|
+
// Récupérer refetchProviders depuis AiProvider si disponible
|
|
58
|
+
let refetchProviders: (() => Promise<void>) | undefined;
|
|
59
|
+
try {
|
|
60
|
+
const aiContext = useAiContext();
|
|
61
|
+
refetchProviders = aiContext.refetchProviders;
|
|
62
|
+
} catch {
|
|
63
|
+
// AiProvider n'est pas disponible, ignorer
|
|
64
|
+
refetchProviders = undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
const [showSigninModal, setShowSigninModal] = useState(false);
|
|
68
|
+
const [showApiKeySelector, setShowApiKeySelector] = useState(false);
|
|
47
69
|
|
|
48
70
|
type BalanceUsage = {
|
|
49
71
|
used?: number;
|
|
@@ -528,6 +550,10 @@ export function AiStatusButton({
|
|
|
528
550
|
onClick={async () => {
|
|
529
551
|
if (logout) {
|
|
530
552
|
await logout();
|
|
553
|
+
// Refresh provider data after logout
|
|
554
|
+
if (refetchProviders) {
|
|
555
|
+
await refetchProviders();
|
|
556
|
+
}
|
|
531
557
|
}
|
|
532
558
|
setShowTooltip(false);
|
|
533
559
|
}}
|
|
@@ -664,28 +690,103 @@ export function AiStatusButton({
|
|
|
664
690
|
>
|
|
665
691
|
<div style={aiStyles.tooltipHeader}>API Status</div>
|
|
666
692
|
|
|
693
|
+
{/* User Info Section */}
|
|
694
|
+
{status.user?.email && (
|
|
695
|
+
<div
|
|
696
|
+
style={{
|
|
697
|
+
...aiStyles.tooltipSection,
|
|
698
|
+
...aiStyles.tooltipSectionFirst,
|
|
699
|
+
}}
|
|
700
|
+
>
|
|
701
|
+
<div style={aiStyles.tooltipRow}>
|
|
702
|
+
<span style={aiStyles.tooltipLabel}>User:</span>
|
|
703
|
+
<span
|
|
704
|
+
style={{
|
|
705
|
+
...aiStyles.tooltipValue,
|
|
706
|
+
fontSize: "12px",
|
|
707
|
+
maxWidth: "200px",
|
|
708
|
+
overflow: "hidden",
|
|
709
|
+
textOverflow: "ellipsis",
|
|
710
|
+
}}
|
|
711
|
+
>
|
|
712
|
+
{status.user.email}
|
|
713
|
+
</span>
|
|
714
|
+
</div>
|
|
715
|
+
</div>
|
|
716
|
+
)}
|
|
717
|
+
|
|
667
718
|
<div
|
|
668
719
|
style={{
|
|
669
720
|
...aiStyles.tooltipSection,
|
|
670
|
-
...aiStyles.tooltipSectionFirst,
|
|
721
|
+
...(status.user?.email ? {} : aiStyles.tooltipSectionFirst),
|
|
671
722
|
}}
|
|
672
723
|
>
|
|
673
724
|
<div style={aiStyles.tooltipRow}>
|
|
674
725
|
<span style={aiStyles.tooltipLabel}>API Key:</span>
|
|
675
|
-
<
|
|
676
|
-
{
|
|
677
|
-
|
|
726
|
+
<div
|
|
727
|
+
style={{
|
|
728
|
+
display: "flex",
|
|
729
|
+
alignItems: "center",
|
|
730
|
+
gap: "8px",
|
|
731
|
+
}}
|
|
732
|
+
>
|
|
733
|
+
<span style={aiStyles.tooltipValue}>
|
|
734
|
+
{status.apiKey?.name || status.api_key?.name || "Unknown"}
|
|
735
|
+
</span>
|
|
736
|
+
{apiKeys.length > 1 && selectApiKeyWithToken && (
|
|
737
|
+
<button
|
|
738
|
+
onClick={(e) => {
|
|
739
|
+
e.stopPropagation();
|
|
740
|
+
setShowApiKeySelector(true);
|
|
741
|
+
}}
|
|
742
|
+
style={{
|
|
743
|
+
background: "rgba(139, 92, 246, 0.1)",
|
|
744
|
+
border: "1px solid rgba(139, 92, 246, 0.3)",
|
|
745
|
+
borderRadius: "50%",
|
|
746
|
+
width: "24px",
|
|
747
|
+
height: "24px",
|
|
748
|
+
display: "flex",
|
|
749
|
+
alignItems: "center",
|
|
750
|
+
justifyContent: "center",
|
|
751
|
+
cursor: "pointer",
|
|
752
|
+
padding: 0,
|
|
753
|
+
transition: "all 0.2s ease",
|
|
754
|
+
}}
|
|
755
|
+
onMouseEnter={(e) => {
|
|
756
|
+
e.currentTarget.style.background =
|
|
757
|
+
"rgba(139, 92, 246, 0.2)";
|
|
758
|
+
e.currentTarget.style.borderColor =
|
|
759
|
+
"rgba(139, 92, 246, 0.5)";
|
|
760
|
+
}}
|
|
761
|
+
onMouseLeave={(e) => {
|
|
762
|
+
e.currentTarget.style.background =
|
|
763
|
+
"rgba(139, 92, 246, 0.1)";
|
|
764
|
+
e.currentTarget.style.borderColor =
|
|
765
|
+
"rgba(139, 92, 246, 0.3)";
|
|
766
|
+
}}
|
|
767
|
+
title="Change API Key"
|
|
768
|
+
>
|
|
769
|
+
<RefreshCw
|
|
770
|
+
size={12}
|
|
771
|
+
style={{ color: "rgba(139, 92, 246, 1)" }}
|
|
772
|
+
/>
|
|
773
|
+
</button>
|
|
774
|
+
)}
|
|
775
|
+
</div>
|
|
678
776
|
</div>
|
|
679
777
|
<div style={aiStyles.tooltipRow}>
|
|
680
778
|
<span style={aiStyles.tooltipLabel}>Env:</span>
|
|
681
779
|
<span style={aiStyles.tooltipValue}>
|
|
682
|
-
{status.api_key?.env || "N/A"}
|
|
780
|
+
{status.apiKey?.env || status.api_key?.env || "N/A"}
|
|
683
781
|
</span>
|
|
684
782
|
</div>
|
|
685
783
|
<div style={aiStyles.tooltipRow}>
|
|
686
784
|
<span style={aiStyles.tooltipLabel}>Rate Limit:</span>
|
|
687
785
|
<span style={aiStyles.tooltipValue}>
|
|
688
|
-
{status.
|
|
786
|
+
{status.apiKey?.rate_limit_rpm ||
|
|
787
|
+
status.api_key?.rate_limit_rpm ||
|
|
788
|
+
0}{" "}
|
|
789
|
+
req/min
|
|
689
790
|
</span>
|
|
690
791
|
</div>
|
|
691
792
|
</div>
|
|
@@ -885,10 +986,105 @@ export function AiStatusButton({
|
|
|
885
986
|
>
|
|
886
987
|
<FolderPlus size={18} />
|
|
887
988
|
</button>
|
|
989
|
+
|
|
990
|
+
{/* Change API Key Button */}
|
|
991
|
+
{selectApiKeyWithToken && apiKeys.length > 1 && (
|
|
992
|
+
<button
|
|
993
|
+
onClick={() => setShowApiKeySelector(true)}
|
|
994
|
+
style={{
|
|
995
|
+
background: "transparent",
|
|
996
|
+
border: "none",
|
|
997
|
+
padding: "14px",
|
|
998
|
+
cursor: "pointer",
|
|
999
|
+
display: "flex",
|
|
1000
|
+
alignItems: "center",
|
|
1001
|
+
justifyContent: "center",
|
|
1002
|
+
color: "#8b5cf6",
|
|
1003
|
+
transition: "all 0.2s ease",
|
|
1004
|
+
}}
|
|
1005
|
+
onMouseEnter={(e) => {
|
|
1006
|
+
Object.assign(e.currentTarget.style, {
|
|
1007
|
+
background: "rgba(139, 92, 246, 0.1)",
|
|
1008
|
+
});
|
|
1009
|
+
}}
|
|
1010
|
+
onMouseLeave={(e) => {
|
|
1011
|
+
Object.assign(e.currentTarget.style, {
|
|
1012
|
+
background: "transparent",
|
|
1013
|
+
});
|
|
1014
|
+
}}
|
|
1015
|
+
title="Change API Key"
|
|
1016
|
+
>
|
|
1017
|
+
<Key size={18} />
|
|
1018
|
+
</button>
|
|
1019
|
+
)}
|
|
1020
|
+
|
|
1021
|
+
{/* Logout Button */}
|
|
1022
|
+
{logout && (
|
|
1023
|
+
<button
|
|
1024
|
+
onClick={async () => {
|
|
1025
|
+
try {
|
|
1026
|
+
await logout();
|
|
1027
|
+
setShowTooltip(false);
|
|
1028
|
+
// Refresh provider data after logout
|
|
1029
|
+
if (refetchProviders) {
|
|
1030
|
+
await refetchProviders();
|
|
1031
|
+
}
|
|
1032
|
+
} catch (error) {
|
|
1033
|
+
console.error("Logout failed:", error);
|
|
1034
|
+
}
|
|
1035
|
+
}}
|
|
1036
|
+
style={{
|
|
1037
|
+
background: "transparent",
|
|
1038
|
+
border: "none",
|
|
1039
|
+
padding: "14px",
|
|
1040
|
+
cursor: "pointer",
|
|
1041
|
+
display: "flex",
|
|
1042
|
+
alignItems: "center",
|
|
1043
|
+
justifyContent: "center",
|
|
1044
|
+
color: "#ef4444",
|
|
1045
|
+
transition: "all 0.2s ease",
|
|
1046
|
+
}}
|
|
1047
|
+
onMouseEnter={(e) => {
|
|
1048
|
+
Object.assign(e.currentTarget.style, {
|
|
1049
|
+
background: "rgba(239, 68, 68, 0.1)",
|
|
1050
|
+
});
|
|
1051
|
+
}}
|
|
1052
|
+
onMouseLeave={(e) => {
|
|
1053
|
+
Object.assign(e.currentTarget.style, {
|
|
1054
|
+
background: "transparent",
|
|
1055
|
+
});
|
|
1056
|
+
}}
|
|
1057
|
+
title="Logout"
|
|
1058
|
+
>
|
|
1059
|
+
<LogOut size={18} />
|
|
1060
|
+
</button>
|
|
1061
|
+
)}
|
|
888
1062
|
</div>
|
|
889
1063
|
</div>,
|
|
890
1064
|
document.body
|
|
891
1065
|
)}
|
|
1066
|
+
|
|
1067
|
+
{/* API Key Selector Modal */}
|
|
1068
|
+
{showApiKeySelector && selectApiKeyWithToken && (
|
|
1069
|
+
<LBApiKeySelector
|
|
1070
|
+
isOpen={showApiKeySelector}
|
|
1071
|
+
apiKeys={apiKeys}
|
|
1072
|
+
onSelect={async (keyId) => {
|
|
1073
|
+
try {
|
|
1074
|
+
await selectApiKeyWithToken(keyId);
|
|
1075
|
+
setShowApiKeySelector(false);
|
|
1076
|
+
setShowTooltip(false);
|
|
1077
|
+
// Refresh provider data after API key selection
|
|
1078
|
+
if (refetchProviders) {
|
|
1079
|
+
await refetchProviders();
|
|
1080
|
+
}
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
console.error("Failed to select API key:", error);
|
|
1083
|
+
}
|
|
1084
|
+
}}
|
|
1085
|
+
onCancel={() => setShowApiKeySelector(false)}
|
|
1086
|
+
/>
|
|
1087
|
+
)}
|
|
892
1088
|
</div>
|
|
893
1089
|
);
|
|
894
1090
|
}
|
|
@@ -29,6 +29,8 @@ interface LBProviderProps {
|
|
|
29
29
|
proxyUrl?: string;
|
|
30
30
|
/** Fonction appelée lors des changements d'état */
|
|
31
31
|
onStatusChange?: (status: LBAuthState["status"]) => void;
|
|
32
|
+
/** Fonction appelée après signin/logout pour refresh les providers */
|
|
33
|
+
onAuthChange?: () => void;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface LBContextValue extends LBAuthState {
|
|
@@ -64,6 +66,7 @@ export function LBProvider({
|
|
|
64
66
|
baseUrl: _baseUrl = "/api/lastbrain",
|
|
65
67
|
proxyUrl = "/api/lastbrain",
|
|
66
68
|
onStatusChange,
|
|
69
|
+
onAuthChange,
|
|
67
70
|
}: LBProviderProps) {
|
|
68
71
|
const [state, setState] = useState<LBAuthState>({
|
|
69
72
|
status: "loading",
|
|
@@ -195,6 +198,7 @@ export function LBProvider({
|
|
|
195
198
|
setAccessToken(undefined); // Nettoyer l'access token temporaire
|
|
196
199
|
setApiKeys([]); // Nettoyer les clés API temporaires
|
|
197
200
|
onStatusChange?.("ready");
|
|
201
|
+
onAuthChange?.(); // Refresh provider after signin
|
|
198
202
|
} catch (error) {
|
|
199
203
|
const message =
|
|
200
204
|
error instanceof Error ? error.message : "Failed to select API key";
|
|
@@ -205,7 +209,7 @@ export function LBProvider({
|
|
|
205
209
|
throw error;
|
|
206
210
|
}
|
|
207
211
|
},
|
|
208
|
-
[proxyUrl, state.user, onStatusChange]
|
|
212
|
+
[proxyUrl, state.user, onStatusChange, onAuthChange]
|
|
209
213
|
);
|
|
210
214
|
|
|
211
215
|
/**
|
|
@@ -371,8 +375,9 @@ export function LBProvider({
|
|
|
371
375
|
setApiKeys([]);
|
|
372
376
|
setAccessToken(undefined);
|
|
373
377
|
onStatusChange?.("needs_auth");
|
|
378
|
+
onAuthChange?.(); // Refresh provider after logout
|
|
374
379
|
}
|
|
375
|
-
}, [proxyUrl, onStatusChange]);
|
|
380
|
+
}, [proxyUrl, onStatusChange, onAuthChange]);
|
|
376
381
|
|
|
377
382
|
/**
|
|
378
383
|
* Recharge la session
|