@lastbrain/ai-ui-react 1.0.25 → 1.0.26
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 +339 -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/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 +553 -0
- package/src/components/AiImageButton.tsx +386 -38
- 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
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from "react";
|
|
4
|
+
import { ImageIcon, Loader2, Download, Copy, ExternalLink, X, } from "lucide-react";
|
|
4
5
|
import { useAiCallImage } from "../hooks/useAiCallImage";
|
|
5
6
|
import { useAiModels } from "../hooks/useAiModels";
|
|
6
7
|
import { AiPromptPanel } from "./AiPromptPanel";
|
|
7
|
-
import {
|
|
8
|
-
|
|
8
|
+
import { useUsageToast } from "./UsageToast";
|
|
9
|
+
import { aiStyles } from "../styles/inline";
|
|
10
|
+
import { useAiContext } from "../context/AiProvider";
|
|
11
|
+
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 }) {
|
|
9
12
|
const [isOpen, setIsOpen] = useState(false);
|
|
13
|
+
const [generatedImage, setGeneratedImage] = useState(null);
|
|
10
14
|
const { showUsageToast, toastData, toastKey, clearToast } = useUsageToast();
|
|
15
|
+
// Récupérer le contexte AiProvider avec fallback sur les props
|
|
16
|
+
const aiContext = useAiContext();
|
|
17
|
+
const baseUrl = propBaseUrl ?? aiContext.baseUrl;
|
|
18
|
+
const apiKeyId = propApiKeyId ?? aiContext.apiKeyId;
|
|
11
19
|
const { models } = useAiModels({ baseUrl, apiKeyId });
|
|
12
20
|
const { generateImage, loading } = useAiCallImage({ baseUrl, apiKeyId });
|
|
13
21
|
const handleOpenPanel = () => {
|
|
@@ -16,24 +24,226 @@ export function AiImageButton({ baseUrl, apiKeyId, uiMode = "modal", context, mo
|
|
|
16
24
|
const handleClosePanel = () => {
|
|
17
25
|
setIsOpen(false);
|
|
18
26
|
};
|
|
27
|
+
// Actions sur l'image
|
|
28
|
+
const handleDownload = () => {
|
|
29
|
+
if (!generatedImage)
|
|
30
|
+
return;
|
|
31
|
+
const link = document.createElement("a");
|
|
32
|
+
link.href = generatedImage.url;
|
|
33
|
+
link.download = `ai-image-${generatedImage.requestId}.png`;
|
|
34
|
+
link.click();
|
|
35
|
+
};
|
|
36
|
+
const handleCopyUrl = async () => {
|
|
37
|
+
if (!generatedImage)
|
|
38
|
+
return;
|
|
39
|
+
try {
|
|
40
|
+
await navigator.clipboard.writeText(generatedImage.url);
|
|
41
|
+
onToast?.({
|
|
42
|
+
type: "success",
|
|
43
|
+
message: "URL copiée dans le presse-papier",
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (_error) {
|
|
47
|
+
onToast?.({ type: "error", message: "Erreur lors de la copie" });
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
const handleSave = async () => {
|
|
51
|
+
if (!generatedImage || !onImageSave)
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
await onImageSave(generatedImage.url);
|
|
55
|
+
onToast?.({ type: "success", message: "Image sauvegardée" });
|
|
56
|
+
}
|
|
57
|
+
catch (_error) {
|
|
58
|
+
onToast?.({ type: "error", message: "Erreur lors de la sauvegarde" });
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const handleCloseImage = () => {
|
|
62
|
+
setGeneratedImage(null);
|
|
63
|
+
};
|
|
64
|
+
// Styles selon le thème
|
|
65
|
+
const getThemeStyles = () => {
|
|
66
|
+
// Détection automatique du thème comme dans les autres composants
|
|
67
|
+
const isDark = typeof document !== "undefined" &&
|
|
68
|
+
(document.documentElement.classList.contains("dark") ||
|
|
69
|
+
(!document.documentElement.classList.contains("light") &&
|
|
70
|
+
window.matchMedia("(prefers-color-scheme: dark)").matches));
|
|
71
|
+
return {
|
|
72
|
+
card: {
|
|
73
|
+
backgroundColor: isDark ? "#1f2937" : "white",
|
|
74
|
+
border: `1px solid ${isDark ? "#374151" : "#e5e7eb"}`,
|
|
75
|
+
color: isDark ? "#f3f4f6" : "#374151",
|
|
76
|
+
},
|
|
77
|
+
header: {
|
|
78
|
+
color: isDark ? "#f9fafb" : "#1f2937",
|
|
79
|
+
},
|
|
80
|
+
closeButton: {
|
|
81
|
+
color: isDark ? "#9ca3af" : "#6b7280",
|
|
82
|
+
hoverColor: isDark ? "#d1d5db" : "#374151",
|
|
83
|
+
},
|
|
84
|
+
imageContainer: {
|
|
85
|
+
backgroundColor: isDark ? "#111827" : "#f9fafb",
|
|
86
|
+
},
|
|
87
|
+
actionButton: {
|
|
88
|
+
backgroundColor: isDark ? "#374151" : "white",
|
|
89
|
+
border: `1px solid ${isDark ? "#4b5563" : "#e5e7eb"}`,
|
|
90
|
+
color: isDark ? "#d1d5db" : "#6b7280",
|
|
91
|
+
hoverBackground: isDark ? "#4b5563" : "#f3f4f6",
|
|
92
|
+
hoverColor: isDark ? "#f3f4f6" : "#1f2937",
|
|
93
|
+
},
|
|
94
|
+
metadata: {
|
|
95
|
+
borderTop: `1px solid ${isDark ? "#374151" : "#f3f4f6"}`,
|
|
96
|
+
color: isDark ? "#9ca3af" : "#6b7280",
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
};
|
|
19
100
|
const handleSubmit = async (selectedModel, selectedPrompt) => {
|
|
20
101
|
try {
|
|
21
102
|
const result = await generateImage({
|
|
22
103
|
model: selectedModel,
|
|
23
104
|
prompt: selectedPrompt,
|
|
105
|
+
size: "1024x1024", // Taille par défaut
|
|
24
106
|
});
|
|
25
107
|
if (result.url) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
108
|
+
// Stocker l'image générée
|
|
109
|
+
const imageData = {
|
|
110
|
+
url: result.url,
|
|
111
|
+
prompt: selectedPrompt,
|
|
112
|
+
requestId: result.requestId,
|
|
113
|
+
tokens: result.debitTokens,
|
|
114
|
+
};
|
|
115
|
+
setGeneratedImage(imageData);
|
|
116
|
+
onImage?.(result.url, {
|
|
117
|
+
requestId: result.requestId,
|
|
118
|
+
tokens: result.debitTokens,
|
|
119
|
+
});
|
|
120
|
+
onToast?.({ type: "success", message: "Image générée avec succès" });
|
|
121
|
+
// Afficher le toast de coût même en mode dev
|
|
122
|
+
showUsageToast({
|
|
123
|
+
requestId: result.requestId,
|
|
124
|
+
debitTokens: result.debitTokens,
|
|
125
|
+
usage: {
|
|
126
|
+
total_tokens: result.debitTokens,
|
|
127
|
+
prompt_tokens: Math.floor(result.debitTokens * 0.8),
|
|
128
|
+
completion_tokens: Math.floor(result.debitTokens * 0.2),
|
|
129
|
+
},
|
|
130
|
+
cost: apiKeyId?.includes("dev") ? 0 : result.debitTokens * 0.002, // Coût simulé
|
|
131
|
+
});
|
|
29
132
|
}
|
|
30
133
|
}
|
|
31
134
|
catch (error) {
|
|
32
|
-
onToast?.({
|
|
135
|
+
onToast?.({
|
|
136
|
+
type: "error",
|
|
137
|
+
message: "Erreur lors de la génération de l'image",
|
|
138
|
+
code: error instanceof Error ? error.message : undefined,
|
|
139
|
+
});
|
|
33
140
|
}
|
|
34
141
|
finally {
|
|
35
142
|
setIsOpen(false);
|
|
36
143
|
}
|
|
37
144
|
};
|
|
38
|
-
return (_jsxs("div", { style: { position: "relative", display: "inline-block" }, children: [_jsx("button", { ...buttonProps, onClick: handleOpenPanel, disabled: disabled || loading, className: className,
|
|
145
|
+
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
|
+
...aiStyles.button,
|
|
147
|
+
display: "flex",
|
|
148
|
+
alignItems: "center",
|
|
149
|
+
gap: "8px",
|
|
150
|
+
cursor: disabled || loading ? "not-allowed" : "pointer",
|
|
151
|
+
opacity: disabled || loading ? 0.6 : 1,
|
|
152
|
+
backgroundColor: loading ? "#8b5cf6" : "#6366f1",
|
|
153
|
+
color: "white",
|
|
154
|
+
border: "none",
|
|
155
|
+
borderRadius: "12px",
|
|
156
|
+
padding: "12px 20px",
|
|
157
|
+
fontSize: "14px",
|
|
158
|
+
fontWeight: "600",
|
|
159
|
+
minWidth: "140px",
|
|
160
|
+
height: "44px",
|
|
161
|
+
transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
|
|
162
|
+
boxShadow: loading
|
|
163
|
+
? "0 4px 12px rgba(139, 92, 246, 0.3)"
|
|
164
|
+
: "0 2px 8px rgba(99, 102, 241, 0.2)",
|
|
165
|
+
transform: "scale(1)",
|
|
166
|
+
...(loading && {
|
|
167
|
+
background: "linear-gradient(135deg, #6366f1, #8b5cf6)",
|
|
168
|
+
animation: "pulse 2s ease-in-out infinite",
|
|
169
|
+
}),
|
|
170
|
+
...buttonProps.style,
|
|
171
|
+
}, onMouseEnter: (e) => {
|
|
172
|
+
if (!disabled && !loading) {
|
|
173
|
+
e.currentTarget.style.transform = "scale(1.02)";
|
|
174
|
+
e.currentTarget.style.boxShadow =
|
|
175
|
+
"0 6px 16px rgba(99, 102, 241, 0.3)";
|
|
176
|
+
}
|
|
177
|
+
}, onMouseLeave: (e) => {
|
|
178
|
+
if (!disabled && !loading) {
|
|
179
|
+
e.currentTarget.style.transform = "scale(1)";
|
|
180
|
+
e.currentTarget.style.boxShadow = loading
|
|
181
|
+
? "0 4px 12px rgba(139, 92, 246, 0.3)"
|
|
182
|
+
: "0 2px 8px rgba(99, 102, 241, 0.2)";
|
|
183
|
+
}
|
|
184
|
+
}, onMouseDown: (e) => {
|
|
185
|
+
if (!disabled && !loading) {
|
|
186
|
+
e.currentTarget.style.transform = "scale(0.98)";
|
|
187
|
+
}
|
|
188
|
+
}, onMouseUp: (e) => {
|
|
189
|
+
if (!disabled && !loading) {
|
|
190
|
+
e.currentTarget.style.transform = "scale(1.02)";
|
|
191
|
+
}
|
|
192
|
+
}, "data-ai-image-button": true, children: loading ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { size: 18, className: "animate-spin", style: {
|
|
193
|
+
color: "white",
|
|
194
|
+
filter: "drop-shadow(0 0 2px rgba(255,255,255,0.3))",
|
|
195
|
+
} }), _jsx("span", { style: { letterSpacing: "0.025em" }, children: "G\u00E9n\u00E9ration..." })] })) : (_jsxs(_Fragment, { children: [_jsx(ImageIcon, { size: 18, style: {
|
|
196
|
+
color: "white",
|
|
197
|
+
filter: "drop-shadow(0 0 2px rgba(255,255,255,0.2))",
|
|
198
|
+
} }), _jsx("span", { style: { letterSpacing: "0.025em" }, children: children || "Générer une image" })] })) }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models?.filter((m) => m.type === "image") || [] }))] }), showImageCard && generatedImage && (_jsxs("div", { className: "relative", style: {
|
|
199
|
+
maxWidth: "320px",
|
|
200
|
+
borderRadius: "12px",
|
|
201
|
+
padding: "16px",
|
|
202
|
+
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.1)",
|
|
203
|
+
...getThemeStyles().card,
|
|
204
|
+
}, children: [_jsxs("div", { className: "flex items-start justify-between mb-3", children: [_jsx("h3", { className: "font-medium text-sm leading-tight", style: {
|
|
205
|
+
maxWidth: "calc(100% - 32px)",
|
|
206
|
+
...getThemeStyles().header,
|
|
207
|
+
}, title: generatedImage.prompt, children: generatedImage.prompt.length > 60
|
|
208
|
+
? `${generatedImage.prompt.substring(0, 60)}...`
|
|
209
|
+
: generatedImage.prompt }), _jsx("button", { onClick: handleCloseImage, className: "transition-colors flex-shrink-0", style: {
|
|
210
|
+
padding: "2px",
|
|
211
|
+
borderRadius: "4px",
|
|
212
|
+
backgroundColor: "transparent",
|
|
213
|
+
border: "none",
|
|
214
|
+
cursor: "pointer",
|
|
215
|
+
color: getThemeStyles().closeButton.color,
|
|
216
|
+
}, onMouseEnter: (e) => {
|
|
217
|
+
e.currentTarget.style.color =
|
|
218
|
+
getThemeStyles().closeButton.hoverColor;
|
|
219
|
+
}, onMouseLeave: (e) => {
|
|
220
|
+
e.currentTarget.style.color =
|
|
221
|
+
getThemeStyles().closeButton.color;
|
|
222
|
+
}, children: _jsx(X, { size: 16 }) })] }), _jsx("div", { className: "mb-4 rounded-lg overflow-hidden", style: getThemeStyles().imageContainer, children: _jsx("img", { src: generatedImage.url, alt: generatedImage.prompt, className: "w-full h-auto", style: {
|
|
223
|
+
maxHeight: "200px",
|
|
224
|
+
objectFit: "contain",
|
|
225
|
+
display: "block",
|
|
226
|
+
} }) }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs("button", { onClick: handleDownload, className: "flex items-center gap-1 px-3 py-2 text-xs font-medium rounded-lg transition-colors", style: getThemeStyles().actionButton, onMouseEnter: (e) => {
|
|
227
|
+
e.currentTarget.style.backgroundColor =
|
|
228
|
+
getThemeStyles().actionButton.hoverBackground;
|
|
229
|
+
e.currentTarget.style.color =
|
|
230
|
+
getThemeStyles().actionButton.hoverColor;
|
|
231
|
+
}, onMouseLeave: (e) => {
|
|
232
|
+
e.currentTarget.style.backgroundColor =
|
|
233
|
+
getThemeStyles().actionButton.backgroundColor;
|
|
234
|
+
e.currentTarget.style.color =
|
|
235
|
+
getThemeStyles().actionButton.color;
|
|
236
|
+
}, title: "T\u00E9l\u00E9charger l'image", children: [_jsx(Download, { size: 14 }), "T\u00E9l\u00E9charger"] }), _jsxs("button", { onClick: handleCopyUrl, className: "flex items-center gap-1 px-3 py-2 text-xs font-medium rounded-lg transition-colors", style: getThemeStyles().actionButton, onMouseEnter: (e) => {
|
|
237
|
+
e.currentTarget.style.backgroundColor =
|
|
238
|
+
getThemeStyles().actionButton.hoverBackground;
|
|
239
|
+
e.currentTarget.style.color =
|
|
240
|
+
getThemeStyles().actionButton.hoverColor;
|
|
241
|
+
}, onMouseLeave: (e) => {
|
|
242
|
+
e.currentTarget.style.backgroundColor =
|
|
243
|
+
getThemeStyles().actionButton.backgroundColor;
|
|
244
|
+
e.currentTarget.style.color =
|
|
245
|
+
getThemeStyles().actionButton.color;
|
|
246
|
+
}, title: "Copier l'URL", children: [_jsx(Copy, { size: 14 }), "Copier URL"] }), 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: {
|
|
247
|
+
...getThemeStyles().metadata,
|
|
248
|
+
}, children: _jsx("div", { className: "flex justify-center", children: _jsxs("span", { children: ["ID: ", generatedImage.requestId.slice(-8)] }) }) })] }))] }));
|
|
39
249
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAYtD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,GACf,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"AiStatusButton.d.ts","sourceRoot":"","sources":["../../src/components/AiStatusButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAYtD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,OAAe,EACf,SAAc,GACf,EAAE,mBAAmB,2CAiiBrB"}
|
|
@@ -164,7 +164,7 @@ export function AiStatusButton({ status, loading = false, className = "", }) {
|
|
|
164
164
|
}, onMouseEnter: () => setShowTooltip(true), onMouseLeave: handleMouseLeave, children: [_jsx("div", { style: aiStyles.tooltipHeader, children: "API Status" }), _jsxs("div", { style: {
|
|
165
165
|
...aiStyles.tooltipSection,
|
|
166
166
|
...aiStyles.tooltipSectionFirst,
|
|
167
|
-
}, children: [_jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "API Key:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key
|
|
167
|
+
}, children: [_jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "API Key:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key?.name || "N/A" })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Env:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key?.env || "N/A" })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Rate Limit:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [status.api_key?.rate_limit_rpm || 0, " req/min"] })] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Balance" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: ["$", formatFixed(balanceUsed, 6), " / $", formatNumber(balanceTotal)] }), renderUsageCircle(balancePercentage)] })] }), _jsxs("div", { style: aiStyles.tooltipSection, children: [_jsx("div", { style: aiStyles.tooltipSubtitle, children: "Storage" }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Total:" }), _jsxs("span", { style: aiStyles.tooltipValue, children: [formatStorage(storageUsed), " /", " ", formatStorage(storageAllocated)] }), renderUsageCircle(storagePercentage)] })] }), _jsxs("div", { style: {
|
|
168
168
|
...aiStyles.tooltipActions,
|
|
169
169
|
width: "100%",
|
|
170
170
|
flexDirection: "row",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UsageToast.d.ts","sourceRoot":"","sources":["../../src/components/UsageToast.tsx"],"names":[],"mappings":"AAKA,UAAU,eAAe;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAyB,EACzB,UAAU,GACX,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"UsageToast.d.ts","sourceRoot":"","sources":["../../src/components/UsageToast.tsx"],"names":[],"mappings":"AAKA,UAAU,eAAe;IACvB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,GAAG,aAAa,GAAG,WAAW,GAAG,UAAU,CAAC;IACrE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;CACzB;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,QAAyB,EACzB,UAAU,GACX,EAAE,eAAe,kDAkJjB;AAED,wBAAgB,aAAa;6BAIK,OAAO;;;;EAgBxC"}
|
|
@@ -8,9 +8,11 @@ export function UsageToast({ result, position = "bottom-right", onComplete, }) {
|
|
|
8
8
|
const fadeTimeoutRef = useRef(null);
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
if (result) {
|
|
11
|
-
// Show toast immediately
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
// Show toast immediately - using startTransition to avoid cascading renders warning
|
|
12
|
+
queueMicrotask(() => {
|
|
13
|
+
setIsVisible(true);
|
|
14
|
+
setIsClosing(false);
|
|
15
|
+
});
|
|
14
16
|
}
|
|
15
17
|
return () => {
|
|
16
18
|
if (fadeTimeoutRef.current) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiChipInputExample.d.ts","sourceRoot":"","sources":["../../src/examples/AiChipInputExample.tsx"],"names":[],"mappings":"AAMA,wBAAgB,kBAAkB,4CA0EjC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { AiChipInput } from "../components/AiChipLabel";
|
|
5
|
+
import { AiProvider } from "../context/AiProvider";
|
|
6
|
+
export function AiChipInputExample() {
|
|
7
|
+
const [tags, setTags] = useState(["react", "typescript"]);
|
|
8
|
+
return (_jsx(AiProvider, { baseUrl: "/api/ai", apiKeyId: "dev-key-example", children: _jsxs("div", { style: { padding: "20px", maxWidth: "600px" }, children: [_jsx("h2", { style: { marginBottom: "16px" }, children: "Exemple AiChipInput" }), _jsxs("div", { style: { marginBottom: "24px" }, children: [_jsx("label", { style: { display: "block", marginBottom: "8px", fontWeight: "500" }, children: "Tags du projet" }), _jsx(AiChipInput, { value: tags, onChange: setTags, placeholder: "Ajoutez des tags s\u00E9par\u00E9s par des virgules ou g\u00E9n\u00E9rez avec l'IA...", context: "web development", maxChips: 10, allowDuplicates: false })] }), _jsxs("div", { style: {
|
|
9
|
+
marginTop: "20px",
|
|
10
|
+
padding: "16px",
|
|
11
|
+
backgroundColor: "#f8f9fa",
|
|
12
|
+
borderRadius: "6px",
|
|
13
|
+
}, children: [_jsx("h3", { style: { margin: "0 0 8px 0", fontSize: "14px" }, children: "Valeur actuelle :" }), _jsx("pre", { style: { margin: 0, fontSize: "12px" }, children: JSON.stringify(tags, null, 2) })] }), _jsxs("div", { style: { marginTop: "16px", fontSize: "14px", color: "#6b7280" }, children: [_jsx("p", { children: _jsx("strong", { children: "Mode d\u00E9veloppement :" }) }), _jsxs("ul", { style: { paddingLeft: "20px" }, children: [_jsx("li", { children: "API Key contient \"dev\" \u2192 Simulation activ\u00E9e" }), _jsx("li", { children: "Pas d'appel API r\u00E9el" }), _jsx("li", { children: "G\u00E9n\u00E9ration de tags simul\u00E9s : \"react, typescript, javascript...\"" })] }), _jsx("p", { children: _jsx("strong", { children: "Instructions :" }) }), _jsxs("ul", { style: { paddingLeft: "20px" }, children: [_jsx("li", { children: "Tapez du texte et appuyez sur Entr\u00E9e pour cr\u00E9er un chip" }), _jsx("li", { children: "S\u00E9parez plusieurs tags avec des virgules (,) ou des points-virgules (;)" }), _jsx("li", { children: "Cliquez sur l'ic\u00F4ne \u2728 pour g\u00E9n\u00E9rer des tags automatiquement" }), _jsx("li", { children: "Utilisez le \u274C sur chaque chip pour le supprimer" }), _jsx("li", { children: "Appuyez sur Backspace dans un champ vide pour supprimer le dernier chip" })] })] })] }) }));
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiContextButtonExample.d.ts","sourceRoot":"","sources":["../../src/examples/AiContextButtonExample.tsx"],"names":[],"mappings":"AAqEA,wBAAgB,sBAAsB,4CA4QrC"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AiContextButton } from "@lastbrain/ai-ui-react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
// Données d'exemple pour le tableau
|
|
5
|
+
const sampleTableData = [
|
|
6
|
+
{
|
|
7
|
+
id: 1,
|
|
8
|
+
user: "Alice Martin",
|
|
9
|
+
product: "Laptop Pro X1",
|
|
10
|
+
sales: 15420,
|
|
11
|
+
commission: 1542,
|
|
12
|
+
region: "Nord",
|
|
13
|
+
month: "Janvier 2026",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 2,
|
|
17
|
+
user: "Bob Durant",
|
|
18
|
+
product: "Smartphone Z10",
|
|
19
|
+
sales: 8750,
|
|
20
|
+
commission: 875,
|
|
21
|
+
region: "Sud",
|
|
22
|
+
month: "Janvier 2026",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 3,
|
|
26
|
+
user: "Claire Dubois",
|
|
27
|
+
product: "Tablet Air 5",
|
|
28
|
+
sales: 12300,
|
|
29
|
+
commission: 1230,
|
|
30
|
+
region: "Est",
|
|
31
|
+
month: "Janvier 2026",
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 4,
|
|
35
|
+
user: "David Moreau",
|
|
36
|
+
product: "Laptop Pro X1",
|
|
37
|
+
sales: 18900,
|
|
38
|
+
commission: 1890,
|
|
39
|
+
region: "Ouest",
|
|
40
|
+
month: "Janvier 2026",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: 5,
|
|
44
|
+
user: "Emma Leroy",
|
|
45
|
+
product: "Smartphone Z10",
|
|
46
|
+
sales: 6420,
|
|
47
|
+
commission: 642,
|
|
48
|
+
region: "Centre",
|
|
49
|
+
month: "Janvier 2026",
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
// Données de produits pour un autre exemple
|
|
53
|
+
const productAnalytics = {
|
|
54
|
+
period: "Q1 2026",
|
|
55
|
+
totalRevenue: 2450000,
|
|
56
|
+
products: [
|
|
57
|
+
{ name: "Laptop Pro X1", revenue: 980000, units: 320, avgPrice: 3062.5 },
|
|
58
|
+
{ name: "Smartphone Z10", revenue: 750000, units: 500, avgPrice: 1500 },
|
|
59
|
+
{ name: "Tablet Air 5", revenue: 720000, units: 400, avgPrice: 1800 },
|
|
60
|
+
],
|
|
61
|
+
regions: {
|
|
62
|
+
Nord: { revenue: 650000, growth: "+12%" },
|
|
63
|
+
Sud: { revenue: 580000, growth: "+8%" },
|
|
64
|
+
Est: { revenue: 520000, growth: "+15%" },
|
|
65
|
+
Ouest: { revenue: 700000, growth: "+20%" },
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
export function AiContextButtonExample() {
|
|
69
|
+
const [analysisResults, setAnalysisResults] = useState([]);
|
|
70
|
+
const handleSalesAnalysis = (result, metadata) => {
|
|
71
|
+
console.log("Analyse des ventes:", { result, metadata });
|
|
72
|
+
setAnalysisResults((prev) => [
|
|
73
|
+
...prev,
|
|
74
|
+
`Ventes: ${result.substring(0, 100)}...`,
|
|
75
|
+
]);
|
|
76
|
+
};
|
|
77
|
+
const handleProductAnalysis = (result, metadata) => {
|
|
78
|
+
console.log("Analyse produits:", { result, metadata });
|
|
79
|
+
setAnalysisResults((prev) => [
|
|
80
|
+
...prev,
|
|
81
|
+
`Produits: ${result.substring(0, 100)}...`,
|
|
82
|
+
]);
|
|
83
|
+
};
|
|
84
|
+
const handleToast = (data) => {
|
|
85
|
+
console.log(`${data.type}: ${data.message}`);
|
|
86
|
+
};
|
|
87
|
+
return (_jsxs("div", { className: "p-6 max-w-6xl mx-auto", children: [_jsx("h1", { className: "text-3xl font-bold mb-6", children: "AI Context Button - Exemples" }), _jsxs("div", { className: "mb-8", children: [_jsx("h2", { className: "text-xl font-semibold mb-4", children: "1. Analyse de donn\u00E9es de ventes" }), _jsx("div", { className: "mb-4 overflow-hidden rounded-lg border border-gray-200", children: _jsxs("table", { className: "min-w-full divide-y divide-gray-200", children: [_jsx("thead", { className: "bg-gray-50", children: _jsxs("tr", { children: [_jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Vendeur" }), _jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Produit" }), _jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Ventes (\u20AC)" }), _jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "Commission (\u20AC)" }), _jsx("th", { className: "px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider", children: "R\u00E9gion" })] }) }), _jsx("tbody", { className: "bg-white divide-y divide-gray-200", children: sampleTableData.map((row) => (_jsxs("tr", { className: "hover:bg-gray-50", children: [_jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900", children: row.user }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-500", children: row.product }), _jsxs("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-900", children: [row.sales.toLocaleString(), " \u20AC"] }), _jsxs("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-900", children: [row.commission.toLocaleString(), " \u20AC"] }), _jsx("td", { className: "px-6 py-4 whitespace-nowrap text-sm text-gray-500", children: row.region })] }, row.id))) })] }) }), _jsxs("div", { className: "flex gap-3 flex-wrap", children: [_jsx(AiContextButton, { contextData: sampleTableData, contextDescription: "Donn\u00E9es de ventes par vendeur pour Janvier 2026", onResult: handleSalesAnalysis, onToast: handleToast, resultModalTitle: "Analyse des Ventes - Janvier 2026", children: "Analyser les ventes" }), _jsx(AiContextButton, { contextData: sampleTableData, contextDescription: "Performance des vendeurs", onResult: handleSalesAnalysis, onToast: handleToast, resultModalTitle: "Top Performers", children: "Top 3 vendeurs" })] })] }), _jsxs("div", { className: "mb-8", children: [_jsx("h2", { className: "text-xl font-semibold mb-4", children: "2. Analyse de performance produits" }), _jsxs("div", { className: "mb-4 p-6 bg-gray-50 rounded-lg", children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-6", children: [_jsxs("div", { className: "bg-white p-4 rounded-lg shadow-sm", children: [_jsx("h3", { className: "text-sm font-medium text-gray-500", children: "Chiffre d'affaires total" }), _jsxs("p", { className: "text-2xl font-bold text-gray-900", children: [productAnalytics.totalRevenue.toLocaleString(), " \u20AC"] })] }), _jsxs("div", { className: "bg-white p-4 rounded-lg shadow-sm", children: [_jsx("h3", { className: "text-sm font-medium text-gray-500", children: "P\u00E9riode" }), _jsx("p", { className: "text-2xl font-bold text-gray-900", children: productAnalytics.period })] }), _jsxs("div", { className: "bg-white p-4 rounded-lg shadow-sm", children: [_jsx("h3", { className: "text-sm font-medium text-gray-500", children: "Produits" }), _jsx("p", { className: "text-2xl font-bold text-gray-900", children: productAnalytics.products.length })] }), _jsxs("div", { className: "bg-white p-4 rounded-lg shadow-sm", children: [_jsx("h3", { className: "text-sm font-medium text-gray-500", children: "R\u00E9gions" }), _jsx("p", { className: "text-2xl font-bold text-gray-900", children: Object.keys(productAnalytics.regions).length })] })] }), _jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-2 gap-6", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-3", children: "Performance par produit" }), _jsx("div", { className: "space-y-2", children: productAnalytics.products.map((product, index) => (_jsxs("div", { className: "flex justify-between items-center p-3 bg-white rounded", children: [_jsx("span", { className: "font-medium", children: product.name }), _jsxs("div", { className: "text-right", children: [_jsxs("div", { className: "text-sm text-gray-600", children: [product.units, " unit\u00E9s"] }), _jsxs("div", { className: "font-semibold", children: [product.revenue.toLocaleString(), " \u20AC"] })] })] }, index))) })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-3", children: "Performance par r\u00E9gion" }), _jsx("div", { className: "space-y-2", children: Object.entries(productAnalytics.regions).map(([region, data]) => (_jsxs("div", { className: "flex justify-between items-center p-3 bg-white rounded", children: [_jsx("span", { className: "font-medium", children: region }), _jsxs("div", { className: "text-right", children: [_jsx("div", { className: "text-sm text-green-600", children: data.growth }), _jsxs("div", { className: "font-semibold", children: [data.revenue.toLocaleString(), " \u20AC"] })] })] }, region))) })] })] })] }), _jsxs("div", { className: "flex gap-3 flex-wrap", children: [_jsx(AiContextButton, { contextData: productAnalytics, contextDescription: "Analytics de performance produits Q1 2026", onResult: handleProductAnalysis, onToast: handleToast, resultModalTitle: "Analyse de Performance - Q1 2026", children: "Analyser les performances" }), _jsx(AiContextButton, { contextData: productAnalytics, contextDescription: "Donn\u00E9es produits et r\u00E9gions", onResult: handleProductAnalysis, onToast: handleToast, resultModalTitle: "Recommandations Strat\u00E9giques", children: "Recommandations" })] })] }), _jsxs("div", { className: "mb-8", children: [_jsx("h2", { className: "text-xl font-semibold mb-4", children: "3. Analyse de texte libre" }), _jsxs("div", { className: "mb-4 p-4 bg-gray-50 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-700 mb-2", children: _jsx("strong", { children: "Feedback client :" }) }), _jsx("p", { className: "italic", children: "\"Le produit est excellent en terme de qualit\u00E9 mais le prix me semble un peu \u00E9lev\u00E9. La livraison a \u00E9t\u00E9 rapide et le service client tr\u00E8s r\u00E9actif quand j'ai eu un probl\u00E8me avec ma commande. Je recommande ce produit mais j'esp\u00E8re que les prix baisseront \u00E0 l'avenir.\"" })] }), _jsx(AiContextButton, { contextData: "Le produit est excellent en terme de qualit\u00E9 mais le prix me semble un peu \u00E9lev\u00E9. La livraison a \u00E9t\u00E9 rapide et le service client tr\u00E8s r\u00E9actif quand j'ai eu un probl\u00E8me avec ma commande. Je recommande ce produit mais j'esp\u00E8re que les prix baisseront \u00E0 l'avenir.", contextDescription: "Feedback client sur un produit", onResult: (result) => console.log("Analyse sentiment:", result), onToast: handleToast, resultModalTitle: "Analyse de Sentiment Client", children: "Analyser le sentiment" })] }), analysisResults.length > 0 && (_jsxs("div", { className: "mt-8", children: [_jsx("h2", { className: "text-xl font-semibold mb-4", children: "Historique des analyses" }), _jsx("div", { className: "space-y-2", children: analysisResults.map((result, index) => (_jsx("div", { className: "p-3 bg-blue-50 rounded-lg text-sm", children: result }, index))) })] }))] }));
|
|
88
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AiImageButtonExample.d.ts","sourceRoot":"","sources":["../../src/examples/AiImageButtonExample.tsx"],"names":[],"mappings":"AAGA,wBAAgB,YAAY,4CAoE3B"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AiImageButton } from "@lastbrain/ai-ui-react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
export function ExampleUsage() {
|
|
5
|
+
const [savedImages, setSavedImages] = useState([]);
|
|
6
|
+
const handleImageGenerated = (url, metadata) => {
|
|
7
|
+
console.log("Image générée:", { url, metadata });
|
|
8
|
+
// L'image est automatiquement affichée dans la card
|
|
9
|
+
};
|
|
10
|
+
const handleImageSave = async (url) => {
|
|
11
|
+
// Simulation d'une sauvegarde en base
|
|
12
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
13
|
+
// Ici vous pouvez faire l'appel API pour sauvegarder en base
|
|
14
|
+
// const response = await fetch('/api/images', {
|
|
15
|
+
// method: 'POST',
|
|
16
|
+
// body: JSON.stringify({ url }),
|
|
17
|
+
// headers: { 'Content-Type': 'application/json' }
|
|
18
|
+
// });
|
|
19
|
+
setSavedImages((prev) => [...prev, url]);
|
|
20
|
+
};
|
|
21
|
+
const handleToast = (data) => {
|
|
22
|
+
// Vous pouvez utiliser votre système de toast préféré ici
|
|
23
|
+
console.log(`${data.type}: ${data.message}`);
|
|
24
|
+
};
|
|
25
|
+
return (_jsxs("div", { className: "p-6", children: [_jsx("h1", { className: "text-2xl font-bold mb-4", children: "G\u00E9n\u00E9rateur d'Images AI" }), _jsx(AiImageButton, { onImage: handleImageGenerated, onImageSave: handleImageSave, onToast: handleToast, showImageCard: true, className: "mb-4", children: "Cr\u00E9er une image" }), savedImages.length > 0 && (_jsxs("div", { className: "mt-8", children: [_jsxs("h2", { className: "text-lg font-semibold mb-2", children: ["Images sauvegard\u00E9es (", savedImages.length, ")"] }), _jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 gap-4", children: savedImages.map((url, index) => (_jsx("div", { className: "border rounded-lg p-2", children: _jsx("img", { src: url, alt: `Saved image ${index + 1}`, className: "w-full h-32 object-cover rounded" }) }, index))) })] }))] }));
|
|
26
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAiCallImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallImage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"useAiCallImage.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallImage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AA8H7E,MAAM,WAAW,qBAAqB;IACpC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACrE,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,cAAc,CAC5B,OAAO,CAAC,EAAE,qBAAqB,GAC9B,oBAAoB,CAsDtB"}
|
|
@@ -1,6 +1,95 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { useState, useCallback } from "react";
|
|
3
3
|
import { useAiClient } from "./useAiClient";
|
|
4
|
+
/**
|
|
5
|
+
* Génère une image Canvas pour l'environnement de développement
|
|
6
|
+
*/
|
|
7
|
+
async function generateDevImage(prompt, width, height) {
|
|
8
|
+
// Créer un canvas
|
|
9
|
+
const canvas = document.createElement("canvas");
|
|
10
|
+
const ctx = canvas.getContext("2d");
|
|
11
|
+
if (!ctx) {
|
|
12
|
+
throw new Error("Could not create canvas context");
|
|
13
|
+
}
|
|
14
|
+
canvas.width = width;
|
|
15
|
+
canvas.height = height;
|
|
16
|
+
// Générer des couleurs basées sur le prompt
|
|
17
|
+
const promptHash = prompt
|
|
18
|
+
.split("")
|
|
19
|
+
.reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) % 255, 0);
|
|
20
|
+
// Couleurs de base dégradées
|
|
21
|
+
const colors = [
|
|
22
|
+
`hsl(${(promptHash * 7) % 360}, 70%, 60%)`,
|
|
23
|
+
`hsl(${(promptHash * 11) % 360}, 60%, 70%)`,
|
|
24
|
+
`hsl(${(promptHash * 13) % 360}, 50%, 80%)`,
|
|
25
|
+
];
|
|
26
|
+
// Créer un dégradé radial
|
|
27
|
+
const gradient = ctx.createRadialGradient(width / 2, height / 2, 0, width / 2, height / 2, Math.max(width, height) / 2);
|
|
28
|
+
gradient.addColorStop(0, colors[0]);
|
|
29
|
+
gradient.addColorStop(0.5, colors[1]);
|
|
30
|
+
gradient.addColorStop(1, colors[2]);
|
|
31
|
+
// Remplir le fond
|
|
32
|
+
ctx.fillStyle = gradient;
|
|
33
|
+
ctx.fillRect(0, 0, width, height);
|
|
34
|
+
// Ajouter des formes géométriques basées sur le prompt
|
|
35
|
+
const shapes = prompt.split(" ").slice(0, 5);
|
|
36
|
+
shapes.forEach((word, index) => {
|
|
37
|
+
const wordHash = word
|
|
38
|
+
.split("")
|
|
39
|
+
.reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) % 1000, 0);
|
|
40
|
+
ctx.save();
|
|
41
|
+
ctx.globalAlpha = 0.3 + index * 0.1;
|
|
42
|
+
ctx.fillStyle = `hsl(${(wordHash * 17) % 360}, 80%, 50%)`;
|
|
43
|
+
// Formes différentes selon l'index
|
|
44
|
+
const x = (wordHash % (width - 100)) + 50;
|
|
45
|
+
const y = ((wordHash * 3) % (height - 100)) + 50;
|
|
46
|
+
const size = 30 + (wordHash % 50);
|
|
47
|
+
switch (index % 4) {
|
|
48
|
+
case 0: // Cercle
|
|
49
|
+
ctx.beginPath();
|
|
50
|
+
ctx.arc(x, y, size, 0, Math.PI * 2);
|
|
51
|
+
ctx.fill();
|
|
52
|
+
break;
|
|
53
|
+
case 1: // Carré
|
|
54
|
+
ctx.fillRect(x - size / 2, y - size / 2, size, size);
|
|
55
|
+
break;
|
|
56
|
+
case 2: // Triangle
|
|
57
|
+
ctx.beginPath();
|
|
58
|
+
ctx.moveTo(x, y - size / 2);
|
|
59
|
+
ctx.lineTo(x - size / 2, y + size / 2);
|
|
60
|
+
ctx.lineTo(x + size / 2, y + size / 2);
|
|
61
|
+
ctx.closePath();
|
|
62
|
+
ctx.fill();
|
|
63
|
+
break;
|
|
64
|
+
case 3: // Losange
|
|
65
|
+
ctx.save();
|
|
66
|
+
ctx.translate(x, y);
|
|
67
|
+
ctx.rotate((wordHash / 100) % (Math.PI * 2));
|
|
68
|
+
ctx.fillRect(-size / 2, -size / 2, size, size);
|
|
69
|
+
ctx.restore();
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
ctx.restore();
|
|
73
|
+
});
|
|
74
|
+
// Ajouter le texte du prompt en overlay
|
|
75
|
+
ctx.save();
|
|
76
|
+
ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
|
|
77
|
+
ctx.font = `bold ${Math.min(width, height) / 30}px system-ui, -apple-system, sans-serif`;
|
|
78
|
+
ctx.textAlign = "center";
|
|
79
|
+
ctx.textBaseline = "middle";
|
|
80
|
+
// Fond semi-transparent pour le texte
|
|
81
|
+
const textMetrics = ctx.measureText(prompt);
|
|
82
|
+
const textWidth = textMetrics.width;
|
|
83
|
+
const textHeight = parseInt(ctx.font);
|
|
84
|
+
ctx.fillStyle = "rgba(0, 0, 0, 0.6)";
|
|
85
|
+
ctx.fillRect(width / 2 - textWidth / 2 - 20, height / 2 - textHeight / 2 - 10, textWidth + 40, textHeight + 20);
|
|
86
|
+
// Texte du prompt
|
|
87
|
+
ctx.fillStyle = "white";
|
|
88
|
+
ctx.fillText(prompt, width / 2, height / 2);
|
|
89
|
+
ctx.restore();
|
|
90
|
+
// Convertir en data URL
|
|
91
|
+
return canvas.toDataURL("image/png", 0.8);
|
|
92
|
+
}
|
|
4
93
|
export function useAiCallImage(options) {
|
|
5
94
|
const client = useAiClient(options);
|
|
6
95
|
const [loading, setLoading] = useState(false);
|
|
@@ -9,6 +98,23 @@ export function useAiCallImage(options) {
|
|
|
9
98
|
setLoading(true);
|
|
10
99
|
setError(null);
|
|
11
100
|
try {
|
|
101
|
+
// Vérifier si on est en mode dev (API key contient "dev")
|
|
102
|
+
if (options?.apiKeyId?.includes("dev")) {
|
|
103
|
+
// Simulation complète pour l'environnement dev
|
|
104
|
+
await new Promise((resolve) => setTimeout(resolve, 2000)); // Délai réaliste pour simuler l'API
|
|
105
|
+
// Parse size format "1024x1024" or default to "1024x1024"
|
|
106
|
+
const sizeMatch = request.size?.match(/(\d+)x(\d+)/);
|
|
107
|
+
const width = sizeMatch ? parseInt(sizeMatch[1]) : 1024;
|
|
108
|
+
const height = sizeMatch ? parseInt(sizeMatch[2]) : 1024;
|
|
109
|
+
// Créer une image générée avec Canvas
|
|
110
|
+
const imageUrl = await generateDevImage(request.prompt, width, height);
|
|
111
|
+
return {
|
|
112
|
+
requestId: `dev-img-${Date.now()}`,
|
|
113
|
+
url: imageUrl,
|
|
114
|
+
debitTokens: 30, // Coût simulé
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Mode production : appel API normal
|
|
12
118
|
const result = await client.generateImage(request);
|
|
13
119
|
return result;
|
|
14
120
|
}
|
|
@@ -20,7 +126,7 @@ export function useAiCallImage(options) {
|
|
|
20
126
|
finally {
|
|
21
127
|
setLoading(false);
|
|
22
128
|
}
|
|
23
|
-
}, [client]);
|
|
129
|
+
}, [client, options?.apiKeyId]);
|
|
24
130
|
return {
|
|
25
131
|
generateImage,
|
|
26
132
|
loading,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAiCallText.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallText.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,
|
|
1
|
+
{"version":3,"file":"useAiCallText.d.ts","sourceRoot":"","sources":["../../src/hooks/useAiCallText.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG3E,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,aAAa,CAC3B,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CA0DrB"}
|