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