@lastbrain/module-ai 0.1.8 → 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 (41) hide show
  1. package/README.md +49 -442
  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 +8 -1
  15. package/dist/components/Doc.d.ts.map +1 -1
  16. package/dist/components/Doc.js +17 -3
  17. package/dist/components/DocUsageCustom.d.ts +9 -0
  18. package/dist/components/DocUsageCustom.d.ts.map +1 -0
  19. package/dist/components/DocUsageCustom.js +70 -0
  20. package/dist/components/admin/UserTokenTab.d.ts +6 -0
  21. package/dist/components/admin/UserTokenTab.d.ts.map +1 -0
  22. package/dist/components/admin/UserTokenTab.js +112 -0
  23. package/dist/index.d.ts +3 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -2
  26. package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
  27. package/dist/web/admin/UserTokenPage.js +103 -2
  28. package/dist/web/auth/TokenPage.d.ts.map +1 -1
  29. package/dist/web/auth/TokenPage.js +110 -2
  30. package/dist/web/components/ImageGenerative.d.ts +6 -3
  31. package/dist/web/components/ImageGenerative.d.ts.map +1 -1
  32. package/dist/web/components/ImageGenerative.js +51 -12
  33. package/dist/web/components/TextareaGenerative.d.ts +5 -3
  34. package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
  35. package/dist/web/components/TextareaGenerative.js +33 -12
  36. package/package.json +4 -4
  37. package/supabase/migrations-down/20251125000000_ai_tokens.sql +45 -0
  38. package/supabase/migrations/20251121093113_module-ai_init.sql +0 -122
  39. package/supabase/migrations-down/20251121000000_ai_tokens.sql +0 -23
  40. package/supabase/migrations-down/20251121093113_module-ai_init.sql +0 -11
  41. /package/supabase/migrations/{20251121000000_ai_tokens.sql → 20251125000000_ai_tokens.sql} +0 -0
@@ -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"}
@@ -1,26 +1,31 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useCallback } from "react";
4
- import { Textarea, Button, Chip, Progress } from "@lastbrain/ui";
5
- import { Sparkles, Loader2, AlertCircle } from "lucide-react";
6
- export function TextareaGenerative({ prompt, model = "gpt-4o-mini", placeholder = "Générer du texte...", defaultValue = "", onChange, onError, className, disabled = false, minRows = 4, maxRows = 20, apiEndpoint = "/api/ai/generate-text", showTokenBalance = true, }) {
4
+ import { Textarea, Button, Chip, Progress, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem, Skeleton, } from "@lastbrain/ui";
5
+ import { Sparkles, Loader2, AlertCircle, Wand2 } from "lucide-react";
6
+ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini", placeholder = "Saisissez votre texte ou générez avec l'IA...", defaultValue = "", onChange, onError, className, disabled = false, minRows = 4, maxRows = 20, apiEndpoint = "/api/ai/generate-text", showTokenBalance = true, label, description, }) {
7
7
  const [value, setValue] = useState(defaultValue);
8
8
  const [isGenerating, setIsGenerating] = useState(false);
9
9
  const [lastResponse, setLastResponse] = useState(null);
10
10
  const [error, setError] = useState(null);
11
11
  const [tokenBalance, setTokenBalance] = useState(null);
12
+ // Modal state
13
+ const [isModalOpen, setIsModalOpen] = useState(false);
14
+ const [userPrompt, setUserPrompt] = useState(defaultPrompt);
15
+ const [selectedModel, setSelectedModel] = useState(model);
12
16
  const handleGenerate = useCallback(async () => {
13
- if (!prompt || isGenerating)
17
+ if (!userPrompt || isGenerating)
14
18
  return;
15
19
  setIsGenerating(true);
16
20
  setError(null);
21
+ setIsModalOpen(false);
17
22
  try {
18
23
  const response = await fetch(apiEndpoint, {
19
24
  method: "POST",
20
25
  headers: { "Content-Type": "application/json" },
21
26
  body: JSON.stringify({
22
- prompt,
23
- model,
27
+ prompt: userPrompt,
28
+ model: selectedModel,
24
29
  context: value,
25
30
  }),
26
31
  });
@@ -33,14 +38,14 @@ export function TextareaGenerative({ prompt, model = "gpt-4o-mini", placeholder
33
38
  text: data.text,
34
39
  tokensUsed: data.tokensUsed || 0,
35
40
  tokensRemaining: data.tokensRemaining || 0,
36
- model: data.model || model,
41
+ model: data.model || selectedModel,
37
42
  cost: data.cost,
38
43
  };
39
44
  setValue(data.text);
40
45
  setLastResponse(generativeResponse);
41
46
  setTokenBalance(data.tokensRemaining);
42
47
  if (onChange) {
43
- onChange(generativeResponse);
48
+ onChange(data.text, generativeResponse);
44
49
  }
45
50
  }
46
51
  catch (err) {
@@ -53,8 +58,24 @@ export function TextareaGenerative({ prompt, model = "gpt-4o-mini", placeholder
53
58
  finally {
54
59
  setIsGenerating(false);
55
60
  }
56
- }, [prompt, model, value, apiEndpoint, onChange, onError, isGenerating]);
57
- return (_jsx("div", { className: className, children: _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Textarea, { value: value, onChange: (e) => setValue(e.target.value), placeholder: placeholder, disabled: disabled || isGenerating, minRows: minRows, maxRows: maxRows, classNames: {
58
- input: "resize-y",
59
- } }), _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" }), 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)] }))] }))] }), isGenerating && (_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": "G\u00E9n\u00E9ration en cours...", className: "max-w-md" })), 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 })] }))] }) }));
61
+ }, [
62
+ userPrompt,
63
+ selectedModel,
64
+ value,
65
+ apiEndpoint,
66
+ onChange,
67
+ onError,
68
+ isGenerating,
69
+ ]);
70
+ const handleValueChange = (newValue) => {
71
+ setValue(newValue);
72
+ if (onChange) {
73
+ onChange(newValue);
74
+ }
75
+ };
76
+ return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-2", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && (_jsx("p", { className: "text-sm text-gray-500 mb-2", children: description })), !isGenerating && (_jsx(Textarea, { value: value, onChange: (e) => handleValueChange(e.target.value), placeholder: placeholder, disabled: disabled || isGenerating, minRows: minRows, maxRows: maxRows,
77
+ // classNames={{
78
+ // input: "resize-y",
79
+ // }}
80
+ endContent: _jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onClick: () => setIsModalOpen(true), disabled: disabled || isGenerating, title: "G\u00E9n\u00E9rer avec l'IA", children: _jsx(Wand2, { size: 18 }) }) })), isGenerating && (_jsxs("div", { className: "w-full h-md 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..." }) })] })), _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 })] }))] }), _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")] }), value && (_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: value })] }))] }) }), _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(Sparkles, { size: 16 })), children: isGenerating ? "Génération..." : "Générer" })] })] }) })] }));
60
81
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lastbrain/module-ai",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
4
4
  "description": "Module de génération IA (texte et images) avec gestion de tokens pour LastBrain",
5
5
  "private": false,
6
6
  "type": "module",
@@ -33,13 +33,13 @@
33
33
  "@heroui/system": "^2.4.23",
34
34
  "@heroui/theme": "^2.4.23",
35
35
  "lucide-react": "^0.554.0",
36
- "next": "^15.5.6",
36
+ "next": "^16.0.4",
37
37
  "openai": "^4.76.0",
38
38
  "react": "^19.0.0",
39
39
  "react-dom": "^19.0.0",
40
40
  "zod": "^3.23.8",
41
- "@lastbrain/core": "0.1.9",
42
- "@lastbrain/ui": "0.1.12"
41
+ "@lastbrain/core": "0.1.12",
42
+ "@lastbrain/ui": "0.1.15"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "next": ">=15.0.0"
@@ -0,0 +1,45 @@
1
+ -- AI Token System Migration - ROLLBACK
2
+ -- Suppression du système de gestion des tokens
3
+
4
+ -- =====================================================
5
+ -- Drop RLS Policies - user_prompts
6
+ -- =====================================================
7
+ DROP POLICY IF EXISTS "Users can delete own prompts" ON public.user_prompts;
8
+ DROP POLICY IF EXISTS "Users can update own prompts" ON public.user_prompts;
9
+ DROP POLICY IF EXISTS "Users can insert own prompts" ON public.user_prompts;
10
+ DROP POLICY IF EXISTS "Users can view own prompts" ON public.user_prompts;
11
+
12
+ -- =====================================================
13
+ -- Drop Triggers & Functions - user_prompts
14
+ -- =====================================================
15
+ DROP TRIGGER IF EXISTS update_user_prompts_updated_at ON public.user_prompts;
16
+ DROP FUNCTION IF EXISTS public.update_user_prompts_updated_at();
17
+
18
+ -- =====================================================
19
+ -- Drop Table - user_prompts
20
+ -- =====================================================
21
+ DROP TABLE IF EXISTS public.user_prompts CASCADE;
22
+
23
+ -- =====================================================
24
+ -- Drop RLS Policies - user_token_ledger
25
+ -- =====================================================
26
+ DROP POLICY IF EXISTS user_token_ledger_delete_admin ON public.user_token_ledger;
27
+ DROP POLICY IF EXISTS user_token_ledger_update_admin ON public.user_token_ledger;
28
+ DROP POLICY IF EXISTS user_token_ledger_insert_own ON public.user_token_ledger;
29
+ DROP POLICY IF EXISTS user_token_ledger_select_own ON public.user_token_ledger;
30
+
31
+ -- =====================================================
32
+ -- Drop Triggers & Functions - user_token_ledger
33
+ -- =====================================================
34
+ DROP TRIGGER IF EXISTS trigger_check_token_balance ON public.user_token_ledger;
35
+ DROP FUNCTION IF EXISTS public.check_token_balance();
36
+
37
+ -- =====================================================
38
+ -- Drop View
39
+ -- =====================================================
40
+ DROP VIEW IF EXISTS public.user_token_balance_v;
41
+
42
+ -- =====================================================
43
+ -- Drop Table - user_token_ledger
44
+ -- =====================================================
45
+ DROP TABLE IF EXISTS public.user_token_ledger CASCADE;