@lastbrain/module-ai 0.1.13 → 0.1.16
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/ai.build.config.js +1 -1
- package/dist/api/auth/generate-text.js +1 -1
- package/dist/web/components/ImageGenerative.d.ts +3 -1
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +18 -4
- package/dist/web/components/TextareaGenerative.d.ts +3 -1
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
- package/dist/web/components/TextareaGenerative.js +44 -23
- package/package.json +4 -4
package/dist/ai.build.config.js
CHANGED
|
@@ -12,7 +12,7 @@ export async function POST(request) {
|
|
|
12
12
|
const { data: { user }, } = await supabase.auth.getUser();
|
|
13
13
|
// L'utilisateur est déjà authentifié grâce au middleware
|
|
14
14
|
const body = await request.json();
|
|
15
|
-
const { prompt, model = "gpt-4o-mini", context, maxTokens =
|
|
15
|
+
const { prompt, model = "gpt-4o-mini", context, maxTokens = 3000, temperature = 0.7, } = body;
|
|
16
16
|
if (!prompt) {
|
|
17
17
|
return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
|
|
18
18
|
}
|
|
@@ -20,6 +20,8 @@ export interface ImageGenerativeProps {
|
|
|
20
20
|
label?: string;
|
|
21
21
|
description?: string;
|
|
22
22
|
defaultStyle?: string;
|
|
23
|
+
hideStyleEditor?: boolean;
|
|
24
|
+
uploadPath?: string;
|
|
23
25
|
}
|
|
24
|
-
export declare function ImageGenerative({ defaultPrompt, model, size, quality, onChange, onError, className, disabled, apiEndpoint, showTokenBalance, label, description, defaultStyle, }: ImageGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare function ImageGenerative({ defaultPrompt, model, size, quality, onChange, onError, className, disabled, apiEndpoint, showTokenBalance, label, description, defaultStyle, hideStyleEditor, uploadPath, }: ImageGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
25
27
|
//# sourceMappingURL=ImageGenerative.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/ImageGenerative.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ImageGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/ImageGenerative.tsx"],"names":[],"mappings":"AA4BA,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAiCD,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;IACvE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAC1E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,eAAe,CAAC,EAC9B,aAAkB,EAClB,KAAkB,EAClB,IAAkB,EAClB,OAAoB,EACpB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,WAAsC,EACtC,gBAAuB,EACvB,KAAK,EACL,WAAW,EACX,YAA0B,EAC1B,eAAuB,EACvB,UAAU,GACX,EAAE,oBAAoB,2CA0TtB"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
|
-
import { Button, Chip, Progress, Card, CardBody, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Textarea, Select, SelectItem, } from "@lastbrain/ui";
|
|
4
|
+
import { Button, Chip, Progress, Card, CardBody, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Textarea, Select, SelectItem, addToast, } from "@lastbrain/ui";
|
|
5
5
|
import { Image as ImageIcon, Loader2, AlertCircle, Download, Wand2, } from "lucide-react";
|
|
6
6
|
import Image from "next/image";
|
|
7
7
|
const imageStyles = [
|
|
@@ -34,7 +34,7 @@ const imageStyles = [
|
|
|
34
34
|
},
|
|
35
35
|
{ key: "pop-art", label: "Pop Art", description: "Style pop art vibrant" },
|
|
36
36
|
];
|
|
37
|
-
export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, label, description, defaultStyle = "realistic", }) {
|
|
37
|
+
export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, label, description, defaultStyle = "realistic", hideStyleEditor = false, uploadPath, }) {
|
|
38
38
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
39
39
|
const [generatedImage, setGeneratedImage] = useState(null);
|
|
40
40
|
const [error, setError] = useState(null);
|
|
@@ -61,6 +61,7 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
61
61
|
model: selectedModel,
|
|
62
62
|
size,
|
|
63
63
|
quality,
|
|
64
|
+
uploadPath,
|
|
64
65
|
}),
|
|
65
66
|
});
|
|
66
67
|
if (!response.ok) {
|
|
@@ -78,6 +79,12 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
78
79
|
};
|
|
79
80
|
setGeneratedImage(imageResponse);
|
|
80
81
|
setTokenBalance(data.tokensRemaining);
|
|
82
|
+
// Show success toast
|
|
83
|
+
addToast({
|
|
84
|
+
color: "success",
|
|
85
|
+
title: "Image générée avec succès",
|
|
86
|
+
description: `${imageResponse.tokensUsed} tokens utilisés, ${imageResponse.tokensRemaining} restants`,
|
|
87
|
+
});
|
|
81
88
|
if (onChange) {
|
|
82
89
|
onChange(data.imageUrl, imageResponse);
|
|
83
90
|
}
|
|
@@ -85,6 +92,12 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
85
92
|
catch (err) {
|
|
86
93
|
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
87
94
|
setError(errorMessage);
|
|
95
|
+
// Show error toast
|
|
96
|
+
addToast({
|
|
97
|
+
color: "danger",
|
|
98
|
+
title: "Erreur de génération d'image",
|
|
99
|
+
description: errorMessage,
|
|
100
|
+
});
|
|
88
101
|
if (onError) {
|
|
89
102
|
onError(err);
|
|
90
103
|
}
|
|
@@ -99,6 +112,7 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
99
112
|
size,
|
|
100
113
|
quality,
|
|
101
114
|
apiEndpoint,
|
|
115
|
+
uploadPath,
|
|
102
116
|
onChange,
|
|
103
117
|
onError,
|
|
104
118
|
isGenerating,
|
|
@@ -122,5 +136,5 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
122
136
|
console.error("Erreur lors du téléchargement:", error);
|
|
123
137
|
}
|
|
124
138
|
}, [generatedImage]);
|
|
125
|
-
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && _jsx("p", { className: "text-sm text-gray-500", children: description }), generatedImage && !isGenerating && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [size, " \u2022 ", quality] })] })] }) })), _jsx(Button, { color: "primary", onClick: () => setIsModalOpen(true), disabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating ? "Génération..." : "Générer une image" }), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] })) }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), generatedImage.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", generatedImage.cost.toFixed(4)] })), _jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: "T\u00E9l\u00E9charger" })] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: "G\u00E9n\u00E9ration de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }), _jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { size: 20 }), _jsx("span", { children: "G\u00E9n\u00E9rer une image avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Description de l'image", placeholder: "D\u00E9crivez l'image que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis et d\u00E9taill\u00E9 pour de meilleurs r\u00E9sultats" }), _jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => setSelectedModel(Array.from(keys)[0]), description: "DALL-E 3 offre la meilleure qualit\u00E9", children: [_jsx(SelectItem, { children: "DALL-E 3 (Haute qualit\u00E9)" }, "dall-e-3"), _jsx(SelectItem, { children: "DALL-E 2 (Standard)" }, "dall-e-2")] }), generatedImage && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-2", children: "Aper\u00E7u de l'image actuelle:" }), _jsx("div", { className: "relative w-full h-32", children: _jsx(Image, { src: generatedImage.imageUrl, alt: "Current", fill: true, className: "object-cover rounded" }) })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(ImageIcon, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) })] }));
|
|
139
|
+
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && _jsx("p", { className: "text-sm text-gray-500", children: description }), generatedImage && !isGenerating && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [size, " \u2022 ", quality] })] })] }) })), _jsx(Button, { color: "primary", onClick: () => setIsModalOpen(true), disabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating ? "Génération..." : "Générer une image" }), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] })) }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), generatedImage.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", generatedImage.cost.toFixed(4)] })), _jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: "T\u00E9l\u00E9charger" })] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: "G\u00E9n\u00E9ration de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }), _jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { size: 20 }), _jsx("span", { children: "G\u00E9n\u00E9rer une image avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Description de l'image", placeholder: "D\u00E9crivez l'image que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis et d\u00E9taill\u00E9 pour de meilleurs r\u00E9sultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => setSelectedModel(Array.from(keys)[0]), description: "DALL-E 3 offre la meilleure qualit\u00E9", children: [_jsx(SelectItem, { children: "DALL-E 3 (Haute qualit\u00E9)" }, "dall-e-3"), _jsx(SelectItem, { children: "DALL-E 2 (Standard)" }, "dall-e-2")] })] })), generatedImage && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-2", children: "Aper\u00E7u de l'image actuelle:" }), _jsx("div", { className: "relative w-full h-32", children: _jsx(Image, { src: generatedImage.imageUrl, alt: "Current", fill: true, className: "object-cover rounded" }) })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(ImageIcon, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) })] }));
|
|
126
140
|
}
|
|
@@ -20,6 +20,8 @@ export interface TextareaGenerativeProps {
|
|
|
20
20
|
showTokenBalance?: boolean;
|
|
21
21
|
label?: string;
|
|
22
22
|
description?: string;
|
|
23
|
+
hidePromptEditor?: boolean;
|
|
24
|
+
showStructuredResponse?: boolean;
|
|
23
25
|
}
|
|
24
|
-
export declare function TextareaGenerative({ defaultPrompt, model, placeholder, defaultValue, onChange, onError, className, disabled, minRows, maxRows, apiEndpoint, showTokenBalance, label, description, }: TextareaGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
export declare function TextareaGenerative({ defaultPrompt, model, placeholder, defaultValue, onChange, onError, className, disabled, minRows, maxRows, apiEndpoint, showTokenBalance, label, description, hidePromptEditor, showStructuredResponse, }: TextareaGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
25
27
|
//# sourceMappingURL=TextareaGenerative.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextareaGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/TextareaGenerative.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TextareaGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/TextareaGenerative.tsx"],"names":[],"mappings":"AAqBA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,wBAAgB,kBAAkB,CAAC,EACjC,aAAkB,EAClB,KAAqB,EACrB,WAA6D,EAC7D,YAAiB,EACjB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,OAAW,EACX,OAAY,EACZ,WAAqC,EACrC,gBAAuB,EACvB,KAAK,EACL,WAAW,EACX,gBAAwB,EACxB,sBAA8B,GAC/B,EAAE,uBAAuB,2CAqRzB"}
|
|
@@ -1,32 +1,39 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
|
-
import { Textarea, Button, Chip, Progress, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem, Skeleton, } from "@lastbrain/ui";
|
|
4
|
+
import { Textarea, Button, Chip, Progress, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem, Skeleton, addToast, } from "@lastbrain/ui";
|
|
5
5
|
import { Sparkles, Loader2, AlertCircle, Wand2 } from "lucide-react";
|
|
6
|
-
export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini", placeholder = "Saisissez votre texte ou générez avec l'IA...", defaultValue = "", onChange, onError, className, disabled = false, minRows = 4, maxRows = 20, apiEndpoint = "/api/ai/generate-text", showTokenBalance = true, label, description, }) {
|
|
7
|
-
const [
|
|
6
|
+
export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini", placeholder = "Saisissez votre texte ou générez avec l'IA...", defaultValue = "", onChange, onError, className, disabled = false, minRows = 4, maxRows = 20, apiEndpoint = "/api/ai/generate-text", showTokenBalance = true, label, description, hidePromptEditor = false, showStructuredResponse = false, }) {
|
|
7
|
+
const [userInput, setUserInput] = useState(defaultValue);
|
|
8
8
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
9
9
|
const [lastResponse, setLastResponse] = useState(null);
|
|
10
10
|
const [error, setError] = useState(null);
|
|
11
11
|
const [tokenBalance, setTokenBalance] = useState(null);
|
|
12
|
-
// Modal state
|
|
12
|
+
// Modal state (only for non-hidePromptEditor mode)
|
|
13
13
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
14
14
|
const [userPrompt, setUserPrompt] = useState(defaultPrompt);
|
|
15
15
|
const [selectedModel, setSelectedModel] = useState(model);
|
|
16
|
-
const handleGenerate = useCallback(async () => {
|
|
17
|
-
|
|
16
|
+
const handleGenerate = useCallback(async (prompt) => {
|
|
17
|
+
const finalPrompt = hidePromptEditor
|
|
18
|
+
? defaultPrompt
|
|
19
|
+
: prompt || userPrompt;
|
|
20
|
+
const finalUserInput = hidePromptEditor ? userInput : "";
|
|
21
|
+
if (!finalPrompt || isGenerating)
|
|
18
22
|
return;
|
|
19
23
|
setIsGenerating(true);
|
|
20
24
|
setError(null);
|
|
21
|
-
|
|
25
|
+
if (!hidePromptEditor)
|
|
26
|
+
setIsModalOpen(false);
|
|
22
27
|
try {
|
|
28
|
+
const fullPrompt = hidePromptEditor
|
|
29
|
+
? `${finalPrompt}\n\nUtilisateur: ${finalUserInput}`
|
|
30
|
+
: finalPrompt;
|
|
23
31
|
const response = await fetch(apiEndpoint, {
|
|
24
32
|
method: "POST",
|
|
25
33
|
headers: { "Content-Type": "application/json" },
|
|
26
34
|
body: JSON.stringify({
|
|
27
|
-
prompt:
|
|
28
|
-
model: selectedModel,
|
|
29
|
-
context: value,
|
|
35
|
+
prompt: fullPrompt,
|
|
36
|
+
model: hidePromptEditor ? model : selectedModel,
|
|
30
37
|
}),
|
|
31
38
|
});
|
|
32
39
|
if (!response.ok) {
|
|
@@ -38,12 +45,17 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
38
45
|
text: data.text,
|
|
39
46
|
tokensUsed: data.tokensUsed || 0,
|
|
40
47
|
tokensRemaining: data.tokensRemaining || 0,
|
|
41
|
-
model: data.model || selectedModel,
|
|
48
|
+
model: data.model || (hidePromptEditor ? model : selectedModel),
|
|
42
49
|
cost: data.cost,
|
|
43
50
|
};
|
|
44
|
-
setValue(data.text);
|
|
45
51
|
setLastResponse(generativeResponse);
|
|
46
52
|
setTokenBalance(data.tokensRemaining);
|
|
53
|
+
// Show success toast
|
|
54
|
+
addToast({
|
|
55
|
+
color: "success",
|
|
56
|
+
title: "Génération réussie",
|
|
57
|
+
description: `${generativeResponse.tokensUsed} tokens utilisés`,
|
|
58
|
+
});
|
|
47
59
|
if (onChange) {
|
|
48
60
|
onChange(data.text, generativeResponse);
|
|
49
61
|
}
|
|
@@ -51,6 +63,12 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
51
63
|
catch (err) {
|
|
52
64
|
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
53
65
|
setError(errorMessage);
|
|
66
|
+
// Show error toast
|
|
67
|
+
addToast({
|
|
68
|
+
color: "danger",
|
|
69
|
+
title: "Erreur de génération",
|
|
70
|
+
description: errorMessage,
|
|
71
|
+
});
|
|
54
72
|
if (onError) {
|
|
55
73
|
onError(err);
|
|
56
74
|
}
|
|
@@ -59,23 +77,26 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
59
77
|
setIsGenerating(false);
|
|
60
78
|
}
|
|
61
79
|
}, [
|
|
80
|
+
userInput,
|
|
62
81
|
userPrompt,
|
|
63
82
|
selectedModel,
|
|
64
|
-
|
|
83
|
+
hidePromptEditor,
|
|
84
|
+
defaultPrompt,
|
|
85
|
+
model,
|
|
65
86
|
apiEndpoint,
|
|
66
87
|
onChange,
|
|
67
88
|
onError,
|
|
68
89
|
isGenerating,
|
|
69
90
|
]);
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
if (onChange) {
|
|
73
|
-
onChange(newValue);
|
|
74
|
-
}
|
|
91
|
+
const handleInputChange = (newValue) => {
|
|
92
|
+
setUserInput(newValue);
|
|
75
93
|
};
|
|
76
|
-
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-2", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && (_jsx("p", { className: "text-sm text-gray-500 mb-2", children: description })), !isGenerating && (_jsx(Textarea, { value:
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
94
|
+
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-2", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && (_jsx("p", { className: "text-sm text-gray-500 mb-2", children: description })), isGenerating && (_jsxs("div", { className: "w-full border border-default-200 p-5 rounded-xl", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Skeleton, { className: "w-lg bg-content-2 h-8 rounded-lg" }), _jsx(Skeleton, { className: "w-md bg-content-2 h-4 rounded-lg" }), _jsx(Skeleton, { className: "w-sm bg-content-2 h-4 rounded-lg" })] }), _jsx("div", { className: "mt-2", children: _jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", label: "G\u00E9n\u00E9ration en cours..." }) })] })), !isGenerating && (_jsx(Textarea, { value: userInput, onChange: (e) => handleInputChange(e.target.value), placeholder: placeholder, disabled: disabled || isGenerating, minRows: minRows, maxRows: maxRows, endContent: _jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onClick: () => {
|
|
95
|
+
if (hidePromptEditor) {
|
|
96
|
+
handleGenerate();
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
setIsModalOpen(true);
|
|
100
|
+
}
|
|
101
|
+
}, disabled: disabled || isGenerating || !userInput.trim(), title: "G\u00E9n\u00E9rer avec l'IA", children: _jsx(Wand2, { size: 18 }) }) })), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] })) }), lastResponse && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [lastResponse.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: lastResponse.model }), lastResponse.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", lastResponse.cost.toFixed(4)] }))] }))] }), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-2 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }), !hidePromptEditor && (_jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", backdrop: "blur", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Sparkles, { size: 20 }), _jsx("span", { children: "G\u00E9n\u00E9rer du texte avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Prompt de g\u00E9n\u00E9ration", placeholder: "D\u00E9crivez ce que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis pour obtenir de meilleurs r\u00E9sultats" }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => setSelectedModel(Array.from(keys)[0]), description: "GPT-4o-mini est plus rapide et \u00E9conomique", children: [_jsx(SelectItem, { children: "GPT-4o Mini (Rapide)" }, "gpt-4o-mini"), _jsx(SelectItem, { children: "GPT-4o (Avanc\u00E9)" }, "gpt-4o"), _jsx(SelectItem, { children: "GPT-3.5 Turbo (\u00C9conomique)" }, "gpt-3.5-turbo")] }), userInput && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-1", children: "Contexte actuel:" }), _jsx("p", { className: "text-xs text-gray-600 dark:text-gray-400 line-clamp-3", children: userInput })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onClick: () => handleGenerate(userPrompt), disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Sparkles, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) }))] }));
|
|
81
102
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/module-ai",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Module de génération IA (texte et images) avec gestion de tokens pour LastBrain",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -34,12 +34,12 @@
|
|
|
34
34
|
"@heroui/theme": "^2.4.23",
|
|
35
35
|
"lucide-react": "^0.554.0",
|
|
36
36
|
"next": "^16.0.4",
|
|
37
|
-
"openai": "^
|
|
37
|
+
"openai": "^6.9.1",
|
|
38
38
|
"react": "^19.0.0",
|
|
39
39
|
"react-dom": "^19.0.0",
|
|
40
40
|
"zod": "^3.23.8",
|
|
41
|
-
"@lastbrain/core": "0.1.
|
|
42
|
-
"@lastbrain/ui": "0.1.
|
|
41
|
+
"@lastbrain/core": "0.1.17",
|
|
42
|
+
"@lastbrain/ui": "0.1.20"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"next": ">=15.0.0"
|