@lastbrain/ai-ui-react 1.0.67 → 1.0.69
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/AiChipLabel.d.ts +8 -3
- package/dist/components/AiChipLabel.d.ts.map +1 -1
- package/dist/components/AiChipLabel.js +21 -70
- package/dist/components/AiContextButton.d.ts +5 -1
- package/dist/components/AiContextButton.d.ts.map +1 -1
- package/dist/components/AiContextButton.js +67 -291
- package/dist/components/AiImageButton.d.ts +5 -1
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +6 -142
- package/dist/components/AiInput.d.ts +5 -3
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +13 -25
- package/dist/components/AiPromptPanel.d.ts.map +1 -1
- package/dist/components/AiPromptPanel.js +58 -212
- package/dist/components/AiSelect.d.ts +5 -3
- package/dist/components/AiSelect.d.ts.map +1 -1
- package/dist/components/AiSelect.js +21 -30
- package/dist/components/AiStatusButton.d.ts +4 -1
- package/dist/components/AiStatusButton.d.ts.map +1 -1
- package/dist/components/AiStatusButton.js +198 -626
- package/dist/components/AiTextarea.d.ts +4 -2
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +14 -26
- package/dist/components/LBApiKeySelector.d.ts.map +1 -1
- package/dist/components/LBApiKeySelector.js +5 -166
- package/dist/components/LBConnectButton.d.ts +4 -7
- package/dist/components/LBConnectButton.d.ts.map +1 -1
- package/dist/components/LBConnectButton.js +17 -86
- package/dist/components/LBSigninModal.d.ts +1 -1
- package/dist/components/LBSigninModal.d.ts.map +1 -1
- package/dist/components/LBSigninModal.js +42 -320
- package/dist/examples/AiUiPremiumShowcase.d.ts +2 -0
- package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
- package/dist/examples/AiUiPremiumShowcase.js +15 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/styles/inline.d.ts +1 -0
- package/dist/styles/inline.d.ts.map +1 -1
- package/dist/styles/inline.js +25 -129
- package/dist/styles.css +1268 -369
- package/dist/types.d.ts +3 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/AiChipLabel.tsx +64 -101
- package/src/components/AiContextButton.tsx +138 -430
- package/src/components/AiImageButton.tsx +29 -190
- package/src/components/AiInput.tsx +49 -74
- package/src/components/AiPromptPanel.tsx +71 -254
- package/src/components/AiSelect.tsx +61 -69
- package/src/components/AiStatusButton.tsx +477 -1219
- package/src/components/AiTextarea.tsx +49 -64
- package/src/components/LBApiKeySelector.tsx +86 -274
- package/src/components/LBConnectButton.tsx +46 -334
- package/src/components/LBSigninModal.tsx +140 -481
- package/src/examples/AiUiPremiumShowcase.tsx +91 -0
- package/src/index.ts +3 -0
- package/src/styles/inline.ts +27 -148
- package/src/styles.css +1268 -369
- package/src/types.ts +3 -0
|
@@ -1,48 +1,163 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { AiStatus } from "@lastbrain/ai-ui-core";
|
|
4
|
-
import {
|
|
4
|
+
import { useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import { createPortal } from "react-dom";
|
|
6
6
|
import {
|
|
7
|
+
ArrowRightLeft,
|
|
7
8
|
BarChart3,
|
|
8
|
-
Settings,
|
|
9
9
|
FileText,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
Folder,
|
|
11
|
+
History,
|
|
12
|
+
Loader2,
|
|
13
13
|
LogOut,
|
|
14
|
-
|
|
14
|
+
Settings,
|
|
15
|
+
Shield,
|
|
15
16
|
} from "lucide-react";
|
|
16
|
-
import { aiStyles, calculateTooltipPosition } from "../styles/inline";
|
|
17
17
|
import { useLB } from "../context/LBAuthProvider";
|
|
18
18
|
import { useAiContext } from "../context/AiProvider";
|
|
19
|
-
import { LBSigninModal } from "./LBSigninModal";
|
|
20
19
|
import { LBApiKeySelector } from "./LBApiKeySelector";
|
|
20
|
+
import { LBSigninModal } from "./LBSigninModal";
|
|
21
|
+
import type { AiRadius, AiSize } from "../types";
|
|
21
22
|
|
|
22
23
|
export interface AiStatusButtonProps {
|
|
23
24
|
status: AiStatus | null;
|
|
24
25
|
loading?: boolean;
|
|
25
26
|
className?: string;
|
|
27
|
+
size?: AiSize;
|
|
28
|
+
radius?: AiRadius;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type BalanceUsage = {
|
|
32
|
+
used?: number;
|
|
33
|
+
total?: number;
|
|
34
|
+
percentage?: number;
|
|
35
|
+
purchased?: number;
|
|
36
|
+
quota?: number;
|
|
37
|
+
remaining?: number;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type StorageUsage = {
|
|
41
|
+
used_mb?: number;
|
|
42
|
+
allocated_mb?: number;
|
|
43
|
+
percentage?: number;
|
|
44
|
+
total_mb?: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const QUICK_LINKS = [
|
|
48
|
+
{
|
|
49
|
+
href: "https://prompt.lastbrain.io/auth/ai/tokens",
|
|
50
|
+
title: "Dashboard",
|
|
51
|
+
icon: BarChart3,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
href: "https://prompt.lastbrain.io/auth/ai/history",
|
|
55
|
+
title: "Historique",
|
|
56
|
+
icon: History,
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
href: "https://prompt.lastbrain.io/auth/ai/settings",
|
|
60
|
+
title: "Settings",
|
|
61
|
+
icon: Settings,
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
href: "https://prompt.lastbrain.io/auth/ai/prompts",
|
|
65
|
+
title: "Prompts",
|
|
66
|
+
icon: FileText,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
href: "https://prompt.lastbrain.io/auth/folder",
|
|
70
|
+
title: "Dossiers",
|
|
71
|
+
icon: Folder,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const clamp = (value?: number) => Math.min(100, Math.max(0, value || 0));
|
|
76
|
+
const num = (value?: number) => (typeof value === "number" ? value : 0);
|
|
77
|
+
const fixed = (value: number | undefined, digits: number) =>
|
|
78
|
+
num(value).toFixed(digits);
|
|
79
|
+
|
|
80
|
+
function formatStorage(mb?: number) {
|
|
81
|
+
const value = num(mb);
|
|
82
|
+
if (value >= 1024) {
|
|
83
|
+
return `${(value / 1024).toFixed(2)} GB`;
|
|
84
|
+
}
|
|
85
|
+
return `${value.toFixed(2)} MB`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function UsageCircle({ percentage }: { percentage?: number }) {
|
|
89
|
+
const safe = clamp(percentage);
|
|
90
|
+
const toneClass =
|
|
91
|
+
safe > 90
|
|
92
|
+
? "ai-usage-circle-value--high"
|
|
93
|
+
: safe > 75
|
|
94
|
+
? "ai-usage-circle-value--warn"
|
|
95
|
+
: "ai-usage-circle-value--low";
|
|
96
|
+
const size = 28;
|
|
97
|
+
const stroke = 3;
|
|
98
|
+
const radius = (size - stroke) / 2;
|
|
99
|
+
const circumference = 2 * Math.PI * radius;
|
|
100
|
+
const dashOffset = circumference - (safe / 100) * circumference;
|
|
101
|
+
return (
|
|
102
|
+
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
103
|
+
<circle
|
|
104
|
+
cx={size / 2}
|
|
105
|
+
cy={size / 2}
|
|
106
|
+
r={radius}
|
|
107
|
+
className="ai-usage-circle-track"
|
|
108
|
+
strokeWidth={stroke}
|
|
109
|
+
fill="transparent"
|
|
110
|
+
/>
|
|
111
|
+
<circle
|
|
112
|
+
cx={size / 2}
|
|
113
|
+
cy={size / 2}
|
|
114
|
+
r={radius}
|
|
115
|
+
className={toneClass}
|
|
116
|
+
strokeWidth={stroke}
|
|
117
|
+
fill="transparent"
|
|
118
|
+
strokeDasharray={circumference}
|
|
119
|
+
strokeDashoffset={dashOffset}
|
|
120
|
+
strokeLinecap="round"
|
|
121
|
+
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
|
122
|
+
/>
|
|
123
|
+
<text
|
|
124
|
+
x={size / 2}
|
|
125
|
+
y={size / 2}
|
|
126
|
+
textAnchor="middle"
|
|
127
|
+
dominantBaseline="central"
|
|
128
|
+
fontSize="7px"
|
|
129
|
+
className={
|
|
130
|
+
toneClass === "ai-usage-circle-value--high"
|
|
131
|
+
? "ai-usage-circle-text--high"
|
|
132
|
+
: toneClass === "ai-usage-circle-value--warn"
|
|
133
|
+
? "ai-usage-circle-text--warn"
|
|
134
|
+
: "ai-usage-circle-text--low"
|
|
135
|
+
}
|
|
136
|
+
fontWeight="700"
|
|
137
|
+
>
|
|
138
|
+
{safe.toFixed(0)}%
|
|
139
|
+
</text>
|
|
140
|
+
</svg>
|
|
141
|
+
);
|
|
26
142
|
}
|
|
27
143
|
|
|
28
144
|
export function AiStatusButton({
|
|
29
145
|
status,
|
|
30
146
|
loading = false,
|
|
31
147
|
className = "",
|
|
148
|
+
size = "md",
|
|
149
|
+
radius = "full",
|
|
32
150
|
}: AiStatusButtonProps) {
|
|
33
|
-
// Rendre l'authentification optionnelle
|
|
34
151
|
let lbStatus: string | undefined;
|
|
35
152
|
let user: any;
|
|
36
153
|
let logout: (() => Promise<void>) | undefined;
|
|
37
154
|
let apiKeys: any[] = [];
|
|
38
|
-
let accessToken: string | undefined;
|
|
39
|
-
let selectApiKeyWithToken: ((apiKeyId: string) => Promise<void>) | undefined;
|
|
40
155
|
let switchApiKey: ((apiKeyId: string) => Promise<void>) | undefined;
|
|
41
156
|
let lbApiStatus: any = null;
|
|
42
157
|
let lbBasicStatus: any = null;
|
|
43
158
|
let lbStorageStatus: any = null;
|
|
44
|
-
let lbIsLoadingStatus
|
|
45
|
-
let lbIsLoadingStorage
|
|
159
|
+
let lbIsLoadingStatus = false;
|
|
160
|
+
let lbIsLoadingStorage = false;
|
|
46
161
|
let lbSelectedKey: any = null;
|
|
47
162
|
let lbRefreshBasicStatus: (() => Promise<void>) | undefined;
|
|
48
163
|
let lbRefreshStorageStatus: ((force?: boolean) => Promise<void>) | undefined;
|
|
@@ -53,8 +168,6 @@ export function AiStatusButton({
|
|
|
53
168
|
user = lbContext.user;
|
|
54
169
|
logout = lbContext.logout;
|
|
55
170
|
apiKeys = lbContext.apiKeys || [];
|
|
56
|
-
accessToken = lbContext.accessToken;
|
|
57
|
-
selectApiKeyWithToken = lbContext.selectApiKeyWithToken;
|
|
58
171
|
switchApiKey = lbContext.switchApiKey;
|
|
59
172
|
lbApiStatus = lbContext.apiStatus;
|
|
60
173
|
lbBasicStatus = lbContext.basicStatus;
|
|
@@ -65,269 +178,97 @@ export function AiStatusButton({
|
|
|
65
178
|
lbRefreshBasicStatus = lbContext.refreshBasicStatus;
|
|
66
179
|
lbRefreshStorageStatus = lbContext.refreshStorageStatus;
|
|
67
180
|
} catch {
|
|
68
|
-
// LBProvider n'est pas disponible, ignorer
|
|
69
181
|
lbStatus = undefined;
|
|
70
|
-
user = undefined;
|
|
71
|
-
logout = undefined;
|
|
72
182
|
}
|
|
73
183
|
|
|
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;
|
|
85
|
-
|
|
86
|
-
// Récupérer refetchProviders depuis AiProvider si disponible
|
|
87
184
|
let refetchProviders: (() => Promise<void>) | undefined;
|
|
88
185
|
try {
|
|
89
|
-
|
|
90
|
-
refetchProviders = aiContext.refetchProviders;
|
|
186
|
+
refetchProviders = useAiContext().refetchProviders;
|
|
91
187
|
} catch {
|
|
92
|
-
// AiProvider n'est pas disponible, ignorer
|
|
93
188
|
refetchProviders = undefined;
|
|
94
189
|
}
|
|
95
190
|
|
|
96
191
|
const [showSigninModal, setShowSigninModal] = useState(false);
|
|
97
192
|
const [showApiKeySelector, setShowApiKeySelector] = useState(false);
|
|
193
|
+
const [showTooltip, setShowTooltip] = useState(false);
|
|
98
194
|
const [isSelectingApiKey, setIsSelectingApiKey] = useState(false);
|
|
99
|
-
const [isLoadingStatus, setIsLoadingStatus] = useState(false);
|
|
100
195
|
|
|
101
|
-
type BalanceUsage = {
|
|
102
|
-
used?: number;
|
|
103
|
-
total?: number;
|
|
104
|
-
percentage?: number;
|
|
105
|
-
purchased?: number;
|
|
106
|
-
quota?: number;
|
|
107
|
-
remaining?: number;
|
|
108
|
-
};
|
|
109
|
-
type StorageUsage = {
|
|
110
|
-
used_mb?: number;
|
|
111
|
-
allocated_mb?: number;
|
|
112
|
-
percentage?: number;
|
|
113
|
-
db_mb?: number;
|
|
114
|
-
files_mb?: number;
|
|
115
|
-
total_mb?: number;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
const formatFixed = (value: number | null | undefined, digits: number) =>
|
|
119
|
-
typeof value === "number" ? value.toFixed(digits) : "0.00";
|
|
120
|
-
const formatStorage = (valueMb: number | null | undefined) => {
|
|
121
|
-
const mb = typeof valueMb === "number" ? valueMb : 0;
|
|
122
|
-
if (mb >= 1024) {
|
|
123
|
-
const gb = mb / 1024;
|
|
124
|
-
return `${gb.toFixed(2)} GB`;
|
|
125
|
-
}
|
|
126
|
-
return `${mb.toFixed(2)} MB`;
|
|
127
|
-
};
|
|
128
|
-
const safeNumber = (value: number | null | undefined) =>
|
|
129
|
-
typeof value === "number" ? value : 0;
|
|
130
|
-
const clampPercentage = (value: number | null | undefined) =>
|
|
131
|
-
Math.min(100, Math.max(0, safeNumber(value)));
|
|
132
|
-
const getUsageColor = (percentage: number) => {
|
|
133
|
-
if (percentage > 90) {
|
|
134
|
-
return aiStyles.tooltipValueWarning.color;
|
|
135
|
-
}
|
|
136
|
-
if (percentage > 75) {
|
|
137
|
-
return aiStyles.tooltipValueWarning.color;
|
|
138
|
-
}
|
|
139
|
-
return aiStyles.tooltipValueSuccess.color;
|
|
140
|
-
};
|
|
141
|
-
const renderUsageCircle = (percentageValue: number | null | undefined) => {
|
|
142
|
-
const percentage = clampPercentage(percentageValue);
|
|
143
|
-
const size = 28;
|
|
144
|
-
const stroke = 3;
|
|
145
|
-
const radius = (size - stroke) / 2;
|
|
146
|
-
const circumference = 2 * Math.PI * radius;
|
|
147
|
-
const offset = circumference - (percentage / 100) * circumference;
|
|
148
|
-
const color = getUsageColor(percentage);
|
|
149
|
-
|
|
150
|
-
return (
|
|
151
|
-
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`}>
|
|
152
|
-
<circle
|
|
153
|
-
cx={size / 2}
|
|
154
|
-
cy={size / 2}
|
|
155
|
-
r={radius}
|
|
156
|
-
stroke={aiStyles.tooltipLabel.color}
|
|
157
|
-
strokeWidth={stroke}
|
|
158
|
-
fill="transparent"
|
|
159
|
-
opacity={0.3}
|
|
160
|
-
/>
|
|
161
|
-
<circle
|
|
162
|
-
cx={size / 2}
|
|
163
|
-
cy={size / 2}
|
|
164
|
-
r={radius}
|
|
165
|
-
stroke={color}
|
|
166
|
-
strokeWidth={stroke}
|
|
167
|
-
fill="transparent"
|
|
168
|
-
strokeDasharray={circumference}
|
|
169
|
-
strokeDashoffset={offset}
|
|
170
|
-
strokeLinecap="round"
|
|
171
|
-
transform={`rotate(-90 ${size / 2} ${size / 2})`}
|
|
172
|
-
/>
|
|
173
|
-
<text
|
|
174
|
-
x={size / 2}
|
|
175
|
-
y={size / 2}
|
|
176
|
-
textAnchor="middle"
|
|
177
|
-
dominantBaseline="central"
|
|
178
|
-
fontSize="7px"
|
|
179
|
-
fill={color}
|
|
180
|
-
fontWeight="600"
|
|
181
|
-
>
|
|
182
|
-
{percentage.toFixed(0)}%
|
|
183
|
-
</text>
|
|
184
|
-
</svg>
|
|
185
|
-
);
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const balanceUsage = effectiveStatus?.balance as BalanceUsage | undefined;
|
|
189
|
-
const storageUsage = effectiveStatus?.storage as StorageUsage | undefined;
|
|
190
|
-
|
|
191
|
-
const balanceUsed = safeNumber(balanceUsage?.used);
|
|
192
|
-
const balanceRemaining = safeNumber(balanceUsage?.remaining);
|
|
193
|
-
const rawBalanceTotal =
|
|
194
|
-
balanceUsage?.total ??
|
|
195
|
-
safeNumber(balanceUsage?.purchased) + safeNumber(balanceUsage?.quota);
|
|
196
|
-
const balanceTotal =
|
|
197
|
-
rawBalanceTotal > 0 ? rawBalanceTotal : balanceUsed + balanceRemaining;
|
|
198
|
-
const balancePercentage =
|
|
199
|
-
balanceUsage?.percentage ??
|
|
200
|
-
(balanceTotal > 0 ? Math.round((balanceUsed / balanceTotal) * 100) : 0);
|
|
201
|
-
|
|
202
|
-
const storageAllocated =
|
|
203
|
-
storageUsage?.allocated_mb ?? storageUsage?.total_mb ?? 0;
|
|
204
|
-
const storageUsed = storageUsage?.used_mb ?? storageUsage?.total_mb ?? 0;
|
|
205
|
-
const storagePercentage =
|
|
206
|
-
storageUsage?.percentage ??
|
|
207
|
-
(storageAllocated > 0
|
|
208
|
-
? Math.round((storageUsed / storageAllocated) * 100)
|
|
209
|
-
: 0);
|
|
210
|
-
const showFastStatusSkeleton =
|
|
211
|
-
lbStatus === "ready" &&
|
|
212
|
-
(lbIsLoadingStatus || isLoadingStatus) &&
|
|
213
|
-
!lbBasicStatus &&
|
|
214
|
-
!effectiveStatus;
|
|
215
|
-
|
|
216
|
-
const [showTooltip, setShowTooltip] = useState(false);
|
|
217
|
-
const [isHovered, setIsHovered] = useState(false);
|
|
218
|
-
const [tooltipPosition, setTooltipPosition] = useState<CSSProperties>({});
|
|
219
196
|
const buttonRef = useRef<HTMLButtonElement>(null);
|
|
220
197
|
const tooltipRef = useRef<HTMLDivElement>(null);
|
|
198
|
+
|
|
221
199
|
const canPortal = typeof document !== "undefined";
|
|
200
|
+
|
|
201
|
+
const effectiveStatus =
|
|
202
|
+
lbStatus === "ready"
|
|
203
|
+
? {
|
|
204
|
+
...(lbApiStatus || {}),
|
|
205
|
+
...(lbBasicStatus || {}),
|
|
206
|
+
storage: lbStorageStatus?.storage || lbApiStatus?.storage,
|
|
207
|
+
}
|
|
208
|
+
: status || null;
|
|
209
|
+
|
|
222
210
|
const hasApiKeySelected = Boolean(
|
|
223
|
-
effectiveStatus?.apiKey?.id ||
|
|
211
|
+
effectiveStatus?.apiKey?.id ||
|
|
212
|
+
effectiveStatus?.api_key?.id ||
|
|
213
|
+
lbSelectedKey?.id
|
|
224
214
|
);
|
|
225
215
|
const requiresApiKeySelection =
|
|
226
216
|
lbStatus === "ready" && !hasApiKeySelected && apiKeys.length > 0;
|
|
217
|
+
const isApiKeyAuthMode = effectiveStatus?.authType === "api_key";
|
|
218
|
+
|
|
219
|
+
const [tooltipStyle, setTooltipStyle] = useState<Record<string, string>>({});
|
|
227
220
|
|
|
228
221
|
useLayoutEffect(() => {
|
|
229
|
-
if (!showTooltip || !buttonRef.current) {
|
|
222
|
+
if (!showTooltip || !buttonRef.current || !canPortal) {
|
|
230
223
|
return;
|
|
231
224
|
}
|
|
232
225
|
|
|
233
|
-
const
|
|
234
|
-
if (!buttonRef.current)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
: buttonRect.top - tooltipHeight - margin;
|
|
260
|
-
if (top < margin) {
|
|
261
|
-
top = margin;
|
|
262
|
-
}
|
|
263
|
-
if (top + tooltipHeight > viewportHeight - margin) {
|
|
264
|
-
top = Math.max(margin, viewportHeight - tooltipHeight - margin);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
let left = preferRight
|
|
268
|
-
? buttonRect.left
|
|
269
|
-
: buttonRect.right - tooltipWidth;
|
|
270
|
-
if (left < margin) {
|
|
271
|
-
left = margin;
|
|
272
|
-
}
|
|
273
|
-
if (left + tooltipWidth > viewportWidth - margin) {
|
|
274
|
-
left = Math.max(margin, viewportWidth - tooltipWidth - margin);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
setTooltipPosition({
|
|
278
|
-
top: `${top}px`,
|
|
279
|
-
left: `${left}px`,
|
|
280
|
-
position: "fixed",
|
|
281
|
-
});
|
|
282
|
-
} else {
|
|
283
|
-
const position = calculateTooltipPosition(buttonRect);
|
|
284
|
-
setTooltipPosition(position);
|
|
285
|
-
}
|
|
226
|
+
const update = () => {
|
|
227
|
+
if (!buttonRef.current) return;
|
|
228
|
+
const rect = buttonRef.current.getBoundingClientRect();
|
|
229
|
+
const tipRect = tooltipRef.current?.getBoundingClientRect();
|
|
230
|
+
const tipWidth = tipRect?.width ?? 360;
|
|
231
|
+
const tipHeight = tipRect?.height ?? 520;
|
|
232
|
+
const margin = 8;
|
|
233
|
+
|
|
234
|
+
const viewportW = window.innerWidth;
|
|
235
|
+
const viewportH = window.innerHeight;
|
|
236
|
+
|
|
237
|
+
const placeBelow = viewportH - rect.bottom >= rect.top;
|
|
238
|
+
let top = placeBelow
|
|
239
|
+
? rect.bottom + margin
|
|
240
|
+
: rect.top - tipHeight - margin;
|
|
241
|
+
top = Math.max(margin, Math.min(top, viewportH - tipHeight - margin));
|
|
242
|
+
|
|
243
|
+
const placeRight = viewportW - rect.right >= rect.left;
|
|
244
|
+
let left = placeRight ? rect.left : rect.right - tipWidth;
|
|
245
|
+
left = Math.max(margin, Math.min(left, viewportW - tipWidth - margin));
|
|
246
|
+
|
|
247
|
+
setTooltipStyle({
|
|
248
|
+
position: "fixed",
|
|
249
|
+
top: `${top}px`,
|
|
250
|
+
left: `${left}px`,
|
|
251
|
+
});
|
|
286
252
|
};
|
|
287
253
|
|
|
288
|
-
const
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
window.addEventListener("resize", handleResize);
|
|
293
|
-
window.addEventListener("scroll", handleResize, true);
|
|
254
|
+
const raf1 = requestAnimationFrame(update);
|
|
255
|
+
const raf2 = requestAnimationFrame(update);
|
|
256
|
+
window.addEventListener("resize", update);
|
|
257
|
+
window.addEventListener("scroll", update, true);
|
|
294
258
|
|
|
295
259
|
return () => {
|
|
296
|
-
cancelAnimationFrame(
|
|
297
|
-
cancelAnimationFrame(
|
|
298
|
-
window.removeEventListener("resize",
|
|
299
|
-
window.removeEventListener("scroll",
|
|
260
|
+
cancelAnimationFrame(raf1);
|
|
261
|
+
cancelAnimationFrame(raf2);
|
|
262
|
+
window.removeEventListener("resize", update);
|
|
263
|
+
window.removeEventListener("scroll", update, true);
|
|
300
264
|
};
|
|
301
265
|
}, [showTooltip, canPortal]);
|
|
302
266
|
|
|
303
|
-
const handleMouseEnter = () => {
|
|
304
|
-
if (requiresApiKeySelection) {
|
|
305
|
-
return;
|
|
306
|
-
}
|
|
307
|
-
setShowTooltip(true);
|
|
308
|
-
setIsHovered(true);
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
const handleMouseLeave = () => {
|
|
312
|
-
if (requiresApiKeySelection) {
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
// Keep tooltip visible if hovering over it
|
|
316
|
-
setTimeout(() => {
|
|
317
|
-
if (
|
|
318
|
-
!tooltipRef.current?.matches(":hover") &&
|
|
319
|
-
!buttonRef.current?.matches(":hover")
|
|
320
|
-
) {
|
|
321
|
-
setShowTooltip(false);
|
|
322
|
-
setIsHovered(false);
|
|
323
|
-
}
|
|
324
|
-
}, 100);
|
|
325
|
-
};
|
|
326
|
-
|
|
327
267
|
useLayoutEffect(() => {
|
|
328
|
-
if (!showTooltip ||
|
|
268
|
+
if (!showTooltip || lbStatus !== "ready" || requiresApiKeySelection) {
|
|
329
269
|
return;
|
|
330
270
|
}
|
|
271
|
+
|
|
331
272
|
if (!lbBasicStatus && !lbIsLoadingStatus && lbRefreshBasicStatus) {
|
|
332
273
|
lbRefreshBasicStatus().catch(() => undefined);
|
|
333
274
|
}
|
|
@@ -340,8 +281,8 @@ export function AiStatusButton({
|
|
|
340
281
|
}
|
|
341
282
|
}, [
|
|
342
283
|
showTooltip,
|
|
343
|
-
requiresApiKeySelection,
|
|
344
284
|
lbStatus,
|
|
285
|
+
requiresApiKeySelection,
|
|
345
286
|
lbBasicStatus,
|
|
346
287
|
lbStorageStatus,
|
|
347
288
|
lbIsLoadingStatus,
|
|
@@ -350,1017 +291,330 @@ export function AiStatusButton({
|
|
|
350
291
|
lbRefreshStorageStatus,
|
|
351
292
|
]);
|
|
352
293
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
<button
|
|
356
|
-
ref={buttonRef}
|
|
357
|
-
style={{
|
|
358
|
-
...aiStyles.statusButton,
|
|
359
|
-
...aiStyles.statusButtonDisabled,
|
|
360
|
-
}}
|
|
361
|
-
className={className}
|
|
362
|
-
disabled
|
|
363
|
-
>
|
|
364
|
-
<svg
|
|
365
|
-
style={aiStyles.spinner}
|
|
366
|
-
width="16"
|
|
367
|
-
height="16"
|
|
368
|
-
viewBox="0 0 24 24"
|
|
369
|
-
fill="none"
|
|
370
|
-
stroke="currentColor"
|
|
371
|
-
>
|
|
372
|
-
<path d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83" />
|
|
373
|
-
</svg>
|
|
374
|
-
</button>
|
|
375
|
-
);
|
|
376
|
-
}
|
|
294
|
+
const balance = (effectiveStatus?.balance || {}) as BalanceUsage;
|
|
295
|
+
const storage = (effectiveStatus?.storage || {}) as StorageUsage;
|
|
377
296
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
}}
|
|
388
|
-
className={className}
|
|
389
|
-
onMouseEnter={() => setIsHovered(true)}
|
|
390
|
-
onMouseLeave={() => setIsHovered(false)}
|
|
391
|
-
onClick={() => setShowApiKeySelector(true)}
|
|
392
|
-
title="Select an API key to enable AI status and generation"
|
|
393
|
-
>
|
|
394
|
-
<svg
|
|
395
|
-
width="16"
|
|
396
|
-
height="16"
|
|
397
|
-
viewBox="0 0 24 24"
|
|
398
|
-
fill="none"
|
|
399
|
-
stroke="currentColor"
|
|
400
|
-
>
|
|
401
|
-
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
|
402
|
-
</svg>
|
|
403
|
-
</button>
|
|
404
|
-
{showApiKeySelector && apiKeys.length > 0 && (
|
|
405
|
-
<LBApiKeySelector
|
|
406
|
-
isOpen={showApiKeySelector}
|
|
407
|
-
apiKeys={apiKeys}
|
|
408
|
-
onSelect={async (keyId) => {
|
|
409
|
-
setIsSelectingApiKey(true);
|
|
410
|
-
try {
|
|
411
|
-
if (switchApiKey) {
|
|
412
|
-
await switchApiKey(keyId);
|
|
413
|
-
}
|
|
414
|
-
setShowApiKeySelector(false);
|
|
415
|
-
if (refetchProviders) {
|
|
416
|
-
await refetchProviders();
|
|
417
|
-
}
|
|
418
|
-
} catch (error) {
|
|
419
|
-
console.error("Failed to select API key:", error);
|
|
420
|
-
} finally {
|
|
421
|
-
setIsSelectingApiKey(false);
|
|
422
|
-
}
|
|
423
|
-
}}
|
|
424
|
-
onCancel={() => setShowApiKeySelector(false)}
|
|
425
|
-
/>
|
|
426
|
-
)}
|
|
427
|
-
</div>
|
|
428
|
-
);
|
|
429
|
-
}
|
|
297
|
+
const balanceUsed = num(balance.used);
|
|
298
|
+
const balanceRemaining = num(balance.remaining);
|
|
299
|
+
const rawBalanceTotal =
|
|
300
|
+
balance.total ?? num(balance.purchased) + num(balance.quota);
|
|
301
|
+
const balanceTotal =
|
|
302
|
+
rawBalanceTotal > 0 ? rawBalanceTotal : balanceUsed + balanceRemaining;
|
|
303
|
+
const balancePct =
|
|
304
|
+
balance.percentage ??
|
|
305
|
+
(balanceTotal > 0 ? Math.round((balanceUsed / balanceTotal) * 100) : 0);
|
|
430
306
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
...tooltipPosition,
|
|
465
|
-
zIndex: 50,
|
|
466
|
-
}}
|
|
467
|
-
onMouseEnter={() => setShowTooltip(true)}
|
|
468
|
-
onMouseLeave={handleMouseLeave}
|
|
469
|
-
>
|
|
470
|
-
No status available
|
|
471
|
-
</div>,
|
|
472
|
-
document.body
|
|
473
|
-
)}
|
|
474
|
-
</div>
|
|
475
|
-
);
|
|
307
|
+
const storageUsed = storage.used_mb ?? storage.total_mb ?? 0;
|
|
308
|
+
const storageTotal = storage.allocated_mb ?? storage.total_mb ?? 0;
|
|
309
|
+
const storagePct =
|
|
310
|
+
storage.percentage ??
|
|
311
|
+
(storageTotal > 0 ? Math.round((storageUsed / storageTotal) * 100) : 0);
|
|
312
|
+
|
|
313
|
+
const showFastSkeleton =
|
|
314
|
+
lbStatus === "ready" &&
|
|
315
|
+
lbIsLoadingStatus &&
|
|
316
|
+
!lbBasicStatus &&
|
|
317
|
+
!effectiveStatus;
|
|
318
|
+
|
|
319
|
+
const showCornerLoading =
|
|
320
|
+
lbStatus === "ready" &&
|
|
321
|
+
(showFastSkeleton || lbIsLoadingStatus || lbIsLoadingStorage);
|
|
322
|
+
|
|
323
|
+
const triggerTone = useMemo(() => {
|
|
324
|
+
if (requiresApiKeySelection) return "warning";
|
|
325
|
+
if (lbStatus && lbStatus !== "ready") return "danger";
|
|
326
|
+
if (effectiveStatus) return "success";
|
|
327
|
+
return "neutral";
|
|
328
|
+
}, [requiresApiKeySelection, lbStatus, effectiveStatus]);
|
|
329
|
+
|
|
330
|
+
const openTooltip = () => {
|
|
331
|
+
if (requiresApiKeySelection) {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
setShowTooltip(true);
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const closeTooltip = () => {
|
|
338
|
+
if (requiresApiKeySelection) {
|
|
339
|
+
return;
|
|
476
340
|
}
|
|
341
|
+
setTimeout(() => {
|
|
342
|
+
if (
|
|
343
|
+
!tooltipRef.current?.matches(":hover") &&
|
|
344
|
+
!buttonRef.current?.matches(":hover")
|
|
345
|
+
) {
|
|
346
|
+
setShowTooltip(false);
|
|
347
|
+
}
|
|
348
|
+
}, 100);
|
|
349
|
+
};
|
|
477
350
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
351
|
+
const renderTriggerIcon = () => {
|
|
352
|
+
if (loading || isSelectingApiKey) {
|
|
353
|
+
return <Loader2 size={14} className="ai-spinner" />;
|
|
354
|
+
}
|
|
355
|
+
return <Shield size={14} />;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const triggerClass = [
|
|
359
|
+
"ai-status-trigger",
|
|
360
|
+
`ai-size-${size}`,
|
|
361
|
+
`ai-radius-${radius}`,
|
|
362
|
+
triggerTone === "warning" ? "ai-status-trigger--warning" : "",
|
|
363
|
+
triggerTone === "danger" ? "ai-status-trigger--danger" : "",
|
|
364
|
+
triggerTone === "success" ? "ai-status-trigger--success" : "",
|
|
365
|
+
className,
|
|
366
|
+
]
|
|
367
|
+
.filter(Boolean)
|
|
368
|
+
.join(" ");
|
|
369
|
+
|
|
370
|
+
const tooltipNode =
|
|
371
|
+
showTooltip && canPortal
|
|
372
|
+
? createPortal(
|
|
373
|
+
<div
|
|
374
|
+
ref={tooltipRef}
|
|
375
|
+
className="ai-popover ai-tooltip ai-status-tooltip"
|
|
376
|
+
style={tooltipStyle}
|
|
377
|
+
onMouseEnter={() => setShowTooltip(true)}
|
|
378
|
+
onMouseLeave={closeTooltip}
|
|
496
379
|
>
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
380
|
+
{lbStatus === "ready" && user ? (
|
|
381
|
+
<>
|
|
382
|
+
<div className="ai-popover-body">
|
|
383
|
+
<div className="ai-popover-header">API Status</div>
|
|
384
|
+
<div className="ai-popover-section ai-popover-section--first">
|
|
385
|
+
<div className="ai-popover-row">
|
|
386
|
+
<span className="ai-popover-label">User</span>
|
|
387
|
+
<span className="ai-popover-value ai-truncate max-w-[200px]">
|
|
388
|
+
{user.email}
|
|
389
|
+
</span>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
|
|
393
|
+
<div className="ai-popover-section">
|
|
394
|
+
<div className="ai-popover-row">
|
|
395
|
+
<span className="ai-popover-label">API Key</span>
|
|
396
|
+
<div className="ai-row">
|
|
397
|
+
{lbIsLoadingStatus ? (
|
|
398
|
+
<div className="ai-kv-skeleton w-[110px]" />
|
|
399
|
+
) : (
|
|
400
|
+
<span className="ai-popover-value">
|
|
401
|
+
{effectiveStatus?.apiKey?.name ||
|
|
402
|
+
effectiveStatus?.api_key?.name ||
|
|
403
|
+
"Unknown"}
|
|
404
|
+
</span>
|
|
405
|
+
)}
|
|
406
|
+
{switchApiKey ? (
|
|
407
|
+
<button
|
|
408
|
+
type="button"
|
|
409
|
+
className="ai-icon-btn"
|
|
410
|
+
onClick={(e) => {
|
|
411
|
+
e.stopPropagation();
|
|
412
|
+
setShowTooltip(false);
|
|
413
|
+
setShowApiKeySelector(true);
|
|
414
|
+
}}
|
|
415
|
+
title="Changer de clé API"
|
|
416
|
+
>
|
|
417
|
+
<ArrowRightLeft size={12} />
|
|
418
|
+
</button>
|
|
419
|
+
) : null}
|
|
532
420
|
</div>
|
|
533
421
|
</div>
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
582
|
-
}}
|
|
583
|
-
/>
|
|
584
|
-
</div>
|
|
422
|
+
|
|
423
|
+
<div className="ai-popover-row">
|
|
424
|
+
<span className="ai-popover-label">Env</span>
|
|
425
|
+
{lbIsLoadingStatus && !effectiveStatus?.apiKey?.env ? (
|
|
426
|
+
<div className="ai-kv-skeleton w-12" />
|
|
427
|
+
) : (
|
|
428
|
+
<span className="ai-popover-value">
|
|
429
|
+
{effectiveStatus?.apiKey?.env ||
|
|
430
|
+
effectiveStatus?.api_key?.env ||
|
|
431
|
+
"N/A"}
|
|
432
|
+
</span>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
<div className="ai-popover-row">
|
|
437
|
+
<span className="ai-popover-label">Rate Limit</span>
|
|
438
|
+
{lbIsLoadingStatus &&
|
|
439
|
+
!effectiveStatus?.apiKey?.rate_limit_rpm ? (
|
|
440
|
+
<div className="ai-kv-skeleton w-[92px]" />
|
|
441
|
+
) : (
|
|
442
|
+
<span className="ai-popover-value">
|
|
443
|
+
{effectiveStatus?.apiKey?.rate_limit_rpm ||
|
|
444
|
+
effectiveStatus?.api_key?.rate_limit_rpm ||
|
|
445
|
+
0}{" "}
|
|
446
|
+
req/min
|
|
447
|
+
</span>
|
|
448
|
+
)}
|
|
449
|
+
</div>
|
|
450
|
+
|
|
451
|
+
<div className="ai-popover-row">
|
|
452
|
+
<span className="ai-popover-label">Auth</span>
|
|
453
|
+
<span className="ai-popover-value">
|
|
454
|
+
{lbIsLoadingStatus
|
|
455
|
+
? "..."
|
|
456
|
+
: effectiveStatus?.authType || lbStatus || "unknown"}
|
|
457
|
+
</span>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
<div className="ai-popover-section">
|
|
462
|
+
<div className="ai-popover-header text-xs mb-2">Wallet</div>
|
|
463
|
+
<div className="ai-popover-row">
|
|
464
|
+
<span className="ai-popover-label">Total</span>
|
|
465
|
+
{lbIsLoadingStatus && !effectiveStatus?.balance ? (
|
|
466
|
+
<div className="ai-row">
|
|
467
|
+
<div className="ai-kv-skeleton w-[120px]" />
|
|
468
|
+
<div className="ai-kv-skeleton w-7 h-7 rounded-full" />
|
|
585
469
|
</div>
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
<
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
height: "16px",
|
|
593
|
-
width: "120px",
|
|
594
|
-
background: "rgba(139, 92, 246, 0.12)",
|
|
595
|
-
borderRadius: "4px",
|
|
596
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
597
|
-
}}
|
|
598
|
-
/>
|
|
599
|
-
</div>
|
|
470
|
+
) : (
|
|
471
|
+
<div className="ai-row">
|
|
472
|
+
<span className="ai-popover-value">
|
|
473
|
+
${fixed(balanceUsed, 2)} / ${fixed(balanceTotal, 2)}
|
|
474
|
+
</span>
|
|
475
|
+
<UsageCircle percentage={balancePct} />
|
|
600
476
|
</div>
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
<button
|
|
646
|
-
onClick={() =>
|
|
647
|
-
window.open(
|
|
648
|
-
"https://prompt.lastbrain.io/auth/ai/settings",
|
|
649
|
-
"_blank"
|
|
650
|
-
)
|
|
651
|
-
}
|
|
652
|
-
style={{
|
|
653
|
-
flex: 1,
|
|
654
|
-
background: "transparent",
|
|
655
|
-
border: "none",
|
|
656
|
-
padding: "14px",
|
|
657
|
-
cursor: "pointer",
|
|
658
|
-
display: "flex",
|
|
659
|
-
alignItems: "center",
|
|
660
|
-
justifyContent: "center",
|
|
661
|
-
color: "#8b5cf6",
|
|
662
|
-
transition: "all 0.2s ease",
|
|
663
|
-
}}
|
|
664
|
-
onMouseEnter={(e) => {
|
|
665
|
-
Object.assign(e.currentTarget.style, {
|
|
666
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
667
|
-
});
|
|
668
|
-
}}
|
|
669
|
-
onMouseLeave={(e) => {
|
|
670
|
-
Object.assign(e.currentTarget.style, {
|
|
671
|
-
background: "transparent",
|
|
672
|
-
});
|
|
673
|
-
}}
|
|
674
|
-
title="Settings"
|
|
675
|
-
>
|
|
676
|
-
<Settings size={18} />
|
|
677
|
-
</button>
|
|
678
|
-
<button
|
|
679
|
-
onClick={() =>
|
|
680
|
-
window.open(
|
|
681
|
-
"https://prompt.lastbrain.io/auth/ai/prompts",
|
|
682
|
-
"_blank"
|
|
683
|
-
)
|
|
684
|
-
}
|
|
685
|
-
style={{
|
|
686
|
-
flex: 1,
|
|
687
|
-
background: "transparent",
|
|
688
|
-
border: "none",
|
|
689
|
-
padding: "14px",
|
|
690
|
-
cursor: "pointer",
|
|
691
|
-
display: "flex",
|
|
692
|
-
alignItems: "center",
|
|
693
|
-
justifyContent: "center",
|
|
694
|
-
color: "#8b5cf6",
|
|
695
|
-
transition: "all 0.2s ease",
|
|
696
|
-
}}
|
|
697
|
-
onMouseEnter={(e) => {
|
|
698
|
-
Object.assign(e.currentTarget.style, {
|
|
699
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
700
|
-
});
|
|
701
|
-
}}
|
|
702
|
-
onMouseLeave={(e) => {
|
|
703
|
-
Object.assign(e.currentTarget.style, {
|
|
704
|
-
background: "transparent",
|
|
705
|
-
});
|
|
706
|
-
}}
|
|
707
|
-
title="My Prompts"
|
|
708
|
-
>
|
|
709
|
-
<FileText size={18} />
|
|
710
|
-
</button>
|
|
711
|
-
<button
|
|
712
|
-
onClick={() =>
|
|
713
|
-
window.open(
|
|
714
|
-
"https://prompt.lastbrain.io/auth/folder",
|
|
715
|
-
"_blank"
|
|
716
|
-
)
|
|
717
|
-
}
|
|
718
|
-
style={{
|
|
719
|
-
flex: 1,
|
|
720
|
-
background: "transparent",
|
|
721
|
-
border: "none",
|
|
722
|
-
padding: "14px",
|
|
723
|
-
cursor: "pointer",
|
|
724
|
-
display: "flex",
|
|
725
|
-
alignItems: "center",
|
|
726
|
-
justifyContent: "center",
|
|
727
|
-
color: "#8b5cf6",
|
|
728
|
-
transition: "all 0.2s ease",
|
|
729
|
-
}}
|
|
730
|
-
onMouseEnter={(e) => {
|
|
731
|
-
Object.assign(e.currentTarget.style, {
|
|
732
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
733
|
-
});
|
|
734
|
-
}}
|
|
735
|
-
onMouseLeave={(e) => {
|
|
736
|
-
Object.assign(e.currentTarget.style, {
|
|
737
|
-
background: "transparent",
|
|
738
|
-
});
|
|
739
|
-
}}
|
|
740
|
-
title="New Folder"
|
|
741
|
-
>
|
|
742
|
-
<FolderPlus size={18} />
|
|
743
|
-
</button>
|
|
477
|
+
)}
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
|
|
481
|
+
<div className="ai-popover-section">
|
|
482
|
+
<div className="ai-popover-header text-xs mb-2">
|
|
483
|
+
Storage
|
|
484
|
+
</div>
|
|
485
|
+
<div className="ai-popover-row">
|
|
486
|
+
<span className="ai-popover-label">Total</span>
|
|
487
|
+
{lbIsLoadingStorage ? (
|
|
488
|
+
<div className="ai-row">
|
|
489
|
+
<div className="ai-kv-skeleton w-[120px]" />
|
|
490
|
+
<div className="ai-kv-skeleton w-7 h-7 rounded-full" />
|
|
491
|
+
</div>
|
|
492
|
+
) : (
|
|
493
|
+
<div className="ai-row">
|
|
494
|
+
<span className="ai-popover-value">
|
|
495
|
+
{formatStorage(storageUsed)} /{" "}
|
|
496
|
+
{formatStorage(storageTotal)}
|
|
497
|
+
</span>
|
|
498
|
+
<UsageCircle percentage={storagePct} />
|
|
499
|
+
</div>
|
|
500
|
+
)}
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
|
|
504
|
+
<div className="ai-status-actions">
|
|
505
|
+
{QUICK_LINKS.map((item) => {
|
|
506
|
+
const Icon = item.icon;
|
|
507
|
+
return (
|
|
508
|
+
<button
|
|
509
|
+
key={item.href}
|
|
510
|
+
type="button"
|
|
511
|
+
className="ai-status-action-btn"
|
|
512
|
+
onClick={() => window.open(item.href, "_blank")}
|
|
513
|
+
title={item.title}
|
|
514
|
+
>
|
|
515
|
+
<Icon size={17} />
|
|
516
|
+
</button>
|
|
517
|
+
);
|
|
518
|
+
})}
|
|
519
|
+
|
|
520
|
+
{logout && !isApiKeyAuthMode ? (
|
|
744
521
|
<button
|
|
522
|
+
type="button"
|
|
523
|
+
className="ai-status-action-btn ai-status-action-btn--danger"
|
|
745
524
|
onClick={async () => {
|
|
746
|
-
|
|
525
|
+
try {
|
|
747
526
|
await logout();
|
|
748
|
-
|
|
527
|
+
setShowTooltip(false);
|
|
749
528
|
if (refetchProviders) {
|
|
750
529
|
await refetchProviders();
|
|
751
530
|
}
|
|
531
|
+
} catch (error) {
|
|
532
|
+
console.error("Logout failed:", error);
|
|
752
533
|
}
|
|
753
|
-
setShowTooltip(false);
|
|
754
|
-
}}
|
|
755
|
-
style={{
|
|
756
|
-
flex: 1,
|
|
757
|
-
background: "transparent",
|
|
758
|
-
border: "none",
|
|
759
|
-
padding: "14px",
|
|
760
|
-
cursor: "pointer",
|
|
761
|
-
display: "flex",
|
|
762
|
-
alignItems: "center",
|
|
763
|
-
justifyContent: "center",
|
|
764
|
-
color: "#ef4444",
|
|
765
|
-
transition: "all 0.2s ease",
|
|
766
|
-
}}
|
|
767
|
-
onMouseEnter={(e) => {
|
|
768
|
-
Object.assign(e.currentTarget.style, {
|
|
769
|
-
background: "rgba(239, 68, 68, 0.1)",
|
|
770
|
-
});
|
|
771
|
-
}}
|
|
772
|
-
onMouseLeave={(e) => {
|
|
773
|
-
Object.assign(e.currentTarget.style, {
|
|
774
|
-
background: "transparent",
|
|
775
|
-
});
|
|
776
534
|
}}
|
|
777
535
|
title="Logout"
|
|
778
536
|
>
|
|
779
|
-
<
|
|
537
|
+
<LogOut size={17} />
|
|
780
538
|
</button>
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
) : (
|
|
784
|
-
<>
|
|
785
|
-
<div style={aiStyles.tooltipHeader}>
|
|
786
|
-
LastBrain Authentication
|
|
787
|
-
</div>
|
|
788
|
-
<div
|
|
789
|
-
style={{
|
|
790
|
-
paddingBottom: "12px",
|
|
791
|
-
}}
|
|
792
|
-
>
|
|
793
|
-
<p
|
|
794
|
-
style={{
|
|
795
|
-
margin: 0,
|
|
796
|
-
fontSize: "13px",
|
|
797
|
-
color: "var(--ai-text-secondary, #9ca3af)",
|
|
798
|
-
lineHeight: "1.5",
|
|
799
|
-
}}
|
|
800
|
-
>
|
|
801
|
-
Connectez-vous pour accéder aux fonctionnalités IA
|
|
802
|
-
</p>
|
|
803
|
-
</div>
|
|
804
|
-
<button
|
|
805
|
-
onClick={() => {
|
|
806
|
-
setShowSigninModal(true);
|
|
807
|
-
setShowTooltip(false);
|
|
808
|
-
}}
|
|
809
|
-
style={{
|
|
810
|
-
width: "100%",
|
|
811
|
-
padding: "10px",
|
|
812
|
-
background:
|
|
813
|
-
"linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)",
|
|
814
|
-
border: "none",
|
|
815
|
-
borderRadius: "6px",
|
|
816
|
-
color: "#ffffff",
|
|
817
|
-
fontSize: "13px",
|
|
818
|
-
fontWeight: 600,
|
|
819
|
-
cursor: "pointer",
|
|
820
|
-
transition: "all 0.2s ease",
|
|
821
|
-
}}
|
|
822
|
-
onMouseEnter={(e) => {
|
|
823
|
-
e.currentTarget.style.transform = "translateY(-1px)";
|
|
824
|
-
e.currentTarget.style.boxShadow =
|
|
825
|
-
"0 4px 12px rgba(139, 92, 246, 0.3)";
|
|
826
|
-
}}
|
|
827
|
-
onMouseLeave={(e) => {
|
|
828
|
-
e.currentTarget.style.transform = "translateY(0)";
|
|
829
|
-
e.currentTarget.style.boxShadow = "none";
|
|
830
|
-
}}
|
|
831
|
-
>
|
|
832
|
-
🔐 Se connecter
|
|
833
|
-
</button>
|
|
834
|
-
</>
|
|
835
|
-
)}
|
|
836
|
-
</div>,
|
|
837
|
-
document.body
|
|
838
|
-
)}
|
|
839
|
-
</div>
|
|
840
|
-
<LBSigninModal
|
|
841
|
-
isOpen={showSigninModal}
|
|
842
|
-
onClose={() => setShowSigninModal(false)}
|
|
843
|
-
/>
|
|
844
|
-
</>
|
|
845
|
-
);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
return (
|
|
849
|
-
<div style={{ position: "relative", display: "inline-block" }}>
|
|
850
|
-
<button
|
|
851
|
-
ref={buttonRef}
|
|
852
|
-
style={{
|
|
853
|
-
...aiStyles.statusButton,
|
|
854
|
-
color: "#10b981",
|
|
855
|
-
...(isHovered && aiStyles.statusButtonHover),
|
|
856
|
-
}}
|
|
857
|
-
className={className}
|
|
858
|
-
onMouseEnter={handleMouseEnter}
|
|
859
|
-
onMouseLeave={handleMouseLeave}
|
|
860
|
-
>
|
|
861
|
-
<svg
|
|
862
|
-
width="16"
|
|
863
|
-
height="16"
|
|
864
|
-
viewBox="0 0 24 24"
|
|
865
|
-
fill="none"
|
|
866
|
-
stroke="currentColor"
|
|
867
|
-
>
|
|
868
|
-
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
|
|
869
|
-
</svg>
|
|
870
|
-
</button>
|
|
871
|
-
|
|
872
|
-
{showTooltip &&
|
|
873
|
-
canPortal &&
|
|
874
|
-
createPortal(
|
|
875
|
-
<div
|
|
876
|
-
ref={tooltipRef}
|
|
877
|
-
style={{
|
|
878
|
-
...aiStyles.tooltip,
|
|
879
|
-
...tooltipPosition,
|
|
880
|
-
zIndex: 50,
|
|
881
|
-
}}
|
|
882
|
-
onMouseEnter={() => setShowTooltip(true)}
|
|
883
|
-
onMouseLeave={handleMouseLeave}
|
|
884
|
-
>
|
|
885
|
-
<div style={aiStyles.tooltipHeader}>API Status</div>
|
|
886
|
-
|
|
887
|
-
{/* User Info Section */}
|
|
888
|
-
{effectiveStatus.user?.email && (
|
|
889
|
-
<div
|
|
890
|
-
style={{
|
|
891
|
-
...aiStyles.tooltipSection,
|
|
892
|
-
...aiStyles.tooltipSectionFirst,
|
|
893
|
-
}}
|
|
894
|
-
>
|
|
895
|
-
<div style={aiStyles.tooltipRow}>
|
|
896
|
-
<span style={aiStyles.tooltipLabel}>User:</span>
|
|
897
|
-
<span
|
|
898
|
-
style={{
|
|
899
|
-
...aiStyles.tooltipValue,
|
|
900
|
-
fontSize: "12px",
|
|
901
|
-
maxWidth: "200px",
|
|
902
|
-
overflow: "hidden",
|
|
903
|
-
textOverflow: "ellipsis",
|
|
904
|
-
}}
|
|
905
|
-
>
|
|
906
|
-
{effectiveStatus.user.email}
|
|
907
|
-
</span>
|
|
539
|
+
) : null}
|
|
540
|
+
</div>
|
|
908
541
|
</div>
|
|
909
|
-
|
|
910
|
-
)
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
...aiStyles.tooltipSection,
|
|
915
|
-
...(effectiveStatus.user?.email
|
|
916
|
-
? {}
|
|
917
|
-
: aiStyles.tooltipSectionFirst),
|
|
918
|
-
}}
|
|
919
|
-
>
|
|
920
|
-
<div style={aiStyles.tooltipRow}>
|
|
921
|
-
<span style={aiStyles.tooltipLabel}>API Key:</span>
|
|
922
|
-
<div
|
|
923
|
-
style={{
|
|
924
|
-
display: "flex",
|
|
925
|
-
alignItems: "center",
|
|
926
|
-
gap: "8px",
|
|
927
|
-
}}
|
|
928
|
-
>
|
|
929
|
-
<span style={aiStyles.tooltipValue}>
|
|
930
|
-
{lbIsLoadingStatus ? (
|
|
931
|
-
<div
|
|
932
|
-
style={{
|
|
933
|
-
height: "16px",
|
|
934
|
-
width: "100px",
|
|
935
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
936
|
-
borderRadius: "4px",
|
|
937
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
938
|
-
}}
|
|
939
|
-
/>
|
|
940
|
-
) : (
|
|
941
|
-
effectiveStatus?.apiKey?.name ||
|
|
942
|
-
effectiveStatus?.api_key?.name ||
|
|
943
|
-
"Unknown"
|
|
944
|
-
)}
|
|
945
|
-
</span>
|
|
946
|
-
{switchApiKey && (
|
|
947
|
-
<button
|
|
948
|
-
onClick={(e) => {
|
|
949
|
-
e.stopPropagation();
|
|
950
|
-
setShowTooltip(false);
|
|
951
|
-
setShowApiKeySelector(true);
|
|
952
|
-
}}
|
|
953
|
-
style={{
|
|
954
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
955
|
-
border: "1px solid rgba(139, 92, 246, 0.3)",
|
|
956
|
-
borderRadius: "50%",
|
|
957
|
-
width: "24px",
|
|
958
|
-
height: "24px",
|
|
959
|
-
display: "flex",
|
|
960
|
-
alignItems: "center",
|
|
961
|
-
justifyContent: "center",
|
|
962
|
-
cursor: "pointer",
|
|
963
|
-
padding: 0,
|
|
964
|
-
transition: "all 0.2s ease",
|
|
965
|
-
}}
|
|
966
|
-
onMouseEnter={(e) => {
|
|
967
|
-
e.currentTarget.style.background =
|
|
968
|
-
"rgba(139, 92, 246, 0.2)";
|
|
969
|
-
e.currentTarget.style.borderColor =
|
|
970
|
-
"rgba(139, 92, 246, 0.5)";
|
|
971
|
-
}}
|
|
972
|
-
onMouseLeave={(e) => {
|
|
973
|
-
e.currentTarget.style.background =
|
|
974
|
-
"rgba(139, 92, 246, 0.1)";
|
|
975
|
-
e.currentTarget.style.borderColor =
|
|
976
|
-
"rgba(139, 92, 246, 0.3)";
|
|
977
|
-
}}
|
|
978
|
-
title="Change API Key"
|
|
979
|
-
>
|
|
980
|
-
<ArrowRightLeft
|
|
981
|
-
size={12}
|
|
982
|
-
style={{ color: "rgba(139, 92, 246, 1)" }}
|
|
983
|
-
/>
|
|
984
|
-
</button>
|
|
985
|
-
)}
|
|
542
|
+
</>
|
|
543
|
+
) : (
|
|
544
|
+
<div className="ai-popover-body">
|
|
545
|
+
<div className="ai-popover-header">
|
|
546
|
+
LastBrain Authentication
|
|
986
547
|
</div>
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
{lbIsLoadingStatus && !effectiveStatus?.apiKey?.env ? (
|
|
991
|
-
<div
|
|
992
|
-
style={{
|
|
993
|
-
height: "16px",
|
|
994
|
-
width: "48px",
|
|
995
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
996
|
-
borderRadius: "4px",
|
|
997
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
998
|
-
}}
|
|
999
|
-
/>
|
|
1000
|
-
) : (
|
|
1001
|
-
<span style={aiStyles.tooltipValue}>
|
|
1002
|
-
{effectiveStatus.apiKey?.env ||
|
|
1003
|
-
effectiveStatus.api_key?.env ||
|
|
1004
|
-
"N/A"}
|
|
1005
|
-
</span>
|
|
1006
|
-
)}
|
|
1007
|
-
</div>
|
|
1008
|
-
<div style={aiStyles.tooltipRow}>
|
|
1009
|
-
<span style={aiStyles.tooltipLabel}>Rate Limit:</span>
|
|
1010
|
-
{lbIsLoadingStatus &&
|
|
1011
|
-
!effectiveStatus?.apiKey?.rate_limit_rpm &&
|
|
1012
|
-
!effectiveStatus?.api_key?.rate_limit_rpm ? (
|
|
1013
|
-
<div
|
|
1014
|
-
style={{
|
|
1015
|
-
height: "16px",
|
|
1016
|
-
width: "92px",
|
|
1017
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1018
|
-
borderRadius: "4px",
|
|
1019
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
1020
|
-
}}
|
|
1021
|
-
/>
|
|
1022
|
-
) : (
|
|
1023
|
-
<span style={aiStyles.tooltipValue}>
|
|
1024
|
-
{effectiveStatus.apiKey?.rate_limit_rpm ||
|
|
1025
|
-
effectiveStatus.api_key?.rate_limit_rpm ||
|
|
1026
|
-
0}{" "}
|
|
1027
|
-
req/min
|
|
1028
|
-
</span>
|
|
1029
|
-
)}
|
|
1030
|
-
</div>
|
|
1031
|
-
<div style={aiStyles.tooltipRow}>
|
|
1032
|
-
<span style={aiStyles.tooltipLabel}>Auth:</span>
|
|
1033
|
-
<span style={aiStyles.tooltipValue}>
|
|
1034
|
-
{lbIsLoadingStatus
|
|
1035
|
-
? "..."
|
|
1036
|
-
: effectiveStatus?.authType || lbStatus || "unknown"}
|
|
1037
|
-
</span>
|
|
1038
|
-
</div>
|
|
1039
|
-
</div>
|
|
1040
|
-
|
|
1041
|
-
<div style={aiStyles.tooltipSection}>
|
|
1042
|
-
<div style={aiStyles.tooltipSubtitle}>Wallet</div>
|
|
1043
|
-
<div style={aiStyles.tooltipRow}>
|
|
1044
|
-
<span style={aiStyles.tooltipLabel}>Total:</span>
|
|
1045
|
-
{lbIsLoadingStatus && !effectiveStatus?.balance ? (
|
|
1046
|
-
<div
|
|
1047
|
-
style={{
|
|
1048
|
-
display: "flex",
|
|
1049
|
-
alignItems: "center",
|
|
1050
|
-
gap: "8px",
|
|
1051
|
-
}}
|
|
1052
|
-
>
|
|
1053
|
-
<div
|
|
1054
|
-
style={{
|
|
1055
|
-
height: "16px",
|
|
1056
|
-
width: "120px",
|
|
1057
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1058
|
-
borderRadius: "4px",
|
|
1059
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
1060
|
-
}}
|
|
1061
|
-
/>
|
|
1062
|
-
<div
|
|
1063
|
-
style={{
|
|
1064
|
-
width: "28px",
|
|
1065
|
-
height: "28px",
|
|
1066
|
-
borderRadius: "50%",
|
|
1067
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1068
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
1069
|
-
}}
|
|
1070
|
-
/>
|
|
1071
|
-
</div>
|
|
1072
|
-
) : (
|
|
1073
|
-
<>
|
|
1074
|
-
<span style={aiStyles.tooltipValue}>
|
|
1075
|
-
${formatFixed(balanceUsed, 2)} / ${formatFixed(balanceTotal, 2)}
|
|
1076
|
-
</span>
|
|
1077
|
-
{renderUsageCircle(balancePercentage)}
|
|
1078
|
-
</>
|
|
1079
|
-
)}
|
|
1080
|
-
</div>
|
|
1081
|
-
</div>
|
|
1082
|
-
|
|
1083
|
-
<div style={aiStyles.tooltipSection}>
|
|
1084
|
-
<div style={aiStyles.tooltipSubtitle}>Storage</div>
|
|
1085
|
-
<div style={aiStyles.tooltipRow}>
|
|
1086
|
-
<span style={aiStyles.tooltipLabel}>Total:</span>
|
|
1087
|
-
{lbIsLoadingStorage ? (
|
|
1088
|
-
<div
|
|
1089
|
-
style={{
|
|
1090
|
-
display: "flex",
|
|
1091
|
-
alignItems: "center",
|
|
1092
|
-
gap: "8px",
|
|
1093
|
-
}}
|
|
1094
|
-
>
|
|
1095
|
-
<div
|
|
1096
|
-
style={{
|
|
1097
|
-
height: "16px",
|
|
1098
|
-
width: "120px",
|
|
1099
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1100
|
-
borderRadius: "4px",
|
|
1101
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
1102
|
-
}}
|
|
1103
|
-
/>
|
|
1104
|
-
<div
|
|
1105
|
-
style={{
|
|
1106
|
-
width: "28px",
|
|
1107
|
-
height: "28px",
|
|
1108
|
-
borderRadius: "50%",
|
|
1109
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1110
|
-
animation: "pulse 2s ease-in-out infinite",
|
|
1111
|
-
}}
|
|
1112
|
-
/>
|
|
1113
|
-
</div>
|
|
1114
|
-
) : (
|
|
1115
|
-
<>
|
|
1116
|
-
<span style={aiStyles.tooltipValue}>
|
|
1117
|
-
{formatStorage(storageUsed)} /{" "}
|
|
1118
|
-
{formatStorage(storageAllocated)}
|
|
1119
|
-
</span>
|
|
1120
|
-
{renderUsageCircle(storagePercentage)}
|
|
1121
|
-
</>
|
|
1122
|
-
)}
|
|
1123
|
-
</div>
|
|
1124
|
-
</div>
|
|
1125
|
-
|
|
1126
|
-
<div
|
|
1127
|
-
style={{
|
|
1128
|
-
...aiStyles.tooltipActions,
|
|
1129
|
-
width: "100%",
|
|
1130
|
-
|
|
1131
|
-
flexDirection: "row",
|
|
1132
|
-
}}
|
|
1133
|
-
>
|
|
1134
|
-
<button
|
|
1135
|
-
onClick={() =>
|
|
1136
|
-
window.open(
|
|
1137
|
-
"https://prompt.lastbrain.io/auth/ai/tokens",
|
|
1138
|
-
"_blank"
|
|
1139
|
-
)
|
|
1140
|
-
}
|
|
1141
|
-
style={{
|
|
1142
|
-
background: "transparent",
|
|
1143
|
-
border: "none",
|
|
1144
|
-
borderRadius: "4px",
|
|
1145
|
-
padding: "14px",
|
|
1146
|
-
cursor: "pointer",
|
|
1147
|
-
display: "flex",
|
|
1148
|
-
alignItems: "center",
|
|
1149
|
-
justifyContent: "center",
|
|
1150
|
-
color: "#8b5cf6",
|
|
1151
|
-
transition: "all 0.2s ease",
|
|
1152
|
-
}}
|
|
1153
|
-
onMouseEnter={(e) => {
|
|
1154
|
-
Object.assign(e.currentTarget.style, {
|
|
1155
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1156
|
-
});
|
|
1157
|
-
}}
|
|
1158
|
-
onMouseLeave={(e) => {
|
|
1159
|
-
Object.assign(e.currentTarget.style, {
|
|
1160
|
-
background: "transparent",
|
|
1161
|
-
});
|
|
1162
|
-
}}
|
|
1163
|
-
title="Dashboard"
|
|
1164
|
-
>
|
|
1165
|
-
<BarChart3 size={18} />
|
|
1166
|
-
</button>
|
|
1167
|
-
<button
|
|
1168
|
-
onClick={() =>
|
|
1169
|
-
window.open(
|
|
1170
|
-
"https://prompt.lastbrain.io/auth/ai/history",
|
|
1171
|
-
"_blank"
|
|
1172
|
-
)
|
|
1173
|
-
}
|
|
1174
|
-
style={{
|
|
1175
|
-
background: "transparent",
|
|
1176
|
-
border: "none",
|
|
1177
|
-
borderRadius: "4px",
|
|
1178
|
-
padding: "14px",
|
|
1179
|
-
cursor: "pointer",
|
|
1180
|
-
display: "flex",
|
|
1181
|
-
alignItems: "center",
|
|
1182
|
-
justifyContent: "center",
|
|
1183
|
-
color: "#8b5cf6",
|
|
1184
|
-
transition: "all 0.2s ease",
|
|
1185
|
-
}}
|
|
1186
|
-
onMouseEnter={(e) => {
|
|
1187
|
-
Object.assign(e.currentTarget.style, {
|
|
1188
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1189
|
-
});
|
|
1190
|
-
}}
|
|
1191
|
-
onMouseLeave={(e) => {
|
|
1192
|
-
Object.assign(e.currentTarget.style, {
|
|
1193
|
-
background: "transparent",
|
|
1194
|
-
});
|
|
1195
|
-
}}
|
|
1196
|
-
title="History"
|
|
1197
|
-
>
|
|
1198
|
-
<HistoryIcon size={18} />
|
|
1199
|
-
</button>
|
|
1200
|
-
<button
|
|
1201
|
-
onClick={() =>
|
|
1202
|
-
window.open(
|
|
1203
|
-
"https://prompt.lastbrain.io/auth/ai/settings",
|
|
1204
|
-
"_blank"
|
|
1205
|
-
)
|
|
1206
|
-
}
|
|
1207
|
-
style={{
|
|
1208
|
-
background: "transparent",
|
|
1209
|
-
border: "none",
|
|
1210
|
-
borderRadius: "4px",
|
|
1211
|
-
padding: "14px",
|
|
1212
|
-
cursor: "pointer",
|
|
1213
|
-
display: "flex",
|
|
1214
|
-
alignItems: "center",
|
|
1215
|
-
justifyContent: "center",
|
|
1216
|
-
color: "#8b5cf6",
|
|
1217
|
-
transition: "all 0.2s ease",
|
|
1218
|
-
}}
|
|
1219
|
-
onMouseEnter={(e) => {
|
|
1220
|
-
Object.assign(e.currentTarget.style, {
|
|
1221
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1222
|
-
});
|
|
1223
|
-
}}
|
|
1224
|
-
onMouseLeave={(e) => {
|
|
1225
|
-
Object.assign(e.currentTarget.style, {
|
|
1226
|
-
background: "transparent",
|
|
1227
|
-
});
|
|
1228
|
-
}}
|
|
1229
|
-
title="Settings"
|
|
1230
|
-
>
|
|
1231
|
-
<Settings size={18} />
|
|
1232
|
-
</button>
|
|
1233
|
-
<button
|
|
1234
|
-
onClick={() =>
|
|
1235
|
-
window.open(
|
|
1236
|
-
"https://prompt.lastbrain.io/auth/ai/prompts",
|
|
1237
|
-
"_blank"
|
|
1238
|
-
)
|
|
1239
|
-
}
|
|
1240
|
-
style={{
|
|
1241
|
-
background: "transparent",
|
|
1242
|
-
border: "none",
|
|
1243
|
-
borderRadius: "4px",
|
|
1244
|
-
padding: "14px",
|
|
1245
|
-
cursor: "pointer",
|
|
1246
|
-
display: "flex",
|
|
1247
|
-
alignItems: "center",
|
|
1248
|
-
justifyContent: "center",
|
|
1249
|
-
color: "#8b5cf6",
|
|
1250
|
-
transition: "all 0.2s ease",
|
|
1251
|
-
}}
|
|
1252
|
-
onMouseEnter={(e) => {
|
|
1253
|
-
Object.assign(e.currentTarget.style, {
|
|
1254
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1255
|
-
});
|
|
1256
|
-
}}
|
|
1257
|
-
onMouseLeave={(e) => {
|
|
1258
|
-
Object.assign(e.currentTarget.style, {
|
|
1259
|
-
background: "transparent",
|
|
1260
|
-
});
|
|
1261
|
-
}}
|
|
1262
|
-
title="New Prompt"
|
|
1263
|
-
>
|
|
1264
|
-
<FileText size={18} />
|
|
1265
|
-
</button>
|
|
1266
|
-
<button
|
|
1267
|
-
onClick={() =>
|
|
1268
|
-
window.open(
|
|
1269
|
-
"https://prompt.lastbrain.io/auth/folder",
|
|
1270
|
-
"_blank"
|
|
1271
|
-
)
|
|
1272
|
-
}
|
|
1273
|
-
style={{
|
|
1274
|
-
background: "transparent",
|
|
1275
|
-
border: "none",
|
|
1276
|
-
padding: "14px",
|
|
1277
|
-
cursor: "pointer",
|
|
1278
|
-
display: "flex",
|
|
1279
|
-
alignItems: "center",
|
|
1280
|
-
justifyContent: "center",
|
|
1281
|
-
color: "#8b5cf6",
|
|
1282
|
-
transition: "all 0.2s ease",
|
|
1283
|
-
}}
|
|
1284
|
-
onMouseEnter={(e) => {
|
|
1285
|
-
Object.assign(e.currentTarget.style, {
|
|
1286
|
-
background: "rgba(139, 92, 246, 0.1)",
|
|
1287
|
-
});
|
|
1288
|
-
}}
|
|
1289
|
-
onMouseLeave={(e) => {
|
|
1290
|
-
Object.assign(e.currentTarget.style, {
|
|
1291
|
-
background: "transparent",
|
|
1292
|
-
});
|
|
1293
|
-
}}
|
|
1294
|
-
title="New Folder"
|
|
1295
|
-
>
|
|
1296
|
-
<FolderPlus size={18} />
|
|
1297
|
-
</button>
|
|
1298
|
-
|
|
1299
|
-
{/* Logout Button */}
|
|
1300
|
-
{logout && (
|
|
548
|
+
<p className="ai-signin-subtitle mt-0">
|
|
549
|
+
Connectez-vous pour accéder aux fonctionnalités IA.
|
|
550
|
+
</p>
|
|
1301
551
|
<button
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
if (refetchProviders) {
|
|
1308
|
-
await refetchProviders();
|
|
1309
|
-
}
|
|
1310
|
-
} catch (error) {
|
|
1311
|
-
console.error("Logout failed:", error);
|
|
1312
|
-
}
|
|
552
|
+
type="button"
|
|
553
|
+
className="ai-btn ai-btn--auth w-full mt-2"
|
|
554
|
+
onClick={() => {
|
|
555
|
+
setShowSigninModal(true);
|
|
556
|
+
setShowTooltip(false);
|
|
1313
557
|
}}
|
|
1314
|
-
style={{
|
|
1315
|
-
background: "transparent",
|
|
1316
|
-
border: "none",
|
|
1317
|
-
padding: "14px",
|
|
1318
|
-
cursor: "pointer",
|
|
1319
|
-
display: "flex",
|
|
1320
|
-
alignItems: "center",
|
|
1321
|
-
justifyContent: "center",
|
|
1322
|
-
color: "#ef4444",
|
|
1323
|
-
transition: "all 0.2s ease",
|
|
1324
|
-
borderRadius: "4px",
|
|
1325
|
-
}}
|
|
1326
|
-
onMouseEnter={(e) => {
|
|
1327
|
-
Object.assign(e.currentTarget.style, {
|
|
1328
|
-
background: "rgba(239, 68, 68, 0.1)",
|
|
1329
|
-
});
|
|
1330
|
-
}}
|
|
1331
|
-
onMouseLeave={(e) => {
|
|
1332
|
-
Object.assign(e.currentTarget.style, {
|
|
1333
|
-
background: "transparent",
|
|
1334
|
-
});
|
|
1335
|
-
}}
|
|
1336
|
-
title="Logout"
|
|
1337
558
|
>
|
|
1338
|
-
|
|
559
|
+
Se connecter
|
|
1339
560
|
</button>
|
|
1340
|
-
|
|
1341
|
-
|
|
561
|
+
</div>
|
|
562
|
+
)}
|
|
1342
563
|
</div>,
|
|
1343
564
|
document.body
|
|
1344
|
-
)
|
|
565
|
+
)
|
|
566
|
+
: null;
|
|
1345
567
|
|
|
1346
|
-
|
|
1347
|
-
|
|
568
|
+
return (
|
|
569
|
+
<>
|
|
570
|
+
<div className="relative inline-block">
|
|
571
|
+
<button
|
|
572
|
+
ref={buttonRef}
|
|
573
|
+
className={triggerClass}
|
|
574
|
+
onMouseEnter={openTooltip}
|
|
575
|
+
onMouseLeave={closeTooltip}
|
|
576
|
+
onClick={() => {
|
|
577
|
+
if (requiresApiKeySelection) {
|
|
578
|
+
setShowApiKeySelector(true);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
if (!effectiveStatus && lbStatus !== "ready") {
|
|
582
|
+
setShowSigninModal(true);
|
|
583
|
+
}
|
|
584
|
+
}}
|
|
585
|
+
disabled={loading || isSelectingApiKey}
|
|
586
|
+
title={
|
|
587
|
+
requiresApiKeySelection
|
|
588
|
+
? "Sélectionnez une clé API"
|
|
589
|
+
: "Voir le status"
|
|
590
|
+
}
|
|
591
|
+
aria-label="AI status"
|
|
592
|
+
>
|
|
593
|
+
{renderTriggerIcon()}
|
|
594
|
+
</button>
|
|
595
|
+
|
|
596
|
+
{showCornerLoading ? (
|
|
597
|
+
<span className="ai-status-loading-dot" aria-hidden="true">
|
|
598
|
+
<Loader2 size={7} className="ai-spinner text-[var(--ai-primary)]" />
|
|
599
|
+
</span>
|
|
600
|
+
) : null}
|
|
601
|
+
</div>
|
|
602
|
+
|
|
603
|
+
{tooltipNode}
|
|
604
|
+
|
|
605
|
+
{showApiKeySelector && apiKeys.length > 0 ? (
|
|
1348
606
|
<LBApiKeySelector
|
|
1349
607
|
isOpen={showApiKeySelector}
|
|
1350
608
|
apiKeys={apiKeys}
|
|
1351
609
|
onSelect={async (keyId) => {
|
|
1352
610
|
setIsSelectingApiKey(true);
|
|
1353
611
|
try {
|
|
1354
|
-
|
|
1355
|
-
if (switchApiKey) {
|
|
1356
|
-
await switchApiKey(keyId);
|
|
1357
|
-
} else {
|
|
612
|
+
if (!switchApiKey) {
|
|
1358
613
|
throw new Error("Switch API key function not available");
|
|
1359
614
|
}
|
|
615
|
+
await switchApiKey(keyId);
|
|
1360
616
|
setShowApiKeySelector(false);
|
|
1361
617
|
setShowTooltip(false);
|
|
1362
|
-
setIsLoadingStatus(true);
|
|
1363
|
-
// Refresh provider data after API key selection
|
|
1364
618
|
if (refetchProviders) {
|
|
1365
619
|
await refetchProviders();
|
|
1366
620
|
}
|
|
@@ -1368,12 +622,16 @@ export function AiStatusButton({
|
|
|
1368
622
|
console.error("Failed to select API key:", error);
|
|
1369
623
|
} finally {
|
|
1370
624
|
setIsSelectingApiKey(false);
|
|
1371
|
-
setIsLoadingStatus(false);
|
|
1372
625
|
}
|
|
1373
626
|
}}
|
|
1374
627
|
onCancel={() => setShowApiKeySelector(false)}
|
|
1375
628
|
/>
|
|
1376
|
-
)}
|
|
1377
|
-
|
|
629
|
+
) : null}
|
|
630
|
+
|
|
631
|
+
<LBSigninModal
|
|
632
|
+
isOpen={showSigninModal}
|
|
633
|
+
onClose={() => setShowSigninModal(false)}
|
|
634
|
+
/>
|
|
635
|
+
</>
|
|
1378
636
|
);
|
|
1379
637
|
}
|