@lastbrain/module-ai 0.1.10 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -2
- package/dist/ai.build.config.d.ts.map +1 -1
- package/dist/ai.build.config.js +24 -6
- package/dist/api/admin/user-token/[id].d.ts +6 -1
- package/dist/api/admin/user-token/[id].d.ts.map +1 -1
- package/dist/api/admin/user-token/[id].js +16 -14
- package/dist/api/admin/user-token.d.ts +19 -3
- package/dist/api/admin/user-token.d.ts.map +1 -1
- package/dist/api/admin/user-token.js +85 -26
- package/dist/api/auth/generate-text.js +1 -1
- package/dist/api/auth/user-tokens.d.ts +17 -0
- package/dist/api/auth/user-tokens.d.ts.map +1 -0
- package/dist/api/auth/user-tokens.js +34 -0
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +2 -5
- package/dist/components/DocUsageCustom.d.ts +9 -0
- package/dist/components/DocUsageCustom.d.ts.map +1 -0
- package/dist/components/DocUsageCustom.js +70 -0
- package/dist/components/admin/UserTokenTab.d.ts +6 -0
- package/dist/components/admin/UserTokenTab.d.ts.map +1 -0
- package/dist/components/admin/UserTokenTab.js +112 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
- package/dist/web/admin/UserTokenPage.js +103 -2
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +110 -2
- package/dist/web/components/ImageGenerative.d.ts +6 -3
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +51 -12
- package/dist/web/components/TextareaGenerative.d.ts +5 -3
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
- package/dist/web/components/TextareaGenerative.js +33 -12
- package/package.json +4 -4
- package/supabase/migrations-down/20251125000000_ai_tokens.sql +45 -0
- package/supabase/migrations/20251121093113_module-ai_init.sql +0 -122
- package/supabase/migrations-down/20251121000000_ai_tokens.sql +0 -23
- package/supabase/migrations-down/20251121093113_module-ai_init.sql +0 -11
- /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 {
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,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;
|
|
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
|
-
|
|
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":"
|
|
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 {
|
|
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
|
-
|
|
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":"
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
10
|
+
defaultPrompt?: string;
|
|
11
11
|
model?: string;
|
|
12
12
|
size?: "256x256" | "512x512" | "1024x1024" | "1792x1024" | "1024x1792";
|
|
13
13
|
quality?: "standard" | "hd";
|
|
14
|
-
onChange?: (
|
|
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({
|
|
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":"
|
|
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 {
|
|
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
|
-
|
|
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 (!
|
|
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 ||
|
|
75
|
+
model: data.model || selectedModel,
|
|
38
76
|
cost: data.cost,
|
|
39
|
-
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
|
-
|
|
59
|
-
|
|
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 (
|
|
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
|
-
|
|
9
|
+
defaultPrompt?: string;
|
|
10
10
|
model?: string;
|
|
11
11
|
placeholder?: string;
|
|
12
12
|
defaultValue?: string;
|
|
13
|
-
onChange?: (
|
|
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({
|
|
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":"
|
|
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"}
|