@lastbrain/module-ai 2.0.15 → 2.0.18

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.
@@ -126,8 +126,8 @@ const buildConfig = {
126
126
  menu: {
127
127
  auth: [
128
128
  {
129
- title: "Mes Tokens",
130
- description: "Historique et balance des tokens IA",
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: "Gestion Tokens",
141
- description: "Gestion des tokens utilisateurs",
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: "Packs de Tokens",
150
- description: "Gestion des packs de tokens",
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: "Tokens IA",
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":"UserTokenTab.d.ts","sourceRoot":"","sources":["../../../src/components/admin/UserTokenTab.tsx"],"names":[],"mappings":"AAeA,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA6OzD"}
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("Échec chargement détails tokens");
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("Erreur lors du chargement des tokens:", 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: "Erreur lors du chargement des données",
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: "Description requise",
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("Échec de l'ajustement");
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" ? "Tokens crédités" : "Tokens débités",
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: "Erreur lors de l'ajustement",
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: "tokens disponibles" })] }) })] }), _jsxs(Card, { className: "flex-1 ", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: "Ajuster le solde" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx(Input, { type: "number", label: "Montant", placeholder: "1000", value: adjustmentAmount, onChange: (e) => setAdjustmentAmount(e.target.value), min: "1", endContent: _jsx("span", { className: "text-sm text-gray-500", children: "tokens" }) }), _jsx(Input, { label: "Description", placeholder: "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: "Cr\u00E9diter" }), _jsx(Button, { color: "danger", variant: "bordered", startContent: _jsx(Minus, { size: 16 }), onPress: () => handleAdjustment("debit"), isLoading: processing, isDisabled: !adjustmentAmount || !adjustmentDescription.trim(), children: "D\u00E9biter" })] })] })] })] }), _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: "Historique des transactions" })] }) }), _jsx(CardBody, { children: ledger.length === 0 ? (_jsx("p", { className: "text-center text-gray-500 py-4", children: "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: ["Solde: ", entry.balance_after.toLocaleString()] })] })] }, entry.id))) })) })] })] }));
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"AdminTokenPacksPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/AdminTokenPacksPage.tsx"],"names":[],"mappings":"AAyCA,wBAAgB,mBAAmB,4CAyTlC"}
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 ? "Pack mis à jour" : "Pack créé",
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("Êtes-vous sûr de vouloir supprimer ce pack ?"))
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({ color: "success", title: "Pack supprimé" });
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: "Token Packs" }), _jsx("p", { className: "text-gray-500", children: "Gestion des packs de tokens disponibles \u00E0 l'achat" })] }), _jsx(Button, { color: "primary", onPress: handleCreate, startContent: _jsx(Plus, { size: 20 }), children: "Nouveau pack" })] }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs(Table, { "aria-label": "Token packs", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "NOM" }), _jsx(TableColumn, { children: "TOKENS" }), _jsx(TableColumn, { children: "PRIX" }), _jsx(TableColumn, { children: "STATUT" }), _jsx(TableColumn, { children: "ORDRE" }), _jsx(TableColumn, { children: "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(), " 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 ? "Actif" : "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 ? "Modifier le pack" : "Nouveau pack" }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Input, { label: "Nom", placeholder: "Pack Starter", value: formData.name, onChange: (e) => setFormData({ ...formData, name: e.target.value }) }), _jsx(Textarea, { label: "Description", placeholder: "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: "Nombre de tokens", type: "number", value: formData.tokens.toString(), onChange: (e) => setFormData({
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: "Stripe Price ID (optionnel)", 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: "Pack actif" })] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onPress: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onPress: handleSave, children: editingPack ? "Mettre à jour" : "Créer" })] })] }) })] }));
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":"AA+DA,wBAAgB,aAAa,4CAuY5B"}
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("Erreur lors du chargement des données");
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: "Erreur lors du chargement des données",
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: "Gestion des Tokens IA" }), _jsx("p", { className: "text-gray-500", children: "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: "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: "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: "Cr\u00E9dits 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: "tokens ajout\u00E9s" })] }) }), _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: "D\u00E9bits 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: "tokens consomm\u00E9s" })] }) }), _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: "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: "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: "P\u00E9riode d'analyse" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": "S\u00E9lectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
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 trouv\u00E9" })] })) : (_jsxs(Table, { "aria-label": "Soldes par utilisateur", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "UTILISATEUR" }), _jsx(TableColumn, { children: "SOLDE ACTUEL" }), _jsx(TableColumn, { children: "TOTAL AJOUT\u00C9" }), _jsx(TableColumn, { children: "TOTAL UTILIS\u00C9" }), _jsx(TableColumn, { children: "DERNI\u00C8RE ACTIVIT\u00C9" })] }), _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
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: "Transactions du mois s\u00E9lectionn\u00E9" }) }), _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: "Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "DATE" }), _jsx(TableColumn, { children: "UTILISATEUR" }), _jsx(TableColumn, { children: "TYPE" }), _jsx(TableColumn, { children: "DESCRIPTION" }), _jsx(TableColumn, { children: "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
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,4CA4dxB"}
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 || "Erreur lors du checkout",
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: "Mes Tokens IA" }), _jsx("p", { className: "text-gray-500", children: "G\u00E9rez 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: "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: "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: "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: "tokens achet\u00E9" })] }) }), _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: "Total utilis\u00E9" })] }), _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: "tokens consomm\u00E9s" })] }) }), _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: "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: "tokens cr\u00E9dit\u00E9s" })] }) })] }), 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: "Statistiques du mois" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": "S\u00E9lectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
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: "Ajout\u00E9s" }), _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: "Utilis\u00E9s" }), _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: "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: "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: "Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { className: "min-w-[120px]", children: "DATE" }), _jsx(TableColumn, { children: "TYPE" }), _jsx(TableColumn, { children: "DESCRIPTION" }), _jsx(TableColumn, { children: "MONTANT" }), _jsx(TableColumn, { children: "PROMPT" }), _jsx(TableColumn, { align: "end", children: "SOLDE APR\u00C8S" })] }), _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
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: "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: "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: "Acheter" })] }) }, pack.id))) })) })] })] }));
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"ImageGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/ImageGenerative.tsx"],"names":[],"mappings":"AAyCA,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;AAiCD,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;IACvE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,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,2CAiiBtB"}
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;AAiCD,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;IACvE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,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";
@@ -47,6 +48,7 @@ const imageStyles = [
47
48
  { key: "pop-art", label: "Pop Art", description: "Style pop art vibrant" },
48
49
  ];
49
50
  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, }) {
51
+ const t = useModuleTranslation("ai");
50
52
  const [isGenerating, setIsGenerating] = useState(false);
51
53
  const [generatedImage, setGeneratedImage] = useState(null);
52
54
  const [error, setError] = useState(null);
@@ -91,7 +93,9 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
91
93
  });
92
94
  if (!response.ok) {
93
95
  const errorData = await response.json();
94
- throw new Error(errorData.error || "Erreur lors de la génération");
96
+ throw new Error(errorData.error ||
97
+ t("error.image_generation_failed") ||
98
+ "Erreur lors de la génération");
95
99
  }
96
100
  const data = await response.json();
97
101
  const imageResponse = {
@@ -107,8 +111,8 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
107
111
  // Show success toast
108
112
  addToast({
109
113
  color: "success",
110
- title: "Image générée avec succès",
111
- description: `${imageResponse.tokensUsed} tokens utilisés, ${imageResponse.tokensRemaining} restants`,
114
+ title: t("toast.image_generated") || "Image générée avec succès",
115
+ description: `${imageResponse.tokensUsed} ${t("toast.tokens_used") || "tokens utilisés"}, ${imageResponse.tokensRemaining} ${t("toast.remaining") || "restants"}`,
112
116
  });
113
117
  if (onChange) {
114
118
  onChange(data.imageUrl, imageResponse);
@@ -120,7 +124,7 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
120
124
  // Show error toast
121
125
  addToast({
122
126
  color: "danger",
123
- title: "Erreur de génération d'image",
127
+ title: t("toast.image_generation_error") || "Erreur de génération d'image",
124
128
  description: errorMessage,
125
129
  });
126
130
  if (onError) {
@@ -158,20 +162,33 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
158
162
  window.URL.revokeObjectURL(url);
159
163
  }
160
164
  catch (error) {
161
- console.error("Erreur lors du téléchargement:", error);
165
+ console.error(t("error.download_failed") || "Erreur lors du téléchargement:", error);
162
166
  }
163
167
  }, [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: "Description de l'image", placeholder: "D\u00E9crivez l'image que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis et d\u00E9taill\u00E9 pour de meilleurs r\u00E9sultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
168
+ 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") ||
169
+ "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") ||
170
+ "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") ||
171
+ "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`) ||
172
+ style.description })] }) }, style.key))) }), _jsxs(Select, { label: t("image.model_label") || "Modèle IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
165
173
  const newModel = Array.from(keys)[0];
166
174
  setSelectedModel(newModel);
167
- // Ajuster la taille/qualité selon le modèle
168
175
  if (newModel === "dall-e-2") {
169
176
  setSelectedQuality("standard");
170
177
  if (!["256x256", "512x512", "1024x1024"].includes(selectedSize)) {
171
178
  setSelectedSize("1024x1024");
172
179
  }
173
180
  }
174
- }, 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"] }) })] })), _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 ? "Génération..." : "Générer" })] })) : (_jsx(Button, { color: "primary", onClick: () => setIsModalOpen(true), disabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating ? "Génération..." : "Générer une image" })), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] })) }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), preview && (_jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: "T\u00E9l\u00E9charger" }))] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: "G\u00E9n\u00E9ration de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }), _jsx(Modal, { 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: "G\u00E9n\u00E9rer une image avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Description de l'image", placeholder: "D\u00E9crivez l'image que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis et d\u00E9taill\u00E9 pour de meilleurs r\u00E9sultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
181
+ }, description: t("image.model_description") ||
182
+ "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") ||
183
+ "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") ||
184
+ "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
185
+ ? t("button.generating") || "Génération..."
186
+ : t("button.generate") || "Générer" })] })) : (_jsx(Button, { color: "primary", onClick: () => setIsModalOpen(true), disabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating
187
+ ? t("button.generating") || "Génération..."
188
+ : 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") ||
189
+ "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") ||
190
+ "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") ||
191
+ "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
192
  const newModel = Array.from(keys)[0];
176
193
  setSelectedModel(newModel);
177
194
  if (newModel === "dall-e-2") {
@@ -180,5 +197,8 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
180
197
  setSelectedSize("1024x1024");
181
198
  }
182
199
  }
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: "Aper\u00E7u 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: "Annuler" }), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(ImageIcon, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) })] }));
200
+ }, 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") ||
201
+ "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
202
+ ? t("button.generating") || "Génération..."
203
+ : t("button.generate") || "Générer" })] })] }) })] }));
184
204
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TextareaGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/TextareaGenerative.tsx"],"names":[],"mappings":"AAoBA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED,wBAAgB,kBAAkB,CAAC,EACjC,aAAkB,EAClB,KAAqB,EACrB,WAA6D,EAC7D,YAAiB,EACjB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,OAAW,EACX,OAAY,EACZ,WAAqC,EACrC,gBAAuB,EACvB,KAAK,EACL,WAAW,EACX,gBAAwB,EACxB,sBAA8B,GAC/B,EAAE,uBAAuB,2CAqRzB"}
1
+ {"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,2CAuTzB"}
@@ -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 || "Erreur lors de la génération");
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,24 @@ 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-lg bg-content-2 h-8 rounded-lg" }), _jsx(Skeleton, { className: "w-md bg-content-2 h-4 rounded-lg" }), _jsx(Skeleton, { className: "w-sm bg-content-2 h-4 rounded-lg" })] }), _jsx("div", { className: "mt-2", children: _jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", label: "G\u00E9n\u00E9ration en cours..." }) })] })), !isGenerating && (_jsx(Textarea, { value: userInput, onChange: (e) => handleInputChange(e.target.value), placeholder: placeholder, disabled: disabled || isGenerating, minRows: minRows, maxRows: maxRows, endContent: _jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onClick: () => {
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-lg bg-content-2 h-8 rounded-lg" }), _jsx(Skeleton, { className: "w-md bg-content-2 h-4 rounded-lg" }), _jsx(Skeleton, { className: "w-sm bg-content-2 h-4 rounded-lg" })] }), _jsx("div", { className: "mt-2", children: _jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": 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 || isGenerating || !userInput.trim(), title: "G\u00E9n\u00E9rer avec l'IA", children: _jsx(Wand2, { size: 18 }) }) })), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] })) }), lastResponse && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [lastResponse.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: lastResponse.model }), lastResponse.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", lastResponse.cost.toFixed(4)] }))] }))] }), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-2 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }), !hidePromptEditor && (_jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", backdrop: "blur", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Sparkles, { size: 20 }), _jsx("span", { children: "G\u00E9n\u00E9rer du texte avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Prompt de g\u00E9n\u00E9ration", placeholder: "D\u00E9crivez ce que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis pour obtenir de meilleurs r\u00E9sultats" }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => setSelectedModel(Array.from(keys)[0]), description: "GPT-4o-mini est plus rapide et \u00E9conomique", children: [_jsx(SelectItem, { children: "GPT-4o Mini (Rapide)" }, "gpt-4o-mini"), _jsx(SelectItem, { children: "GPT-4o (Avanc\u00E9)" }, "gpt-4o"), _jsx(SelectItem, { children: "GPT-3.5 Turbo (\u00C9conomique)" }, "gpt-3.5-turbo")] }), userInput && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-1", children: "Contexte actuel:" }), _jsx("p", { className: "text-xs text-gray-600 dark:text-gray-400 line-clamp-3", children: userInput })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onClick: () => handleGenerate(userPrompt), disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Sparkles, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) }))] }));
112
+ }, disabled: disabled || isGenerating || !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") ||
113
+ "Décrivez ce que vous souhaitez générer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: t("modal.prompt_description") ||
114
+ "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") ||
115
+ "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
116
+ ? t("button.generating") || "Génération..."
117
+ : t("button.generate") || "Générer" })] })] }) }))] }));
102
118
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/module-ai",
3
- "version": "2.0.15",
3
+ "version": "2.0.18",
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.15",
39
- "@lastbrain/core": "^2.0.16",
40
- "@lastbrain/ui": "^2.0.16",
38
+ "@lastbrain-labs/module-core-payment-pro": "^2.0.18",
39
+ "@lastbrain/core": "^2.0.19",
40
+ "@lastbrain/ui": "^2.0.19",
41
41
  "lucide-react": "^0.554.0",
42
42
  "next": "^16.0.7",
43
43
  "openai": "^6.9.1",