@lastbrain/ai-ui-react 1.0.68 → 1.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/components/AiChipLabel.d.ts +8 -3
  2. package/dist/components/AiChipLabel.d.ts.map +1 -1
  3. package/dist/components/AiChipLabel.js +23 -70
  4. package/dist/components/AiContextButton.d.ts +10 -2
  5. package/dist/components/AiContextButton.d.ts.map +1 -1
  6. package/dist/components/AiContextButton.js +73 -291
  7. package/dist/components/AiImageButton.d.ts +5 -1
  8. package/dist/components/AiImageButton.d.ts.map +1 -1
  9. package/dist/components/AiImageButton.js +6 -142
  10. package/dist/components/AiInput.d.ts +5 -3
  11. package/dist/components/AiInput.d.ts.map +1 -1
  12. package/dist/components/AiInput.js +13 -25
  13. package/dist/components/AiPromptPanel.d.ts.map +1 -1
  14. package/dist/components/AiPromptPanel.js +64 -212
  15. package/dist/components/AiSelect.d.ts +5 -3
  16. package/dist/components/AiSelect.d.ts.map +1 -1
  17. package/dist/components/AiSelect.js +21 -30
  18. package/dist/components/AiStatusButton.d.ts +4 -1
  19. package/dist/components/AiStatusButton.d.ts.map +1 -1
  20. package/dist/components/AiStatusButton.js +211 -676
  21. package/dist/components/AiTextarea.d.ts +4 -2
  22. package/dist/components/AiTextarea.d.ts.map +1 -1
  23. package/dist/components/AiTextarea.js +14 -26
  24. package/dist/components/LBApiKeySelector.d.ts.map +1 -1
  25. package/dist/components/LBApiKeySelector.js +5 -166
  26. package/dist/components/LBConnectButton.d.ts +4 -7
  27. package/dist/components/LBConnectButton.d.ts.map +1 -1
  28. package/dist/components/LBConnectButton.js +17 -86
  29. package/dist/components/LBSigninModal.d.ts +1 -1
  30. package/dist/components/LBSigninModal.d.ts.map +1 -1
  31. package/dist/components/LBSigninModal.js +42 -320
  32. package/dist/context/LBAuthProvider.d.ts +35 -3
  33. package/dist/context/LBAuthProvider.d.ts.map +1 -1
  34. package/dist/context/LBAuthProvider.js +2 -0
  35. package/dist/examples/AiUiPremiumShowcase.d.ts +2 -0
  36. package/dist/examples/AiUiPremiumShowcase.d.ts.map +1 -0
  37. package/dist/examples/AiUiPremiumShowcase.js +15 -0
  38. package/dist/hooks/useAiModels.d.ts.map +1 -1
  39. package/dist/hooks/useModelManagement.d.ts.map +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +2 -0
  43. package/dist/styles/inline.d.ts +1 -0
  44. package/dist/styles/inline.d.ts.map +1 -1
  45. package/dist/styles/inline.js +25 -129
  46. package/dist/styles.css +1268 -369
  47. package/dist/types.d.ts +3 -0
  48. package/dist/types.d.ts.map +1 -1
  49. package/dist/utils/errorHandler.d.ts +2 -2
  50. package/dist/utils/errorHandler.d.ts.map +1 -1
  51. package/dist/utils/errorHandler.js +8 -1
  52. package/dist/utils/modelManagement.d.ts +13 -10
  53. package/dist/utils/modelManagement.d.ts.map +1 -1
  54. package/dist/utils/modelManagement.js +19 -2
  55. package/package.json +2 -2
  56. package/src/components/AiChipLabel.tsx +68 -101
  57. package/src/components/AiContextButton.tsx +142 -413
  58. package/src/components/AiImageButton.tsx +29 -190
  59. package/src/components/AiInput.tsx +49 -74
  60. package/src/components/AiPromptPanel.tsx +81 -260
  61. package/src/components/AiSelect.tsx +61 -69
  62. package/src/components/AiStatusButton.tsx +496 -1327
  63. package/src/components/AiTextarea.tsx +50 -63
  64. package/src/components/LBApiKeySelector.tsx +93 -271
  65. package/src/components/LBConnectButton.tsx +39 -336
  66. package/src/components/LBSigninModal.tsx +141 -472
  67. package/src/context/LBAuthProvider.tsx +45 -6
  68. package/src/examples/AiUiPremiumShowcase.tsx +94 -0
  69. package/src/hooks/useAiModels.ts +2 -1
  70. package/src/hooks/useModelManagement.ts +2 -1
  71. package/src/index.ts +3 -0
  72. package/src/styles/inline.ts +27 -148
  73. package/src/styles.css +1268 -369
  74. package/src/types.ts +3 -0
  75. package/src/utils/errorHandler.ts +16 -3
  76. package/src/utils/modelManagement.ts +53 -15
@@ -1,24 +1,26 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, type ButtonHTMLAttributes } from "react";
4
- import { Loader2, X, FileText, Sparkle, Download, Lock } from "lucide-react";
4
+ import { Download, FileText, Loader2, Lock, Sparkles, X } from "lucide-react";
5
5
  import type { BaseAiProps } from "../types";
6
+ import type { AiRadius, AiSize, AiVariant } from "../types";
6
7
  import { useAiCallText } from "../hooks/useAiCallText";
7
8
  import { AiPromptPanel } from "./AiPromptPanel";
8
9
  import { useUsageToast } from "./UsageToast";
9
10
  import { useErrorToast, ErrorToast } from "./ErrorToast";
10
- import { aiStyles } from "../styles/inline";
11
11
  import { useAiContext } from "../context/AiProvider";
12
12
  import { handleAIError } from "../utils/errorHandler";
13
13
  import { useLB } from "../context/LBAuthProvider";
14
14
  import { LBSigninModal } from "./LBSigninModal";
15
15
 
16
+ // Types pour les données de contexte
17
+ type ContextData = string | number | boolean | object | unknown[] | { [key: string]: unknown };
18
+
16
19
  export interface AiContextButtonProps
17
20
  extends
18
21
  Omit<BaseAiProps, "onValue" | "type">,
19
22
  Omit<ButtonHTMLAttributes<HTMLButtonElement>, "baseUrl" | "apiKeyId"> {
20
- // Données de contexte à analyser
21
- contextData: any;
23
+ contextData: ContextData;
22
24
  contextDescription?: string;
23
25
  onResult?: (
24
26
  result: string,
@@ -26,11 +28,13 @@ export interface AiContextButtonProps
26
28
  ) => void;
27
29
  uiMode?: "modal" | "drawer";
28
30
  resultModalTitle?: string;
29
- storeOutputs?: boolean; // For external API calls - whether to store outputs
30
- artifactTitle?: string; // Title for stored artifacts
31
- // Props optionnelles pour override du contexte
31
+ storeOutputs?: boolean;
32
+ artifactTitle?: string;
32
33
  baseUrl?: string;
33
34
  apiKeyId?: string;
35
+ size?: AiSize;
36
+ radius?: AiRadius;
37
+ variant?: AiVariant;
34
38
  }
35
39
 
36
40
  export function AiContextButton({
@@ -45,11 +49,10 @@ export function AiContextButton({
45
49
  className,
46
50
  children,
47
51
  resultModalTitle = "Résultat de l'analyse",
48
- storeOutputs,
49
- artifactTitle,
52
+ size = "md",
53
+ radius = "full",
54
+ variant = "default",
50
55
  context: _context,
51
- model: _model,
52
- prompt: _prompt,
53
56
  ...buttonProps
54
57
  }: AiContextButtonProps) {
55
58
  const [isOpen, setIsOpen] = useState(false);
@@ -62,20 +65,18 @@ export function AiContextButton({
62
65
  tokens: number;
63
66
  cost: number;
64
67
  } | null>(null);
65
- const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
68
+
69
+ const { showUsageToast } = useUsageToast();
66
70
  const { showErrorToast, errorData, errorKey, clearError } = useErrorToast();
67
71
 
68
- // Rendre l'authentification optionnelle
69
72
  let lbStatus: string | undefined;
70
73
  try {
71
74
  const lbContext = useLB();
72
75
  lbStatus = lbContext.status;
73
76
  } catch {
74
- // LBProvider n'est pas disponible, ignorer
75
77
  lbStatus = undefined;
76
78
  }
77
79
 
78
- // Récupérer le contexte AiProvider avec fallback sur les props
79
80
  const aiContext = useAiContext();
80
81
  const baseUrl = propBaseUrl ?? aiContext.baseUrl;
81
82
  const apiKeyId = propApiKeyId ?? aiContext.apiKeyId;
@@ -95,80 +96,10 @@ export function AiContextButton({
95
96
  setIsOpen(true);
96
97
  };
97
98
 
98
- const handleClosePanel = () => {
99
- setIsOpen(false);
100
- };
101
-
102
- const handleCloseResult = () => {
103
- setIsResultOpen(false);
104
- setAnalysisResult(null);
105
- };
106
-
107
- const saveToFile = () => {
108
- if (!analysisResult) return;
109
-
110
- const currentDate = new Date()
111
- .toLocaleDateString("fr-FR")
112
- .replace(/\//g, "-");
113
- const defaultName = `analyse-${currentDate}.txt`;
114
- const fileName = prompt("Nom du fichier :", defaultName) || defaultName;
115
-
116
- const content = `ANALYSE DES DONNÉES - ${new Date().toLocaleString("fr-FR")}
117
-
118
- PROMPT UTILISÉ :
119
- ${analysisResult.prompt}
120
-
121
- RÉSULTAT DE L'ANALYSE :
122
- ${analysisResult.content}
123
-
124
- --- MÉTADONNÉES ---
125
- Tokens utilisés: ${analysisResult.tokens.toLocaleString()}
126
- Coût: $${(apiKeyId?.includes("dev") ? 0 : analysisResult.cost).toFixed(6)}
127
- ID de requête: ${analysisResult.requestId || "N/A"}
128
- Date: ${new Date().toLocaleString("fr-FR")}`;
129
-
130
- const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
131
- const url = URL.createObjectURL(blob);
132
- const a = document.createElement("a");
133
- a.href = url;
134
- a.download = fileName.endsWith(".txt") ? fileName : `${fileName}.txt`;
135
- document.body.appendChild(a);
136
- a.click();
137
- document.body.removeChild(a);
138
- URL.revokeObjectURL(url);
139
- };
140
-
141
- // Styles selon le thème
142
- const getThemeStyles = () => {
143
- const isDark =
144
- typeof document !== "undefined" &&
145
- (document.documentElement.classList.contains("dark") ||
146
- (!document.documentElement.classList.contains("light") &&
147
- window.matchMedia("(prefers-color-scheme: dark)").matches));
148
-
149
- return {
150
- modal: {
151
- backgroundColor: isDark ? "#1f2937" : "white",
152
- border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
153
- color: isDark ? "#f3f4f6" : "#374151",
154
- },
155
- header: {
156
- color: isDark ? "#f9fafb" : "#1f2937",
157
- borderBottom: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
158
- },
159
- content: {
160
- backgroundColor: isDark ? "#111827" : "#f9fafb",
161
- border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
162
- },
163
- closeButton: {
164
- color: isDark ? "#9ca3af" : "#6b7280",
165
- hoverColor: isDark ? "#d1d5db" : "#374151",
166
- },
167
- };
168
- };
169
-
170
- const formatContextData = (data: any): string => {
171
- if (typeof data === "string") return data;
99
+ const formatContextData = (data: ContextData): string => {
100
+ if (typeof data === "string") {
101
+ return data;
102
+ }
172
103
  if (typeof data === "object" && data !== null) {
173
104
  return JSON.stringify(data, null, 2);
174
105
  }
@@ -180,14 +111,8 @@ Date: ${new Date().toLocaleString("fr-FR")}`;
180
111
  selectedPrompt: string
181
112
  ) => {
182
113
  try {
183
- // Construire le prompt avec le contexte
184
114
  const contextString = formatContextData(contextData);
185
- const fullPrompt = `${selectedPrompt}
186
-
187
- CONTEXTE (${contextDescription}):
188
- ${contextString}
189
-
190
- Analyse ces données et réponds de manière structurée et claire.`;
115
+ const fullPrompt = `${selectedPrompt}\n\nCONTEXTE (${contextDescription}):\n${contextString}\n\nAnalyse ces données et réponds de manière structurée et claire.`;
191
116
 
192
117
  const result = await callText({
193
118
  prompt: fullPrompt,
@@ -197,48 +122,46 @@ Analyse ces données et réponds de manière structurée et claire.`;
197
122
  temperature: 0.7,
198
123
  });
199
124
 
200
- if (result.text) {
201
- // Calculer le total des tokens depuis la réponse réelle
202
- const resultAny = result as any;
203
- const totalTokens =
204
- (resultAny.inputTokens || 0) + (resultAny.outputTokens || 0) ||
205
- result.debitTokens ||
206
- 0;
207
- const actualCost = resultAny.cost || 0;
208
-
209
- const resultData = {
210
- content: result.text,
211
- prompt: selectedPrompt,
212
- requestId: result.requestId,
213
- tokens: totalTokens,
214
- cost: actualCost,
215
- };
125
+ if (!result.text) {
126
+ return;
127
+ }
216
128
 
217
- setAnalysisResult(resultData);
218
- setIsResultOpen(true);
129
+ const resultAny = result as any;
130
+ const totalTokens =
131
+ (resultAny.inputTokens || 0) + (resultAny.outputTokens || 0) ||
132
+ result.debitTokens ||
133
+ 0;
134
+ const actualCost = resultAny.cost || 0;
135
+
136
+ setAnalysisResult({
137
+ content: result.text,
138
+ prompt: selectedPrompt,
139
+ requestId: result.requestId,
140
+ tokens: totalTokens,
141
+ cost: actualCost,
142
+ });
143
+ setIsResultOpen(true);
219
144
 
220
- onResult?.(result.text, {
221
- requestId: result.requestId,
222
- tokens: result.debitTokens || 0,
223
- });
145
+ onResult?.(result.text, {
146
+ requestId: result.requestId,
147
+ tokens: result.debitTokens || 0,
148
+ });
224
149
 
225
- onToast?.({
226
- type: "success",
227
- message: `Analyse terminée - Coût: $${(apiKeyId?.includes("dev") ? 0 : actualCost).toFixed(6)}`,
228
- });
150
+ onToast?.({
151
+ type: "success",
152
+ message: `Analyse terminée - Coût: $${(apiKeyId?.includes("dev") ? 0 : actualCost).toFixed(6)}`,
153
+ });
229
154
 
230
- // Afficher le toast de coût
231
- showUsageToast({
232
- requestId: result.requestId,
233
- debitTokens: totalTokens,
234
- usage: {
235
- total_tokens: totalTokens,
236
- prompt_tokens: resultAny.inputTokens || 0,
237
- completion_tokens: resultAny.outputTokens || 0,
238
- },
239
- cost: apiKeyId?.includes("dev") ? 0 : actualCost,
240
- });
241
- }
155
+ showUsageToast({
156
+ requestId: result.requestId,
157
+ debitTokens: totalTokens,
158
+ usage: {
159
+ total_tokens: totalTokens,
160
+ prompt_tokens: resultAny.inputTokens || 0,
161
+ completion_tokens: resultAny.outputTokens || 0,
162
+ },
163
+ cost: apiKeyId?.includes("dev") ? 0 : actualCost,
164
+ });
242
165
  } catch (error) {
243
166
  console.error("AiContextButton error:", error);
244
167
  handleAIError(error, onToast, showErrorToast);
@@ -247,352 +170,158 @@ Analyse ces données et réponds de manière structurée et claire.`;
247
170
  }
248
171
  };
249
172
 
173
+ const saveToFile = () => {
174
+ if (!analysisResult) {
175
+ return;
176
+ }
177
+
178
+ const currentDate = new Date()
179
+ .toLocaleDateString("fr-FR")
180
+ .replace(/\//g, "-");
181
+ const defaultName = `analyse-${currentDate}.txt`;
182
+ const fileName = prompt("Nom du fichier :", defaultName) || defaultName;
183
+
184
+ const content = `ANALYSE DES DONNÉES - ${new Date().toLocaleString("fr-FR")}\n\nPROMPT UTILISÉ :\n${analysisResult.prompt}\n\nRÉSULTAT DE L'ANALYSE :\n${analysisResult.content}\n\n--- MÉTADONNÉES ---\nTokens utilisés: ${analysisResult.tokens.toLocaleString()}\nCoût: $${(apiKeyId?.includes(
185
+ "dev"
186
+ )
187
+ ? 0
188
+ : analysisResult.cost
189
+ ).toFixed(6)}\nID de requête: ${analysisResult.requestId || "N/A"}`;
190
+
191
+ const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
192
+ const url = URL.createObjectURL(blob);
193
+ const anchor = document.createElement("a");
194
+ anchor.href = url;
195
+ anchor.download = fileName.endsWith(".txt") ? fileName : `${fileName}.txt`;
196
+ document.body.appendChild(anchor);
197
+ anchor.click();
198
+ document.body.removeChild(anchor);
199
+ URL.revokeObjectURL(url);
200
+ };
201
+
202
+ const sizeClass = `ai-size-${size}`;
203
+ const radiusClass = `ai-radius-${radius}`;
204
+ const variantClass = variant === "light" ? "ai-btn--light" : "";
205
+
250
206
  return (
251
207
  <>
252
- <div style={{ position: "relative", display: "inline-block" }}>
208
+ <div className="relative inline-block ai-glow">
253
209
  <button
254
210
  {...buttonProps}
255
211
  onClick={handleOpenPanel}
256
212
  disabled={disabled || loading || !isAuthReady}
257
- className={className}
258
- style={{
259
- ...aiStyles.button,
260
- display: "flex",
261
- alignItems: "center",
262
- gap: "8px",
263
- cursor:
264
- disabled || loading || !isAuthReady ? "not-allowed" : "pointer",
265
- opacity: disabled || loading || !isAuthReady ? 0.6 : 1,
266
- backgroundColor: loading
267
- ? "#8b5cf6"
268
- : !isAuthReady
269
- ? "#94a3b8"
270
- : "#7c3aed",
271
- color: "white",
272
- border: "none",
273
- borderRadius: "12px",
274
- // padding: "12px 20px",
275
-
276
- fontSize: "14px",
277
- fontWeight: "600",
278
- minWidth: "20px",
279
- height: "44px",
280
- transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
281
- boxShadow: loading
282
- ? "0 4px 12px rgba(139, 92, 246, 0.3)"
283
- : "0 2px 8px rgba(124, 58, 237, 0.2)",
284
- transform: "scale(1)",
285
- ...(loading && {
286
- background: "linear-gradient(135deg, #7c3aed, #8b5cf6)",
287
- animation: "pulse 2s ease-in-out infinite",
288
- }),
289
- ...buttonProps.style,
290
- }}
291
- onMouseEnter={(e) => {
292
- if (!disabled && !loading && isAuthReady) {
293
- e.currentTarget.style.transform = "scale(1.02)";
294
- e.currentTarget.style.boxShadow =
295
- "0 6px 16px rgba(124, 58, 237, 0.3)";
296
- }
297
- }}
298
- onMouseLeave={(e) => {
299
- if (!disabled && !loading && isAuthReady) {
300
- e.currentTarget.style.transform = "scale(1)";
301
- e.currentTarget.style.boxShadow = loading
302
- ? "0 4px 12px rgba(139, 92, 246, 0.3)"
303
- : "0 2px 8px rgba(124, 58, 237, 0.2)";
304
- }
305
- }}
306
- onMouseDown={(e) => {
307
- if (!disabled && !loading && isAuthReady) {
308
- e.currentTarget.style.transform = "scale(0.98)";
309
- }
310
- }}
311
- onMouseUp={(e) => {
312
- if (!disabled && !loading && isAuthReady) {
313
- e.currentTarget.style.transform = "scale(1.02)";
314
- }
315
- }}
316
- data-ai-context-button
213
+ className={`ai-btn ai-context-btn ${variantClass} ${sizeClass} ${radiusClass} ${className || ""}`}
317
214
  title={
318
215
  !isAuthReady ? "Authentication required" : "Analyser avec l'IA"
319
216
  }
320
217
  >
321
218
  {loading ? (
322
219
  <>
323
- <Loader2
324
- size={18}
325
- className="animate-spin"
326
- style={{
327
- color: "white",
328
- filter: "drop-shadow(0 0 2px rgba(255,255,255,0.3))",
329
- }}
330
- />
331
- <span style={{ letterSpacing: "0.025em" }}>Analyse...</span>
220
+ <Loader2 size={16} className="ai-spinner" />
221
+ <span>Analyse...</span>
332
222
  </>
333
223
  ) : !isAuthReady ? (
334
224
  <>
335
- <Lock
336
- size={18}
337
- style={{
338
- color: "white",
339
- filter: "drop-shadow(0 0 2px rgba(255,255,255,0.2))",
340
- }}
341
- />
225
+ <Lock size={16} />
342
226
  {children || <span>Connexion requise</span>}
343
227
  </>
344
228
  ) : (
345
229
  <>
346
- <Sparkle
347
- size={18}
348
- style={{
349
- color: "white",
350
- filter: "drop-shadow(0 0 2px rgba(255,255,255,0.2))",
351
- }}
352
- />
353
- {/* <span style={{ letterSpacing: "0.025em" }}>{children || ""}</span> */}
230
+ <Sparkles size={16} />
231
+ {children || <span>Analyser</span>}
354
232
  </>
355
233
  )}
356
234
  </button>
357
235
 
358
- {isOpen && (
236
+ {isOpen ? (
359
237
  <AiPromptPanel
360
238
  isOpen={isOpen}
361
- onClose={handleClosePanel}
239
+ onClose={() => setIsOpen(false)}
362
240
  onSubmit={handleSubmit}
363
241
  uiMode={uiMode}
364
242
  models={[]}
365
- enableModelManagement={true}
243
+ enableModelManagement
366
244
  modelCategory="text"
367
245
  baseUrl={baseUrl}
368
246
  apiKey={apiKeyId}
369
247
  />
370
- )}
248
+ ) : null}
371
249
  </div>
372
250
 
373
- {/* Modal de résultat */}
374
- {isResultOpen && analysisResult && (
251
+ {isResultOpen && analysisResult ? (
375
252
  <div
376
- style={{
377
- position: "fixed",
378
- top: 0,
379
- left: 0,
380
- right: 0,
381
- bottom: 0,
382
- backgroundColor: "rgba(0, 0, 0, 0.5)",
383
- backdropFilter: "blur(8px)",
384
- display: "flex",
385
- alignItems: "center",
386
- justifyContent: "center",
387
- zIndex: 1000,
388
- padding: "20px",
389
- }}
253
+ className="ai-signin-overlay ai-overlay-panel"
390
254
  onClick={(e) => {
391
255
  if (e.target === e.currentTarget) {
392
- handleCloseResult();
256
+ setIsResultOpen(false);
257
+ setAnalysisResult(null);
393
258
  }
394
259
  }}
395
260
  >
396
261
  <div
397
- style={{
398
- maxWidth: "800px",
399
- position: "relative",
400
- width: "100%",
401
- maxHeight: "90vh",
402
- borderRadius: "16px",
403
- boxShadow: "0 20px 25px -5px rgba(0, 0, 0, 0.1)",
404
- overflow: "hidden",
405
- ...getThemeStyles().modal,
406
- }}
262
+ className="ai-popover ai-result-modal"
263
+ onClick={(e) => e.stopPropagation()}
407
264
  >
408
- {/* Header */}
409
- <div
410
- style={{
411
- padding: "20px 24px 16px",
412
- marginBottom: "12px",
413
- display: "flex",
414
- alignItems: "center",
415
- gap: "12px",
416
- flexDirection: "column",
417
- justifyContent: "space-between",
418
- ...getThemeStyles().header,
419
- }}
420
- >
421
- <div
422
- style={{
423
- display: "flex",
424
-
425
- alignItems: "center",
426
- gap: "12px",
427
- }}
428
- >
429
- <FileText size={20} />
430
- <h2
431
- style={{
432
- fontSize: "18px",
433
- fontWeight: "600",
434
- margin: 0,
435
- }}
436
- >
437
- {resultModalTitle}
438
- </h2>
265
+ <div className="ai-result-header">
266
+ <div className="ai-row">
267
+ <FileText size={18} />
268
+ <h2 className="ai-result-title">{resultModalTitle}</h2>
439
269
  </div>
440
- <div style={{ display: "flex", gap: "8px" }}>
270
+ <div className="ai-row">
441
271
  <button
272
+ type="button"
273
+ className={`ai-btn ai-btn--ghost ai-btn--compact ${sizeClass} ${radiusClass}`}
442
274
  onClick={saveToFile}
443
- style={{
444
- padding: "8px 12px",
445
- borderRadius: "6px",
446
- backgroundColor: "transparent",
447
- border: `1px solid ${getThemeStyles().closeButton.color}30`,
448
- cursor: "pointer",
449
- color: getThemeStyles().closeButton.color,
450
- transition: "all 0.2s",
451
- display: "flex",
452
- alignItems: "center",
453
- gap: "6px",
454
- fontSize: "12px",
455
- }}
456
- onMouseEnter={(e) => {
457
- e.currentTarget.style.backgroundColor =
458
- getThemeStyles().closeButton.hoverColor + "10";
459
- e.currentTarget.style.color =
460
- getThemeStyles().closeButton.hoverColor;
461
- }}
462
- onMouseLeave={(e) => {
463
- e.currentTarget.style.backgroundColor = "transparent";
464
- e.currentTarget.style.color =
465
- getThemeStyles().closeButton.color;
466
- }}
467
- title="Télécharger l'analyse"
468
275
  >
469
276
  <Download size={14} />
470
277
  Sauvegarder
471
278
  </button>
472
279
  <button
473
- onClick={handleCloseResult}
474
- style={{
475
- position: "absolute",
476
- top: "16px",
477
- right: "16px",
478
- padding: "4px",
479
- borderRadius: "6px",
480
- backgroundColor: "transparent",
481
- border: "none",
482
- cursor: "pointer",
483
- color: getThemeStyles().closeButton.color,
484
- transition: "color 0.2s",
485
- }}
486
- onMouseEnter={(e) => {
487
- e.currentTarget.style.color =
488
- getThemeStyles().closeButton.hoverColor;
489
- }}
490
- onMouseLeave={(e) => {
491
- e.currentTarget.style.color =
492
- getThemeStyles().closeButton.color;
280
+ type="button"
281
+ className="ai-icon-btn"
282
+ onClick={() => {
283
+ setIsResultOpen(false);
284
+ setAnalysisResult(null);
493
285
  }}
286
+ aria-label="Fermer"
494
287
  >
495
- <X size={18} />
288
+ <X size={16} />
496
289
  </button>
497
290
  </div>
291
+ </div>
498
292
 
499
- {/* Content */}
500
- <div
501
- style={{
502
- padding: "0 24px 24px",
503
- overflow: "auto",
504
- maxHeight: "calc(90vh - 80px)",
505
- }}
506
- >
507
- {/* Prompt utilisé */}
508
- <div style={{ marginBottom: "20px" }}>
509
- <h3
510
- style={{
511
- fontSize: "14px",
512
- fontWeight: "600",
513
- marginBottom: "8px",
514
- color: getThemeStyles().modal.color,
515
- }}
516
- >
517
- Prompt utilisé :
518
- </h3>
519
- <div
520
- style={{
521
- padding: "12px",
522
- borderRadius: "8px",
523
- fontSize: "13px",
524
- fontFamily: "monospace",
525
- ...getThemeStyles().content,
526
- }}
527
- >
528
- {analysisResult.prompt}
529
- </div>
530
- </div>
293
+ <div className="ai-result-body">
294
+ <div className="ai-result-block">
295
+ <h3 className="ai-result-subtitle">Prompt utilisé</h3>
296
+ <pre className="ai-result-code">{analysisResult.prompt}</pre>
297
+ </div>
531
298
 
532
- {/* Résultat */}
533
- <div>
534
- <h3
535
- style={{
536
- fontSize: "14px",
537
- fontWeight: "600",
538
- marginBottom: "12px",
539
- color: getThemeStyles().modal.color,
540
- }}
541
- >
542
- Résultat de l'analyse :
543
- </h3>
544
- <div
545
- style={{
546
- padding: "16px",
547
- borderRadius: "8px",
548
- lineHeight: "1.6",
549
- fontSize: "14px",
550
- whiteSpace: "pre-wrap",
551
- ...getThemeStyles().content,
552
- }}
553
- >
554
- {analysisResult.content}
555
- </div>
299
+ <div className="ai-result-block">
300
+ <h3 className="ai-result-subtitle">Résultat</h3>
301
+ <div className="ai-result-content">
302
+ {analysisResult.content}
556
303
  </div>
304
+ </div>
557
305
 
558
- {/* Metadata */}
559
- <div style={{ height: "120px" }}>
560
- <div
561
- style={{
562
- marginTop: "20px",
563
- padding: "12px",
564
- fontSize: "12px",
565
- borderRadius: "8px",
566
- display: "flex",
567
-
568
- justifyContent: "space-between",
569
- ...getThemeStyles().content,
570
- }}
571
- >
572
- <span>
573
- Coût: $
574
- {(apiKeyId?.includes("dev")
575
- ? 0
576
- : analysisResult.cost
577
- ).toFixed(6)}
578
- </span>
579
- <span>
580
- ID: {analysisResult.requestId?.slice(-8) || "N/A"}
581
- </span>
582
- </div>
583
- </div>
306
+ <div className="ai-result-meta ai-between">
307
+ <span>
308
+ Coût: $
309
+ {(apiKeyId?.includes("dev")
310
+ ? 0
311
+ : analysisResult.cost
312
+ ).toFixed(6)}
313
+ </span>
314
+ <span>ID: {analysisResult.requestId?.slice(-8) || "N/A"}</span>
584
315
  </div>
585
316
  </div>
586
317
  </div>
587
318
  </div>
588
- )}
319
+ ) : null}
589
320
 
590
321
  <LBSigninModal
591
322
  isOpen={showAuthModal}
592
323
  onClose={() => setShowAuthModal(false)}
593
324
  />
594
-
595
- {/* Error Toast */}
596
325
  <ErrorToast key={errorKey} error={errorData} onComplete={clearError} />
597
326
  </>
598
327
  );