@lastbrain/module-ai 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +1 -2
  2. package/dist/ai.build.config.d.ts.map +1 -1
  3. package/dist/ai.build.config.js +24 -6
  4. package/dist/api/admin/user-token/[id].d.ts +6 -1
  5. package/dist/api/admin/user-token/[id].d.ts.map +1 -1
  6. package/dist/api/admin/user-token/[id].js +16 -14
  7. package/dist/api/admin/user-token.d.ts +19 -3
  8. package/dist/api/admin/user-token.d.ts.map +1 -1
  9. package/dist/api/admin/user-token.js +85 -26
  10. package/dist/api/auth/generate-text.js +1 -1
  11. package/dist/api/auth/user-tokens.d.ts +17 -0
  12. package/dist/api/auth/user-tokens.d.ts.map +1 -0
  13. package/dist/api/auth/user-tokens.js +34 -0
  14. package/dist/components/Doc.d.ts.map +1 -1
  15. package/dist/components/Doc.js +2 -5
  16. package/dist/components/DocUsageCustom.d.ts +9 -0
  17. package/dist/components/DocUsageCustom.d.ts.map +1 -0
  18. package/dist/components/DocUsageCustom.js +70 -0
  19. package/dist/components/admin/UserTokenTab.d.ts +6 -0
  20. package/dist/components/admin/UserTokenTab.d.ts.map +1 -0
  21. package/dist/components/admin/UserTokenTab.js +112 -0
  22. package/dist/index.d.ts +1 -1
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +2 -1
  25. package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
  26. package/dist/web/admin/UserTokenPage.js +103 -2
  27. package/dist/web/auth/TokenPage.d.ts.map +1 -1
  28. package/dist/web/auth/TokenPage.js +110 -2
  29. package/dist/web/components/ImageGenerative.d.ts +6 -3
  30. package/dist/web/components/ImageGenerative.d.ts.map +1 -1
  31. package/dist/web/components/ImageGenerative.js +51 -12
  32. package/dist/web/components/TextareaGenerative.d.ts +5 -3
  33. package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
  34. package/dist/web/components/TextareaGenerative.js +33 -12
  35. package/package.json +4 -4
  36. package/supabase/migrations-down/20251125000000_ai_tokens.sql +45 -0
  37. package/supabase/migrations/20251121093113_module-ai_init.sql +0 -122
  38. package/supabase/migrations-down/20251121000000_ai_tokens.sql +0 -23
  39. package/supabase/migrations-down/20251121093113_module-ai_init.sql +0 -11
  40. /package/supabase/migrations/{20251121000000_ai_tokens.sql → 20251125000000_ai_tokens.sql} +0 -0
@@ -0,0 +1,112 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect } from "react";
4
+ import { Card, CardBody, CardHeader, Button, Input, Spinner, addToast, Chip, } from "@lastbrain/ui";
5
+ import { Plus, Minus, History, Coins } from "lucide-react";
6
+ export function UserTokenTab({ userId }) {
7
+ const [loading, setLoading] = useState(true);
8
+ const [balance, setBalance] = useState(0);
9
+ const [ledger, setLedger] = useState([]);
10
+ const [adjustmentAmount, setAdjustmentAmount] = useState("");
11
+ const [adjustmentDescription, setAdjustmentDescription] = useState("");
12
+ const [processing, setProcessing] = useState(false);
13
+ // Charger le solde et l'historique
14
+ const fetchTokenData = async () => {
15
+ try {
16
+ setLoading(true);
17
+ const res = await fetch(`/api/ai/admin/user-token/${userId}`);
18
+ if (!res.ok)
19
+ throw new Error("Échec chargement détails tokens");
20
+ const data = await res.json();
21
+ const currentBalance = data.balance || 0;
22
+ setBalance(currentBalance);
23
+ const history = data.history || data.data || [];
24
+ // Si l'API ne fournit pas balance_after, le calculer en partant du solde courant (ordre déjà décroissant)
25
+ let running = currentBalance;
26
+ const computed = history.map((entry) => {
27
+ const balanceAfter = running;
28
+ running -= entry.amount; // préparer balance pour l'entrée suivante
29
+ return {
30
+ ...entry,
31
+ balance_after: balanceAfter,
32
+ display_amount: entry.amount,
33
+ display_description: entry.meta?.reason ||
34
+ entry.description ||
35
+ entry.type ||
36
+ "Transaction",
37
+ };
38
+ });
39
+ setLedger(computed);
40
+ }
41
+ catch (error) {
42
+ console.error("Erreur lors du chargement des tokens:", error);
43
+ addToast({
44
+ color: "danger",
45
+ title: "Erreur lors du chargement des données",
46
+ });
47
+ }
48
+ finally {
49
+ setLoading(false);
50
+ }
51
+ };
52
+ useEffect(() => {
53
+ fetchTokenData();
54
+ }, [userId]);
55
+ // Ajuster le solde (crédit ou débit)
56
+ const handleAdjustment = async (type) => {
57
+ const amount = parseInt(adjustmentAmount);
58
+ if (isNaN(amount) || amount <= 0) {
59
+ addToast({
60
+ color: "danger",
61
+ title: "Montant invalide",
62
+ });
63
+ return;
64
+ }
65
+ if (!adjustmentDescription.trim()) {
66
+ addToast({
67
+ color: "danger",
68
+ title: "Description requise",
69
+ });
70
+ return;
71
+ }
72
+ try {
73
+ setProcessing(true);
74
+ const response = await fetch(`/api/ai/admin/user-token`, {
75
+ method: "POST",
76
+ headers: { "Content-Type": "application/json" },
77
+ body: JSON.stringify({
78
+ userId,
79
+ amount: type === "credit" ? amount : -amount,
80
+ type: "adjust",
81
+ reason: adjustmentDescription,
82
+ }),
83
+ });
84
+ if (!response.ok) {
85
+ throw new Error("Échec de l'ajustement");
86
+ }
87
+ addToast({
88
+ color: "success",
89
+ title: type === "credit" ? "Tokens crédités" : "Tokens débités",
90
+ });
91
+ // Reset
92
+ setAdjustmentAmount("");
93
+ setAdjustmentDescription("");
94
+ // Recharger les données
95
+ await fetchTokenData();
96
+ }
97
+ catch (error) {
98
+ console.error("Erreur:", error);
99
+ addToast({
100
+ color: "danger",
101
+ title: "Erreur lors de l'ajustement",
102
+ });
103
+ }
104
+ finally {
105
+ setProcessing(false);
106
+ }
107
+ };
108
+ if (loading) {
109
+ return (_jsx("div", { className: "flex justify-center items-center min-h-64", children: _jsx(Spinner, { size: "lg" }) }));
110
+ }
111
+ return (_jsxs("div", { className: "space-y-6 mt-4", children: [_jsxs(Card, { 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, { 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, { 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))) })) })] })] }));
112
+ }
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { TextareaGenerative } from "./web/components/TextareaGenerative.js";
2
2
  export { ImageGenerative } from "./web/components/ImageGenerative.js";
3
3
  export { TokenPage } from "./web/auth/TokenPage.js";
4
4
  export { UserTokenPage } from "./web/admin/UserTokenPage.js";
5
- export { UserTokenIdPage } from "./web/admin/UserTokenIdPage.js";
5
+ export { UserTokenTab } from "./components/admin/UserTokenTab.js";
6
6
  export { Doc } from "./components/Doc.js";
7
7
  export { Doc as AiModuleDoc } from "./components/Doc.js";
8
8
  export type { GenerativeResponse, TextareaGenerativeProps, } from "./web/components/TextareaGenerative.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGjE,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,GAAG,IAAI,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,wCAAwC,CAAC;AAEhD,YAAY,EACV,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7D,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAGlE,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,GAAG,IAAI,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,wCAAwC,CAAC;AAEhD,YAAY,EACV,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ export { ImageGenerative } from "./web/components/ImageGenerative.js";
5
5
  export { TokenPage } from "./web/auth/TokenPage.js";
6
6
  // Pages Admin
7
7
  export { UserTokenPage } from "./web/admin/UserTokenPage.js";
8
- export { UserTokenIdPage } from "./web/admin/UserTokenIdPage.js";
8
+ // Admin Components - User Tabs
9
+ export { UserTokenTab } from "./components/admin/UserTokenTab.js";
9
10
  // Documentation
10
11
  export { Doc } from "./components/Doc.js";
11
12
  export { Doc as AiModuleDoc } from "./components/Doc.js";
@@ -1 +1 @@
1
- {"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,4CAe5B"}
1
+ {"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AA+DA,wBAAgB,aAAa,4CAuY5B"}
@@ -1,6 +1,107 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Card, CardBody, CardHeader } from "@lastbrain/ui";
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { Card, CardBody, CardHeader, Spinner, Chip, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Avatar, Input, addToast, } from "@lastbrain/ui";
5
+ import { Coins, TrendingUp, TrendingDown, Calendar, Search, Users, AlertCircle, } from "lucide-react";
4
6
  export function UserTokenPage() {
5
- return (_jsx("div", { className: "container mx-auto p-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h1", { className: "text-2xl font-bold", children: "UserToken" }) }), _jsx(CardBody, { children: _jsx("p", { className: "text-default-600", children: "Contenu de la page UserToken (section: admin)" }) })] }) }));
7
+ const [loading, setLoading] = useState(true);
8
+ const [users, setUsers] = useState([]);
9
+ const [transactions, setTransactions] = useState([]);
10
+ const [selectedMonth, setSelectedMonth] = useState(new Date().toISOString().slice(0, 7));
11
+ const [monthlyStats, setMonthlyStats] = useState(null);
12
+ const [searchQuery, setSearchQuery] = useState("");
13
+ // Générer les 12 derniers mois
14
+ const availableMonths = Array.from({ length: 12 }, (_, i) => {
15
+ const date = new Date();
16
+ date.setMonth(date.getMonth() - i);
17
+ return date.toISOString().slice(0, 7);
18
+ });
19
+ const fetchTokenData = useCallback(async () => {
20
+ try {
21
+ setLoading(true);
22
+ // Récupérer toutes les données tokens de tous les utilisateurs
23
+ const response = await fetch("/api/ai/admin/user-token");
24
+ if (!response.ok) {
25
+ throw new Error("Erreur lors du chargement des données");
26
+ }
27
+ const data = await response.json();
28
+ // Traiter les données utilisateurs
29
+ const usersData = data.users || [];
30
+ setUsers(usersData);
31
+ // Filtrer les transactions par mois
32
+ const allTransactions = data.transactions || [];
33
+ const filtered = allTransactions.filter((t) => t.created_at.startsWith(selectedMonth));
34
+ setTransactions(filtered);
35
+ // Calculer les stats mensuelles globales
36
+ const totalBalance = usersData.reduce((sum, user) => sum + user.balance, 0);
37
+ const totalCredit = filtered
38
+ .filter((t) => t.amount > 0)
39
+ .reduce((sum, t) => sum + t.amount, 0);
40
+ const totalDebit = filtered
41
+ .filter((t) => t.amount < 0)
42
+ .reduce((sum, t) => sum + Math.abs(t.amount), 0);
43
+ const activeUsers = new Set(filtered.map((t) => t.userId)).size;
44
+ setMonthlyStats({
45
+ month: selectedMonth,
46
+ totalBalance,
47
+ totalCredit,
48
+ totalDebit,
49
+ activeUsers,
50
+ });
51
+ }
52
+ catch (error) {
53
+ console.error("Erreur:", error);
54
+ addToast({
55
+ color: "danger",
56
+ title: "Erreur lors du chargement des données",
57
+ });
58
+ }
59
+ finally {
60
+ setLoading(false);
61
+ }
62
+ }, [selectedMonth]);
63
+ useEffect(() => {
64
+ fetchTokenData();
65
+ }, [fetchTokenData]);
66
+ const formatDate = (dateString) => {
67
+ return new Date(dateString).toLocaleDateString("fr-FR", {
68
+ day: "2-digit",
69
+ month: "short",
70
+ year: "numeric",
71
+ hour: "2-digit",
72
+ minute: "2-digit",
73
+ });
74
+ };
75
+ const getTypeLabel = (type) => {
76
+ const labels = {
77
+ purchase: "Achat",
78
+ gift: "Cadeau",
79
+ use: "Utilisation",
80
+ adjust: "Ajustement",
81
+ };
82
+ return labels[type] || type;
83
+ };
84
+ const getTypeColor = (type) => {
85
+ const colors = {
86
+ purchase: "success",
87
+ gift: "primary",
88
+ use: "danger",
89
+ adjust: "warning",
90
+ };
91
+ return colors[type] || "default";
92
+ };
93
+ // Filtrer les utilisateurs par recherche
94
+ const filteredUsers = users.filter((user) => user.email.toLowerCase().includes(searchQuery.toLowerCase()) ||
95
+ user.fullName?.toLowerCase().includes(searchQuery.toLowerCase()));
96
+ if (loading) {
97
+ return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
98
+ }
99
+ return (_jsxs("div", { className: "container mx-auto 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", {
100
+ month: "long",
101
+ 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
103
+ ? 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
105
+ ? "text-success"
106
+ : "text-danger"}`, children: [transaction.amount > 0 ? "+" : "", transaction.amount.toLocaleString()] }) })] }, transaction.id))) })] })) })] })] }));
6
107
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/TokenPage.tsx"],"names":[],"mappings":"AAIA,wBAAgB,SAAS,4CAexB"}
1
+ {"version":3,"file":"TokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/TokenPage.tsx"],"names":[],"mappings":"AAqDA,wBAAgB,SAAS,4CAoWxB"}
@@ -1,6 +1,114 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { Card, CardBody, CardHeader } from "@lastbrain/ui";
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { Card, CardBody, CardHeader, Spinner, Chip, Button, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, addToast, } from "@lastbrain/ui";
5
+ import { Coins, TrendingUp, TrendingDown, Calendar, ShoppingCart, AlertCircle, } from "lucide-react";
6
+ import { useAuth } from "@lastbrain/core";
4
7
  export function TokenPage() {
5
- return (_jsx("div", { className: "container mx-auto p-6", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h1", { className: "text-2xl font-bold", children: "Token" }) }), _jsx(CardBody, { children: _jsx("p", { className: "text-default-600", children: "Contenu de la page token (section: auth)" }) })] }) }));
8
+ const { user } = useAuth();
9
+ const [loading, setLoading] = useState(true);
10
+ const [balance, setBalance] = useState(null);
11
+ const [transactions, setTransactions] = useState([]);
12
+ const [selectedMonth, setSelectedMonth] = useState(new Date().toISOString().slice(0, 7));
13
+ const [monthlyStats, setMonthlyStats] = useState(null);
14
+ // Générer les 12 derniers mois
15
+ const availableMonths = Array.from({ length: 12 }, (_, i) => {
16
+ const date = new Date();
17
+ date.setMonth(date.getMonth() - i);
18
+ return date.toISOString().slice(0, 7);
19
+ });
20
+ const fetchTokenData = useCallback(async () => {
21
+ if (!user)
22
+ return;
23
+ try {
24
+ setLoading(true);
25
+ // Récupérer le solde et l'historique
26
+ const response = await fetch("/api/ai/user/tokens");
27
+ if (!response.ok) {
28
+ throw new Error("Erreur lors du chargement des données");
29
+ }
30
+ const data = await response.json();
31
+ setBalance({
32
+ balance: data.balance || 0,
33
+ totalAdded: data.stats?.totalPurchased + data.stats?.totalGifted || 0,
34
+ totalUsed: data.stats?.totalUsed || 0,
35
+ });
36
+ // Filtrer les transactions par mois
37
+ const allTransactions = data.history || [];
38
+ const filtered = allTransactions.filter((t) => t.created_at.startsWith(selectedMonth));
39
+ // Calculer le running balance
40
+ let runningBalance = data.balance || 0;
41
+ const withBalance = filtered.map((t) => {
42
+ const txWithBalance = { ...t, running_balance: runningBalance };
43
+ runningBalance -= t.amount;
44
+ return txWithBalance;
45
+ });
46
+ setTransactions(withBalance);
47
+ // Calculer les stats mensuelles
48
+ const added = filtered
49
+ .filter((t) => t.amount > 0)
50
+ .reduce((sum, t) => sum + t.amount, 0);
51
+ const used = filtered
52
+ .filter((t) => t.amount < 0)
53
+ .reduce((sum, t) => sum + Math.abs(t.amount), 0);
54
+ setMonthlyStats({
55
+ month: selectedMonth,
56
+ added,
57
+ used,
58
+ net: added - used,
59
+ });
60
+ }
61
+ catch (error) {
62
+ console.error("Erreur:", error);
63
+ addToast({
64
+ color: "danger",
65
+ title: "Erreur lors du chargement des données",
66
+ });
67
+ }
68
+ finally {
69
+ setLoading(false);
70
+ }
71
+ }, [user, selectedMonth]);
72
+ useEffect(() => {
73
+ fetchTokenData();
74
+ }, [fetchTokenData]);
75
+ const formatDate = (dateString) => {
76
+ return new Date(dateString).toLocaleDateString("fr-FR", {
77
+ day: "2-digit",
78
+ month: "short",
79
+ year: "numeric",
80
+ hour: "2-digit",
81
+ minute: "2-digit",
82
+ });
83
+ };
84
+ const getTypeLabel = (type) => {
85
+ const labels = {
86
+ purchase: "Achat",
87
+ gift: "Cadeau",
88
+ use: "Utilisation",
89
+ adjust: "Ajustement",
90
+ };
91
+ return labels[type] || type;
92
+ };
93
+ const getTypeColor = (type) => {
94
+ const colors = {
95
+ purchase: "success",
96
+ gift: "primary",
97
+ use: "danger",
98
+ adjust: "warning",
99
+ };
100
+ return colors[type] || "default";
101
+ };
102
+ if (!user) {
103
+ 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" })] }) }));
104
+ }
105
+ if (loading) {
106
+ return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
107
+ }
108
+ return (_jsxs("div", { className: "container mx-auto 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-3 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: balance?.balance.toLocaleString() }), _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 ajout\u00E9" })] }), _jsx("p", { className: "text-4xl font-bold text-success", children: balance?.totalAdded.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "tokens re\u00E7us" })] }) }), _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: balance?.totalUsed.toLocaleString() }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: "tokens consomm\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", {
109
+ month: "long",
110
+ year: "numeric",
111
+ }) }, 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: ["+", monthlyStats.added.toLocaleString()] })] }), _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: ["-", monthlyStats.used.toLocaleString()] })] }), _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 ? "+" : "", monthlyStats.net.toLocaleString()] })] })] }) })] })), _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, { children: "DATE" }), _jsx(TableColumn, { children: "TYPE" }), _jsx(TableColumn, { children: "DESCRIPTION" }), _jsx(TableColumn, { children: "MONTANT" }), _jsx(TableColumn, { 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
112
+ ? "text-success"
113
+ : "text-danger"}`, children: [transaction.amount > 0 ? "+" : "", transaction.amount.toLocaleString()] }) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: transaction.running_balance.toLocaleString() }) })] }, 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: _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: "L'achat de tokens sera bient\u00F4t disponible" }), _jsx("p", { className: "text-sm text-gray-400", children: "Module Stripe requis \u2022 Fonctionnalit\u00E9 \u00E0 venir" }), _jsx(Button, { color: "primary", variant: "flat", isDisabled: true, className: "mt-4", startContent: _jsx(ShoppingCart, { size: 16 }), children: "Acheter des tokens" })] }) })] })] }));
6
114
  }
@@ -7,16 +7,19 @@ export interface GenerativeImageResponse {
7
7
  prompt: string;
8
8
  }
9
9
  export interface ImageGenerativeProps {
10
- prompt: string;
10
+ defaultPrompt?: string;
11
11
  model?: string;
12
12
  size?: "256x256" | "512x512" | "1024x1024" | "1792x1024" | "1024x1792";
13
13
  quality?: "standard" | "hd";
14
- onChange?: (response: GenerativeImageResponse) => void;
14
+ onChange?: (imageUrl: string, response?: GenerativeImageResponse) => void;
15
15
  onError?: (error: Error) => void;
16
16
  className?: string;
17
17
  disabled?: boolean;
18
18
  apiEndpoint?: string;
19
19
  showTokenBalance?: boolean;
20
+ label?: string;
21
+ description?: string;
22
+ defaultStyle?: string;
20
23
  }
21
- export declare function ImageGenerative({ prompt, model, size, quality, onChange, onError, className, disabled, apiEndpoint, showTokenBalance, }: ImageGenerativeProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function ImageGenerative({ defaultPrompt, model, size, quality, onChange, onError, className, disabled, apiEndpoint, showTokenBalance, label, description, defaultStyle, }: ImageGenerativeProps): import("react/jsx-runtime").JSX.Element;
22
25
  //# sourceMappingURL=ImageGenerative.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ImageGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/ImageGenerative.tsx"],"names":[],"mappings":"AAOA,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;IACvE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACvD,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;CAC5B;AAED,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,KAAkB,EAClB,IAAkB,EAClB,OAAoB,EACpB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,WAAsC,EACtC,gBAAuB,GACxB,EAAE,oBAAoB,2CA6LtB"}
1
+ {"version":3,"file":"ImageGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/ImageGenerative.tsx"],"names":[],"mappings":"AA2BA,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAiCD,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,CAAC;IACvE,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,uBAAuB,KAAK,IAAI,CAAC;IAC1E,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;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,GAC3B,EAAE,oBAAoB,2CAoStB"}
@@ -1,26 +1,64 @@
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 { Button, Chip, Progress, Card, CardBody } from "@lastbrain/ui";
5
- import { Sparkles, Loader2, AlertCircle, Download } from "lucide-react";
4
+ import { Button, Chip, Progress, Card, CardBody, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Textarea, Select, SelectItem, } from "@lastbrain/ui";
5
+ import { Image as ImageIcon, Loader2, AlertCircle, Download, Wand2, } from "lucide-react";
6
6
  import Image from "next/image";
7
- export function ImageGenerative({ prompt, model = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, }) {
7
+ const imageStyles = [
8
+ {
9
+ key: "realistic",
10
+ label: "Réaliste",
11
+ description: "Photo réaliste haute définition",
12
+ },
13
+ {
14
+ key: "artistic",
15
+ label: "Artistique",
16
+ description: "Style peinture artistique",
17
+ },
18
+ {
19
+ key: "digital-art",
20
+ label: "Art Digital",
21
+ description: "Illustration digitale moderne",
22
+ },
23
+ { key: "anime", label: "Anime", description: "Style manga/anime japonais" },
24
+ {
25
+ key: "3d-render",
26
+ label: "Rendu 3D",
27
+ description: "Rendu 3D photoréaliste",
28
+ },
29
+ { key: "sketch", label: "Croquis", description: "Dessin au crayon" },
30
+ {
31
+ key: "watercolor",
32
+ label: "Aquarelle",
33
+ description: "Peinture à l'aquarelle",
34
+ },
35
+ { key: "pop-art", label: "Pop Art", description: "Style pop art vibrant" },
36
+ ];
37
+ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, label, description, defaultStyle = "realistic", }) {
8
38
  const [isGenerating, setIsGenerating] = useState(false);
9
39
  const [generatedImage, setGeneratedImage] = useState(null);
10
40
  const [error, setError] = useState(null);
11
41
  const [tokenBalance, setTokenBalance] = useState(null);
42
+ // Modal state
43
+ const [isModalOpen, setIsModalOpen] = useState(false);
44
+ const [userPrompt, setUserPrompt] = useState(defaultPrompt);
45
+ const [selectedStyle, setSelectedStyle] = useState(defaultStyle);
46
+ const [selectedModel, setSelectedModel] = useState(model);
12
47
  const handleGenerate = useCallback(async () => {
13
- if (!prompt || isGenerating)
48
+ if (!userPrompt || isGenerating)
14
49
  return;
15
50
  setIsGenerating(true);
16
51
  setError(null);
52
+ setIsModalOpen(false);
17
53
  try {
54
+ const styleInfo = imageStyles.find((s) => s.key === selectedStyle);
55
+ const enhancedPrompt = `${userPrompt}, ${styleInfo?.description || selectedStyle} style`;
18
56
  const response = await fetch(apiEndpoint, {
19
57
  method: "POST",
20
58
  headers: { "Content-Type": "application/json" },
21
59
  body: JSON.stringify({
22
- prompt,
23
- model,
60
+ prompt: enhancedPrompt,
61
+ model: selectedModel,
24
62
  size,
25
63
  quality,
26
64
  }),
@@ -34,14 +72,14 @@ export function ImageGenerative({ prompt, model = "dall-e-3", size = "1024x1024"
34
72
  imageUrl: data.imageUrl,
35
73
  tokensUsed: data.tokensUsed || 0,
36
74
  tokensRemaining: data.tokensRemaining || 0,
37
- model: data.model || model,
75
+ model: data.model || selectedModel,
38
76
  cost: data.cost,
39
- prompt: prompt,
77
+ prompt: userPrompt,
40
78
  };
41
79
  setGeneratedImage(imageResponse);
42
80
  setTokenBalance(data.tokensRemaining);
43
81
  if (onChange) {
44
- onChange(imageResponse);
82
+ onChange(data.imageUrl, imageResponse);
45
83
  }
46
84
  }
47
85
  catch (err) {
@@ -55,8 +93,9 @@ export function ImageGenerative({ prompt, model = "dall-e-3", size = "1024x1024"
55
93
  setIsGenerating(false);
56
94
  }
57
95
  }, [
58
- prompt,
59
- model,
96
+ userPrompt,
97
+ selectedStyle,
98
+ selectedModel,
60
99
  size,
61
100
  quality,
62
101
  apiEndpoint,
@@ -83,5 +122,5 @@ export function ImageGenerative({ prompt, model = "dall-e-3", size = "1024x1024"
83
122
  console.error("Erreur lors du téléchargement:", error);
84
123
  }
85
124
  }, [generatedImage]);
86
- return (_jsx("div", { className: className, children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { color: "primary", size: "sm", onClick: handleGenerate, disabled: disabled || isGenerating || !prompt, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Sparkles, { size: 16 })), children: isGenerating ? "Génération..." : "Générer l'image" }), showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] }))] }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), generatedImage.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", generatedImage.cost.toFixed(4)] })), _jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: "T\u00E9l\u00E9charger" })] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: "G\u00E9n\u00E9ration de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] })), generatedImage && !isGenerating && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [size, " \u2022 ", quality] })] })] }) }))] }) }));
125
+ return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && _jsx("p", { className: "text-sm text-gray-500", children: description }), generatedImage && !isGenerating && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [size, " \u2022 ", quality] })] })] }) })), _jsx(Button, { color: "primary", onClick: () => setIsModalOpen(true), disabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating ? "Génération..." : "Générer une image" }), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance && tokenBalance !== null && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " tokens restants"] })) }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " tokens utilis\u00E9s"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), generatedImage.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", generatedImage.cost.toFixed(4)] })), _jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: "T\u00E9l\u00E9charger" })] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: "G\u00E9n\u00E9ration de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }))] }), _jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { size: 20 }), _jsx("span", { children: "G\u00E9n\u00E9rer une image avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: "Description de l'image", placeholder: "D\u00E9crivez l'image que vous souhaitez g\u00E9n\u00E9rer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: "Soyez pr\u00E9cis et d\u00E9taill\u00E9 pour de meilleurs r\u00E9sultats" }), _jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => setSelectedModel(Array.from(keys)[0]), description: "DALL-E 3 offre la meilleure qualit\u00E9", children: [_jsx(SelectItem, { children: "DALL-E 3 (Haute qualit\u00E9)" }, "dall-e-3"), _jsx(SelectItem, { children: "DALL-E 2 (Standard)" }, "dall-e-2")] }), generatedImage && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-2", children: "Aper\u00E7u de l'image actuelle:" }), _jsx("div", { className: "relative w-full h-32", children: _jsx(Image, { src: generatedImage.imageUrl, alt: "Current", fill: true, className: "object-cover rounded" }) })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: "Annuler" }), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(ImageIcon, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) })] }));
87
126
  }
@@ -6,11 +6,11 @@ export interface GenerativeResponse {
6
6
  cost?: number;
7
7
  }
8
8
  export interface TextareaGenerativeProps {
9
- prompt: string;
9
+ defaultPrompt?: string;
10
10
  model?: string;
11
11
  placeholder?: string;
12
12
  defaultValue?: string;
13
- onChange?: (response: GenerativeResponse) => void;
13
+ onChange?: (value: string, response?: GenerativeResponse) => void;
14
14
  onError?: (error: Error) => void;
15
15
  className?: string;
16
16
  disabled?: boolean;
@@ -18,6 +18,8 @@ export interface TextareaGenerativeProps {
18
18
  maxRows?: number;
19
19
  apiEndpoint?: string;
20
20
  showTokenBalance?: boolean;
21
+ label?: string;
22
+ description?: string;
21
23
  }
22
- export declare function TextareaGenerative({ prompt, model, placeholder, defaultValue, onChange, onError, className, disabled, minRows, maxRows, apiEndpoint, showTokenBalance, }: TextareaGenerativeProps): import("react/jsx-runtime").JSX.Element;
24
+ export declare function TextareaGenerative({ defaultPrompt, model, placeholder, defaultValue, onChange, onError, className, disabled, minRows, maxRows, apiEndpoint, showTokenBalance, label, description, }: TextareaGenerativeProps): import("react/jsx-runtime").JSX.Element;
23
25
  //# sourceMappingURL=TextareaGenerative.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TextareaGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/TextareaGenerative.tsx"],"names":[],"mappings":"AAMA,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,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAClD,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;CAC5B;AAED,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,KAAqB,EACrB,WAAmC,EACnC,YAAiB,EACjB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,OAAW,EACX,OAAY,EACZ,WAAqC,EACrC,gBAAuB,GACxB,EAAE,uBAAuB,2CAwIzB"}
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;CACtB;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,GACZ,EAAE,uBAAuB,2CAiPzB"}