@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,10 +1,26 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
- export declare function GET(_request: NextRequest): Promise<NextResponse<{
2
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
3
3
  users: {
4
+ userId: string;
5
+ email: string;
6
+ fullName: string | undefined;
7
+ avatarUrl: any;
8
+ balance: number;
9
+ totalAdded: number;
10
+ totalUsed: number;
11
+ lastActivity: string | undefined;
12
+ }[];
13
+ transactions: {
4
14
  id: any;
15
+ userId: any;
5
16
  email: string;
6
- balance: any;
7
- createdAt: string | undefined;
17
+ fullName: string | undefined;
18
+ avatarUrl: any;
19
+ amount: any;
20
+ type: any;
21
+ description: any;
22
+ model: any;
23
+ created_at: any;
8
24
  }[];
9
25
  total: number;
10
26
  }> | NextResponse<{
@@ -1 +1 @@
1
- {"version":3,"file":"user-token.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKxD,wBAAsB,GAAG,CAAC,QAAQ,EAAE,WAAW;;;;;;;;;;IA0C9C;AAGD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;IAqD9C"}
1
+ {"version":3,"file":"user-token.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKxD,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;IA2H7C;AAGD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;IAiD9C"}
@@ -1,36 +1,96 @@
1
1
  import { NextResponse } from "next/server";
2
- import { getSupabaseServerClient } from "@lastbrain/core/server";
2
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
3
3
  import { addTokens } from "../../server";
4
- // GET /api/ai/admin/user-token - Liste tous les utilisateurs avec leur balance
5
- export async function GET(_request) {
4
+ // GET /api/ai/admin/user-token - Liste tous les utilisateurs avec leur balance et transactions
5
+ export async function GET(request) {
6
6
  try {
7
- const supabase = await getSupabaseServerClient();
7
+ const supabase = getSupabaseServiceClient();
8
8
  // L'authentification et les droits superadmin sont déjà vérifiés par le middleware
9
- // Récupérer tous les utilisateurs avec leur balance
10
- const { data: balances, error: balanceError } = await supabase
11
- .from("user_token_balance_v")
12
- .select("owner_id, balance")
13
- .order("balance", { ascending: false });
14
- if (balanceError)
15
- throw balanceError;
16
- // Récupérer les informations des utilisateurs
17
- const _userIds = balances?.map((b) => b.owner_id) || [];
18
- const { data: users, error: usersError } = await supabase.auth.admin.listUsers();
9
+ // Récupérer tous les utilisateurs
10
+ const { data: authUsers, error: usersError } = await supabase.auth.admin.listUsers();
19
11
  if (usersError)
20
12
  throw usersError;
21
- // Combiner les données
22
- const usersWithBalance = balances?.map((balance) => {
23
- const userInfo = users.users.find((u) => u.id === balance.owner_id);
13
+ // Récupérer toutes les transactions pour calculer les balances et stats
14
+ const { data: transactions, error: txError } = await supabase
15
+ .from("user_token_ledger")
16
+ .select("*")
17
+ .order("ts", { ascending: false })
18
+ .limit(1000);
19
+ if (txError)
20
+ throw txError;
21
+ // Calculer les stats par utilisateur
22
+ const userStatsMap = new Map();
23
+ transactions?.forEach((tx) => {
24
+ const stats = userStatsMap.get(tx.owner_id) || {
25
+ balance: 0,
26
+ totalAdded: 0,
27
+ totalUsed: 0,
28
+ };
29
+ stats.balance += tx.amount;
30
+ if (tx.amount > 0) {
31
+ stats.totalAdded += tx.amount;
32
+ }
33
+ else {
34
+ stats.totalUsed += Math.abs(tx.amount);
35
+ }
36
+ if (!stats.lastActivity || tx.ts > stats.lastActivity) {
37
+ stats.lastActivity = tx.ts;
38
+ }
39
+ userStatsMap.set(tx.owner_id, stats);
40
+ });
41
+ // Récupérer les profils utilisateurs pour avatar et nom
42
+ const userIds = authUsers.users.map((u) => u.id);
43
+ const { data: profiles } = await supabase
44
+ .from("user_profil")
45
+ .select("id, first_name, last_name, avatar_url")
46
+ .in("id", userIds);
47
+ const profilesMap = new Map(profiles?.map((p) => [
48
+ p.id,
49
+ {
50
+ fullName: [p.first_name, p.last_name].filter(Boolean).join(" "),
51
+ avatarUrl: p.avatar_url,
52
+ },
53
+ ]));
54
+ // Combiner toutes les données
55
+ const usersData = authUsers.users.map((user) => {
56
+ const stats = userStatsMap.get(user.id) || {
57
+ balance: 0,
58
+ totalAdded: 0,
59
+ totalUsed: 0,
60
+ };
61
+ const profile = profilesMap.get(user.id);
62
+ return {
63
+ userId: user.id,
64
+ email: user.email || "Unknown",
65
+ fullName: profile?.fullName,
66
+ avatarUrl: profile?.avatarUrl,
67
+ balance: stats.balance,
68
+ totalAdded: stats.totalAdded,
69
+ totalUsed: stats.totalUsed,
70
+ lastActivity: stats.lastActivity,
71
+ };
72
+ });
73
+ // Préparer les transactions avec infos utilisateurs
74
+ const transactionsWithUsers = transactions?.map((tx) => {
75
+ const user = authUsers.users.find((u) => u.id === tx.owner_id);
76
+ const profile = profilesMap.get(tx.owner_id);
24
77
  return {
25
- id: balance.owner_id,
26
- email: userInfo?.email || "Unknown",
27
- balance: balance.balance,
28
- createdAt: userInfo?.created_at,
78
+ id: tx.id,
79
+ userId: tx.owner_id,
80
+ email: user?.email || "Unknown",
81
+ fullName: profile?.fullName,
82
+ avatarUrl: profile?.avatarUrl,
83
+ amount: tx.amount,
84
+ type: tx.type,
85
+ description: tx.meta?.reason,
86
+ model: tx.model,
87
+ created_at: tx.ts,
29
88
  };
30
89
  });
31
90
  return NextResponse.json({
32
- users: usersWithBalance,
33
- total: usersWithBalance?.length || 0,
91
+ users: usersData,
92
+ transactions: transactionsWithUsers,
93
+ total: usersData.length,
34
94
  });
35
95
  }
36
96
  catch (error) {
@@ -41,9 +101,8 @@ export async function GET(_request) {
41
101
  // POST /api/ai/admin/user-token - Ajouter/retirer des tokens à un utilisateur
42
102
  export async function POST(request) {
43
103
  try {
44
- const supabase = await getSupabaseServerClient();
104
+ const supabase = getSupabaseServiceClient();
45
105
  // L'authentification et les droits superadmin sont déjà vérifiés par le middleware
46
- const { data: { user }, } = await supabase.auth.getUser();
47
106
  const body = await request.json();
48
107
  const { userId, amount, type, reason } = body;
49
108
  if (!userId || !amount) {
@@ -54,7 +113,7 @@ export async function POST(request) {
54
113
  }
55
114
  // Déterminer le type d'opération
56
115
  const operationType = type || (amount > 0 ? "adjust" : "adjust");
57
- const result = await addTokens(userId, amount, operationType, { reason: reason || "Ajustement manuel par admin", admin_id: user.id }, user.id);
116
+ const result = await addTokens(userId, amount, operationType, { reason: reason || "Ajustement manuel par admin" }, undefined);
58
117
  if (!result.success) {
59
118
  return NextResponse.json({ error: result.error }, { status: 500 });
60
119
  }
@@ -12,7 +12,7 @@ export async function POST(request) {
12
12
  const { data: { user }, } = await supabase.auth.getUser();
13
13
  // L'utilisateur est déjà authentifié grâce au middleware
14
14
  const body = await request.json();
15
- const { prompt, model = "gpt-4o-mini", context, maxTokens = 2000, temperature = 0.7, } = body;
15
+ const { prompt, model = "gpt-4o-mini", context, maxTokens = 100, temperature = 0.7, } = body;
16
16
  if (!prompt) {
17
17
  return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
18
18
  }
@@ -0,0 +1,17 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
3
+ userId: string;
4
+ balance: number;
5
+ stats: {
6
+ balance: number;
7
+ totalPurchased: number;
8
+ totalGifted: number;
9
+ totalUsed: number;
10
+ totalAdjusted: number;
11
+ };
12
+ history: import("../../server").TokenLedgerEntry[];
13
+ count: number;
14
+ }> | NextResponse<{
15
+ error: any;
16
+ }>>;
17
+ //# sourceMappingURL=user-tokens.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-tokens.d.ts","sourceRoot":"","sources":["../../../src/api/auth/user-tokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKxD,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;IA0C7C"}
@@ -0,0 +1,34 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+ import { getTokenBalance, getTokenHistory, getTokenStats } from "../../server";
4
+ // GET /api/ai/user/tokens - Récupérer le solde et l'historique de l'utilisateur courant
5
+ export async function GET(request) {
6
+ try {
7
+ const supabase = await getSupabaseServerClient();
8
+ // Récupérer l'utilisateur courant
9
+ const { data: { user }, error: authError, } = await supabase.auth.getUser();
10
+ if (authError || !user) {
11
+ return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
12
+ }
13
+ const userId = user.id;
14
+ // Récupérer le solde
15
+ const balance = await getTokenBalance(userId);
16
+ // Récupérer les statistiques
17
+ const stats = await getTokenStats(userId);
18
+ // Récupérer l'historique complet (12 derniers mois)
19
+ const searchParams = request.nextUrl.searchParams;
20
+ const limit = parseInt(searchParams.get("limit") || "500");
21
+ const history = await getTokenHistory(userId, limit, 0);
22
+ return NextResponse.json({
23
+ userId,
24
+ balance,
25
+ stats,
26
+ history,
27
+ count: history.length,
28
+ });
29
+ }
30
+ catch (error) {
31
+ console.error("[GET /api/ai/user/tokens] Error:", error);
32
+ return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
33
+ }
34
+ }
@@ -1,2 +1,9 @@
1
- export declare function AiModuleDoc(): import("react/jsx-runtime").JSX.Element;
1
+ /**
2
+ * Documentation component for @lastbrain/module-ai
3
+ * Auto-generated from module-ai.build.config.ts
4
+ *
5
+ * To regenerate this file, run:
6
+ * pnpm update:module-docs
7
+ */
8
+ export declare function Doc(): import("react/jsx-runtime").JSX.Element;
2
9
  //# sourceMappingURL=Doc.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Doc.d.ts","sourceRoot":"","sources":["../../src/components/Doc.tsx"],"names":[],"mappings":"AAUA,wBAAgB,WAAW,4CAqU1B"}
1
+ {"version":3,"file":"Doc.d.ts","sourceRoot":"","sources":["../../src/components/Doc.tsx"],"names":[],"mappings":"AAiBA;;;;;;GAMG;AACH,wBAAgB,GAAG,4CAsOlB"}
@@ -1,5 +1,19 @@
1
+ "use client";
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Card, CardBody, CardHeader, Snippet, Chip, TableStructure, } from "@lastbrain/ui";
3
- export function AiModuleDoc() {
4
- return (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "flex items-start justify-between", children: [_jsxs("div", { children: [_jsx("h2", { className: "text-3xl font-bold mb-2", children: "Module AI" }), _jsx("p", { className: "text-slate-600 dark:text-slate-400", children: "G\u00E9n\u00E9ration IA (texte et images) avec syst\u00E8me de tokens et gestion des co\u00FBts" })] }), _jsx(Chip, { color: "primary", variant: "flat", children: "v0.1.0" })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCDD Informations" }) }), _jsxs(CardBody, { className: "space-y-2", children: [_jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Auteur:" }), " LastBrain Team"] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Package:" }), " @lastbrain/module-ai"] }), _jsxs("div", { children: [_jsx("span", { className: "font-semibold", children: "Derni\u00E8re mise \u00E0 jour:" }), " 21 novembre 2025"] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83C\uDFA8 Composants Disponibles" }) }), _jsxs(CardBody, { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "TextareaGenerative" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Textarea avec g\u00E9n\u00E9ration de texte IA int\u00E9gr\u00E9e. Bouton de g\u00E9n\u00E9ration avec s\u00E9lection de prompts." }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", hideSymbol: true, children: _jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("span", { children: ["import ", "{ TextareaGenerative }", " from \"@lastbrain/module-ai\";"] }), _jsx("span", {}), _jsx("span", { children: "<TextareaGenerative" }), _jsx("span", { children: " label=\"Description\"" }), _jsx("span", { children: " placeholder=\"Entrez une description...\"" }), _jsxs("span", { children: [" value=", "{value}"] }), _jsxs("span", { children: [" onChange=", "{setValue}"] }), _jsx("span", { children: "/>" })] }) })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2 mt-4", children: "ImageGenerative" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-2", children: "Composant de g\u00E9n\u00E9ration d'images avec DALL-E. Upload ou g\u00E9n\u00E9ration IA." }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", hideSymbol: true, children: _jsxs("div", { className: "flex flex-col gap-1", children: [_jsxs("span", { children: ["import ", "{ ImageGenerative }", " from \"@lastbrain/module-ai\";"] }), _jsx("span", {}), _jsx("span", { children: "<ImageGenerative" }), _jsx("span", { children: " label=\"Image du produit\"" }), _jsxs("span", { children: [" value=", "{imageUrl}"] }), _jsxs("span", { children: [" onChange=", "{setImageUrl}"] }), _jsx("span", { children: "/>" })] }) })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCC4 Pages Disponibles" }) }), _jsxs(CardBody, { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Pages Prot\u00E9g\u00E9es (Auth)" }), _jsx("div", { className: "space-y-2", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "primary", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/auth/token" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Historique et balance des tokens" })] }) })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Pages Admin" }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "danger", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/admin/user-token" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Gestion des tokens utilisateurs" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "danger", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/admin/user-token/[id]" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- D\u00E9tail d'un utilisateur" })] })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDD0C Routes API" }) }), _jsxs(CardBody, { className: "space-y-2", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "POST" }), _jsx("code", { className: "text-sm", children: "/api/ai/generate-text" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- G\u00E9n\u00E9ration de texte" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "POST" }), _jsx("code", { className: "text-sm", children: "/api/ai/generate-image" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- G\u00E9n\u00E9ration d'image" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "success", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/api/ai/admin/user-token" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Liste des tokens utilisateurs" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "POST" }), _jsx("code", { className: "text-sm", children: "/api/ai/admin/user-token" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- Cr\u00E9er/modifier des tokens" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Chip, { size: "sm", color: "success", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/api/ai/admin/user-token/[id]" }), _jsx("span", { className: "text-slate-600 dark:text-slate-400", children: "- D\u00E9tails tokens utilisateur" })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCC1 Structure" }) }), _jsx(CardBody, { children: _jsx(Snippet, { symbol: "", color: "default", size: "sm", hideSymbol: true, children: _jsxs("div", { className: "flex flex-col gap-1 text-xs font-mono", children: [_jsx("span", { children: "module-ai/" }), _jsx("span", { children: "\u251C\u2500\u2500 src/" }), _jsx("span", { children: "\u2502 \u251C\u2500\u2500 web/" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 TextareaGenerative.tsx" }), _jsx("span", { children: "\u2502 \u2502 \u2514\u2500\u2500 ImageGenerative.tsx" }), _jsx("span", { children: "\u2502 \u251C\u2500\u2500 api/" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 generate-text.ts" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 generate-image.ts" }), _jsx("span", { children: "\u2502 \u2502 \u251C\u2500\u2500 prompts.ts" }), _jsx("span", { children: "\u2502 \u2502 \u2514\u2500\u2500 balance.ts" }), _jsx("span", { children: "\u2502 \u251C\u2500\u2500 components/" }), _jsx("span", { children: "\u2502 \u2502 \u2514\u2500\u2500 Doc.tsx" }), _jsx("span", { children: "\u2502 \u2514\u2500\u2500 ai.build.config.ts" }), _jsx("span", { children: "\u2514\u2500\u2500 supabase/" }), _jsx("span", { children: " \u2514\u2500\u2500 migrations/" }), _jsx("span", { children: " \u251C\u2500\u2500 20251121000000_ai_tokens.sql" }), _jsx("span", { children: " \u2514\u2500\u2500 20251121093113_module-ai_init.sql" })] }) }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDDC4\uFE0F Tables de Donn\u00E9es" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsx(TableStructure, { tableName: "user_token_ledger", title: "user_token_ledger", description: "Comptabilit\u00E9 des tokens (cr\u00E9dits, d\u00E9bits, balance). Vue user_token_balance pour le solde actuel." }), _jsx(TableStructure, { tableName: "user_prompts", title: "user_prompts", description: "Biblioth\u00E8que de prompts r\u00E9utilisables par utilisateur (titre, contenu, type)." })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCE6 Installation" }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Snippet, { symbol: "", color: "default", size: "sm", children: "pnpm lastbrain add-module ai" }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", children: "pnpm build:modules" }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", children: "supabase migration up" })] }), _jsxs("div", { className: "border-2 border-danger rounded-lg p-4 mt-6", children: [_jsx("h4", { className: "text-lg font-semibold text-danger mb-3", children: "\u26A0\uFE0F Danger Zone" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400 mb-3", children: "La suppression du module supprimera toutes les pages, routes API et migrations associ\u00E9es. Cette action est irr\u00E9versible." }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Snippet, { symbol: "", color: "danger", size: "sm", children: "pnpm lastbrain remove-module ai" }), _jsx(Snippet, { symbol: "", color: "danger", size: "sm", children: "pnpm build:modules" })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\u2699\uFE0F Configuration" }) }), _jsxs(CardBody, { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400", children: "Ajoutez votre cl\u00E9 API OpenAI dans vos variables d'environnement :" }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", children: "OPENAI_API_KEY=sk-..." })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "\uD83D\uDCB0 Syst\u00E8me de Tokens" }) }), _jsxs(CardBody, { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2", children: "Co\u00FBts" }), _jsxs("ul", { className: "text-sm text-slate-600 dark:text-slate-400 space-y-1", children: [_jsx("li", { children: "\u2022 G\u00E9n\u00E9ration de texte : 10 tokens" }), _jsx("li", { children: "\u2022 G\u00E9n\u00E9ration d'image : 50 tokens" })] })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2 mt-4", children: "Trigger de validation" }), _jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400", children: "Un trigger emp\u00EAche les transactions si le solde devient n\u00E9gatif." })] }), _jsxs("div", { children: [_jsx("h4", { className: "font-semibold mb-2 mt-4", children: "Ajouter des tokens" }), _jsx(Snippet, { symbol: "", color: "default", size: "sm", hideSymbol: true, children: _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { children: "INSERT INTO user_token_ledger" }), _jsx("span", { children: "(user_id, amount, transaction_type, description)" }), _jsx("span", { children: "VALUES" }), _jsx("span", { children: "(\"('user-uuid', 1000, 'credit', 'Achat de tokens');\")" })] }) })] })] })] })] }));
3
+ import { Card, CardBody, CardHeader } from "@lastbrain/ui";
4
+ import { Chip } from "@lastbrain/ui";
5
+ import { Snippet } from "@lastbrain/ui";
6
+ import { Alert } from "@lastbrain/ui";
7
+ import { TableStructure } from "@lastbrain/ui";
8
+ import { FileText, Zap, Database, Package, BookOpen, AlertTriangle, } from "lucide-react";
9
+ import { DocUsageCustom } from "./DocUsageCustom";
10
+ /**
11
+ * Documentation component for @lastbrain/module-ai
12
+ * Auto-generated from module-ai.build.config.ts
13
+ *
14
+ * To regenerate this file, run:
15
+ * pnpm update:module-docs
16
+ */
17
+ export function Doc() {
18
+ return (_jsxs("div", { className: "container mx-auto p-6 space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: "\uD83D\uDCE6 Module ai" }), _jsx("p", { className: "text-slate-600 dark:text-slate-400", children: "@lastbrain/module-ai" })] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400", children: "Package" }), _jsx("code", { className: "text-sm font-semibold", children: "@lastbrain/module-ai" })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400", children: "Slug" }), _jsx("code", { className: "text-sm font-semibold", children: "module-ai" })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-slate-600 dark:text-slate-400", children: "Type" }), _jsx("code", { className: "text-sm font-semibold", children: "Module LastBrain" })] })] }) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(FileText, { size: 24 }), "Pages Disponibles"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Pages Prot\u00E9g\u00E9es (Auth)" }), _jsx("div", { className: "space-y-2", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Chip, { size: "sm", color: "primary", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/token" }), _jsx("span", { className: "text-sm text-slate-600 dark:text-slate-400", children: "- TokenPage" })] }) })] }), _jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Pages Admin" }), _jsx("div", { className: "space-y-2", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Chip, { size: "sm", color: "secondary", variant: "flat", children: "GET" }), _jsx("code", { className: "text-sm", children: "/user-token" }), _jsx("span", { className: "text-sm text-slate-600 dark:text-slate-400", children: "- UserTokenPage" })] }) })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Zap, { size: 24 }), "API Routes"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: _jsx("code", { children: "/api/auth/user_token_ledger" }) }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Chip, { size: "sm", color: "success", variant: "flat", children: "GET" }), _jsx(Chip, { size: "sm", color: "primary", variant: "flat", children: "POST" }), _jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "PUT" }), _jsx(Chip, { size: "sm", color: "danger", variant: "flat", children: "DELETE" })] })] }), _jsxs("div", { children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: _jsx("code", { children: "/api/auth/user_prompts" }) }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Chip, { size: "sm", color: "success", variant: "flat", children: "GET" }), _jsx(Chip, { size: "sm", color: "primary", variant: "flat", children: "POST" }), _jsx(Chip, { size: "sm", color: "warning", variant: "flat", children: "PUT" }), _jsx(Chip, { size: "sm", color: "danger", variant: "flat", children: "DELETE" })] })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Database, { size: 24 }), "Base de Donn\u00E9es"] }) }), _jsxs(CardBody, { className: "space-y-6", children: [_jsx(TableStructure, { tableName: "user_token_ledger", title: "user_token_ledger", description: "Table user_token_ledger du module ai" }), _jsx(TableStructure, { tableName: "user_prompts", title: "user_prompts", description: "Table user_prompts du module ai" })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(Package, { size: 24 }), "Installation"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Ajouter le module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: "pnpm lastbrain add-module ai" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: "pnpm build:modules" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Appliquer les migrations" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: "cd apps/votre-app" }), _jsx(Snippet, { symbol: "", hideSymbol: true, className: "text-sm mb-2", children: "supabase migration up" })] })] })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2", children: [_jsx(BookOpen, { size: 24 }), "Utilisation"] }) }), _jsx(CardBody, { className: "space-y-4", children: _jsx(DocUsageCustom, {}) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("h2", { className: "text-2xl font-semibold flex items-center gap-2 text-danger", children: [_jsx(AlertTriangle, { size: 24 }), "Danger Zone"] }) }), _jsxs(CardBody, { className: "space-y-4", children: [_jsxs(Alert, { color: "danger", className: "mb-4", children: [_jsx("p", { className: "text-sm font-semibold", children: "Cette action est irr\u00E9versible" }), _jsx("p", { className: "text-sm mt-2", children: "La suppression du module supprimera toutes les pages, routes API et migrations associ\u00E9es." })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("h3", { className: "text-lg font-semibold mb-2", children: "Supprimer le module" }), _jsx(Snippet, { symbol: "", hideSymbol: true, color: "danger", className: "text-sm mb-2", children: "pnpm lastbrain remove-module ai" }), _jsx(Snippet, { symbol: "", hideSymbol: true, color: "danger", className: "text-sm mb-2", children: "pnpm build:modules" })] })] })] })] }));
5
19
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Section personnalisée de documentation pour le module-ai
3
+ * Ce fichier NE SERA JAMAIS écrasé par update:module-docs
4
+ *
5
+ * Utilisez ce composant pour documenter l'utilisation spécifique
6
+ * de vos composants avec des exemples interactifs.
7
+ */
8
+ export declare function DocUsageCustom(): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=DocUsageCustom.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocUsageCustom.d.ts","sourceRoot":"","sources":["../../src/components/DocUsageCustom.tsx"],"names":[],"mappings":"AAOA;;;;;;GAMG;AACH,wBAAgB,cAAc,4CA2V7B"}
@@ -0,0 +1,70 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Card, CardBody, CardHeader, Alert, Tabs, Tab } from "@lastbrain/ui";
4
+ import { TextareaGenerative } from "../web/components/TextareaGenerative";
5
+ import { ImageGenerative } from "../web/components/ImageGenerative";
6
+ import { Lightbulb } from "lucide-react";
7
+ /**
8
+ * Section personnalisée de documentation pour le module-ai
9
+ * Ce fichier NE SERA JAMAIS écrasé par update:module-docs
10
+ *
11
+ * Utilisez ce composant pour documenter l'utilisation spécifique
12
+ * de vos composants avec des exemples interactifs.
13
+ */
14
+ export function DocUsageCustom() {
15
+ return (_jsxs("div", { className: "space-y-6", children: [_jsx(Alert, { hideIcon: true, variant: "flat", color: "primary", className: "mb-6", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(Lightbulb, { size: 20, className: "mt-0.5 flex-shrink-0" }), _jsxs("div", { children: [_jsx("p", { className: "font-semibold", children: "Composants IA G\u00E9n\u00E9ratives" }), _jsx("p", { className: "text-sm mt-1", children: "Le module AI fournit des composants pr\u00EAts \u00E0 l'emploi pour int\u00E9grer la g\u00E9n\u00E9ration de texte et d'images par IA dans vos applications." })] })] }) }), _jsxs(Tabs, { "aria-label": "Exemples d'utilisation", color: "primary", variant: "bordered", fullWidth: true, children: [_jsx(Tab, { title: "G\u00E9n\u00E9ration de Texte", children: _jsxs(Card, { className: "mt-4", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "TextareaGenerative" }) }), _jsxs(CardBody, { className: "space-y-6", children: [_jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Description" }), _jsx("p", { className: "text-slate-600 dark:text-slate-400", children: "Composant de g\u00E9n\u00E9ration de texte avec gestion automatique des tokens, support de streaming, et affichage du co\u00FBt en temps r\u00E9el." })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-3", children: "Exemple interactif" }), _jsx(TextareaGenerative, { defaultPrompt: "\u00C9cris une courte description pour un produit innovant", placeholder: "Le texte g\u00E9n\u00E9r\u00E9 appara\u00EEtra ici...", model: "gpt-4o-mini", showTokenBalance: true })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Code d'utilisation" }), _jsx(Alert, { hideIcon: true, variant: "flat", color: "primary", className: "p-4", children: _jsx("pre", { className: "whitespace-pre-wrap text-sm", children: `import { TextareaGenerative } from "@lastbrain/module-ai";
16
+
17
+ export function MyComponent() {
18
+ return (
19
+ <TextareaGenerative
20
+ defaultPrompt="Votre prompt par défaut ici"
21
+ placeholder="Le texte généré apparaîtra ici..."
22
+ model="gpt-4o-mini"
23
+ showTokenBalance={true}
24
+ onChange={(text, response) => {
25
+ console.log("Texte généré:", text);
26
+ console.log("Tokens utilisés:", response?.tokensUsed);
27
+ }}
28
+ onError={(error) => {
29
+ console.error("Erreur:", error);
30
+ }}
31
+ />
32
+ );
33
+ }` }) })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Props disponibles" }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "border-b", children: _jsxs("tr", { children: [_jsx("th", { className: "text-left py-2 px-2", children: "Prop" }), _jsx("th", { className: "text-left py-2 px-2", children: "Type" }), _jsx("th", { className: "text-left py-2 px-2", children: "Description" })] }) }), _jsxs("tbody", { className: "divide-y", children: [_jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "prompt" }), _jsx("td", { className: "py-2 px-2", children: "string" }), _jsx("td", { className: "py-2 px-2", children: "Le prompt \u00E0 envoyer \u00E0 l'IA" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "model" }), _jsx("td", { className: "py-2 px-2", children: "string" }), _jsx("td", { className: "py-2 px-2", children: "Mod\u00E8le \u00E0 utiliser (d\u00E9faut: gpt-4o-mini)" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "placeholder" }), _jsx("td", { className: "py-2 px-2", children: "string" }), _jsx("td", { className: "py-2 px-2", children: "Texte placeholder du textarea" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "onChange" }), _jsx("td", { className: "py-2 px-2", children: "function" }), _jsx("td", { className: "py-2 px-2", children: "Callback apr\u00E8s g\u00E9n\u00E9ration r\u00E9ussie" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "onError" }), _jsx("td", { className: "py-2 px-2", children: "function" }), _jsx("td", { className: "py-2 px-2", children: "Callback en cas d'erreur" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "showTokenBalance" }), _jsx("td", { className: "py-2 px-2", children: "boolean" }), _jsx("td", { className: "py-2 px-2", children: "Afficher le solde de tokens" })] })] })] }) })] })] })] }) }, "textarea"), _jsx(Tab, { title: "G\u00E9n\u00E9ration d'Images", children: _jsxs(Card, { className: "mt-4", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "ImageGenerative" }) }), _jsxs(CardBody, { className: "space-y-6", children: [_jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Description" }), _jsx("p", { className: "text-slate-600 dark:text-slate-400", children: "Composant de g\u00E9n\u00E9ration d'images avec DALL-E, support de diff\u00E9rentes tailles et qualit\u00E9s, et t\u00E9l\u00E9chargement direct de l'image." })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-3", children: "Exemple interactif" }), _jsx(ImageGenerative, { defaultPrompt: "Un chat astronaute dans l'espace, style cartoon color\u00E9", model: "dall-e-3", size: "1024x1024", quality: "standard", showTokenBalance: true, defaultStyle: "digital-art" })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Code d'utilisation" }), _jsx(Alert, { hideIcon: true, variant: "flat", color: "primary", className: "p-4", children: _jsx("pre", { className: "whitespace-pre-wrap text-sm", children: `import { ImageGenerative } from "@lastbrain/module-ai";
34
+
35
+ export function MyComponent() {
36
+ return (
37
+ <ImageGenerative
38
+ defaultPrompt="Un paysage futuriste avec des buildings"
39
+ defaultStyle="realistic"
40
+ model="dall-e-3"
41
+ size="1024x1024"
42
+ quality="hd"
43
+ showTokenBalance={true}
44
+ onChange={(imageUrl, response) => {
45
+ console.log("Image générée:", imageUrl);
46
+ console.log("Tokens utilisés:", response?.tokensUsed);
47
+ }}
48
+ onError={(error) => {
49
+ console.error("Erreur:", error);
50
+ }}
51
+ />
52
+ );
53
+ }` }) })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Props disponibles" }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "border-b", children: _jsxs("tr", { children: [_jsx("th", { className: "text-left py-2 px-2", children: "Prop" }), _jsx("th", { className: "text-left py-2 px-2", children: "Type" }), _jsx("th", { className: "text-left py-2 px-2", children: "Description" })] }) }), _jsxs("tbody", { className: "divide-y", children: [_jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "prompt" }), _jsx("td", { className: "py-2 px-2", children: "string" }), _jsx("td", { className: "py-2 px-2", children: "Description de l'image \u00E0 g\u00E9n\u00E9rer" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "model" }), _jsx("td", { className: "py-2 px-2", children: "string" }), _jsx("td", { className: "py-2 px-2", children: "Mod\u00E8le (dall-e-2 ou dall-e-3)" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "size" }), _jsx("td", { className: "py-2 px-2", children: "string" }), _jsx("td", { className: "py-2 px-2", children: "Taille: 256x256, 512x512, 1024x1024, etc." })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "quality" }), _jsx("td", { className: "py-2 px-2", children: "string" }), _jsx("td", { className: "py-2 px-2", children: "Qualit\u00E9: standard ou hd" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "onChange" }), _jsx("td", { className: "py-2 px-2", children: "function" }), _jsx("td", { className: "py-2 px-2", children: "Callback apr\u00E8s g\u00E9n\u00E9ration r\u00E9ussie" })] }), _jsxs("tr", { children: [_jsx("td", { className: "py-2 px-2 font-mono", children: "onError" }), _jsx("td", { className: "py-2 px-2", children: "function" }), _jsx("td", { className: "py-2 px-2", children: "Callback en cas d'erreur" })] })] })] }) })] })] })] }) }, "image"), _jsx(Tab, { title: "Utilisation Avanc\u00E9e", children: _jsxs(Card, { className: "mt-4", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-xl font-semibold", children: "Configuration Avanc\u00E9e" }) }), _jsxs(CardBody, { className: "space-y-6", children: [_jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "API Endpoints Personnalis\u00E9s" }), _jsx(Alert, { hideIcon: true, variant: "flat", color: "primary", className: "p-4", children: _jsx("pre", { className: "whitespace-pre-wrap text-sm", children: `// Utilisez vos propres endpoints API
54
+ <TextareaGenerative
55
+ prompt="Votre prompt"
56
+ apiEndpoint="/api/custom/generate-text"
57
+ />
58
+
59
+ <ImageGenerative
60
+ prompt="Votre prompt"
61
+ apiEndpoint="/api/custom/generate-image"
62
+ />` }) })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Gestion des Tokens" }), _jsx("p", { className: "text-slate-600 dark:text-slate-400 mb-3", children: "Les composants utilisent automatiquement le syst\u00E8me de tokens du module. Chaque g\u00E9n\u00E9ration d\u00E9duit le nombre de tokens utilis\u00E9s du solde de l'utilisateur." }), _jsx(Alert, { hideIcon: true, variant: "flat", color: "warning", className: "p-4", children: _jsx("pre", { className: "whitespace-pre-wrap text-sm", children: `// Les tokens sont automatiquement gérés
63
+ // via la table user_token_ledger
64
+
65
+ // Pour recharger les tokens :
66
+ // 1. Accédez à /admin/user-token
67
+ // 2. Créditez les utilisateurs
68
+ // 3. Les composants détectent automatiquement
69
+ // le nouveau solde` }) })] }), _jsxs("div", { children: [_jsx("h4", { className: "text-lg font-semibold mb-2", children: "Best Practices" }), _jsxs("ul", { className: "list-disc list-inside space-y-2 text-slate-600 dark:text-slate-400", children: [_jsxs("li", { children: ["Utilisez", " ", _jsx("code", { className: "text-xs bg-slate-100 dark:bg-slate-800 px-1 py-0.5 rounded", children: "gpt-4o-mini" }), " ", "pour les t\u00E2ches simples (moins cher)"] }), _jsx("li", { children: "Limitez la longueur des prompts pour \u00E9conomiser les tokens" }), _jsxs("li", { children: ["G\u00E9rez les erreurs avec", " ", _jsx("code", { className: "text-xs bg-slate-100 dark:bg-slate-800 px-1 py-0.5 rounded", children: "onError" })] }), _jsxs("li", { children: ["Affichez toujours le solde de tokens avec", " ", _jsx("code", { className: "text-xs bg-slate-100 dark:bg-slate-800 px-1 py-0.5 rounded", children: "showTokenBalance" })] }), _jsxs("li", { children: ["Pour les images, utilisez", " ", _jsx("code", { className: "text-xs bg-slate-100 dark:bg-slate-800 px-1 py-0.5 rounded", children: "standard" }), " ", "par d\u00E9faut,", " ", _jsx("code", { className: "text-xs bg-slate-100 dark:bg-slate-800 px-1 py-0.5 rounded", children: "hd" }), " ", "uniquement si n\u00E9cessaire"] })] })] })] })] }) }, "advanced")] })] }));
70
+ }
@@ -0,0 +1,6 @@
1
+ interface UserTokenTabProps {
2
+ userId: string;
3
+ }
4
+ export declare function UserTokenTab({ userId }: UserTokenTabProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=UserTokenTab.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserTokenTab.d.ts","sourceRoot":"","sources":["../../../src/components/admin/UserTokenTab.tsx"],"names":[],"mappings":"AAeA,UAAU,iBAAiB;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,EAAE,iBAAiB,2CA2OzD"}
@@ -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,8 +2,9 @@ export { TextareaGenerative } from "./web/components/TextareaGenerative.js";
2
2
  export { ImageGenerative } from "./web/components/ImageGenerative.js";
3
3
  export { TokenPage } from "./web/auth/TokenPage.js";
4
4
  export { UserTokenPage } from "./web/admin/UserTokenPage.js";
5
- export { UserTokenIdPage } from "./web/admin/UserTokenIdPage.js";
6
- export { AiModuleDoc } from "./components/Doc.js";
5
+ export { UserTokenTab } from "./components/admin/UserTokenTab.js";
6
+ export { Doc } from "./components/Doc.js";
7
+ export { Doc as AiModuleDoc } from "./components/Doc.js";
7
8
  export type { GenerativeResponse, TextareaGenerativeProps, } from "./web/components/TextareaGenerative.js";
8
9
  export type { GenerativeImageResponse, ImageGenerativeProps, } from "./web/components/ImageGenerative.js";
9
10
  export { default as buildConfig } from "./ai.build.config.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAGjE,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGlD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,wCAAwC,CAAC;AAEhD,YAAY,EACV,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,wCAAwC,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAGtE,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAGpD,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAG7D,OAAO,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAGlE,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,GAAG,IAAI,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGzD,YAAY,EACV,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,wCAAwC,CAAC;AAEhD,YAAY,EACV,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js CHANGED
@@ -5,8 +5,10 @@ export { ImageGenerative } from "./web/components/ImageGenerative.js";
5
5
  export { TokenPage } from "./web/auth/TokenPage.js";
6
6
  // Pages Admin
7
7
  export { UserTokenPage } from "./web/admin/UserTokenPage.js";
8
- export { UserTokenIdPage } from "./web/admin/UserTokenIdPage.js";
8
+ // Admin Components - User Tabs
9
+ export { UserTokenTab } from "./components/admin/UserTokenTab.js";
9
10
  // Documentation
10
- export { AiModuleDoc } from "./components/Doc.js";
11
+ export { Doc } from "./components/Doc.js";
12
+ export { Doc as AiModuleDoc } from "./components/Doc.js";
11
13
  // Configuration de build
12
14
  export { default as buildConfig } from "./ai.build.config.js";
@@ -1 +1 @@
1
- {"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AAIA,wBAAgB,aAAa,4CAe5B"}
1
+ {"version":3,"file":"UserTokenPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenPage.tsx"],"names":[],"mappings":"AA+DA,wBAAgB,aAAa,4CAuY5B"}