@lastbrain/module-ai 2.0.15 → 2.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/ai.build.config.js +7 -7
- package/dist/api/auth/generate-text.d.ts +0 -1
- package/dist/api/auth/generate-text.d.ts.map +1 -1
- package/dist/api/auth/generate-text.js +6 -4
- package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
- package/dist/components/admin/UserTokenTab.js +25 -10
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/web/admin/AdminTokenPacksPage.d.ts.map +1 -1
- package/dist/web/admin/AdminTokenPacksPage.js +27 -10
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
- package/dist/web/admin/UserTokenPage.js +21 -10
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +20 -14
- package/dist/web/components/ButtonGenerative.d.ts +24 -0
- package/dist/web/components/ButtonGenerative.d.ts.map +1 -0
- package/dist/web/components/ButtonGenerative.js +91 -0
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +130 -9
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
- package/dist/web/components/TextareaGenerative.js +25 -7
- package/package.json +4 -4
package/dist/ai.build.config.js
CHANGED
|
@@ -126,8 +126,8 @@ const buildConfig = {
|
|
|
126
126
|
menu: {
|
|
127
127
|
auth: [
|
|
128
128
|
{
|
|
129
|
-
title: "
|
|
130
|
-
description: "
|
|
129
|
+
title: "module-ai.menu.my_tokens",
|
|
130
|
+
description: "module-ai.menu.my_tokens_desc",
|
|
131
131
|
icon: "Coins",
|
|
132
132
|
path: "/auth/ai/token",
|
|
133
133
|
order: 100,
|
|
@@ -137,8 +137,8 @@ const buildConfig = {
|
|
|
137
137
|
],
|
|
138
138
|
admin: [
|
|
139
139
|
{
|
|
140
|
-
title: "
|
|
141
|
-
description: "
|
|
140
|
+
title: "module-ai.menu.tokens_admin",
|
|
141
|
+
description: "module-ai.menu.tokens_admin_desc",
|
|
142
142
|
icon: "Coins",
|
|
143
143
|
path: "/admin/ai/user-token",
|
|
144
144
|
order: 100,
|
|
@@ -146,8 +146,8 @@ const buildConfig = {
|
|
|
146
146
|
shortcutDisplay: "⌘⇧G",
|
|
147
147
|
},
|
|
148
148
|
{
|
|
149
|
-
title: "
|
|
150
|
-
description: "
|
|
149
|
+
title: "module-ai.menu.token_packs",
|
|
150
|
+
description: "module-ai.menu.token_packs_desc",
|
|
151
151
|
icon: "Package",
|
|
152
152
|
path: "/admin/ai/token-packs",
|
|
153
153
|
order: 101,
|
|
@@ -157,7 +157,7 @@ const buildConfig = {
|
|
|
157
157
|
userTabs: [
|
|
158
158
|
{
|
|
159
159
|
key: "tokens",
|
|
160
|
-
title: "
|
|
160
|
+
title: "module-ai.user_tabs.tokens",
|
|
161
161
|
icon: "Coins",
|
|
162
162
|
componentExport: "UserTokenTab",
|
|
163
163
|
entryPoint: "components/admin/UserTokenTab",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-text.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW
|
|
1
|
+
{"version":3,"file":"generate-text.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;IAuI9C"}
|
|
@@ -22,12 +22,15 @@ export async function POST(request) {
|
|
|
22
22
|
if (!prompt) {
|
|
23
23
|
return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
|
|
24
24
|
}
|
|
25
|
+
if (!user) {
|
|
26
|
+
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
|
27
|
+
}
|
|
25
28
|
// Vérifier le solde de tokens
|
|
26
29
|
const currentBalance = await getTokenBalance(user.id);
|
|
27
|
-
const estimatedCost = Math.ceil(prompt.length / 4) + maxTokens; // Estimation approximative
|
|
28
|
-
if (currentBalance < estimatedCost) {
|
|
30
|
+
const estimatedCost = 0; //Math.ceil(prompt.length / 4) + maxTokens; // Estimation approximative
|
|
31
|
+
if (currentBalance < estimatedCost || currentBalance <= 0) {
|
|
29
32
|
return NextResponse.json({
|
|
30
|
-
error: `Solde insuffisant. Disponible: ${currentBalance}
|
|
33
|
+
error: `Solde insuffisant. Disponible: ${currentBalance}`,
|
|
31
34
|
}, { status: 402 });
|
|
32
35
|
}
|
|
33
36
|
// Construire les messages
|
|
@@ -91,7 +94,6 @@ export async function POST(request) {
|
|
|
91
94
|
tokensUsed,
|
|
92
95
|
tokensRemaining: tokenResult.balance,
|
|
93
96
|
model,
|
|
94
|
-
cost,
|
|
95
97
|
});
|
|
96
98
|
}
|
|
97
99
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserTokenTab.d.ts","sourceRoot":"","sources":["../../../src/components/admin/UserTokenTab.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"UserTokenTab.d.ts","sourceRoot":"","sources":["../../../src/components/admin/UserTokenTab.tsx"],"names":[],"mappings":"AAgBA,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA0RzD"}
|
|
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import { Card, CardBody, CardHeader, Button, Input, Spinner, addToast, Chip, } from "@lastbrain/ui";
|
|
5
5
|
import { Plus, Minus, History, Coins } from "lucide-react";
|
|
6
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
6
7
|
export function UserTokenTab({ userId }) {
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
7
9
|
const [loading, setLoading] = useState(true);
|
|
8
10
|
const [balance, setBalance] = useState(0);
|
|
9
11
|
const [ledger, setLedger] = useState([]);
|
|
@@ -16,7 +18,8 @@ export function UserTokenTab({ userId }) {
|
|
|
16
18
|
setLoading(true);
|
|
17
19
|
const res = await fetch(`/api/ai/admin/user-token/${userId}`);
|
|
18
20
|
if (!res.ok)
|
|
19
|
-
throw new Error("
|
|
21
|
+
throw new Error(t("admin.user_token.error.loading") ||
|
|
22
|
+
"Échec chargement détails tokens");
|
|
20
23
|
const data = await res.json();
|
|
21
24
|
const currentBalance = data.balance || 0;
|
|
22
25
|
setBalance(currentBalance);
|
|
@@ -33,16 +36,19 @@ export function UserTokenTab({ userId }) {
|
|
|
33
36
|
display_description: entry.meta?.reason ||
|
|
34
37
|
entry.description ||
|
|
35
38
|
entry.type ||
|
|
39
|
+
t("admin.user_token.transaction_label") ||
|
|
36
40
|
"Transaction",
|
|
37
41
|
};
|
|
38
42
|
});
|
|
39
43
|
setLedger(computed);
|
|
40
44
|
}
|
|
41
45
|
catch (error) {
|
|
42
|
-
console.error("
|
|
46
|
+
console.error(t("admin.user_token.error.loading_tokens") ||
|
|
47
|
+
"Erreur lors du chargement des tokens:", error);
|
|
43
48
|
addToast({
|
|
44
49
|
color: "danger",
|
|
45
|
-
title: "
|
|
50
|
+
title: t("admin.user_token.error.loading_data") ||
|
|
51
|
+
"Erreur lors du chargement des données",
|
|
46
52
|
});
|
|
47
53
|
}
|
|
48
54
|
finally {
|
|
@@ -58,14 +64,15 @@ export function UserTokenTab({ userId }) {
|
|
|
58
64
|
if (isNaN(amount) || amount <= 0) {
|
|
59
65
|
addToast({
|
|
60
66
|
color: "danger",
|
|
61
|
-
title: "Montant invalide",
|
|
67
|
+
title: t("admin.user_token.error.invalid_amount") || "Montant invalide",
|
|
62
68
|
});
|
|
63
69
|
return;
|
|
64
70
|
}
|
|
65
71
|
if (!adjustmentDescription.trim()) {
|
|
66
72
|
addToast({
|
|
67
73
|
color: "danger",
|
|
68
|
-
title: "
|
|
74
|
+
title: t("admin.user_token.error.description_required") ||
|
|
75
|
+
"Description requise",
|
|
69
76
|
});
|
|
70
77
|
return;
|
|
71
78
|
}
|
|
@@ -82,11 +89,14 @@ export function UserTokenTab({ userId }) {
|
|
|
82
89
|
}),
|
|
83
90
|
});
|
|
84
91
|
if (!response.ok) {
|
|
85
|
-
throw new Error("
|
|
92
|
+
throw new Error(t("admin.user_token.error.adjustment_failed") ||
|
|
93
|
+
"Échec de l'ajustement");
|
|
86
94
|
}
|
|
87
95
|
addToast({
|
|
88
96
|
color: "success",
|
|
89
|
-
title: type === "credit"
|
|
97
|
+
title: type === "credit"
|
|
98
|
+
? t("admin.user_token.success.credited") || "Tokens crédités"
|
|
99
|
+
: t("admin.user_token.success.debited") || "Tokens débités",
|
|
90
100
|
});
|
|
91
101
|
// Reset
|
|
92
102
|
setAdjustmentAmount("");
|
|
@@ -95,10 +105,11 @@ export function UserTokenTab({ userId }) {
|
|
|
95
105
|
await fetchTokenData();
|
|
96
106
|
}
|
|
97
107
|
catch (error) {
|
|
98
|
-
console.error("Erreur:", error);
|
|
108
|
+
console.error(t("admin.user_token.error.adjustment") || "Erreur:", error);
|
|
99
109
|
addToast({
|
|
100
110
|
color: "danger",
|
|
101
|
-
title: "
|
|
111
|
+
title: t("admin.user_token.error.adjustment_error") ||
|
|
112
|
+
"Erreur lors de l'ajustement",
|
|
102
113
|
});
|
|
103
114
|
}
|
|
104
115
|
finally {
|
|
@@ -108,5 +119,9 @@ export function UserTokenTab({ userId }) {
|
|
|
108
119
|
if (loading) {
|
|
109
120
|
return (_jsx("div", { className: "flex justify-center items-center min-h-64", children: _jsx(Spinner, { size: "lg" }) }));
|
|
110
121
|
}
|
|
111
|
-
return (_jsxs("div", { className: "w-full space-y-6 mt-4", children: [_jsxs("div", { className: "w-full flex flex-col md:flex-row gap-6", children: [_jsxs(Card, { className: "min-w-72", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Coins, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: "Solde de tokens" })] }) }), _jsx(CardBody, { className: "flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-4xl font-bold text-primary", children: balance.toLocaleString() }), _jsx("p", { className: "text-sm text-gray-500 mt-1", children:
|
|
122
|
+
return (_jsxs("div", { className: "w-full space-y-6 mt-4", children: [_jsxs("div", { className: "w-full flex flex-col md:flex-row gap-6", children: [_jsxs(Card, { className: "min-w-72", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Coins, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("admin.user_token.balance.title") || "Solde de tokens" })] }) }), _jsx(CardBody, { className: "flex items-center justify-center", children: _jsxs("div", { className: "text-center", children: [_jsx("p", { className: "text-4xl font-bold text-primary", children: balance.toLocaleString() }), _jsx("p", { className: "text-sm text-gray-500 mt-1", children: t("admin.user_token.balance.available") ||
|
|
123
|
+
"tokens disponibles" })] }) })] }), _jsxs(Card, { className: "flex-1 ", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("admin.user_token.adjustment.title") || "Ajuster le solde" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx(Input, { type: "number", label: t("admin.user_token.adjustment.amount") || "Montant", placeholder: t("admin.user_token.adjustment.amount_placeholder") || "1000", value: adjustmentAmount, onChange: (e) => setAdjustmentAmount(e.target.value), min: "1", endContent: _jsx("span", { className: "text-sm text-gray-500", children: t("chip.tokens") || "tokens" }) }), _jsx(Input, { label: t("admin.user_token.adjustment.description") || "Description", placeholder: t("admin.user_token.adjustment.description_placeholder") ||
|
|
124
|
+
"Raison de l'ajustement...", value: adjustmentDescription, onChange: (e) => setAdjustmentDescription(e.target.value), maxLength: 200 }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { color: "success", startContent: _jsx(Plus, { size: 16 }), onPress: () => handleAdjustment("credit"), isLoading: processing, isDisabled: !adjustmentAmount || !adjustmentDescription.trim(), children: t("admin.user_token.adjustment.credit_button") || "Créditer" }), _jsx(Button, { color: "danger", variant: "bordered", startContent: _jsx(Minus, { size: 16 }), onPress: () => handleAdjustment("debit"), isLoading: processing, isDisabled: !adjustmentAmount || !adjustmentDescription.trim(), children: t("admin.user_token.adjustment.debit_button") || "Débiter" })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(History, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("admin.user_token.history.title") ||
|
|
125
|
+
"Historique des transactions" })] }) }), _jsx(CardBody, { children: ledger.length === 0 ? (_jsx("p", { className: "text-center text-gray-500 py-4", children: t("admin.user_token.history.no_transactions") ||
|
|
126
|
+
"Aucune transaction" })) : (_jsx("div", { className: "space-y-2", children: ledger.map((entry) => (_jsxs("div", { className: "flex items-center justify-between p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsxs("div", { className: "flex-1", children: [_jsx("p", { className: "text-sm font-medium", children: entry.display_description }), _jsx("p", { className: "text-xs text-gray-500", children: new Date(entry.created_at).toLocaleString() })] }), _jsxs("div", { className: "text-right", children: [_jsxs(Chip, { size: "sm", color: entry.display_amount > 0 ? "success" : "danger", variant: "flat", children: [entry.display_amount > 0 ? "+" : "", entry.display_amount.toLocaleString()] }), _jsxs("p", { className: "text-xs text-gray-500 mt-1", children: [t("admin.user_token.history.balance_label") || "Solde", ":", " ", entry.balance_after.toLocaleString()] })] })] }, entry.id))) })) })] })] }));
|
|
112
127
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { TextareaGenerative } from "./web/components/TextareaGenerative";
|
|
2
2
|
export { ImageGenerative } from "./web/components/ImageGenerative";
|
|
3
|
+
export { ButtonGenerative } from "./web/components/ButtonGenerative";
|
|
3
4
|
export { TokenPage } from "./web/auth/TokenPage";
|
|
4
5
|
export { UserTokenPage } from "./web/admin/UserTokenPage";
|
|
5
6
|
export { AdminTokenPacksPage } from "./web/admin/AdminTokenPacksPage";
|
|
@@ -8,5 +9,6 @@ export { Doc } from "./components/Doc";
|
|
|
8
9
|
export { Doc as AiModuleDoc } from "./components/Doc";
|
|
9
10
|
export type { GenerativeResponse, TextareaGenerativeProps, } from "./web/components/TextareaGenerative";
|
|
10
11
|
export type { GenerativeImageResponse, ImageGenerativeProps, } from "./web/components/ImageGenerative";
|
|
12
|
+
export type { ButtonGenerativeResponse, ButtonGenerativeProps, } from "./web/components/ButtonGenerative";
|
|
11
13
|
export { default as buildConfig } from "./ai.build.config";
|
|
12
14
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAGrE,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAGjD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAGtE,OAAO,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AAG/D,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,GAAG,IAAI,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGtD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,qCAAqC,CAAC;AAE7C,YAAY,EACV,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,kCAAkC,CAAC;AAE1C,YAAY,EACV,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,mCAAmC,CAAC;AAG3C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Client Components - Composants réutilisables
|
|
2
2
|
export { TextareaGenerative } from "./web/components/TextareaGenerative";
|
|
3
3
|
export { ImageGenerative } from "./web/components/ImageGenerative";
|
|
4
|
+
export { ButtonGenerative } from "./web/components/ButtonGenerative";
|
|
4
5
|
// Pages Auth
|
|
5
6
|
export { TokenPage } from "./web/auth/TokenPage";
|
|
6
7
|
// Pages Admin
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminTokenPacksPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/AdminTokenPacksPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AdminTokenPacksPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/AdminTokenPacksPage.tsx"],"names":[],"mappings":"AA0CA,wBAAgB,mBAAmB,4CAkXlC"}
|
|
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import { Card, CardBody, Button, Input, Textarea, Switch, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Spinner, addToast, Chip, } from "@lastbrain/ui";
|
|
5
5
|
import { Plus, Edit, Trash2 } from "lucide-react";
|
|
6
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
6
7
|
export function AdminTokenPacksPage() {
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
7
9
|
const [packs, setPacks] = useState([]);
|
|
8
10
|
const [loading, setLoading] = useState(true);
|
|
9
11
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
@@ -26,7 +28,7 @@ export function AdminTokenPacksPage() {
|
|
|
26
28
|
setLoading(true);
|
|
27
29
|
const response = await fetch("/api/ai/admin/token-packs");
|
|
28
30
|
if (!response.ok)
|
|
29
|
-
throw new Error("Erreur lors du chargement");
|
|
31
|
+
throw new Error(t("admin.packs.error.loading") || "Erreur lors du chargement");
|
|
30
32
|
const result = await response.json();
|
|
31
33
|
setPacks(result.data || []);
|
|
32
34
|
}
|
|
@@ -77,10 +79,12 @@ export function AdminTokenPacksPage() {
|
|
|
77
79
|
body: JSON.stringify(formData),
|
|
78
80
|
});
|
|
79
81
|
if (!response.ok)
|
|
80
|
-
throw new Error("Erreur lors de la sauvegarde");
|
|
82
|
+
throw new Error(t("admin.packs.error.saving") || "Erreur lors de la sauvegarde");
|
|
81
83
|
addToast({
|
|
82
84
|
color: "success",
|
|
83
|
-
title: editingPack
|
|
85
|
+
title: editingPack
|
|
86
|
+
? t("admin.packs.success.updated") || "Pack mis à jour"
|
|
87
|
+
: t("admin.packs.success.created") || "Pack créé",
|
|
84
88
|
});
|
|
85
89
|
setIsModalOpen(false);
|
|
86
90
|
fetchPacks();
|
|
@@ -90,15 +94,19 @@ export function AdminTokenPacksPage() {
|
|
|
90
94
|
}
|
|
91
95
|
};
|
|
92
96
|
const handleDelete = async (id) => {
|
|
93
|
-
if (!confirm("
|
|
97
|
+
if (!confirm(t("admin.packs.confirm.delete") ||
|
|
98
|
+
"Êtes-vous sûr de vouloir supprimer ce pack ?"))
|
|
94
99
|
return;
|
|
95
100
|
try {
|
|
96
101
|
const response = await fetch(`/api/ai/admin/token-packs/${id}`, {
|
|
97
102
|
method: "DELETE",
|
|
98
103
|
});
|
|
99
104
|
if (!response.ok)
|
|
100
|
-
throw new Error("Erreur lors de la suppression");
|
|
101
|
-
addToast({
|
|
105
|
+
throw new Error(t("admin.packs.error.deleting") || "Erreur lors de la suppression");
|
|
106
|
+
addToast({
|
|
107
|
+
color: "success",
|
|
108
|
+
title: t("admin.packs.success.deleted") || "Pack supprimé",
|
|
109
|
+
});
|
|
102
110
|
fetchPacks();
|
|
103
111
|
}
|
|
104
112
|
catch (error) {
|
|
@@ -114,14 +122,23 @@ export function AdminTokenPacksPage() {
|
|
|
114
122
|
if (loading) {
|
|
115
123
|
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
|
|
116
124
|
}
|
|
117
|
-
return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "flex justify-between items-center mb-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children:
|
|
125
|
+
return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "flex justify-between items-center mb-6", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("admin.packs.page.title") || "Token Packs" }), _jsx("p", { className: "text-gray-500", children: t("admin.packs.page.description") ||
|
|
126
|
+
"Gestion des packs de tokens disponibles à l'achat" })] }), _jsx(Button, { color: "primary", onPress: handleCreate, startContent: _jsx(Plus, { size: 20 }), children: t("admin.packs.button.new") || "Nouveau pack" })] }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs(Table, { "aria-label": t("admin.packs.table.aria_label") || "Token packs", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: t("admin.packs.table.column_name") || "NOM" }), _jsx(TableColumn, { children: t("admin.packs.table.column_tokens") || "TOKENS" }), _jsx(TableColumn, { children: t("admin.packs.table.column_price") || "PRIX" }), _jsx(TableColumn, { children: t("admin.packs.table.column_status") || "STATUT" }), _jsx(TableColumn, { children: t("admin.packs.table.column_order") || "ORDRE" }), _jsx(TableColumn, { children: t("admin.packs.table.column_actions") || "ACTIONS" })] }), _jsx(TableBody, { children: packs.map((pack) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: pack.name }), pack.description && (_jsx("p", { className: "text-sm text-gray-500", children: pack.description }))] }) }), _jsx(TableCell, { children: _jsxs(Chip, { size: "sm", variant: "flat", color: "primary", children: [pack.tokens.toLocaleString(), " ", t("chip.tokens") || "tokens"] }) }), _jsx(TableCell, { children: _jsx("span", { className: "font-semibold", children: formatPrice(pack.price_cents, pack.currency) }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: pack.is_active ? "success" : "default", children: pack.is_active
|
|
127
|
+
? t("admin.packs.status.active") || "Actif"
|
|
128
|
+
: t("admin.packs.status.inactive") || "Inactif" }) }), _jsx(TableCell, { children: pack.sort_order }), _jsx(TableCell, { children: _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { isIconOnly: true, size: "sm", variant: "light", onPress: () => handleEdit(pack), children: _jsx(Edit, { size: 16 }) }), _jsx(Button, { isIconOnly: true, size: "sm", variant: "light", color: "danger", onPress: () => handleDelete(pack.id), children: _jsx(Trash2, { size: 16 }) })] }) })] }, pack.id))) })] }) }) }), _jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: editingPack
|
|
129
|
+
? t("admin.packs.modal.edit_title") || "Modifier le pack"
|
|
130
|
+
: t("admin.packs.modal.new_title") || "Nouveau pack" }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Input, { label: t("admin.packs.form.name") || "Nom", placeholder: t("admin.packs.form.name_placeholder") || "Pack Starter", value: formData.name, onChange: (e) => setFormData({ ...formData, name: e.target.value }) }), _jsx(Textarea, { label: t("admin.packs.form.description") || "Description", placeholder: t("admin.packs.form.description_placeholder") ||
|
|
131
|
+
"Description du pack", value: formData.description, onChange: (e) => setFormData({ ...formData, description: e.target.value }) }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: t("admin.packs.form.tokens") || "Nombre de tokens", type: "number", value: formData.tokens.toString(), onChange: (e) => setFormData({
|
|
118
132
|
...formData,
|
|
119
133
|
tokens: parseInt(e.target.value) || 0,
|
|
120
|
-
}) }), _jsx(Input, { label: "Prix (centimes)", type: "number", value: formData.price_cents.toString(), onChange: (e) => setFormData({
|
|
134
|
+
}) }), _jsx(Input, { label: t("admin.packs.form.price") || "Prix (centimes)", type: "number", value: formData.price_cents.toString(), onChange: (e) => setFormData({
|
|
121
135
|
...formData,
|
|
122
136
|
price_cents: parseInt(e.target.value) || 0,
|
|
123
|
-
}) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: "Devise", value: formData.currency, onChange: (e) => setFormData({ ...formData, currency: e.target.value }) }), _jsx(Input, { label: "Ordre d'affichage", type: "number", value: formData.sort_order.toString(), onChange: (e) => setFormData({
|
|
137
|
+
}) })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsx(Input, { label: t("admin.packs.form.currency") || "Devise", value: formData.currency, onChange: (e) => setFormData({ ...formData, currency: e.target.value }) }), _jsx(Input, { label: t("admin.packs.form.sort_order") || "Ordre d'affichage", type: "number", value: formData.sort_order.toString(), onChange: (e) => setFormData({
|
|
124
138
|
...formData,
|
|
125
139
|
sort_order: parseInt(e.target.value) || 0,
|
|
126
|
-
}) })] }), _jsx(Input, { label:
|
|
140
|
+
}) })] }), _jsx(Input, { label: t("admin.packs.form.stripe_id") ||
|
|
141
|
+
"Stripe Price ID (optionnel)", placeholder: t("admin.packs.form.stripe_id_placeholder") || "price_...", value: formData.stripe_price_id, onChange: (e) => setFormData({ ...formData, stripe_price_id: e.target.value }) }), _jsx(Switch, { isSelected: formData.is_active, onValueChange: (checked) => setFormData({ ...formData, is_active: checked }), children: t("admin.packs.form.active") || "Pack actif" })] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onPress: () => setIsModalOpen(false), children: t("admin.packs.button.cancel") || "Annuler" }), _jsx(Button, { color: "primary", onPress: handleSave, children: editingPack
|
|
142
|
+
? t("admin.packs.button.update") || "Mettre à jour"
|
|
143
|
+
: t("admin.packs.button.create") || "Créer" })] })] }) })] }));
|
|
127
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AAgEA,wBAAgB,aAAa,4CAwc5B"}
|
|
@@ -3,7 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useEffect, useCallback } from "react";
|
|
4
4
|
import { Card, CardBody, CardHeader, Spinner, Chip, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Avatar, Input, addToast, } from "@lastbrain/ui";
|
|
5
5
|
import { Coins, TrendingUp, TrendingDown, Calendar, Search, Users, AlertCircle, } from "lucide-react";
|
|
6
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
6
7
|
export function UserTokenPage() {
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
7
9
|
const [loading, setLoading] = useState(true);
|
|
8
10
|
const [users, setUsers] = useState([]);
|
|
9
11
|
const [transactions, setTransactions] = useState([]);
|
|
@@ -22,7 +24,8 @@ export function UserTokenPage() {
|
|
|
22
24
|
// Récupérer toutes les données tokens de tous les utilisateurs
|
|
23
25
|
const response = await fetch("/api/ai/admin/user-token");
|
|
24
26
|
if (!response.ok) {
|
|
25
|
-
throw new Error("
|
|
27
|
+
throw new Error(t("admin.tokens.error.loading") ||
|
|
28
|
+
"Erreur lors du chargement des données");
|
|
26
29
|
}
|
|
27
30
|
const data = await response.json();
|
|
28
31
|
// Traiter les données utilisateurs
|
|
@@ -50,10 +53,11 @@ export function UserTokenPage() {
|
|
|
50
53
|
});
|
|
51
54
|
}
|
|
52
55
|
catch (error) {
|
|
53
|
-
console.error("Erreur:", error);
|
|
56
|
+
console.error(t("admin.tokens.error.loading") || "Erreur:", error);
|
|
54
57
|
addToast({
|
|
55
58
|
color: "danger",
|
|
56
|
-
title: "
|
|
59
|
+
title: t("admin.tokens.error.loading") ||
|
|
60
|
+
"Erreur lors du chargement des données",
|
|
57
61
|
});
|
|
58
62
|
}
|
|
59
63
|
finally {
|
|
@@ -74,10 +78,10 @@ export function UserTokenPage() {
|
|
|
74
78
|
};
|
|
75
79
|
const getTypeLabel = (type) => {
|
|
76
80
|
const labels = {
|
|
77
|
-
purchase: "Achat",
|
|
78
|
-
gift: "Cadeau",
|
|
79
|
-
use: "Utilisation",
|
|
80
|
-
adjust: "Ajustement",
|
|
81
|
+
purchase: t("tokens.type.purchase") || "Achat",
|
|
82
|
+
gift: t("tokens.type.gift") || "Cadeau",
|
|
83
|
+
use: t("tokens.type.use") || "Utilisation",
|
|
84
|
+
adjust: t("tokens.type.adjust") || "Ajustement",
|
|
81
85
|
};
|
|
82
86
|
return labels[type] || type;
|
|
83
87
|
};
|
|
@@ -96,12 +100,19 @@ export function UserTokenPage() {
|
|
|
96
100
|
if (loading) {
|
|
97
101
|
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
|
|
98
102
|
}
|
|
99
|
-
return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children:
|
|
103
|
+
return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("admin.tokens.page.title") || "Gestion des Tokens IA" }), _jsx("p", { className: "text-gray-500", children: t("admin.tokens.page.description") ||
|
|
104
|
+
"Vue d'ensemble de la consommation des tokens par utilisateur" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4 mb-6", children: [_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Coins, { size: 24, className: "text-primary" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.total_balance") || "Solde total" })] }), _jsx("p", { className: "text-3xl font-bold text-primary", children: monthlyStats?.totalBalance.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.in_circulation") ||
|
|
105
|
+
"tokens en circulation" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingUp, { size: 24, className: "text-success" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.monthly_credits") || "Crédits du mois" })] }), _jsx("p", { className: "text-3xl font-bold text-success", children: monthlyStats?.totalCredit.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.tokens_added") || "tokens ajoutés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingDown, { size: 24, className: "text-danger" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.monthly_debits") || "Débits du mois" })] }), _jsx("p", { className: "text-3xl font-bold text-danger", children: monthlyStats?.totalDebit.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.tokens_consumed") || "tokens consommés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Users, { size: 24, className: "text-warning" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("admin.tokens.stats.active_users") || "Utilisateurs actifs" })] }), _jsx("p", { className: "text-3xl font-bold text-warning", children: monthlyStats?.activeUsers }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("admin.tokens.stats.this_month") || "ce mois-ci" })] }) })] }), _jsx(Card, { className: "mb-6", children: _jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("admin.tokens.period.title") || "Période d'analyse" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": t("admin.tokens.period.select") || "Sélectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
|
|
100
106
|
month: "long",
|
|
101
107
|
year: "numeric",
|
|
102
|
-
}) }, month))) })] }) }) }), _jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsx("h3", { className: "text-lg font-semibold", children: "Soldes par utilisateur" }), _jsx(Input, { size: "sm", placeholder: "Rechercher un utilisateur...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), startContent: _jsx(Search, { size: 16 }), className: "w-64" })] }) }), _jsx(CardBody, { children: filteredUsers.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Users, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: "Aucun utilisateur
|
|
108
|
+
}) }, month))) })] }) }) }), _jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsx("h3", { className: "text-lg font-semibold", children: t("admin.tokens.users.title") || "Soldes par utilisateur" }), _jsx(Input, { size: "sm", placeholder: t("admin.tokens.users.search") || "Rechercher un utilisateur...", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value), startContent: _jsx(Search, { size: 16 }), className: "w-64" })] }) }), _jsx(CardBody, { children: filteredUsers.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Users, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("admin.tokens.users.no_users") || "Aucun utilisateur trouvé" })] })) : (_jsxs(Table, { "aria-label": t("admin.tokens.users.aria_label") || "Soldes par utilisateur", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: t("admin.tokens.users.column_user") || "UTILISATEUR" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_balance") || "SOLDE ACTUEL" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_added") || "TOTAL AJOUTÉ" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_used") || "TOTAL UTILISÉ" }), _jsx(TableColumn, { children: t("admin.tokens.users.column_activity") ||
|
|
109
|
+
"DERNIÈRE ACTIVITÉ" })] }), _jsx(TableBody, { children: filteredUsers.map((user) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Avatar, { src: user.avatarUrl, name: user.fullName || user.email, size: "sm" }), _jsxs("div", { children: [_jsx("p", { className: "font-medium", children: user.fullName || user.email }), user.fullName && (_jsx("p", { className: "text-xs text-gray-500", children: user.email }))] })] }) }), _jsx(TableCell, { children: _jsx("span", { className: "font-semibold text-primary", children: user.balance.toLocaleString() }) }), _jsx(TableCell, { children: _jsxs("span", { className: "text-success", children: ["+", user.totalAdded.toLocaleString()] }) }), _jsx(TableCell, { children: _jsxs("span", { className: "text-danger", children: ["-", user.totalUsed.toLocaleString()] }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: user.lastActivity
|
|
103
110
|
? formatDate(user.lastActivity)
|
|
104
|
-
: "-" }) })] }, user.userId))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children:
|
|
111
|
+
: "-" }) })] }, user.userId))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("admin.tokens.transactions.title") ||
|
|
112
|
+
"Transactions du mois sélectionné" }) }), _jsx(CardBody, { children: transactions.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(AlertCircle, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("admin.tokens.transactions.no_transactions") ||
|
|
113
|
+
"Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": t("admin.tokens.transactions.aria_label") ||
|
|
114
|
+
"Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: t("admin.tokens.transactions.column_date") || "DATE" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_user") || "UTILISATEUR" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_type") || "TYPE" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_description") ||
|
|
115
|
+
"DESCRIPTION" }), _jsx(TableColumn, { children: t("admin.tokens.transactions.column_amount") || "MONTANT" })] }), _jsx(TableBody, { children: transactions.map((transaction) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: formatDate(transaction.created_at) }) }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Avatar, { src: transaction.avatarUrl, name: transaction.fullName || transaction.email, size: "sm" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: transaction.fullName || transaction.email }), transaction.fullName && (_jsx("p", { className: "text-xs text-gray-500", children: transaction.email }))] })] }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: getTypeColor(transaction.type), children: getTypeLabel(transaction.type) }) }), _jsx(TableCell, { children: _jsx("div", { className: "max-w-md", children: _jsx("p", { className: "text-sm truncate", children: transaction.description || transaction.model || "-" }) }) }), _jsx(TableCell, { children: _jsxs("span", { className: `font-semibold ${transaction.amount > 0
|
|
105
116
|
? "text-success"
|
|
106
117
|
: "text-danger"}`, children: [transaction.amount > 0 ? "+" : "", transaction.amount.toLocaleString()] }) })] }, transaction.id))) })] })) })] })] }));
|
|
107
118
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/TokenPage.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,SAAS,
|
|
1
|
+
{"version":3,"file":"TokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/TokenPage.tsx"],"names":[],"mappings":"AAoEA,wBAAgB,SAAS,4CAkhBxB"}
|
|
@@ -3,8 +3,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useState, useEffect, useCallback } from "react";
|
|
4
4
|
import { Card, CardBody, CardHeader, Spinner, Chip, Button, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, addToast, } from "@lastbrain/ui";
|
|
5
5
|
import { Coins, TrendingUp, TrendingDown, Calendar, ShoppingCart, AlertCircle, Gift, Eye, } from "lucide-react";
|
|
6
|
-
import { useAuth } from "@lastbrain/core";
|
|
6
|
+
import { useAuth, useModuleTranslation } from "@lastbrain/core";
|
|
7
7
|
export function TokenPage() {
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
8
9
|
const { user } = useAuth();
|
|
9
10
|
const [loading, setLoading] = useState(true);
|
|
10
11
|
const [balance, setBalance] = useState(null);
|
|
@@ -27,7 +28,7 @@ export function TokenPage() {
|
|
|
27
28
|
// Récupérer le solde et l'historique
|
|
28
29
|
const response = await fetch("/api/ai/user/tokens");
|
|
29
30
|
if (!response.ok) {
|
|
30
|
-
throw new Error("Erreur lors du chargement des données");
|
|
31
|
+
throw new Error(t("tokens.error.loading") || "Erreur lors du chargement des données");
|
|
31
32
|
}
|
|
32
33
|
const data = await response.json();
|
|
33
34
|
// Historique complet pour calculs globaux
|
|
@@ -73,10 +74,10 @@ export function TokenPage() {
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
catch (error) {
|
|
76
|
-
console.error("Erreur:", error);
|
|
77
|
+
console.error(t("tokens.error.loading") || "Erreur:", error);
|
|
77
78
|
addToast({
|
|
78
79
|
color: "danger",
|
|
79
|
-
title: "Erreur lors du chargement des données",
|
|
80
|
+
title: t("tokens.error.loading") || "Erreur lors du chargement des données",
|
|
80
81
|
});
|
|
81
82
|
}
|
|
82
83
|
finally {
|
|
@@ -97,10 +98,10 @@ export function TokenPage() {
|
|
|
97
98
|
};
|
|
98
99
|
const getTypeLabel = (type) => {
|
|
99
100
|
const labels = {
|
|
100
|
-
purchase: "Achat",
|
|
101
|
-
gift: "Cadeau",
|
|
102
|
-
use: "Utilisation",
|
|
103
|
-
adjust: "Ajustement",
|
|
101
|
+
purchase: t("tokens.type.purchase") || "Achat",
|
|
102
|
+
gift: t("tokens.type.gift") || "Cadeau",
|
|
103
|
+
use: t("tokens.type.use") || "Utilisation",
|
|
104
|
+
adjust: t("tokens.type.adjust") || "Ajustement",
|
|
104
105
|
};
|
|
105
106
|
return labels[type] || type;
|
|
106
107
|
};
|
|
@@ -123,7 +124,7 @@ export function TokenPage() {
|
|
|
123
124
|
});
|
|
124
125
|
if (!response.ok) {
|
|
125
126
|
const error = await response.json();
|
|
126
|
-
throw new Error(error.error || "Erreur lors du checkout");
|
|
127
|
+
throw new Error(error.error || t("tokens.error.checkout") || "Erreur lors du checkout");
|
|
127
128
|
}
|
|
128
129
|
const data = await response.json();
|
|
129
130
|
if (data.checkout_url) {
|
|
@@ -133,7 +134,9 @@ export function TokenPage() {
|
|
|
133
134
|
catch (error) {
|
|
134
135
|
addToast({
|
|
135
136
|
color: "danger",
|
|
136
|
-
title: error.message ||
|
|
137
|
+
title: error.message ||
|
|
138
|
+
t("tokens.error.checkout") ||
|
|
139
|
+
"Erreur lors du checkout",
|
|
137
140
|
});
|
|
138
141
|
}
|
|
139
142
|
finally {
|
|
@@ -155,15 +158,18 @@ export function TokenPage() {
|
|
|
155
158
|
return tokens.toLocaleString();
|
|
156
159
|
};
|
|
157
160
|
if (!user) {
|
|
158
|
-
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsxs("div", { className: "text-center", children: [_jsx(AlertCircle, { className: "mx-auto mb-4", size: 48 }), _jsx("p", { className: "text-gray-500", children: "Veuillez vous connecter" })] }) }));
|
|
161
|
+
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsxs("div", { className: "text-center", children: [_jsx(AlertCircle, { className: "mx-auto mb-4", size: 48 }), _jsx("p", { className: "text-gray-500", children: t("tokens.error.login_required") || "Veuillez vous connecter" })] }) }));
|
|
159
162
|
}
|
|
160
163
|
if (loading) {
|
|
161
164
|
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
|
|
162
165
|
}
|
|
163
|
-
return (_jsxs("div", { className: " container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children:
|
|
166
|
+
return (_jsxs("div", { className: " container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("tokens.page.title") || "Mes Tokens IA" }), _jsx("p", { className: "text-gray-500", children: t("tokens.page.description") ||
|
|
167
|
+
"Gérez votre solde et consultez votre historique de consommation" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4 mb-6", children: [_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Coins, { size: 24, className: "text-primary" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.current") || "Solde actuel" })] }), _jsx("p", { className: "text-4xl font-bold text-primary", children: formatTokensShort(balance?.balance || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.available") || "tokens disponibles" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingUp, { size: 24, className: "text-success" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.total") || "Total" })] }), _jsx("p", { className: "text-4xl font-bold text-success", children: formatTokensShort(balance?.totalAdded || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.purchased") || "tokens acheté" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingDown, { size: 24, className: "text-danger" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.total_used") || "Total utilisé" })] }), _jsx("p", { className: "text-4xl font-bold text-danger", children: formatTokensShort(balance?.totalUsed || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.consumed") || "tokens consommés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Gift, { size: 24, className: "text-warning" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.gifted") || "Tokens offerts / ajustements" })] }), _jsx("p", { className: "text-4xl font-bold text-warning", children: formatTokensShort(balance?.totalGifted || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.credited") || "tokens crédités" })] }) })] }), monthlyStats && (_jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.stats.monthly_title") || "Statistiques du mois" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": t("tokens.stats.select_month") || "Sélectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
|
|
164
168
|
month: "long",
|
|
165
169
|
year: "numeric",
|
|
166
|
-
}) }, month))) })] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-900/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: "
|
|
170
|
+
}) }, month))) })] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-900/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.added") || "Ajoutés" }), _jsxs("p", { className: "text-2xl font-bold text-success", children: ["+", formatTokensShort(monthlyStats.added)] })] }), _jsxs("div", { className: "text-center p-4 bg-danger-50 dark:bg-danger-900/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.used") || "Utilisés" }), _jsxs("p", { className: "text-2xl font-bold text-danger", children: ["-", formatTokensShort(monthlyStats.used)] })] }), _jsxs("div", { className: "text-center p-4 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.net_balance") || "Solde net" }), _jsxs("p", { className: `text-2xl font-bold ${monthlyStats.net >= 0 ? "text-success" : "text-danger"}`, children: [monthlyStats.net >= 0 ? "+" : "", formatTokensShort(Math.abs(monthlyStats.net))] })] })] }) })] })), _jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.history.title") || "Historique des transactions" }) }), _jsx(CardBody, { children: transactions.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Coins, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("tokens.history.no_transactions") ||
|
|
171
|
+
"Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": t("tokens.history.aria_label") || "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { className: "min-w-[120px]", children: t("tokens.history.column_date") || "DATE" }), _jsx(TableColumn, { children: t("tokens.history.column_type") || "TYPE" }), _jsx(TableColumn, { children: t("tokens.history.column_description") || "DESCRIPTION" }), _jsx(TableColumn, { children: t("tokens.history.column_amount") || "MONTANT" }), _jsx(TableColumn, { children: t("tokens.history.column_prompt") || "PROMPT" }), _jsx(TableColumn, { align: "end", children: t("tokens.history.column_balance") || "SOLDE APRÈS" })] }), _jsx(TableBody, { children: transactions.map((transaction) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: " text-sm text-gray-600", children: formatDate(transaction.created_at) }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: getTypeColor(transaction.type), children: getTypeLabel(transaction.type) }) }), _jsx(TableCell, { children: _jsx("div", { className: "max-w-md", children: _jsx("p", { className: "text-sm truncate", children: transaction.description || transaction.model || "-" }) }) }), _jsx(TableCell, { children: _jsxs("span", { className: `font-semibold ${transaction.amount > 0
|
|
167
172
|
? "text-success"
|
|
168
|
-
: "text-danger"}`, children: [transaction.amount > 0 ? "+" : "", formatTokensShort(Math.abs(transaction.amount))] }) }), _jsx(TableCell, { children: transaction.prompt && (_jsxs("div", { className: "flex flex-inline gap-2 items-center", children: [_jsx("p", { className: "max-w-xs truncate", children: transaction.prompt?.slice(0, 100) }), _jsx(Button, { size: "sm", variant: "flat", isIconOnly: true, className: "truncate max-w-xs", startContent: _jsx(Eye, { size: 12 }) })] })) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: formatTokensShort(transaction.running_balance) }) })] }, transaction.id))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ShoppingCart, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: "Acheter des tokens" })] }) }), _jsx(CardBody, { children: tokenPacks.length === 0 ? (_jsxs("div", { className: "text-center py-8", children: [_jsx(ShoppingCart, { size: 48, className: "mx-auto mb-4 text-gray-300 dark:text-gray-600" }), _jsx("p", { className: "text-gray-500 mb-4", children:
|
|
173
|
+
: "text-danger"}`, children: [transaction.amount > 0 ? "+" : "", formatTokensShort(Math.abs(transaction.amount))] }) }), _jsx(TableCell, { children: transaction.prompt && (_jsxs("div", { className: "flex flex-inline gap-2 items-center", children: [_jsx("p", { className: "max-w-xs truncate", children: transaction.prompt?.slice(0, 100) }), _jsx(Button, { size: "sm", variant: "flat", isIconOnly: true, className: "truncate max-w-xs", startContent: _jsx(Eye, { size: 12 }) })] })) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: formatTokensShort(transaction.running_balance) }) })] }, transaction.id))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ShoppingCart, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.buy.title") || "Acheter des tokens" })] }) }), _jsx(CardBody, { children: tokenPacks.length === 0 ? (_jsxs("div", { className: "text-center py-8", children: [_jsx(ShoppingCart, { size: 48, className: "mx-auto mb-4 text-gray-300 dark:text-gray-600" }), _jsx("p", { className: "text-gray-500 mb-4", children: t("tokens.buy.no_packs") ||
|
|
174
|
+
"Aucun pack disponible pour le moment" })] })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: tokenPacks.map((pack) => (_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsx("div", { className: "mx-auto", children: _jsx(Coins, { size: 24, className: "mx-auto mb-2 text-gray-400" }) }), _jsx("h4", { className: "text-lg font-bold mb-2", children: pack.name }), pack.description && (_jsx("p", { className: "text-sm text-gray-500 mb-4", children: pack.description })), _jsxs("div", { className: "mb-4", children: [_jsx("p", { className: "text-3xl font-bold text-primary", children: formatTokensShort(pack.tokens) }), _jsx("p", { className: "text-xs text-gray-400", children: t("chip.tokens") || "tokens" })] }), _jsx("p", { className: "text-xl font-semibold mb-4", children: formatPrice(pack.price_cents, pack.currency) }), _jsx(Button, { color: "primary", className: "w-full", onPress: () => handleBuyTokens(pack.id), isLoading: checkoutLoading === pack.id, startContent: checkoutLoading !== pack.id && (_jsx(ShoppingCart, { size: 16 })), children: t("tokens.buy.button") || "Acheter" })] }) }, pack.id))) })) })] })] }));
|
|
169
175
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface ButtonGenerativeResponse {
|
|
2
|
+
text: string;
|
|
3
|
+
tokensUsed: number;
|
|
4
|
+
tokensRemaining: number;
|
|
5
|
+
model: string;
|
|
6
|
+
cost?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface ButtonGenerativeProps {
|
|
9
|
+
label: string;
|
|
10
|
+
size?: "sm" | "md" | "lg";
|
|
11
|
+
className?: string;
|
|
12
|
+
prompt: string;
|
|
13
|
+
model?: string;
|
|
14
|
+
onResult: (response: ButtonGenerativeResponse) => void;
|
|
15
|
+
onError?: (error: Error) => void;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
apiEndpoint?: string;
|
|
18
|
+
showTokenBalance?: boolean;
|
|
19
|
+
maxTokens?: number;
|
|
20
|
+
temperature?: number;
|
|
21
|
+
skeleton?: boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function ButtonGenerative({ label, size, className, prompt, model, onResult, onError, disabled, apiEndpoint, showTokenBalance, maxTokens, temperature, skeleton, }: ButtonGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=ButtonGenerative.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ButtonGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/ButtonGenerative.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,wBAAwB;IACvC,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,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACvD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,IAAW,EACX,SAAc,EACd,MAAM,EACN,KAAqB,EACrB,QAAQ,EACR,OAAO,EACP,QAAgB,EAChB,WAAqC,EACrC,gBAAuB,EACvB,SAAgB,EAChB,WAAiB,EACjB,QAAe,GAChB,EAAE,qBAAqB,2CA+JvB"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useCallback } from "react";
|
|
4
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
5
|
+
import { Button, Chip, Progress, Skeleton, addToast } from "@lastbrain/ui";
|
|
6
|
+
import { Sparkles, AlertCircle } from "lucide-react";
|
|
7
|
+
export function ButtonGenerative({ label, size = "md", className = "", prompt, model = "gpt-4o-mini", onResult, onError, disabled = false, apiEndpoint = "/api/ai/generate-text", showTokenBalance = true, maxTokens = 3000, temperature = 0.7, skeleton = true, }) {
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
9
|
+
const [isGenerating, setIsGenerating] = useState(false);
|
|
10
|
+
const [tokenBalance, setTokenBalance] = useState(null);
|
|
11
|
+
const [error, setError] = useState(null);
|
|
12
|
+
const handleGenerate = useCallback(async () => {
|
|
13
|
+
if (!prompt || isGenerating) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
setIsGenerating(true);
|
|
17
|
+
setError(null);
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetch(apiEndpoint, {
|
|
20
|
+
method: "POST",
|
|
21
|
+
headers: { "Content-Type": "application/json" },
|
|
22
|
+
body: JSON.stringify({
|
|
23
|
+
prompt,
|
|
24
|
+
model,
|
|
25
|
+
maxTokens,
|
|
26
|
+
temperature,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
if (!response.ok) {
|
|
30
|
+
const errorData = await response.json();
|
|
31
|
+
// Handle specific error codes
|
|
32
|
+
if (response.status === 402) {
|
|
33
|
+
throw new Error(errorData.error ||
|
|
34
|
+
t("error.insufficient_tokens") ||
|
|
35
|
+
"Solde insuffisant");
|
|
36
|
+
}
|
|
37
|
+
throw new Error(errorData.error ||
|
|
38
|
+
t("error.generation_failed") ||
|
|
39
|
+
"Erreur lors de la génération");
|
|
40
|
+
}
|
|
41
|
+
const data = await response.json();
|
|
42
|
+
const generativeResponse = {
|
|
43
|
+
text: data.text,
|
|
44
|
+
tokensUsed: data.tokensUsed || 0,
|
|
45
|
+
tokensRemaining: data.tokensRemaining || 0,
|
|
46
|
+
model: data.model || model,
|
|
47
|
+
cost: data.cost,
|
|
48
|
+
};
|
|
49
|
+
setTokenBalance(data.tokensRemaining);
|
|
50
|
+
// Show success toast with credit info
|
|
51
|
+
addToast({
|
|
52
|
+
color: "success",
|
|
53
|
+
title: t("toast.generation_success") || "Génération réussie",
|
|
54
|
+
description: `${generativeResponse.tokensUsed} ${t("toast.tokens_used") || "tokens utilisés"} - ${t("toast.remaining") || "Restants"}: ${generativeResponse.tokensRemaining}`,
|
|
55
|
+
});
|
|
56
|
+
onResult(generativeResponse);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
const errorMessage = err instanceof Error ? err.message : "Erreur inconnue";
|
|
60
|
+
setError(errorMessage);
|
|
61
|
+
// Show error toast
|
|
62
|
+
addToast({
|
|
63
|
+
color: "danger",
|
|
64
|
+
title: t("toast.generation_error") || "Erreur de génération",
|
|
65
|
+
description: errorMessage,
|
|
66
|
+
});
|
|
67
|
+
if (onError) {
|
|
68
|
+
if (err instanceof Error) {
|
|
69
|
+
onError(err);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
onError(new Error(errorMessage));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
setIsGenerating(false);
|
|
78
|
+
}
|
|
79
|
+
}, [
|
|
80
|
+
prompt,
|
|
81
|
+
model,
|
|
82
|
+
isGenerating,
|
|
83
|
+
maxTokens,
|
|
84
|
+
temperature,
|
|
85
|
+
apiEndpoint,
|
|
86
|
+
onResult,
|
|
87
|
+
onError,
|
|
88
|
+
t,
|
|
89
|
+
]);
|
|
90
|
+
return (_jsx("div", { className: className, children: _jsxs("div", { className: "flex flex-col gap-2", children: [isGenerating && skeleton && (_jsxs("div", { className: "w-full border border-default-200 p-4 rounded-xl", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Skeleton, { className: "w-lg bg-content-2 h-6 rounded-lg" }), _jsx(Skeleton, { className: "w-md bg-content-2 h-3 rounded-lg" }), _jsx(Skeleton, { className: "w-sm bg-content-2 h-3 rounded-lg" })] }), _jsx("div", { className: "mt-2", children: _jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": t("progress.generating") || "Génération en cours...", label: t("progress.generating") || "Génération en cours..." }) })] })), _jsx(Button, { color: "primary", size: size, variant: "flat", startContent: _jsx(Sparkles, { size: 16 }), onPress: handleGenerate, disabled: disabled || isGenerating || !prompt.trim(), title: label, isLoading: isGenerating, className: "w-full sm:w-auto", children: label }), showTokenBalance && tokenBalance !== null && (_jsx("div", { className: "flex items-center gap-2", children: _jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " ", t("chip.tokens_remaining") || "tokens restants"] }) })), 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 })] }))] }) }));
|
|
91
|
+
}
|
|
@@ -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":"AA0CA,MAAM,WAAW,uBAAuB;IACtC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,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;AAsID,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,CACT,gBAAgB,EAAE,MAAM,EACxB,QAAQ,CAAC,EAAE,uBAAuB,KAC/B,IAAI,CAAC;IACV,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;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;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,EACV,OAAc,EACd,MAAc,GACf,EAAE,oBAAoB,2CA0lBtB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useCallback } from "react";
|
|
4
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
4
5
|
import { Button, Chip, Progress, Card, CardBody, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Textarea, Select, SelectItem, addToast, } from "@lastbrain/ui";
|
|
5
6
|
import { Image as ImageIcon, Loader2, AlertCircle, Download, Wand2, } from "lucide-react";
|
|
6
7
|
import Image from "next/image";
|
|
@@ -45,8 +46,110 @@ const imageStyles = [
|
|
|
45
46
|
description: "Peinture à l'aquarelle",
|
|
46
47
|
},
|
|
47
48
|
{ key: "pop-art", label: "Pop Art", description: "Style pop art vibrant" },
|
|
49
|
+
// 🔥 Styles ajoutés
|
|
50
|
+
{
|
|
51
|
+
key: "oil-painting",
|
|
52
|
+
label: "Peinture à l'huile",
|
|
53
|
+
description: "Textures riches, style classique",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
key: "pixel-art",
|
|
57
|
+
label: "Pixel Art",
|
|
58
|
+
description: "Graphismes rétro pixelisés",
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: "low-poly",
|
|
62
|
+
label: "Low Poly",
|
|
63
|
+
description: "Modélisation simple en polygones",
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: "cyberpunk",
|
|
67
|
+
label: "Cyberpunk",
|
|
68
|
+
description: "Esthétique futuriste néon sombre",
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
key: "noir",
|
|
72
|
+
label: "Film Noir",
|
|
73
|
+
description: "Ambiance dramatique noir et blanc",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "vaporwave",
|
|
77
|
+
label: "Vaporwave",
|
|
78
|
+
description: "Style rétro futur rose/bleu pastel",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
key: "fantasy",
|
|
82
|
+
label: "Fantasy",
|
|
83
|
+
description: "Univers magique et héros épiques",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
key: "comic-book",
|
|
87
|
+
label: "Comic Book",
|
|
88
|
+
description: "Style bande dessinée américaine",
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
key: "flat-design",
|
|
92
|
+
label: "Flat Design",
|
|
93
|
+
description: "Illustration minimaliste sans ombres",
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
key: "isometric",
|
|
97
|
+
label: "Isométrique",
|
|
98
|
+
description: "Perspective 3D isométrique",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
key: "steampunk",
|
|
102
|
+
label: "Steampunk",
|
|
103
|
+
description: "Univers mécanique rétro industriel",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
key: "cinematic",
|
|
107
|
+
label: "Cinematic",
|
|
108
|
+
description: "Style film avec mise en scène dramatique",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
key: "retro",
|
|
112
|
+
label: "Rétro",
|
|
113
|
+
description: "Style années 80/90",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
key: "minimalist",
|
|
117
|
+
label: "Minimaliste",
|
|
118
|
+
description: "Formes simples, ambiance épurée",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
key: "neon",
|
|
122
|
+
label: "Néon",
|
|
123
|
+
description: "Glow électrique et couleurs vibrantes",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: "line-art",
|
|
127
|
+
label: "Line Art",
|
|
128
|
+
description: "Illustration en lignes fines",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
key: "pastel",
|
|
132
|
+
label: "Pastel",
|
|
133
|
+
description: "Couleurs douces et lumineuses",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
key: "dark-fantasy",
|
|
137
|
+
label: "Dark Fantasy",
|
|
138
|
+
description: "Fantasy sombre, atmosphère dramatique",
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
key: "surreal",
|
|
142
|
+
label: "Surréaliste",
|
|
143
|
+
description: "Style rêve éveillé, étrange",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "photographic-food",
|
|
147
|
+
label: "Gastronomique",
|
|
148
|
+
description: "Photo culinaire professionnelle",
|
|
149
|
+
},
|
|
48
150
|
];
|
|
49
151
|
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, preview = true, inline = false, }) {
|
|
152
|
+
const t = useModuleTranslation("ai");
|
|
50
153
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
51
154
|
const [generatedImage, setGeneratedImage] = useState(null);
|
|
52
155
|
const [error, setError] = useState(null);
|
|
@@ -91,7 +194,9 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
91
194
|
});
|
|
92
195
|
if (!response.ok) {
|
|
93
196
|
const errorData = await response.json();
|
|
94
|
-
throw new Error(errorData.error ||
|
|
197
|
+
throw new Error(errorData.error ||
|
|
198
|
+
t("error.image_generation_failed") ||
|
|
199
|
+
"Erreur lors de la génération");
|
|
95
200
|
}
|
|
96
201
|
const data = await response.json();
|
|
97
202
|
const imageResponse = {
|
|
@@ -107,8 +212,8 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
107
212
|
// Show success toast
|
|
108
213
|
addToast({
|
|
109
214
|
color: "success",
|
|
110
|
-
title: "Image générée avec succès",
|
|
111
|
-
description: `${imageResponse.tokensUsed} tokens utilisés, ${imageResponse.tokensRemaining} restants`,
|
|
215
|
+
title: t("toast.image_generated") || "Image générée avec succès",
|
|
216
|
+
description: `${imageResponse.tokensUsed} ${t("toast.tokens_used") || "tokens utilisés"}, ${imageResponse.tokensRemaining} ${t("toast.remaining") || "restants"}`,
|
|
112
217
|
});
|
|
113
218
|
if (onChange) {
|
|
114
219
|
onChange(data.imageUrl, imageResponse);
|
|
@@ -120,7 +225,7 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
120
225
|
// Show error toast
|
|
121
226
|
addToast({
|
|
122
227
|
color: "danger",
|
|
123
|
-
title: "Erreur de génération d'image",
|
|
228
|
+
title: t("toast.image_generation_error") || "Erreur de génération d'image",
|
|
124
229
|
description: errorMessage,
|
|
125
230
|
});
|
|
126
231
|
if (onError) {
|
|
@@ -158,20 +263,33 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
158
263
|
window.URL.revokeObjectURL(url);
|
|
159
264
|
}
|
|
160
265
|
catch (error) {
|
|
161
|
-
console.error("Erreur lors du téléchargement:", error);
|
|
266
|
+
console.error(t("error.download_failed") || "Erreur lors du téléchargement:", error);
|
|
162
267
|
}
|
|
163
268
|
}, [generatedImage]);
|
|
164
|
-
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 && preview && (_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: [selectedSize, " \u2022 ", selectedQuality] })] })] }) })), inline ? (_jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "
|
|
269
|
+
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 && preview && (_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: t("image.prompt_label") || "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [selectedSize, " \u2022 ", selectedQuality] })] })] }) })), inline ? (_jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: t("image.description_label") || "Description de l'image", placeholder: t("image.description_placeholder") ||
|
|
270
|
+
"Décrivez l'image que vous souhaitez générer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: t("image.description_help") ||
|
|
271
|
+
"Soyez précis et détaillé pour de meilleurs résultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: t("image.style_label") || "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: t("image.style_description") ||
|
|
272
|
+
"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: t(`image.style_${style.key}`) || style.label }), _jsx("span", { className: "text-xs text-gray-500", children: t(`image.style_${style.key}_desc`) ||
|
|
273
|
+
style.description })] }) }, style.key))) }), _jsxs(Select, { label: t("image.model_label") || "Modèle IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
|
|
165
274
|
const newModel = Array.from(keys)[0];
|
|
166
275
|
setSelectedModel(newModel);
|
|
167
|
-
// Ajuster la taille/qualité selon le modèle
|
|
168
276
|
if (newModel === "dall-e-2") {
|
|
169
277
|
setSelectedQuality("standard");
|
|
170
278
|
if (!["256x256", "512x512", "1024x1024"].includes(selectedSize)) {
|
|
171
279
|
setSelectedSize("1024x1024");
|
|
172
280
|
}
|
|
173
281
|
}
|
|
174
|
-
}, description:
|
|
282
|
+
}, description: t("image.model_description") ||
|
|
283
|
+
"DALL-E 3 offre la meilleure qualité", children: [_jsx(SelectItem, { children: t("image.model_dalle3") || "DALL-E 3 (Haute qualité)" }, "dall-e-3"), _jsx(SelectItem, { children: t("image.model_dalle2") || "DALL-E 2 (Standard)" }, "dall-e-2")] }), _jsx(Select, { label: t("image.size_label") || "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: t("image.size_description") ||
|
|
284
|
+
"Tailles disponibles selon le modèle", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3000 tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4000 tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5000 tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6000-8000 tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8000-10000 tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8000-10000 tokens)" }, "1792x1024")] })) }), selectedModel === "dall-e-3" && (_jsxs(Select, { label: t("image.quality_label") || "Qualité", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: t("image.quality_description") ||
|
|
285
|
+
"HD offre plus de détails mais coûte plus de tokens", children: [_jsx(SelectItem, { children: t("image.quality_standard") || "Standard" }, "standard"), _jsx(SelectItem, { children: t("image.quality_hd") || "HD (Haute Définition)" }, "hd")] })), _jsx("div", { className: "p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: _jsxs("p", { className: "text-sm font-medium text-primary-700 dark:text-primary-300", children: [t("image.estimated_cost") || "Coût estimé:", " ", estimatedCost().toLocaleString(), " ", t("chip.tokens") || "tokens"] }) })] })), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || disabled || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Wand2, { size: 18 })), className: "w-full", children: isGenerating
|
|
286
|
+
? t("button.generating") || "Génération..."
|
|
287
|
+
: t("button.generate") || "Générer" })] })) : (_jsx(Button, { color: "primary", onPress: () => setIsModalOpen(true), isDisabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating
|
|
288
|
+
? t("button.generating") || "Génération..."
|
|
289
|
+
: t("button.generate_image") || "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(), " ", t("chip.tokens_remaining") || "tokens restants"] })) }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " ", t("chip.tokens_used") || "tokens utilisés"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), preview && (_jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: t("button.download") || "Télécharger" }))] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": t("progress.generating_image") || "Génération en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: t("progress.image_generation_message") ||
|
|
290
|
+
"Génération 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, { backdrop: "blur", 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: t("modal.image_title") || "Générer une image avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: t("image.description_label") || "Description de l'image", placeholder: t("image.description_placeholder") ||
|
|
291
|
+
"Décrivez l'image que vous souhaitez générer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: t("image.description_help") ||
|
|
292
|
+
"Soyez précis et détaillé pour de meilleurs résultats" }), !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) => {
|
|
175
293
|
const newModel = Array.from(keys)[0];
|
|
176
294
|
setSelectedModel(newModel);
|
|
177
295
|
if (newModel === "dall-e-2") {
|
|
@@ -180,5 +298,8 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
180
298
|
setSelectedSize("1024x1024");
|
|
181
299
|
}
|
|
182
300
|
}
|
|
183
|
-
}, 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")] }), _jsx(Select, { label: "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: "Tailles disponibles selon le mod\u00E8le", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3000 tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4000 tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5000 tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6000-8000 tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8000-10000 tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8000-10000 tokens)" }, "1792x1024")] })) }), selectedModel === "dall-e-3" && (_jsxs(Select, { label: "Qualit\u00E9", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: "HD offre plus de d\u00E9tails mais co\u00FBte plus de tokens", children: [_jsx(SelectItem, { children: "Standard" }, "standard"), _jsx(SelectItem, { children: "HD (Haute D\u00E9finition)" }, "hd")] })), _jsx("div", { className: "p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: _jsxs("p", { className: "text-sm font-medium text-primary-700 dark:text-primary-300", children: ["Co\u00FBt estim\u00E9: ", estimatedCost().toLocaleString(), " tokens"] }) })] })), 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: "
|
|
301
|
+
}, 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")] }), _jsx(Select, { label: "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: "Tailles disponibles selon le mod\u00E8le", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3000 tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4000 tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5000 tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6000-8000 tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8000-10000 tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8000-10000 tokens)" }, "1792x1024")] })) }), selectedModel === "dall-e-3" && (_jsxs(Select, { label: "Qualit\u00E9", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: "HD offre plus de d\u00E9tails mais co\u00FBte plus de tokens", children: [_jsx(SelectItem, { children: "Standard" }, "standard"), _jsx(SelectItem, { children: "HD (Haute D\u00E9finition)" }, "hd")] })), _jsx("div", { className: "p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: _jsxs("p", { className: "text-sm font-medium text-primary-700 dark:text-primary-300", children: ["Co\u00FBt estim\u00E9: ", estimatedCost().toLocaleString(), " tokens"] }) })] })), 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: t("image.current_preview") ||
|
|
302
|
+
"Aperçu de l'image actuelle:" }), _jsx("div", { className: "relative w-full", 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: t("button.cancel") || "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
|
|
303
|
+
? t("button.generating") || "Génération..."
|
|
304
|
+
: t("button.generate") || "Générer" })] })] }) })] }));
|
|
184
305
|
}
|
|
@@ -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,2CA2TzB"}
|
|
@@ -1,9 +1,11 @@
|
|
|
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 { useModuleTranslation } from "@lastbrain/core";
|
|
4
5
|
import { Textarea, Button, Chip, Progress, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem, Skeleton, addToast, } from "@lastbrain/ui";
|
|
5
6
|
import { Sparkles, Loader2, AlertCircle, Wand2 } from "lucide-react";
|
|
6
7
|
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, }) {
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
7
9
|
const [userInput, setUserInput] = useState(defaultValue);
|
|
8
10
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
9
11
|
const [lastResponse, setLastResponse] = useState(null);
|
|
@@ -18,8 +20,9 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
18
20
|
? defaultPrompt
|
|
19
21
|
: prompt || userPrompt;
|
|
20
22
|
const finalUserInput = hidePromptEditor ? userInput : "";
|
|
21
|
-
if (!finalPrompt || isGenerating)
|
|
23
|
+
if (!finalPrompt || isGenerating) {
|
|
22
24
|
return;
|
|
25
|
+
}
|
|
23
26
|
setIsGenerating(true);
|
|
24
27
|
setError(null);
|
|
25
28
|
if (!hidePromptEditor)
|
|
@@ -38,7 +41,9 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
38
41
|
});
|
|
39
42
|
if (!response.ok) {
|
|
40
43
|
const errorData = await response.json();
|
|
41
|
-
throw new Error(errorData.error ||
|
|
44
|
+
throw new Error(errorData.error ||
|
|
45
|
+
t("error.generation_failed") ||
|
|
46
|
+
"Erreur lors de la génération");
|
|
42
47
|
}
|
|
43
48
|
const data = await response.json();
|
|
44
49
|
const generativeResponse = {
|
|
@@ -53,8 +58,8 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
53
58
|
// Show success toast
|
|
54
59
|
addToast({
|
|
55
60
|
color: "success",
|
|
56
|
-
title: "Génération réussie",
|
|
57
|
-
description: `${generativeResponse.tokensUsed} tokens utilisés`,
|
|
61
|
+
title: t("toast.generation_success") || "Génération réussie",
|
|
62
|
+
description: `${generativeResponse.tokensUsed} ${t("toast.tokens_used") || "tokens utilisés"}`,
|
|
58
63
|
});
|
|
59
64
|
if (onChange) {
|
|
60
65
|
onChange(data.text, generativeResponse);
|
|
@@ -66,7 +71,7 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
66
71
|
// Show error toast
|
|
67
72
|
addToast({
|
|
68
73
|
color: "danger",
|
|
69
|
-
title: "Erreur de génération",
|
|
74
|
+
title: t("toast.generation_error") || "Erreur de génération",
|
|
70
75
|
description: errorMessage,
|
|
71
76
|
});
|
|
72
77
|
if (onError) {
|
|
@@ -90,13 +95,26 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
90
95
|
]);
|
|
91
96
|
const handleInputChange = (newValue) => {
|
|
92
97
|
setUserInput(newValue);
|
|
98
|
+
// Notifier le parent à chaque changement de saisie
|
|
99
|
+
if (onChange) {
|
|
100
|
+
onChange(newValue);
|
|
101
|
+
}
|
|
93
102
|
};
|
|
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-
|
|
103
|
+
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-[30%] bg-content-2 h-8 rounded-lg" }), _jsx(Skeleton, { className: "w-[50%] bg-content-2 h-4 rounded-lg" }), _jsx(Skeleton, { className: "w-[40%] bg-content-2 h-4 rounded-lg" })] }), _jsx("div", { className: "mt-2", children: _jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": t("progress.generating") || "Génération en cours...", label: t("progress.generating") || "Génération en cours..." }) })] })), !isGenerating && (_jsx(Textarea, { value: userInput, onChange: (e) => handleInputChange(e.target.value), placeholder: placeholder ||
|
|
104
|
+
t("textarea.placeholder") ||
|
|
105
|
+
"Saisissez votre texte ou générez avec l'IA...", disabled: disabled || isGenerating, minRows: minRows, maxRows: maxRows, endContent: _jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onPress: () => {
|
|
95
106
|
if (hidePromptEditor) {
|
|
96
107
|
handleGenerate();
|
|
97
108
|
}
|
|
98
109
|
else {
|
|
99
110
|
setIsModalOpen(true);
|
|
100
111
|
}
|
|
101
|
-
}, disabled: disabled ||
|
|
112
|
+
}, disabled: disabled ||
|
|
113
|
+
isGenerating ||
|
|
114
|
+
(hidePromptEditor && !userInput.trim()), title: t("button.generate_with_ai") || "Générer 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(), " ", t("chip.tokens_remaining") || "tokens restants"] })) }), lastResponse && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [lastResponse.tokensUsed, " ", t("chip.tokens_used") || "tokens utilisés"] }), _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: t("modal.title") || "Générer du texte avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: t("modal.prompt_label") || "Prompt de génération", placeholder: t("modal.prompt_placeholder") ||
|
|
115
|
+
"Décrivez ce que vous souhaitez générer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: t("modal.prompt_description") ||
|
|
116
|
+
"Soyez précis pour obtenir de meilleurs résultats" }), _jsxs(Select, { label: t("modal.model_label") || "Modèle IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => setSelectedModel(Array.from(keys)[0]), description: t("modal.model_description") ||
|
|
117
|
+
"GPT-4o-mini est plus rapide et économique", children: [_jsx(SelectItem, { children: t("modal.model_gpt4o_mini") || "GPT-4o Mini (Rapide)" }, "gpt-4o-mini"), _jsx(SelectItem, { children: t("modal.model_gpt4o") || "GPT-4o (Avancé)" }, "gpt-4o"), _jsx(SelectItem, { children: t("modal.model_gpt35") || "GPT-3.5 Turbo (Économique)" }, "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: t("modal.current_context") || "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: t("button.cancel") || "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
|
|
118
|
+
? t("button.generating") || "Génération..."
|
|
119
|
+
: t("button.generate") || "Générer" })] })] }) }))] }));
|
|
102
120
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lastbrain/module-ai",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.26",
|
|
4
4
|
"description": "Module de génération IA (texte et images) avec gestion de tokens pour LastBrain",
|
|
5
5
|
"private": false,
|
|
6
6
|
"release": {
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"@heroui/react": "^2.4.23",
|
|
36
36
|
"@heroui/system": "^2.4.23",
|
|
37
37
|
"@heroui/theme": "^2.4.23",
|
|
38
|
-
"@lastbrain-labs/module-core-payment-pro": "^2.0.
|
|
39
|
-
"@lastbrain/core": "^2.0.
|
|
40
|
-
"@lastbrain/ui": "^2.0.
|
|
38
|
+
"@lastbrain-labs/module-core-payment-pro": "^2.0.26",
|
|
39
|
+
"@lastbrain/core": "^2.0.27",
|
|
40
|
+
"@lastbrain/ui": "^2.0.27",
|
|
41
41
|
"lucide-react": "^0.554.0",
|
|
42
42
|
"next": "^16.0.7",
|
|
43
43
|
"openai": "^6.9.1",
|