@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.
Files changed (44) hide show
  1. package/dist/components/AiChipLabel.d.ts +12 -0
  2. package/dist/components/AiChipLabel.d.ts.map +1 -1
  3. package/dist/components/AiChipLabel.js +129 -1
  4. package/dist/components/AiContextButton.d.ts +18 -0
  5. package/dist/components/AiContextButton.d.ts.map +1 -0
  6. package/dist/components/AiContextButton.js +339 -0
  7. package/dist/components/AiImageButton.d.ts +12 -3
  8. package/dist/components/AiImageButton.d.ts.map +1 -1
  9. package/dist/components/AiImageButton.js +218 -8
  10. package/dist/components/AiStatusButton.d.ts.map +1 -1
  11. package/dist/components/AiStatusButton.js +1 -1
  12. package/dist/components/UsageToast.d.ts.map +1 -1
  13. package/dist/components/UsageToast.js +5 -3
  14. package/dist/examples/AiChipInputExample.d.ts +2 -0
  15. package/dist/examples/AiChipInputExample.d.ts.map +1 -0
  16. package/dist/examples/AiChipInputExample.js +14 -0
  17. package/dist/examples/AiContextButtonExample.d.ts +2 -0
  18. package/dist/examples/AiContextButtonExample.d.ts.map +1 -0
  19. package/dist/examples/AiContextButtonExample.js +88 -0
  20. package/dist/examples/AiImageButtonExample.d.ts +2 -0
  21. package/dist/examples/AiImageButtonExample.d.ts.map +1 -0
  22. package/dist/examples/AiImageButtonExample.js +26 -0
  23. package/dist/hooks/useAiCallImage.d.ts.map +1 -1
  24. package/dist/hooks/useAiCallImage.js +107 -1
  25. package/dist/hooks/useAiCallText.d.ts.map +1 -1
  26. package/dist/hooks/useAiCallText.js +25 -1
  27. package/dist/index.d.ts +4 -0
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +4 -0
  30. package/dist/styles/inline.d.ts.map +1 -1
  31. package/dist/styles/inline.js +3 -1
  32. package/package.json +2 -2
  33. package/src/components/AiChipLabel.tsx +218 -1
  34. package/src/components/AiContextButton.tsx +553 -0
  35. package/src/components/AiImageButton.tsx +386 -38
  36. package/src/components/AiStatusButton.tsx +7 -3
  37. package/src/components/UsageToast.tsx +5 -3
  38. package/src/examples/AiChipInputExample.tsx +81 -0
  39. package/src/examples/AiContextButtonExample.tsx +338 -0
  40. package/src/examples/AiImageButtonExample.tsx +72 -0
  41. package/src/hooks/useAiCallImage.ts +149 -1
  42. package/src/hooks/useAiCallText.ts +30 -1
  43. package/src/index.ts +4 -0
  44. 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 { UsageToast, useUsageToast } from "./UsageToast";
8
- export function AiImageButton({ baseUrl, apiKeyId, uiMode = "modal", context, model, prompt, onImage, onToast, disabled, className, children, ...buttonProps }) {
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
- onImage?.(result.url);
27
- onToast?.({ type: "success", message: "Image generated successfully" });
28
- showUsageToast(result);
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?.({ type: "error", message: "Failed to generate image" });
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, "data-ai-image-button": true, children: loading ? "Generating..." : children || "Generate Image" }), isOpen && (_jsx(AiPromptPanel, { isOpen: isOpen, onClose: handleClosePanel, onSubmit: handleSubmit, uiMode: uiMode, models: models?.filter((m) => m.type === "image") || [] })), Boolean(toastData) && (_jsx(UsageToast, { result: toastData, position: "bottom-right", onComplete: clearToast }, toastKey))] }));
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,2CA6hBrB"}
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.name })] }), _jsxs("div", { style: aiStyles.tooltipRow, children: [_jsx("span", { style: aiStyles.tooltipLabel, children: "Env:" }), _jsx("span", { style: aiStyles.tooltipValue, children: status.api_key.env })] }), _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, " 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: {
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,kDAgJjB;AAED,wBAAgB,aAAa;6BAIK,OAAO;;;;EAgBxC"}
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
- setIsVisible(true);
13
- setIsClosing(false);
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,2 @@
1
+ export declare function AiChipInputExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=AiChipInputExample.d.ts.map
@@ -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,2 @@
1
+ export declare function AiContextButtonExample(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=AiContextButtonExample.d.ts.map
@@ -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,2 @@
1
+ export declare function ExampleUsage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=AiImageButtonExample.d.ts.map
@@ -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;AAG7E,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,CA6BtB"}
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,CA6BrB"}
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"}