@lastbrain/ai-ui-react 1.0.39 → 1.0.41
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 +24 -11
- package/dist/components/AiImageButton.d.ts.map +1 -1
- package/dist/components/AiImageButton.js +24 -11
- package/dist/components/AiInput.d.ts.map +1 -1
- package/dist/components/AiInput.js +15 -1
- package/dist/components/AiTextarea.d.ts.map +1 -1
- package/dist/components/AiTextarea.js +14 -1
- package/dist/components/LBConnectButton.d.ts.map +1 -1
- package/dist/components/LBConnectButton.js +11 -7
- package/dist/context/LBAuthProvider.d.ts.map +1 -1
- package/dist/context/LBAuthProvider.js +1 -1
- package/package.json +2 -2
- package/src/components/AiContextButton.tsx +45 -8
- package/src/components/AiImageButton.tsx +43 -8
- package/src/components/AiInput.tsx +34 -3
- package/src/components/AiTextarea.tsx +31 -3
- package/src/components/LBConnectButton.tsx +217 -87
- package/src/context/LBAuthProvider.tsx +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiContextButton.d.ts","sourceRoot":"","sources":["../../src/components/AiContextButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"AiContextButton.d.ts","sourceRoot":"","sources":["../../src/components/AiContextButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAW5C,MAAM,WAAW,oBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IAEvE,WAAW,EAAE,GAAG,CAAC;IACjB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,EAAE,CACT,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,WAAW,EACX,kBAAyC,EACzC,QAAQ,EACR,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,gBAA0C,EAC1C,YAAY,EACZ,aAAa,EACb,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,GAAG,WAAW,EACf,EAAE,oBAAoB,2CA2hBtB"}
|
|
@@ -9,12 +9,16 @@ import { useErrorToast, ErrorToast } from "./ErrorToast";
|
|
|
9
9
|
import { aiStyles } from "../styles/inline";
|
|
10
10
|
import { useAiContext } from "../context/AiProvider";
|
|
11
11
|
import { handleAIError } from "../utils/errorHandler";
|
|
12
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
13
|
+
import { LBAuthModal } from "./LBConnectButton";
|
|
12
14
|
export function AiContextButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode = "modal", contextData, contextDescription = "Données à analyser", onResult, onToast, disabled, className, children, resultModalTitle = "Résultat de l'analyse", storeOutputs, artifactTitle, context: _context, model: _model, prompt: _prompt, ...buttonProps }) {
|
|
13
15
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
14
17
|
const [isResultOpen, setIsResultOpen] = useState(false);
|
|
15
18
|
const [analysisResult, setAnalysisResult] = useState(null);
|
|
16
19
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
17
20
|
const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
|
|
21
|
+
const { status: lbStatus } = useLB();
|
|
18
22
|
// Récupérer le contexte AiProvider avec fallback sur les props
|
|
19
23
|
const aiContext = useAiContext();
|
|
20
24
|
const baseUrl = propBaseUrl ?? aiContext.baseUrl;
|
|
@@ -23,7 +27,12 @@ export function AiContextButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId,
|
|
|
23
27
|
baseUrl,
|
|
24
28
|
apiKeyId,
|
|
25
29
|
});
|
|
30
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
26
31
|
const handleOpenPanel = () => {
|
|
32
|
+
if (!isAuthReady) {
|
|
33
|
+
setShowAuthModal(true);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
27
36
|
setIsOpen(true);
|
|
28
37
|
};
|
|
29
38
|
const handleClosePanel = () => {
|
|
@@ -160,14 +169,18 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
160
169
|
setIsOpen(false);
|
|
161
170
|
}
|
|
162
171
|
};
|
|
163
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading, className: className, style: {
|
|
172
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading || !isAuthReady, className: className, style: {
|
|
164
173
|
...aiStyles.button,
|
|
165
174
|
display: "flex",
|
|
166
175
|
alignItems: "center",
|
|
167
176
|
gap: "8px",
|
|
168
|
-
cursor: disabled || loading ? "not-allowed" : "pointer",
|
|
169
|
-
opacity: disabled || loading ? 0.6 : 1,
|
|
170
|
-
backgroundColor: loading
|
|
177
|
+
cursor: disabled || loading || !isAuthReady ? "not-allowed" : "pointer",
|
|
178
|
+
opacity: disabled || loading || !isAuthReady ? 0.6 : 1,
|
|
179
|
+
backgroundColor: loading
|
|
180
|
+
? "#8b5cf6"
|
|
181
|
+
: !isAuthReady
|
|
182
|
+
? "#94a3b8"
|
|
183
|
+
: "#7c3aed",
|
|
171
184
|
color: "white",
|
|
172
185
|
border: "none",
|
|
173
186
|
borderRadius: "12px",
|
|
@@ -188,30 +201,30 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
188
201
|
}),
|
|
189
202
|
...buttonProps.style,
|
|
190
203
|
}, onMouseEnter: (e) => {
|
|
191
|
-
if (!disabled && !loading) {
|
|
204
|
+
if (!disabled && !loading && isAuthReady) {
|
|
192
205
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
193
206
|
e.currentTarget.style.boxShadow =
|
|
194
207
|
"0 6px 16px rgba(124, 58, 237, 0.3)";
|
|
195
208
|
}
|
|
196
209
|
}, onMouseLeave: (e) => {
|
|
197
|
-
if (!disabled && !loading) {
|
|
210
|
+
if (!disabled && !loading && isAuthReady) {
|
|
198
211
|
e.currentTarget.style.transform = "scale(1)";
|
|
199
212
|
e.currentTarget.style.boxShadow = loading
|
|
200
213
|
? "0 4px 12px rgba(139, 92, 246, 0.3)"
|
|
201
214
|
: "0 2px 8px rgba(124, 58, 237, 0.2)";
|
|
202
215
|
}
|
|
203
216
|
}, onMouseDown: (e) => {
|
|
204
|
-
if (!disabled && !loading) {
|
|
217
|
+
if (!disabled && !loading && isAuthReady) {
|
|
205
218
|
e.currentTarget.style.transform = "scale(0.98)";
|
|
206
219
|
}
|
|
207
220
|
}, onMouseUp: (e) => {
|
|
208
|
-
if (!disabled && !loading) {
|
|
221
|
+
if (!disabled && !loading && isAuthReady) {
|
|
209
222
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
210
223
|
}
|
|
211
|
-
}, "data-ai-context-button": true, children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 18, className: "animate-spin", style: {
|
|
224
|
+
}, "data-ai-context-button": true, title: !isAuthReady ? "Authentication required" : "Analyser avec l'IA", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 18, className: "animate-spin", style: {
|
|
212
225
|
color: "white",
|
|
213
226
|
filter: "drop-shadow(0 0 2px rgba(255,255,255,0.3))",
|
|
214
|
-
} }), _jsx("span", { style: { letterSpacing: "0.025em" }, children: "Analyse..." })] })) : (_jsx(_Fragment, { children: _jsx(Sparkle, { size: 18, style: {
|
|
227
|
+
} }), _jsx("span", { style: { letterSpacing: "0.025em" }, children: "Analyse..." })] })) : !isAuthReady ? (_jsxs(_Fragment, { children: [_jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }), _jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })] }), children || _jsx("span", { children: "Connexion requise" })] })) : (_jsx(_Fragment, { children: _jsx(Sparkle, { size: 18, style: {
|
|
215
228
|
color: "white",
|
|
216
229
|
filter: "drop-shadow(0 0 2px rgba(255,255,255,0.2))",
|
|
217
230
|
} }) })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: [], enableModelManagement: true, modelCategory: "text", baseUrl: baseUrl, apiKey: apiKeyId }))] }), isResultOpen && analysisResult && (_jsx("div", { style: {
|
|
@@ -332,5 +345,5 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
332
345
|
...getThemeStyles().content,
|
|
333
346
|
}, children: [_jsxs("span", { children: ["Co\u00FBt: $", (apiKeyId?.includes("dev")
|
|
334
347
|
? 0
|
|
335
|
-
: analysisResult.cost).toFixed(6)] }), _jsxs("span", { children: ["ID: ", analysisResult.requestId?.slice(-8) || "N/A"] })] }) })] })] }) }) })), _jsx(ErrorToast, { error: errorData, onComplete: clearError }, errorKey)] }));
|
|
348
|
+
: analysisResult.cost).toFixed(6)] }), _jsxs("span", { children: ["ID: ", analysisResult.requestId?.slice(-8) || "N/A"] })] }) })] })] }) }) })), showAuthModal && (_jsx(LBAuthModal, { onClose: () => setShowAuthModal(false) })), _jsx(ErrorToast, { error: errorData, onComplete: clearError }, errorKey)] }));
|
|
336
349
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiImageButton.d.ts","sourceRoot":"","sources":["../../src/components/AiImageButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"AiImageButton.d.ts","sourceRoot":"","sources":["../../src/components/AiImageButton.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,oBAAoB,EAAE,MAAM,OAAO,CAAC;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAW5C,MAAM,WAAW,kBACf,SACE,IAAI,CAAC,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC,EACrC,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,SAAS,GAAG,UAAU,CAAC;IACvE,OAAO,CAAC,EAAE,CACR,QAAQ,EAAE,MAAM,EAChB,QAAQ,CAAC,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,KAC7C,IAAI,CAAC;IACV,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,WAAW,EACpB,QAAQ,EAAE,YAAY,EACtB,MAAgB,EAChB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,EACf,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,WAAW,EACX,YAAY,EACZ,aAAa,EACb,GAAG,WAAW,EACf,EAAE,kBAAkB,2CAsZpB"}
|
|
@@ -9,17 +9,26 @@ import { useErrorToast, ErrorToast } from "./ErrorToast";
|
|
|
9
9
|
import { aiStyles } from "../styles/inline";
|
|
10
10
|
import { useAiContext } from "../context/AiProvider";
|
|
11
11
|
import { handleAIError } from "../utils/errorHandler";
|
|
12
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
13
|
+
import { LBAuthModal } from "./LBConnectButton";
|
|
12
14
|
export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, uiMode = "modal", context: _context, model: _model, prompt: _prompt, onImage, onToast, disabled, className, children, showImageCard = true, onImageSave, storeOutputs, artifactTitle, ...buttonProps }) {
|
|
13
15
|
const [isOpen, setIsOpen] = useState(false);
|
|
16
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
14
17
|
const [generatedImage, setGeneratedImage] = useState(null);
|
|
15
18
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
16
19
|
const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
|
|
20
|
+
const { status: lbStatus } = useLB();
|
|
17
21
|
// Récupérer le contexte AiProvider avec fallback sur les props
|
|
18
22
|
const aiContext = useAiContext();
|
|
19
23
|
const baseUrl = propBaseUrl ?? aiContext.baseUrl;
|
|
20
24
|
const apiKeyId = propApiKeyId ?? aiContext.apiKeyId;
|
|
21
25
|
const { generateImage, loading } = useAiCallImage({ baseUrl, apiKeyId });
|
|
26
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
22
27
|
const handleOpenPanel = () => {
|
|
28
|
+
if (!isAuthReady) {
|
|
29
|
+
setShowAuthModal(true);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
23
32
|
setIsOpen(true);
|
|
24
33
|
};
|
|
25
34
|
const handleClosePanel = () => {
|
|
@@ -134,14 +143,18 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
|
|
|
134
143
|
setIsOpen(false);
|
|
135
144
|
}
|
|
136
145
|
};
|
|
137
|
-
return (_jsxs("div", { className: "flex items-start gap-4", children: [_jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading, className: className, style: {
|
|
146
|
+
return (_jsxs("div", { className: "flex items-start gap-4", children: [_jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading || !isAuthReady, className: className, style: {
|
|
138
147
|
...aiStyles.button,
|
|
139
148
|
display: "flex",
|
|
140
149
|
alignItems: "center",
|
|
141
150
|
gap: "8px",
|
|
142
|
-
cursor: disabled || loading ? "not-allowed" : "pointer",
|
|
143
|
-
opacity: disabled || loading ? 0.6 : 1,
|
|
144
|
-
backgroundColor: loading
|
|
151
|
+
cursor: disabled || loading || !isAuthReady ? "not-allowed" : "pointer",
|
|
152
|
+
opacity: disabled || loading || !isAuthReady ? 0.6 : 1,
|
|
153
|
+
backgroundColor: loading
|
|
154
|
+
? "#8b5cf6"
|
|
155
|
+
: !isAuthReady
|
|
156
|
+
? "#94a3b8"
|
|
157
|
+
: "#6366f1",
|
|
145
158
|
color: "white",
|
|
146
159
|
border: "none",
|
|
147
160
|
borderRadius: "12px",
|
|
@@ -161,30 +174,30 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
|
|
|
161
174
|
}),
|
|
162
175
|
...buttonProps.style,
|
|
163
176
|
}, onMouseEnter: (e) => {
|
|
164
|
-
if (!disabled && !loading) {
|
|
177
|
+
if (!disabled && !loading && isAuthReady) {
|
|
165
178
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
166
179
|
e.currentTarget.style.boxShadow =
|
|
167
180
|
"0 6px 16px rgba(99, 102, 241, 0.3)";
|
|
168
181
|
}
|
|
169
182
|
}, onMouseLeave: (e) => {
|
|
170
|
-
if (!disabled && !loading) {
|
|
183
|
+
if (!disabled && !loading && isAuthReady) {
|
|
171
184
|
e.currentTarget.style.transform = "scale(1)";
|
|
172
185
|
e.currentTarget.style.boxShadow = loading
|
|
173
186
|
? "0 4px 12px rgba(139, 92, 246, 0.3)"
|
|
174
187
|
: "0 2px 8px rgba(99, 102, 241, 0.2)";
|
|
175
188
|
}
|
|
176
189
|
}, onMouseDown: (e) => {
|
|
177
|
-
if (!disabled && !loading) {
|
|
190
|
+
if (!disabled && !loading && isAuthReady) {
|
|
178
191
|
e.currentTarget.style.transform = "scale(0.98)";
|
|
179
192
|
}
|
|
180
193
|
}, onMouseUp: (e) => {
|
|
181
|
-
if (!disabled && !loading) {
|
|
194
|
+
if (!disabled && !loading && isAuthReady) {
|
|
182
195
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
183
196
|
}
|
|
184
|
-
}, "data-ai-image-button": true, children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 18, className: "animate-spin", style: {
|
|
197
|
+
}, "data-ai-image-button": true, title: !isAuthReady ? "Authentication required" : "Générer une image", children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 18, className: "animate-spin", style: {
|
|
185
198
|
color: "white",
|
|
186
199
|
filter: "drop-shadow(0 0 2px rgba(255,255,255,0.3))",
|
|
187
|
-
} }), _jsx("span", { style: { letterSpacing: "0.025em" }, children: "G\u00E9n\u00E9ration..." })] })) : (_jsxs(_Fragment, { children: [_jsx(ImageIcon, { size: 18, style: {
|
|
200
|
+
} }), _jsx("span", { style: { letterSpacing: "0.025em" }, children: "G\u00E9n\u00E9ration..." })] })) : !isAuthReady ? (_jsxs(_Fragment, { children: [_jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }), _jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })] }), children || _jsx("span", { children: "Connexion requise" })] })) : (_jsxs(_Fragment, { children: [_jsx(ImageIcon, { size: 18, style: {
|
|
188
201
|
color: "white",
|
|
189
202
|
filter: "drop-shadow(0 0 2px rgba(255,255,255,0.2))",
|
|
190
203
|
} }), _jsx("span", { style: { letterSpacing: "0.025em" }, children: children || "Générer une image" })] })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, enableModelManagement: true, modelCategory: "image", baseUrl: baseUrl, apiKey: apiKeyId, models: [] }))] }), showImageCard && generatedImage && (_jsxs("div", { className: "relative", style: {
|
|
@@ -231,5 +244,5 @@ export function AiImageButton({ baseUrl: propBaseUrl, apiKeyId: propApiKeyId, ui
|
|
|
231
244
|
e.currentTarget.style.boxShadow = "";
|
|
232
245
|
}, title: "T\u00E9l\u00E9charger l'image", children: [_jsx(Download, { size: 16 }), "T\u00E9l\u00E9charger l'image"] }), onImageSave && (_jsxs("button", { onClick: handleSave, className: "flex items-center gap-1 px-3 py-2 text-xs font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-lg transition-colors", title: "Sauvegarder en base", children: [_jsx(ExternalLink, { size: 14 }), "Sauvegarder"] }))] }), _jsx("div", { className: "mt-3 pt-3 text-xs", style: {
|
|
233
246
|
...getThemeStyles().metadata,
|
|
234
|
-
}, children: _jsx("div", { className: "flex justify-center", children: _jsxs("span", { children: ["ID: ", generatedImage.requestId.slice(-8)] }) }) })] })), _jsx(ErrorToast, { error: errorData, onComplete: clearError }, errorKey)] }));
|
|
247
|
+
}, children: _jsx("div", { className: "flex justify-center", children: _jsxs("span", { children: ["ID: ", generatedImage.requestId.slice(-8)] }) }) })] })), showAuthModal && (_jsx(LBAuthModal, { onClose: () => setShowAuthModal(false) })), _jsx(ErrorToast, { error: errorData, onComplete: clearError }, errorKey)] }));
|
|
235
248
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"AiInput.d.ts","sourceRoot":"","sources":["../../src/components/AiInput.tsx"],"names":[],"mappings":"AAEA,OAAc,EAAoB,KAAK,mBAAmB,EAAE,MAAM,OAAO,CAAC;AAE1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAU5C,MAAM,WAAW,YACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,SAAS,CAAC;IACxD,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,wBAAgB,OAAO,CAAC,EACtB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,qBAA4B,EAC5B,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,UAAU,EACd,EAAE,YAAY,2CA+Md"}
|
|
@@ -8,13 +8,17 @@ import { AiPromptPanel } from "./AiPromptPanel";
|
|
|
8
8
|
import { UsageToast, useUsageToast } from "./UsageToast";
|
|
9
9
|
import { aiStyles } from "../styles/inline";
|
|
10
10
|
import { handleAIError } from "../utils/errorHandler";
|
|
11
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
12
|
+
import { LBAuthModal } from "./LBConnectButton";
|
|
11
13
|
export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, enableModelManagement = true, storeOutputs, artifactTitle, onValue, onToast, disabled, className, ...inputProps }) {
|
|
12
14
|
const [isOpen, setIsOpen] = useState(false);
|
|
15
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
13
16
|
const [inputValue, setInputValue] = useState(inputProps.value?.toString() || inputProps.defaultValue?.toString() || "");
|
|
14
17
|
const [isFocused, setIsFocused] = useState(false);
|
|
15
18
|
const [isButtonHovered, setIsButtonHovered] = useState(false);
|
|
16
19
|
const inputRef = useRef(null);
|
|
17
20
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
21
|
+
const { status: lbStatus } = useLB();
|
|
18
22
|
const { models } = useAiModels({
|
|
19
23
|
baseUrl,
|
|
20
24
|
apiKeyId,
|
|
@@ -22,7 +26,13 @@ export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, p
|
|
|
22
26
|
});
|
|
23
27
|
const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
|
|
24
28
|
const hasConfiguration = Boolean(model && prompt);
|
|
29
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
30
|
+
const shouldShowSparkles = isAuthReady && !disabled;
|
|
25
31
|
const handleOpenPanel = () => {
|
|
32
|
+
if (!isAuthReady) {
|
|
33
|
+
setShowAuthModal(true);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
26
36
|
setIsOpen(true);
|
|
27
37
|
};
|
|
28
38
|
const handleClosePanel = () => {
|
|
@@ -109,5 +119,9 @@ export function AiInput({ baseUrl, apiKeyId, uiMode = "modal", context, model, p
|
|
|
109
119
|
...(disabled || loading
|
|
110
120
|
? { opacity: 0.5, cursor: "not-allowed" }
|
|
111
121
|
: {}),
|
|
112
|
-
}, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title:
|
|
122
|
+
}, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading || !isAuthReady, type: "button", title: !isAuthReady
|
|
123
|
+
? "Authentication required"
|
|
124
|
+
: hasConfiguration
|
|
125
|
+
? "Generate with AI"
|
|
126
|
+
: "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("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" }) })) : shouldShowSparkles ? (_jsx(Sparkles, { size: 16 })) : (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }), _jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })] })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: [], modelCategory: "text", sourceText: inputValue || undefined, apiKey: apiKeyId, baseUrl: baseUrl, enableModelManagement: enableModelManagement })), showAuthModal && (_jsx(LBAuthModal, { onClose: () => setShowAuthModal(false) })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey))] }));
|
|
113
127
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"AiTextarea.d.ts","sourceRoot":"","sources":["../../src/components/AiTextarea.tsx"],"names":[],"mappings":"AAEA,OAAc,EAIZ,KAAK,sBAAsB,EAC5B,MAAM,OAAO,CAAC;AAEf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAU5C,MAAM,WAAW,eACf,SACE,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EACzB,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,SAAS,CAAC;IAC9D,MAAM,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAC7B;AAED,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAQ,EACR,MAAgB,EAChB,OAAO,EACP,KAAK,EACL,MAAM,EACN,QAAgB,EAChB,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,OAAO,EACP,OAAO,EACP,QAAQ,EACR,SAAS,EACT,GAAG,aAAa,EACjB,EAAE,eAAe,2CA6NjB"}
|
|
@@ -8,8 +8,10 @@ import { AiPromptPanel } from "./AiPromptPanel";
|
|
|
8
8
|
import { UsageToast, useUsageToast } from "./UsageToast";
|
|
9
9
|
import { aiStyles } from "../styles/inline";
|
|
10
10
|
import { handleAIError } from "../utils/errorHandler";
|
|
11
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
11
12
|
export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, editMode = false, enableModelManagement, storeOutputs, artifactTitle, onValue, onToast, disabled, className, ...textareaProps }) {
|
|
12
13
|
const [isOpen, setIsOpen] = useState(false);
|
|
14
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
13
15
|
const [textareaValue, setTextareaValue] = useState(textareaProps.value?.toString() ||
|
|
14
16
|
textareaProps.defaultValue?.toString() ||
|
|
15
17
|
"");
|
|
@@ -17,6 +19,7 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
|
|
|
17
19
|
const [isButtonHovered, setIsButtonHovered] = useState(false);
|
|
18
20
|
const textareaRef = useRef(null);
|
|
19
21
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
22
|
+
const { status: lbStatus } = useLB();
|
|
20
23
|
const { models } = useAiModels({
|
|
21
24
|
baseUrl,
|
|
22
25
|
apiKeyId,
|
|
@@ -24,7 +27,13 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
|
|
|
24
27
|
});
|
|
25
28
|
const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
|
|
26
29
|
const hasConfiguration = Boolean(model && prompt);
|
|
30
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
31
|
+
const shouldShowSparkles = isAuthReady && !disabled;
|
|
27
32
|
const handleOpenPanel = () => {
|
|
33
|
+
if (!isAuthReady) {
|
|
34
|
+
setShowAuthModal(true);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
28
37
|
setIsOpen(true);
|
|
29
38
|
};
|
|
30
39
|
const handleClosePanel = () => {
|
|
@@ -123,5 +132,9 @@ export function AiTextarea({ baseUrl, apiKeyId, uiMode = "modal", context, model
|
|
|
123
132
|
...(disabled || loading
|
|
124
133
|
? { opacity: 0.5, cursor: "not-allowed" }
|
|
125
134
|
: {}),
|
|
126
|
-
}, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading, type: "button", title:
|
|
135
|
+
}, onClick: hasConfiguration ? handleQuickGenerate : handleOpenPanel, onMouseEnter: () => setIsButtonHovered(true), onMouseLeave: () => setIsButtonHovered(false), disabled: disabled || loading || !isAuthReady, type: "button", title: !isAuthReady
|
|
136
|
+
? "Authentication required"
|
|
137
|
+
: hasConfiguration
|
|
138
|
+
? "Generate with AI"
|
|
139
|
+
: "Setup AI", children: loading ? (_jsx("svg", { style: aiStyles.spinner, width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", children: _jsx("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" }) })) : shouldShowSparkles ? (_jsx(Sparkles, { size: 16 })) : (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [_jsx("rect", { x: "3", y: "11", width: "18", height: "11", rx: "2", ry: "2" }), _jsx("path", { d: "M7 11V7a5 5 0 0 1 10 0v4" })] })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: [], modelCategory: "text", sourceText: textareaValue || undefined, baseUrl: baseUrl, apiKey: apiKeyId, enableModelManagement: enableModelManagement })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey))] }));
|
|
127
140
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LBConnectButton.d.ts","sourceRoot":"","sources":["../../src/components/LBConnectButton.tsx"],"names":[],"mappings":"AAUA,UAAU,oBAAoB;IAC5B,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAkC,EAClC,SAAc,EACd,WAAW,EACX,WAAW,GACZ,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"LBConnectButton.d.ts","sourceRoot":"","sources":["../../src/components/LBConnectButton.tsx"],"names":[],"mappings":"AAUA,UAAU,oBAAoB;IAC5B,sBAAsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB,yCAAyC;IACzC,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED,wBAAgB,eAAe,CAAC,EAC9B,KAAkC,EAClC,SAAc,EACd,WAAW,EACX,WAAW,GACZ,EAAE,oBAAoB,2CA4CtB;AAED;;GAEG;AACH,UAAU,gBAAgB;IACxB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACrC;AAED,iBAAS,WAAW,CAAC,EAAE,OAAO,EAAE,EAAE,gBAAgB,2CAsRjD;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
|
@@ -27,11 +27,13 @@ export function LBConnectButton({ label = "Se connecter à LastBrain", className
|
|
|
27
27
|
}
|
|
28
28
|
};
|
|
29
29
|
return (_jsxs(_Fragment, { children: [_jsx("button", { onClick: handleClick, className: className ||
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
(status === "ready" && user
|
|
31
|
+
? "px-4 py-2 bg-gradient-to-r from-emerald-500 to-teal-600 text-white rounded-lg hover:from-emerald-600 hover:to-teal-700 transition-all duration-200 shadow-md hover:shadow-lg"
|
|
32
|
+
: "px-4 py-2 bg-gradient-to-r from-violet-500 to-purple-600 text-white rounded-lg hover:from-violet-600 hover:to-purple-700 transition-all duration-200 shadow-md hover:shadow-lg"), disabled: status === "loading", children: status === "loading"
|
|
33
|
+
? "⏳ Chargement..."
|
|
32
34
|
: status === "ready" && user
|
|
33
|
-
?
|
|
34
|
-
: label }), showModal && _jsx(LBAuthModal, { onClose: handleModalClose })] }));
|
|
35
|
+
? `✓ ${user.email}`
|
|
36
|
+
: `🔐 ${label}` }), showModal && _jsx(LBAuthModal, { onClose: handleModalClose })] }));
|
|
35
37
|
}
|
|
36
38
|
function LBAuthModal({ onClose }) {
|
|
37
39
|
const { login, fetchApiKeys, selectApiKey, status } = useLB();
|
|
@@ -80,8 +82,10 @@ function LBAuthModal({ onClose }) {
|
|
|
80
82
|
setLoading(false);
|
|
81
83
|
}
|
|
82
84
|
};
|
|
83
|
-
return (_jsx("div", { className: "fixed inset-0 bg-
|
|
84
|
-
|
|
85
|
-
|
|
85
|
+
return (_jsx("div", { className: "fixed inset-0 bg-gradient-to-br from-slate-900/95 via-purple-900/95 to-slate-900/95 backdrop-blur-sm flex items-center justify-center z-50 p-4", children: _jsxs("div", { className: "bg-white dark:bg-gradient-to-br dark:from-slate-800 dark:to-slate-900 rounded-2xl shadow-2xl max-w-md w-full border border-slate-200 dark:border-slate-700 overflow-hidden", children: [_jsx("div", { className: "bg-gradient-to-r from-violet-500 to-purple-600 p-6 text-white", children: _jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-2xl font-bold flex items-center gap-2", children: step === "login" ? "🔐 Connexion" : "🔑 Sélection clé API" }), _jsx("p", { className: "text-violet-100 text-sm mt-1", children: step === "login"
|
|
86
|
+
? "Accédez à vos outils IA"
|
|
87
|
+
: "Créez une session sécurisée 72h" })] }), _jsx("button", { onClick: () => onClose(false), className: "text-white/80 hover:text-white hover:bg-white/20 rounded-lg p-2 transition-colors", children: _jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) }) })] }) }), _jsx("div", { className: "p-6", children: step === "login" ? (_jsxs("form", { onSubmit: handleLogin, className: "space-y-4", children: [_jsxs("div", { children: [_jsx("label", { className: "block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300", children: "\uD83D\uDCE7 Email" }), _jsx("input", { type: "email", value: email, onChange: (e) => setEmail(e.target.value), className: "w-full px-4 py-3 border-2 border-slate-200 dark:border-slate-600 rounded-lg dark:bg-slate-800 dark:text-white focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900 transition-all outline-none", placeholder: "votre@email.com", required: true, autoFocus: true })] }), _jsxs("div", { children: [_jsx("label", { className: "block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300", children: "\uD83D\uDD12 Mot de passe" }), _jsx("input", { type: "password", value: password, onChange: (e) => setPassword(e.target.value), className: "w-full px-4 py-3 border-2 border-slate-200 dark:border-slate-600 rounded-lg dark:bg-slate-800 dark:text-white focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900 transition-all outline-none", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", required: true })] }), error && (_jsx("div", { className: "bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 p-4 rounded", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-red-500 text-xl", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-red-700 dark:text-red-400 text-sm font-medium", children: error })] }) })), _jsx("button", { type: "submit", disabled: loading, className: "w-full px-6 py-3 bg-gradient-to-r from-violet-500 to-purple-600 text-white font-semibold rounded-lg hover:from-violet-600 hover:to-purple-700 disabled:from-slate-400 disabled:to-slate-500 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5", children: loading ? (_jsxs("span", { className: "flex items-center justify-center gap-2", children: [_jsxs("svg", { className: "animate-spin h-5 w-5", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [_jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), _jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" })] }), "Connexion en cours..."] })) : ("🚀 Se connecter") }), _jsxs("div", { className: "relative my-6", children: [_jsx("div", { className: "absolute inset-0 flex items-center", children: _jsx("div", { className: "w-full border-t border-slate-200 dark:border-slate-700" }) }), _jsx("div", { className: "relative flex justify-center text-sm", children: _jsx("span", { className: "px-4 bg-white dark:bg-slate-800 text-slate-500", children: "ou" }) })] }), _jsxs("div", { className: "bg-gradient-to-r from-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-900/20 rounded-lg p-4 border border-violet-200 dark:border-violet-800", children: [_jsx("p", { className: "text-sm text-slate-700 dark:text-slate-300 text-center", children: "Pas encore de compte ?" }), _jsx("a", { href: "https://lastbrain.io/signup", target: "_blank", rel: "noopener noreferrer", className: "block mt-2 text-center text-violet-600 dark:text-violet-400 hover:text-violet-700 dark:hover:text-violet-300 font-semibold text-sm hover:underline", children: "\u2728 Cr\u00E9er un compte gratuitement" })] })] })) : (_jsxs("div", { className: "space-y-4", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg border border-blue-200 dark:border-blue-800", children: "\u2139\uFE0F S\u00E9lectionnez une cl\u00E9 API pour cr\u00E9er une session s\u00E9curis\u00E9e de 72h" }), _jsx("div", { className: "space-y-3 max-h-96 overflow-y-auto", children: apiKeys.map((key) => (_jsx("button", { onClick: () => handleSelectKey(key.id), disabled: !key.isActive || loading, className: `w-full text-left px-5 py-4 border-2 rounded-lg transition-all transform hover:scale-[1.02] ${key.isActive
|
|
88
|
+
? "border-slate-200 dark:border-slate-600 hover:border-violet-400 dark:hover:border-violet-500 hover:bg-gradient-to-r hover:from-violet-50 hover:to-purple-50 dark:hover:from-violet-900/20 dark:hover:to-purple-900/20"
|
|
89
|
+
: "border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50 opacity-50 cursor-not-allowed"}`, children: _jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "font-semibold text-slate-900 dark:text-white flex items-center gap-2", children: [key.isActive ? "🔑" : "🔒", key.name] }), _jsxs("div", { className: "text-sm text-slate-500 dark:text-slate-400 mt-1 font-mono", children: [key.keyPrefix, "..."] }), key.scopes && (_jsxs("div", { className: "flex gap-1 mt-2 flex-wrap", children: [key.scopes.slice(0, 3).map((scope) => (_jsx("span", { className: "text-xs px-2 py-1 bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 rounded", children: scope }, scope))), key.scopes.length > 3 && (_jsxs("span", { className: "text-xs px-2 py-1 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-400 rounded", children: ["+", key.scopes.length - 3] }))] }))] }), key.isActive ? (_jsx("svg", { className: "w-5 h-5 text-violet-500 flex-shrink-0 mt-1", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 5l7 7-7 7" }) })) : (_jsx("span", { className: "text-xs text-red-600 dark:text-red-400 font-semibold bg-red-100 dark:bg-red-900/30 px-2 py-1 rounded", children: "Inactive" }))] }) }, key.id))) }), error && (_jsx("div", { className: "bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 p-4 rounded", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx("span", { className: "text-red-500 text-xl", children: "\u26A0\uFE0F" }), _jsx("p", { className: "text-red-700 dark:text-red-400 text-sm font-medium", children: error })] }) }))] })) })] }) }));
|
|
86
90
|
}
|
|
87
91
|
export { LBAuthModal };
|
|
@@ -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,EAER,aAAa,EAEd,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;CAC1D;AAED,UAAU,cAAe,SAAQ,WAAW;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IACnE,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,kDAAkD;IAClD,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,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,
|
|
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,EAER,aAAa,EAEd,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;CAC1D;AAED,UAAU,cAAe,SAAQ,WAAW;IAC1C,4BAA4B;IAC5B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IACnE,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,kDAAkD;IAClD,YAAY,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,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,GACf,EAAE,eAAe,2CAwMjB;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/
|
|
9
|
+
export function LBProvider({ children, baseUrl: _baseUrl = "/api/lastbrain", proxyUrl = "/api/lastbrain", onStatusChange, }) {
|
|
10
10
|
const [state, setState] = useState({
|
|
11
11
|
status: "loading",
|
|
12
12
|
});
|
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.41",
|
|
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.30"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/react": "^19.2.0",
|
|
@@ -10,6 +10,8 @@ import { useErrorToast, ErrorToast } from "./ErrorToast";
|
|
|
10
10
|
import { aiStyles } from "../styles/inline";
|
|
11
11
|
import { useAiContext } from "../context/AiProvider";
|
|
12
12
|
import { handleAIError } from "../utils/errorHandler";
|
|
13
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
14
|
+
import { LBAuthModal } from "./LBConnectButton";
|
|
13
15
|
|
|
14
16
|
export interface AiContextButtonProps
|
|
15
17
|
extends
|
|
@@ -51,6 +53,7 @@ export function AiContextButton({
|
|
|
51
53
|
...buttonProps
|
|
52
54
|
}: AiContextButtonProps) {
|
|
53
55
|
const [isOpen, setIsOpen] = useState(false);
|
|
56
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
54
57
|
const [isResultOpen, setIsResultOpen] = useState(false);
|
|
55
58
|
const [analysisResult, setAnalysisResult] = useState<{
|
|
56
59
|
content: string;
|
|
@@ -61,6 +64,7 @@ export function AiContextButton({
|
|
|
61
64
|
} | null>(null);
|
|
62
65
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
63
66
|
const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
|
|
67
|
+
const { status: lbStatus } = useLB();
|
|
64
68
|
|
|
65
69
|
// Récupérer le contexte AiProvider avec fallback sur les props
|
|
66
70
|
const aiContext = useAiContext();
|
|
@@ -72,7 +76,13 @@ export function AiContextButton({
|
|
|
72
76
|
apiKeyId,
|
|
73
77
|
});
|
|
74
78
|
|
|
79
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
80
|
+
|
|
75
81
|
const handleOpenPanel = () => {
|
|
82
|
+
if (!isAuthReady) {
|
|
83
|
+
setShowAuthModal(true);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
76
86
|
setIsOpen(true);
|
|
77
87
|
};
|
|
78
88
|
|
|
@@ -234,16 +244,21 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
234
244
|
<button
|
|
235
245
|
{...buttonProps}
|
|
236
246
|
onClick={handleOpenPanel}
|
|
237
|
-
disabled={disabled || loading}
|
|
247
|
+
disabled={disabled || loading || !isAuthReady}
|
|
238
248
|
className={className}
|
|
239
249
|
style={{
|
|
240
250
|
...aiStyles.button,
|
|
241
251
|
display: "flex",
|
|
242
252
|
alignItems: "center",
|
|
243
253
|
gap: "8px",
|
|
244
|
-
cursor:
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
cursor:
|
|
255
|
+
disabled || loading || !isAuthReady ? "not-allowed" : "pointer",
|
|
256
|
+
opacity: disabled || loading || !isAuthReady ? 0.6 : 1,
|
|
257
|
+
backgroundColor: loading
|
|
258
|
+
? "#8b5cf6"
|
|
259
|
+
: !isAuthReady
|
|
260
|
+
? "#94a3b8"
|
|
261
|
+
: "#7c3aed",
|
|
247
262
|
color: "white",
|
|
248
263
|
border: "none",
|
|
249
264
|
borderRadius: "12px",
|
|
@@ -265,14 +280,14 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
265
280
|
...buttonProps.style,
|
|
266
281
|
}}
|
|
267
282
|
onMouseEnter={(e) => {
|
|
268
|
-
if (!disabled && !loading) {
|
|
283
|
+
if (!disabled && !loading && isAuthReady) {
|
|
269
284
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
270
285
|
e.currentTarget.style.boxShadow =
|
|
271
286
|
"0 6px 16px rgba(124, 58, 237, 0.3)";
|
|
272
287
|
}
|
|
273
288
|
}}
|
|
274
289
|
onMouseLeave={(e) => {
|
|
275
|
-
if (!disabled && !loading) {
|
|
290
|
+
if (!disabled && !loading && isAuthReady) {
|
|
276
291
|
e.currentTarget.style.transform = "scale(1)";
|
|
277
292
|
e.currentTarget.style.boxShadow = loading
|
|
278
293
|
? "0 4px 12px rgba(139, 92, 246, 0.3)"
|
|
@@ -280,16 +295,19 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
280
295
|
}
|
|
281
296
|
}}
|
|
282
297
|
onMouseDown={(e) => {
|
|
283
|
-
if (!disabled && !loading) {
|
|
298
|
+
if (!disabled && !loading && isAuthReady) {
|
|
284
299
|
e.currentTarget.style.transform = "scale(0.98)";
|
|
285
300
|
}
|
|
286
301
|
}}
|
|
287
302
|
onMouseUp={(e) => {
|
|
288
|
-
if (!disabled && !loading) {
|
|
303
|
+
if (!disabled && !loading && isAuthReady) {
|
|
289
304
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
290
305
|
}
|
|
291
306
|
}}
|
|
292
307
|
data-ai-context-button
|
|
308
|
+
title={
|
|
309
|
+
!isAuthReady ? "Authentication required" : "Analyser avec l'IA"
|
|
310
|
+
}
|
|
293
311
|
>
|
|
294
312
|
{loading ? (
|
|
295
313
|
<>
|
|
@@ -303,6 +321,21 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
303
321
|
/>
|
|
304
322
|
<span style={{ letterSpacing: "0.025em" }}>Analyse...</span>
|
|
305
323
|
</>
|
|
324
|
+
) : !isAuthReady ? (
|
|
325
|
+
<>
|
|
326
|
+
<svg
|
|
327
|
+
width="18"
|
|
328
|
+
height="18"
|
|
329
|
+
viewBox="0 0 24 24"
|
|
330
|
+
fill="none"
|
|
331
|
+
stroke="currentColor"
|
|
332
|
+
strokeWidth="2"
|
|
333
|
+
>
|
|
334
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
335
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
336
|
+
</svg>
|
|
337
|
+
{children || <span>Connexion requise</span>}
|
|
338
|
+
</>
|
|
306
339
|
) : (
|
|
307
340
|
<>
|
|
308
341
|
<Sparkle
|
|
@@ -549,6 +582,10 @@ Analyse ces données et réponds de manière structurée et claire.`;
|
|
|
549
582
|
</div>
|
|
550
583
|
)}
|
|
551
584
|
|
|
585
|
+
{showAuthModal && (
|
|
586
|
+
<LBAuthModal onClose={() => setShowAuthModal(false)} />
|
|
587
|
+
)}
|
|
588
|
+
|
|
552
589
|
{/* Error Toast */}
|
|
553
590
|
<ErrorToast key={errorKey} error={errorData} onComplete={clearError} />
|
|
554
591
|
</>
|
|
@@ -10,6 +10,8 @@ import { useErrorToast, ErrorToast } from "./ErrorToast";
|
|
|
10
10
|
import { aiStyles } from "../styles/inline";
|
|
11
11
|
import { useAiContext } from "../context/AiProvider";
|
|
12
12
|
import { handleAIError } from "../utils/errorHandler";
|
|
13
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
14
|
+
import { LBAuthModal } from "./LBConnectButton";
|
|
13
15
|
|
|
14
16
|
export interface AiImageButtonProps
|
|
15
17
|
extends
|
|
@@ -48,6 +50,7 @@ export function AiImageButton({
|
|
|
48
50
|
...buttonProps
|
|
49
51
|
}: AiImageButtonProps) {
|
|
50
52
|
const [isOpen, setIsOpen] = useState(false);
|
|
53
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
51
54
|
const [generatedImage, setGeneratedImage] = useState<{
|
|
52
55
|
url: string;
|
|
53
56
|
prompt: string;
|
|
@@ -56,6 +59,7 @@ export function AiImageButton({
|
|
|
56
59
|
} | null>(null);
|
|
57
60
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
58
61
|
const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
|
|
62
|
+
const { status: lbStatus } = useLB();
|
|
59
63
|
|
|
60
64
|
// Récupérer le contexte AiProvider avec fallback sur les props
|
|
61
65
|
const aiContext = useAiContext();
|
|
@@ -64,7 +68,13 @@ export function AiImageButton({
|
|
|
64
68
|
|
|
65
69
|
const { generateImage, loading } = useAiCallImage({ baseUrl, apiKeyId });
|
|
66
70
|
|
|
71
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
72
|
+
|
|
67
73
|
const handleOpenPanel = () => {
|
|
74
|
+
if (!isAuthReady) {
|
|
75
|
+
setShowAuthModal(true);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
68
78
|
setIsOpen(true);
|
|
69
79
|
};
|
|
70
80
|
|
|
@@ -196,16 +206,21 @@ export function AiImageButton({
|
|
|
196
206
|
<button
|
|
197
207
|
{...buttonProps}
|
|
198
208
|
onClick={handleOpenPanel}
|
|
199
|
-
disabled={disabled || loading}
|
|
209
|
+
disabled={disabled || loading || !isAuthReady}
|
|
200
210
|
className={className}
|
|
201
211
|
style={{
|
|
202
212
|
...aiStyles.button,
|
|
203
213
|
display: "flex",
|
|
204
214
|
alignItems: "center",
|
|
205
215
|
gap: "8px",
|
|
206
|
-
cursor:
|
|
207
|
-
|
|
208
|
-
|
|
216
|
+
cursor:
|
|
217
|
+
disabled || loading || !isAuthReady ? "not-allowed" : "pointer",
|
|
218
|
+
opacity: disabled || loading || !isAuthReady ? 0.6 : 1,
|
|
219
|
+
backgroundColor: loading
|
|
220
|
+
? "#8b5cf6"
|
|
221
|
+
: !isAuthReady
|
|
222
|
+
? "#94a3b8"
|
|
223
|
+
: "#6366f1",
|
|
209
224
|
color: "white",
|
|
210
225
|
border: "none",
|
|
211
226
|
borderRadius: "12px",
|
|
@@ -226,14 +241,14 @@ export function AiImageButton({
|
|
|
226
241
|
...buttonProps.style,
|
|
227
242
|
}}
|
|
228
243
|
onMouseEnter={(e) => {
|
|
229
|
-
if (!disabled && !loading) {
|
|
244
|
+
if (!disabled && !loading && isAuthReady) {
|
|
230
245
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
231
246
|
e.currentTarget.style.boxShadow =
|
|
232
247
|
"0 6px 16px rgba(99, 102, 241, 0.3)";
|
|
233
248
|
}
|
|
234
249
|
}}
|
|
235
250
|
onMouseLeave={(e) => {
|
|
236
|
-
if (!disabled && !loading) {
|
|
251
|
+
if (!disabled && !loading && isAuthReady) {
|
|
237
252
|
e.currentTarget.style.transform = "scale(1)";
|
|
238
253
|
e.currentTarget.style.boxShadow = loading
|
|
239
254
|
? "0 4px 12px rgba(139, 92, 246, 0.3)"
|
|
@@ -241,16 +256,17 @@ export function AiImageButton({
|
|
|
241
256
|
}
|
|
242
257
|
}}
|
|
243
258
|
onMouseDown={(e) => {
|
|
244
|
-
if (!disabled && !loading) {
|
|
259
|
+
if (!disabled && !loading && isAuthReady) {
|
|
245
260
|
e.currentTarget.style.transform = "scale(0.98)";
|
|
246
261
|
}
|
|
247
262
|
}}
|
|
248
263
|
onMouseUp={(e) => {
|
|
249
|
-
if (!disabled && !loading) {
|
|
264
|
+
if (!disabled && !loading && isAuthReady) {
|
|
250
265
|
e.currentTarget.style.transform = "scale(1.02)";
|
|
251
266
|
}
|
|
252
267
|
}}
|
|
253
268
|
data-ai-image-button
|
|
269
|
+
title={!isAuthReady ? "Authentication required" : "Générer une image"}
|
|
254
270
|
>
|
|
255
271
|
{loading ? (
|
|
256
272
|
<>
|
|
@@ -264,6 +280,21 @@ export function AiImageButton({
|
|
|
264
280
|
/>
|
|
265
281
|
<span style={{ letterSpacing: "0.025em" }}>Génération...</span>
|
|
266
282
|
</>
|
|
283
|
+
) : !isAuthReady ? (
|
|
284
|
+
<>
|
|
285
|
+
<svg
|
|
286
|
+
width="18"
|
|
287
|
+
height="18"
|
|
288
|
+
viewBox="0 0 24 24"
|
|
289
|
+
fill="none"
|
|
290
|
+
stroke="currentColor"
|
|
291
|
+
strokeWidth="2"
|
|
292
|
+
>
|
|
293
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
294
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
295
|
+
</svg>
|
|
296
|
+
{children || <span>Connexion requise</span>}
|
|
297
|
+
</>
|
|
267
298
|
) : (
|
|
268
299
|
<>
|
|
269
300
|
<ImageIcon
|
|
@@ -415,6 +446,10 @@ export function AiImageButton({
|
|
|
415
446
|
</div>
|
|
416
447
|
)}
|
|
417
448
|
|
|
449
|
+
{showAuthModal && (
|
|
450
|
+
<LBAuthModal onClose={() => setShowAuthModal(false)} />
|
|
451
|
+
)}
|
|
452
|
+
|
|
418
453
|
{/* Error Toast */}
|
|
419
454
|
<ErrorToast key={errorKey} error={errorData} onComplete={clearError} />
|
|
420
455
|
</div>
|
|
@@ -9,6 +9,8 @@ import { AiPromptPanel } from "./AiPromptPanel";
|
|
|
9
9
|
import { UsageToast, useUsageToast } from "./UsageToast";
|
|
10
10
|
import { aiStyles } from "../styles/inline";
|
|
11
11
|
import { handleAIError } from "../utils/errorHandler";
|
|
12
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
13
|
+
import { LBAuthModal } from "./LBConnectButton";
|
|
12
14
|
|
|
13
15
|
export interface AiInputProps
|
|
14
16
|
extends
|
|
@@ -36,6 +38,7 @@ export function AiInput({
|
|
|
36
38
|
...inputProps
|
|
37
39
|
}: AiInputProps) {
|
|
38
40
|
const [isOpen, setIsOpen] = useState(false);
|
|
41
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
39
42
|
const [inputValue, setInputValue] = useState(
|
|
40
43
|
inputProps.value?.toString() || inputProps.defaultValue?.toString() || ""
|
|
41
44
|
);
|
|
@@ -43,6 +46,7 @@ export function AiInput({
|
|
|
43
46
|
const [isButtonHovered, setIsButtonHovered] = useState(false);
|
|
44
47
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
45
48
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
49
|
+
const { status: lbStatus } = useLB();
|
|
46
50
|
|
|
47
51
|
const { models } = useAiModels({
|
|
48
52
|
baseUrl,
|
|
@@ -52,8 +56,14 @@ export function AiInput({
|
|
|
52
56
|
const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
|
|
53
57
|
|
|
54
58
|
const hasConfiguration = Boolean(model && prompt);
|
|
59
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
60
|
+
const shouldShowSparkles = isAuthReady && !disabled;
|
|
55
61
|
|
|
56
62
|
const handleOpenPanel = () => {
|
|
63
|
+
if (!isAuthReady) {
|
|
64
|
+
setShowAuthModal(true);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
57
67
|
setIsOpen(true);
|
|
58
68
|
};
|
|
59
69
|
|
|
@@ -169,9 +179,15 @@ export function AiInput({
|
|
|
169
179
|
onClick={hasConfiguration ? handleQuickGenerate : handleOpenPanel}
|
|
170
180
|
onMouseEnter={() => setIsButtonHovered(true)}
|
|
171
181
|
onMouseLeave={() => setIsButtonHovered(false)}
|
|
172
|
-
disabled={disabled || loading}
|
|
182
|
+
disabled={disabled || loading || !isAuthReady}
|
|
173
183
|
type="button"
|
|
174
|
-
title={
|
|
184
|
+
title={
|
|
185
|
+
!isAuthReady
|
|
186
|
+
? "Authentication required"
|
|
187
|
+
: hasConfiguration
|
|
188
|
+
? "Generate with AI"
|
|
189
|
+
: "Setup AI"
|
|
190
|
+
}
|
|
175
191
|
>
|
|
176
192
|
{loading ? (
|
|
177
193
|
<svg
|
|
@@ -184,8 +200,20 @@ export function AiInput({
|
|
|
184
200
|
>
|
|
185
201
|
<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" />
|
|
186
202
|
</svg>
|
|
187
|
-
) : (
|
|
203
|
+
) : shouldShowSparkles ? (
|
|
188
204
|
<Sparkles size={16} />
|
|
205
|
+
) : (
|
|
206
|
+
<svg
|
|
207
|
+
width="16"
|
|
208
|
+
height="16"
|
|
209
|
+
viewBox="0 0 24 24"
|
|
210
|
+
fill="none"
|
|
211
|
+
stroke="currentColor"
|
|
212
|
+
strokeWidth="2"
|
|
213
|
+
>
|
|
214
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
215
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
216
|
+
</svg>
|
|
189
217
|
)}
|
|
190
218
|
</button>
|
|
191
219
|
{isOpen && (
|
|
@@ -202,6 +230,9 @@ export function AiInput({
|
|
|
202
230
|
enableModelManagement={enableModelManagement}
|
|
203
231
|
/>
|
|
204
232
|
)}
|
|
233
|
+
{showAuthModal && (
|
|
234
|
+
<LBAuthModal onClose={() => setShowAuthModal(false)} />
|
|
235
|
+
)}
|
|
205
236
|
{Boolean(toastData) && (
|
|
206
237
|
<UsageToast
|
|
207
238
|
key={toastKey}
|
|
@@ -14,6 +14,8 @@ import { AiPromptPanel } from "./AiPromptPanel";
|
|
|
14
14
|
import { UsageToast, useUsageToast } from "./UsageToast";
|
|
15
15
|
import { aiStyles } from "../styles/inline";
|
|
16
16
|
import { handleAIError } from "../utils/errorHandler";
|
|
17
|
+
import { useLB } from "../context/LBAuthProvider";
|
|
18
|
+
import { LBAuthModal } from "./LBConnectButton";
|
|
17
19
|
|
|
18
20
|
export interface AiTextareaProps
|
|
19
21
|
extends
|
|
@@ -40,6 +42,7 @@ export function AiTextarea({
|
|
|
40
42
|
...textareaProps
|
|
41
43
|
}: AiTextareaProps) {
|
|
42
44
|
const [isOpen, setIsOpen] = useState(false);
|
|
45
|
+
const [showAuthModal, setShowAuthModal] = useState(false);
|
|
43
46
|
const [textareaValue, setTextareaValue] = useState(
|
|
44
47
|
textareaProps.value?.toString() ||
|
|
45
48
|
textareaProps.defaultValue?.toString() ||
|
|
@@ -49,6 +52,7 @@ export function AiTextarea({
|
|
|
49
52
|
const [isButtonHovered, setIsButtonHovered] = useState(false);
|
|
50
53
|
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
|
51
54
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
55
|
+
const { status: lbStatus } = useLB();
|
|
52
56
|
|
|
53
57
|
const { models } = useAiModels({
|
|
54
58
|
baseUrl,
|
|
@@ -58,8 +62,14 @@ export function AiTextarea({
|
|
|
58
62
|
const { generateText, loading } = useAiCallText({ baseUrl, apiKeyId });
|
|
59
63
|
|
|
60
64
|
const hasConfiguration = Boolean(model && prompt);
|
|
65
|
+
const isAuthReady = lbStatus === "ready" || Boolean(process.env.LB_API_KEY);
|
|
66
|
+
const shouldShowSparkles = isAuthReady && !disabled;
|
|
61
67
|
|
|
62
68
|
const handleOpenPanel = () => {
|
|
69
|
+
if (!isAuthReady) {
|
|
70
|
+
setShowAuthModal(true);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
63
73
|
setIsOpen(true);
|
|
64
74
|
};
|
|
65
75
|
|
|
@@ -190,9 +200,15 @@ export function AiTextarea({
|
|
|
190
200
|
onClick={hasConfiguration ? handleQuickGenerate : handleOpenPanel}
|
|
191
201
|
onMouseEnter={() => setIsButtonHovered(true)}
|
|
192
202
|
onMouseLeave={() => setIsButtonHovered(false)}
|
|
193
|
-
disabled={disabled || loading}
|
|
203
|
+
disabled={disabled || loading || !isAuthReady}
|
|
194
204
|
type="button"
|
|
195
|
-
title={
|
|
205
|
+
title={
|
|
206
|
+
!isAuthReady
|
|
207
|
+
? "Authentication required"
|
|
208
|
+
: hasConfiguration
|
|
209
|
+
? "Generate with AI"
|
|
210
|
+
: "Setup AI"
|
|
211
|
+
}
|
|
196
212
|
>
|
|
197
213
|
{loading ? (
|
|
198
214
|
<svg
|
|
@@ -205,8 +221,20 @@ export function AiTextarea({
|
|
|
205
221
|
>
|
|
206
222
|
<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" />
|
|
207
223
|
</svg>
|
|
208
|
-
) : (
|
|
224
|
+
) : shouldShowSparkles ? (
|
|
209
225
|
<Sparkles size={16} />
|
|
226
|
+
) : (
|
|
227
|
+
<svg
|
|
228
|
+
width="16"
|
|
229
|
+
height="16"
|
|
230
|
+
viewBox="0 0 24 24"
|
|
231
|
+
fill="none"
|
|
232
|
+
stroke="currentColor"
|
|
233
|
+
strokeWidth="2"
|
|
234
|
+
>
|
|
235
|
+
<rect x="3" y="11" width="18" height="11" rx="2" ry="2" />
|
|
236
|
+
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
237
|
+
</svg>
|
|
210
238
|
)}
|
|
211
239
|
</button>
|
|
212
240
|
{isOpen && (
|
|
@@ -52,15 +52,17 @@ export function LBConnectButton({
|
|
|
52
52
|
onClick={handleClick}
|
|
53
53
|
className={
|
|
54
54
|
className ||
|
|
55
|
-
|
|
55
|
+
(status === "ready" && user
|
|
56
|
+
? "px-4 py-2 bg-gradient-to-r from-emerald-500 to-teal-600 text-white rounded-lg hover:from-emerald-600 hover:to-teal-700 transition-all duration-200 shadow-md hover:shadow-lg"
|
|
57
|
+
: "px-4 py-2 bg-gradient-to-r from-violet-500 to-purple-600 text-white rounded-lg hover:from-violet-600 hover:to-purple-700 transition-all duration-200 shadow-md hover:shadow-lg")
|
|
56
58
|
}
|
|
57
59
|
disabled={status === "loading"}
|
|
58
60
|
>
|
|
59
61
|
{status === "loading"
|
|
60
|
-
? "Chargement..."
|
|
62
|
+
? "⏳ Chargement..."
|
|
61
63
|
: status === "ready" && user
|
|
62
|
-
?
|
|
63
|
-
: label}
|
|
64
|
+
? `✓ ${user.email}`
|
|
65
|
+
: `🔐 ${label}`}
|
|
64
66
|
</button>
|
|
65
67
|
|
|
66
68
|
{showModal && <LBAuthModal onClose={handleModalClose} />}
|
|
@@ -129,99 +131,227 @@ function LBAuthModal({ onClose }: LBAuthModalProps) {
|
|
|
129
131
|
};
|
|
130
132
|
|
|
131
133
|
return (
|
|
132
|
-
<div className="fixed inset-0 bg-
|
|
133
|
-
<div className="bg-white dark:bg-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
? "Connexion LastBrain"
|
|
138
|
-
: "Sélectionner une clé API"}
|
|
139
|
-
</h2>
|
|
140
|
-
<button
|
|
141
|
-
onClick={() => onClose(false)}
|
|
142
|
-
className="text-gray-500 hover:text-gray-700"
|
|
143
|
-
>
|
|
144
|
-
✕
|
|
145
|
-
</button>
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
{step === "login" ? (
|
|
149
|
-
<form onSubmit={handleLogin} className="space-y-4">
|
|
150
|
-
<div>
|
|
151
|
-
<label className="block text-sm font-medium mb-1">Email</label>
|
|
152
|
-
<input
|
|
153
|
-
type="email"
|
|
154
|
-
value={email}
|
|
155
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
156
|
-
className="w-full px-3 py-2 border rounded dark:bg-gray-700"
|
|
157
|
-
required
|
|
158
|
-
autoFocus
|
|
159
|
-
/>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
134
|
+
<div className="fixed inset-0 bg-gradient-to-br from-slate-900/95 via-purple-900/95 to-slate-900/95 backdrop-blur-sm flex items-center justify-center z-50 p-4">
|
|
135
|
+
<div className="bg-white dark:bg-gradient-to-br dark:from-slate-800 dark:to-slate-900 rounded-2xl shadow-2xl max-w-md w-full border border-slate-200 dark:border-slate-700 overflow-hidden">
|
|
136
|
+
{/* Header avec gradient */}
|
|
137
|
+
<div className="bg-gradient-to-r from-violet-500 to-purple-600 p-6 text-white">
|
|
138
|
+
<div className="flex justify-between items-center">
|
|
162
139
|
<div>
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
</
|
|
166
|
-
<
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
required
|
|
172
|
-
/>
|
|
140
|
+
<h2 className="text-2xl font-bold flex items-center gap-2">
|
|
141
|
+
{step === "login" ? "🔐 Connexion" : "🔑 Sélection clé API"}
|
|
142
|
+
</h2>
|
|
143
|
+
<p className="text-violet-100 text-sm mt-1">
|
|
144
|
+
{step === "login"
|
|
145
|
+
? "Accédez à vos outils IA"
|
|
146
|
+
: "Créez une session sécurisée 72h"}
|
|
147
|
+
</p>
|
|
173
148
|
</div>
|
|
174
|
-
|
|
175
|
-
{error && <div className="text-red-600 text-sm">{error}</div>}
|
|
176
|
-
|
|
177
149
|
<button
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
className="w-full px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
|
|
150
|
+
onClick={() => onClose(false)}
|
|
151
|
+
className="text-white/80 hover:text-white hover:bg-white/20 rounded-lg p-2 transition-colors"
|
|
181
152
|
>
|
|
182
|
-
|
|
153
|
+
<svg
|
|
154
|
+
className="w-5 h-5"
|
|
155
|
+
fill="none"
|
|
156
|
+
stroke="currentColor"
|
|
157
|
+
viewBox="0 0 24 24"
|
|
158
|
+
>
|
|
159
|
+
<path
|
|
160
|
+
strokeLinecap="round"
|
|
161
|
+
strokeLinejoin="round"
|
|
162
|
+
strokeWidth={2}
|
|
163
|
+
d="M6 18L18 6M6 6l12 12"
|
|
164
|
+
/>
|
|
165
|
+
</svg>
|
|
183
166
|
</button>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
|
|
170
|
+
<div className="p-6">
|
|
171
|
+
{step === "login" ? (
|
|
172
|
+
<form onSubmit={handleLogin} className="space-y-4">
|
|
173
|
+
<div>
|
|
174
|
+
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">
|
|
175
|
+
📧 Email
|
|
176
|
+
</label>
|
|
177
|
+
<input
|
|
178
|
+
type="email"
|
|
179
|
+
value={email}
|
|
180
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
181
|
+
className="w-full px-4 py-3 border-2 border-slate-200 dark:border-slate-600 rounded-lg dark:bg-slate-800 dark:text-white focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900 transition-all outline-none"
|
|
182
|
+
placeholder="votre@email.com"
|
|
183
|
+
required
|
|
184
|
+
autoFocus
|
|
185
|
+
/>
|
|
186
|
+
</div>
|
|
184
187
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
<div>
|
|
189
|
+
<label className="block text-sm font-semibold mb-2 text-slate-700 dark:text-slate-300">
|
|
190
|
+
🔒 Mot de passe
|
|
191
|
+
</label>
|
|
192
|
+
<input
|
|
193
|
+
type="password"
|
|
194
|
+
value={password}
|
|
195
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
196
|
+
className="w-full px-4 py-3 border-2 border-slate-200 dark:border-slate-600 rounded-lg dark:bg-slate-800 dark:text-white focus:border-violet-500 focus:ring-2 focus:ring-violet-200 dark:focus:ring-violet-900 transition-all outline-none"
|
|
197
|
+
placeholder="••••••••"
|
|
198
|
+
required
|
|
199
|
+
/>
|
|
200
|
+
</div>
|
|
201
|
+
|
|
202
|
+
{error && (
|
|
203
|
+
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 p-4 rounded">
|
|
204
|
+
<div className="flex items-start gap-2">
|
|
205
|
+
<span className="text-red-500 text-xl">⚠️</span>
|
|
206
|
+
<p className="text-red-700 dark:text-red-400 text-sm font-medium">
|
|
207
|
+
{error}
|
|
208
|
+
</p>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
<button
|
|
214
|
+
type="submit"
|
|
215
|
+
disabled={loading}
|
|
216
|
+
className="w-full px-6 py-3 bg-gradient-to-r from-violet-500 to-purple-600 text-white font-semibold rounded-lg hover:from-violet-600 hover:to-purple-700 disabled:from-slate-400 disabled:to-slate-500 disabled:cursor-not-allowed transition-all duration-200 shadow-lg hover:shadow-xl transform hover:-translate-y-0.5"
|
|
192
217
|
>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
218
|
+
{loading ? (
|
|
219
|
+
<span className="flex items-center justify-center gap-2">
|
|
220
|
+
<svg
|
|
221
|
+
className="animate-spin h-5 w-5"
|
|
222
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
223
|
+
fill="none"
|
|
224
|
+
viewBox="0 0 24 24"
|
|
225
|
+
>
|
|
226
|
+
<circle
|
|
227
|
+
className="opacity-25"
|
|
228
|
+
cx="12"
|
|
229
|
+
cy="12"
|
|
230
|
+
r="10"
|
|
231
|
+
stroke="currentColor"
|
|
232
|
+
strokeWidth="4"
|
|
233
|
+
></circle>
|
|
234
|
+
<path
|
|
235
|
+
className="opacity-75"
|
|
236
|
+
fill="currentColor"
|
|
237
|
+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
238
|
+
></path>
|
|
239
|
+
</svg>
|
|
240
|
+
Connexion en cours...
|
|
241
|
+
</span>
|
|
242
|
+
) : (
|
|
243
|
+
"🚀 Se connecter"
|
|
244
|
+
)}
|
|
245
|
+
</button>
|
|
246
|
+
|
|
247
|
+
<div className="relative my-6">
|
|
248
|
+
<div className="absolute inset-0 flex items-center">
|
|
249
|
+
<div className="w-full border-t border-slate-200 dark:border-slate-700"></div>
|
|
250
|
+
</div>
|
|
251
|
+
<div className="relative flex justify-center text-sm">
|
|
252
|
+
<span className="px-4 bg-white dark:bg-slate-800 text-slate-500">
|
|
253
|
+
ou
|
|
254
|
+
</span>
|
|
255
|
+
</div>
|
|
256
|
+
</div>
|
|
257
|
+
|
|
258
|
+
<div className="bg-gradient-to-r from-violet-50 to-purple-50 dark:from-violet-900/20 dark:to-purple-900/20 rounded-lg p-4 border border-violet-200 dark:border-violet-800">
|
|
259
|
+
<p className="text-sm text-slate-700 dark:text-slate-300 text-center">
|
|
260
|
+
Pas encore de compte ?
|
|
261
|
+
</p>
|
|
262
|
+
<a
|
|
263
|
+
href="https://lastbrain.io/signup"
|
|
264
|
+
target="_blank"
|
|
265
|
+
rel="noopener noreferrer"
|
|
266
|
+
className="block mt-2 text-center text-violet-600 dark:text-violet-400 hover:text-violet-700 dark:hover:text-violet-300 font-semibold text-sm hover:underline"
|
|
210
267
|
>
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
268
|
+
✨ Créer un compte gratuitement
|
|
269
|
+
</a>
|
|
270
|
+
</div>
|
|
271
|
+
</form>
|
|
272
|
+
) : (
|
|
273
|
+
<div className="space-y-4">
|
|
274
|
+
<p className="text-sm text-slate-600 dark:text-slate-400 bg-blue-50 dark:bg-blue-900/20 p-3 rounded-lg border border-blue-200 dark:border-blue-800">
|
|
275
|
+
ℹ️ Sélectionnez une clé API pour créer une session sécurisée de
|
|
276
|
+
72h
|
|
277
|
+
</p>
|
|
278
|
+
|
|
279
|
+
<div className="space-y-3 max-h-96 overflow-y-auto">
|
|
280
|
+
{apiKeys.map((key) => (
|
|
281
|
+
<button
|
|
282
|
+
key={key.id}
|
|
283
|
+
onClick={() => handleSelectKey(key.id)}
|
|
284
|
+
disabled={!key.isActive || loading}
|
|
285
|
+
className={`w-full text-left px-5 py-4 border-2 rounded-lg transition-all transform hover:scale-[1.02] ${
|
|
286
|
+
key.isActive
|
|
287
|
+
? "border-slate-200 dark:border-slate-600 hover:border-violet-400 dark:hover:border-violet-500 hover:bg-gradient-to-r hover:from-violet-50 hover:to-purple-50 dark:hover:from-violet-900/20 dark:hover:to-purple-900/20"
|
|
288
|
+
: "border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50 opacity-50 cursor-not-allowed"
|
|
289
|
+
}`}
|
|
290
|
+
>
|
|
291
|
+
<div className="flex items-start justify-between gap-2">
|
|
292
|
+
<div className="flex-1">
|
|
293
|
+
<div className="font-semibold text-slate-900 dark:text-white flex items-center gap-2">
|
|
294
|
+
{key.isActive ? "🔑" : "🔒"}
|
|
295
|
+
{key.name}
|
|
296
|
+
</div>
|
|
297
|
+
<div className="text-sm text-slate-500 dark:text-slate-400 mt-1 font-mono">
|
|
298
|
+
{key.keyPrefix}...
|
|
299
|
+
</div>
|
|
300
|
+
{key.scopes && (
|
|
301
|
+
<div className="flex gap-1 mt-2 flex-wrap">
|
|
302
|
+
{key.scopes.slice(0, 3).map((scope: string) => (
|
|
303
|
+
<span
|
|
304
|
+
key={scope}
|
|
305
|
+
className="text-xs px-2 py-1 bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-300 rounded"
|
|
306
|
+
>
|
|
307
|
+
{scope}
|
|
308
|
+
</span>
|
|
309
|
+
))}
|
|
310
|
+
{key.scopes.length > 3 && (
|
|
311
|
+
<span className="text-xs px-2 py-1 bg-slate-100 dark:bg-slate-700 text-slate-600 dark:text-slate-400 rounded">
|
|
312
|
+
+{key.scopes.length - 3}
|
|
313
|
+
</span>
|
|
314
|
+
)}
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
{key.isActive ? (
|
|
319
|
+
<svg
|
|
320
|
+
className="w-5 h-5 text-violet-500 flex-shrink-0 mt-1"
|
|
321
|
+
fill="none"
|
|
322
|
+
stroke="currentColor"
|
|
323
|
+
viewBox="0 0 24 24"
|
|
324
|
+
>
|
|
325
|
+
<path
|
|
326
|
+
strokeLinecap="round"
|
|
327
|
+
strokeLinejoin="round"
|
|
328
|
+
strokeWidth={2}
|
|
329
|
+
d="M9 5l7 7-7 7"
|
|
330
|
+
/>
|
|
331
|
+
</svg>
|
|
332
|
+
) : (
|
|
333
|
+
<span className="text-xs text-red-600 dark:text-red-400 font-semibold bg-red-100 dark:bg-red-900/30 px-2 py-1 rounded">
|
|
334
|
+
Inactive
|
|
335
|
+
</span>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
</button>
|
|
339
|
+
))}
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
{error && (
|
|
343
|
+
<div className="bg-red-50 dark:bg-red-900/20 border-l-4 border-red-500 p-4 rounded">
|
|
344
|
+
<div className="flex items-start gap-2">
|
|
345
|
+
<span className="text-red-500 text-xl">⚠️</span>
|
|
346
|
+
<p className="text-red-700 dark:text-red-400 text-sm font-medium">
|
|
347
|
+
{error}
|
|
348
|
+
</p>
|
|
214
349
|
</div>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
)}
|
|
218
|
-
</button>
|
|
219
|
-
))}
|
|
350
|
+
</div>
|
|
351
|
+
)}
|
|
220
352
|
</div>
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
</div>
|
|
224
|
-
)}
|
|
353
|
+
)}
|
|
354
|
+
</div>
|
|
225
355
|
</div>
|
|
226
356
|
</div>
|
|
227
357
|
);
|
|
@@ -52,8 +52,8 @@ const LBContext = createContext<LBContextValue | undefined>(undefined);
|
|
|
52
52
|
|
|
53
53
|
export function LBProvider({
|
|
54
54
|
children,
|
|
55
|
-
baseUrl: _baseUrl = "/api/
|
|
56
|
-
proxyUrl = "/api/
|
|
55
|
+
baseUrl: _baseUrl = "/api/lastbrain",
|
|
56
|
+
proxyUrl = "/api/lastbrain",
|
|
57
57
|
onStatusChange,
|
|
58
58
|
}: LBProviderProps) {
|
|
59
59
|
const [state, setState] = useState<LBAuthState>({
|