@lastbrain/module-ai 0.1.0

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 (56) hide show
  1. package/dist/ai.build.config.d.ts +4 -0
  2. package/dist/ai.build.config.d.ts.map +1 -0
  3. package/dist/ai.build.config.js +86 -0
  4. package/dist/api/admin/user-token/[id].d.ts +29 -0
  5. package/dist/api/admin/user-token/[id].d.ts.map +1 -0
  6. package/dist/api/admin/user-token/[id].js +57 -0
  7. package/dist/api/admin/user-token.d.ts +20 -0
  8. package/dist/api/admin/user-token.d.ts.map +1 -0
  9. package/dist/api/admin/user-token.js +92 -0
  10. package/dist/api/admin/user_prompts.d.ts +17 -0
  11. package/dist/api/admin/user_prompts.d.ts.map +1 -0
  12. package/dist/api/admin/user_prompts.js +94 -0
  13. package/dist/api/admin/user_token_ledger.d.ts +17 -0
  14. package/dist/api/admin/user_token_ledger.d.ts.map +1 -0
  15. package/dist/api/admin/user_token_ledger.js +94 -0
  16. package/dist/api/auth/generate-image.d.ts +11 -0
  17. package/dist/api/auth/generate-image.d.ts.map +1 -0
  18. package/dist/api/auth/generate-image.js +104 -0
  19. package/dist/api/auth/generate-text.d.ts +11 -0
  20. package/dist/api/auth/generate-text.d.ts.map +1 -0
  21. package/dist/api/auth/generate-text.js +96 -0
  22. package/dist/api/auth/user_prompts.d.ts +17 -0
  23. package/dist/api/auth/user_prompts.d.ts.map +1 -0
  24. package/dist/api/auth/user_prompts.js +94 -0
  25. package/dist/api/auth/user_token_ledger.d.ts +17 -0
  26. package/dist/api/auth/user_token_ledger.d.ts.map +1 -0
  27. package/dist/api/auth/user_token_ledger.js +94 -0
  28. package/dist/components/Doc.d.ts +2 -0
  29. package/dist/components/Doc.d.ts.map +1 -0
  30. package/dist/components/Doc.js +5 -0
  31. package/dist/index.d.ts +10 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +12 -0
  34. package/dist/server.d.ts +61 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js +186 -0
  37. package/dist/web/admin/UserTokenIdPage.d.ts +6 -0
  38. package/dist/web/admin/UserTokenIdPage.d.ts.map +1 -0
  39. package/dist/web/admin/UserTokenIdPage.js +8 -0
  40. package/dist/web/admin/UserTokenPage.d.ts +2 -0
  41. package/dist/web/admin/UserTokenPage.d.ts.map +1 -0
  42. package/dist/web/admin/UserTokenPage.js +6 -0
  43. package/dist/web/auth/TokenPage.d.ts +2 -0
  44. package/dist/web/auth/TokenPage.d.ts.map +1 -0
  45. package/dist/web/auth/TokenPage.js +6 -0
  46. package/dist/web/components/ImageGenerative.d.ts +22 -0
  47. package/dist/web/components/ImageGenerative.d.ts.map +1 -0
  48. package/dist/web/components/ImageGenerative.js +87 -0
  49. package/dist/web/components/TextareaGenerative.d.ts +23 -0
  50. package/dist/web/components/TextareaGenerative.d.ts.map +1 -0
  51. package/dist/web/components/TextareaGenerative.js +60 -0
  52. package/package.json +72 -0
  53. package/supabase/migrations/20251121000000_ai_tokens.sql +189 -0
  54. package/supabase/migrations/20251121093113_module-ai_init.sql +122 -0
  55. package/supabase/migrations-down/20251121000000_ai_tokens.sql +23 -0
  56. package/supabase/migrations-down/20251121093113_module-ai_init.sql +11 -0
@@ -0,0 +1,96 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+ import { useTokens, getTokenBalance } from "../../server";
4
+ import OpenAI from "openai";
5
+ const openai = new OpenAI({
6
+ apiKey: process.env.OPENAI_API_KEY,
7
+ });
8
+ export async function POST(request) {
9
+ try {
10
+ const supabase = await getSupabaseServerClient();
11
+ // Vérifier l'authentification
12
+ const { data: { user }, error: authError, } = await supabase.auth.getUser();
13
+ if (authError || !user) {
14
+ return NextResponse.json({ error: "Non autorisé" }, { status: 401 });
15
+ }
16
+ const body = await request.json();
17
+ const { prompt, model = "gpt-4o-mini", context, maxTokens = 2000, temperature = 0.7, } = body;
18
+ if (!prompt) {
19
+ return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
20
+ }
21
+ // Vérifier le solde de tokens
22
+ const currentBalance = await getTokenBalance(user.id);
23
+ const estimatedCost = Math.ceil(prompt.length / 4) + maxTokens; // Estimation approximative
24
+ if (currentBalance < estimatedCost) {
25
+ return NextResponse.json({
26
+ error: `Solde insuffisant. Disponible: ${currentBalance}, estimé requis: ${estimatedCost}`,
27
+ }, { status: 402 });
28
+ }
29
+ // Construire les messages
30
+ const messages = [
31
+ {
32
+ role: "system",
33
+ content: "Tu es un assistant IA qui aide à générer du texte de qualité.",
34
+ },
35
+ ];
36
+ if (context) {
37
+ messages.push({
38
+ role: "user",
39
+ content: `Contexte actuel: ${context}`,
40
+ });
41
+ }
42
+ messages.push({
43
+ role: "user",
44
+ content: prompt,
45
+ });
46
+ // Appeler OpenAI
47
+ const completion = await openai.chat.completions.create({
48
+ model,
49
+ messages,
50
+ temperature,
51
+ max_tokens: maxTokens,
52
+ });
53
+ const generatedText = completion.choices[0]?.message?.content || "";
54
+ const tokensUsed = completion.usage?.total_tokens || 0;
55
+ // Calculer le coût approximatif (exemple pour gpt-4o-mini: $0.150/1M input, $0.600/1M output)
56
+ const inputTokens = completion.usage?.prompt_tokens || 0;
57
+ const outputTokens = completion.usage?.completion_tokens || 0;
58
+ let cost = 0;
59
+ if (model.includes("gpt-4o-mini")) {
60
+ cost = (inputTokens / 1_000_000) * 0.15 + (outputTokens / 1_000_000) * 0.6;
61
+ }
62
+ else if (model.includes("gpt-4o")) {
63
+ cost = (inputTokens / 1_000_000) * 2.5 + (outputTokens / 1_000_000) * 10;
64
+ }
65
+ else if (model.includes("gpt-4")) {
66
+ cost = (inputTokens / 1_000_000) * 30 + (outputTokens / 1_000_000) * 60;
67
+ }
68
+ else {
69
+ cost = (tokensUsed / 1_000_000) * 2; // Fallback
70
+ }
71
+ // Déduire les tokens utilisés
72
+ const tokenResult = await useTokens(user.id, tokensUsed, model, prompt, {
73
+ inputTokens,
74
+ outputTokens,
75
+ cost,
76
+ generatedText: generatedText.substring(0, 500), // Stocker un extrait
77
+ });
78
+ if (!tokenResult.success) {
79
+ return NextResponse.json({ error: tokenResult.error || "Erreur lors de la déduction des tokens" }, { status: 500 });
80
+ }
81
+ return NextResponse.json({
82
+ text: generatedText,
83
+ tokensUsed,
84
+ tokensRemaining: tokenResult.balance,
85
+ model,
86
+ cost,
87
+ });
88
+ }
89
+ catch (error) {
90
+ console.error("Erreur de génération:", error);
91
+ if (error.code === 'insufficient_quota') {
92
+ return NextResponse.json({ error: "Quota OpenAI dépassé. Contactez l'administrateur." }, { status: 503 });
93
+ }
94
+ return NextResponse.json({ error: error.message || "Erreur lors de la génération" }, { status: 500 });
95
+ }
96
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * GET - Liste tous les enregistrements de user_prompts
3
+ */
4
+ export declare function GET(request: Request): Promise<Response>;
5
+ /**
6
+ * POST - Crée un nouvel enregistrement dans user_prompts
7
+ */
8
+ export declare function POST(request: Request): Promise<Response>;
9
+ /**
10
+ * PUT - Met à jour un enregistrement dans user_prompts
11
+ */
12
+ export declare function PUT(request: Request): Promise<Response>;
13
+ /**
14
+ * DELETE - Supprime un enregistrement de user_prompts
15
+ */
16
+ export declare function DELETE(request: Request): Promise<Response>;
17
+ //# sourceMappingURL=user_prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user_prompts.d.ts","sourceRoot":"","sources":["../../../src/api/auth/user_prompts.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,qBAkBzC;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,qBAsB1C;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,qBA4BzC;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,qBA0B5C"}
@@ -0,0 +1,94 @@
1
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
2
+ const jsonResponse = (payload, status = 200) => {
3
+ return new Response(JSON.stringify(payload), {
4
+ headers: {
5
+ "content-type": "application/json"
6
+ },
7
+ status
8
+ });
9
+ };
10
+ /**
11
+ * GET - Liste tous les enregistrements de user_prompts
12
+ */
13
+ export async function GET(request) {
14
+ const supabase = await getSupabaseServerClient();
15
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
16
+ if (authError || !user) {
17
+ return jsonResponse({ error: "Non authentifié" }, 401);
18
+ }
19
+ const { data, error } = await supabase
20
+ .from("user_prompts")
21
+ .select("*");
22
+ if (error) {
23
+ return jsonResponse({ error: error.message }, 400);
24
+ }
25
+ return jsonResponse({ data });
26
+ }
27
+ /**
28
+ * POST - Crée un nouvel enregistrement dans user_prompts
29
+ */
30
+ export async function POST(request) {
31
+ const supabase = await getSupabaseServerClient();
32
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
33
+ if (authError || !user) {
34
+ return jsonResponse({ error: "Non authentifié" }, 401);
35
+ }
36
+ const body = await request.json();
37
+ const { data, error } = await supabase
38
+ .from("user_prompts")
39
+ .insert(body)
40
+ .select()
41
+ .single();
42
+ if (error) {
43
+ return jsonResponse({ error: error.message }, 400);
44
+ }
45
+ return jsonResponse({ data }, 201);
46
+ }
47
+ /**
48
+ * PUT - Met à jour un enregistrement dans user_prompts
49
+ */
50
+ export async function PUT(request) {
51
+ const supabase = await getSupabaseServerClient();
52
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
53
+ if (authError || !user) {
54
+ return jsonResponse({ error: "Non authentifié" }, 401);
55
+ }
56
+ const body = await request.json();
57
+ const { id, ...updateData } = body;
58
+ if (!id) {
59
+ return jsonResponse({ error: "ID requis pour la mise à jour" }, 400);
60
+ }
61
+ const { data, error } = await supabase
62
+ .from("user_prompts")
63
+ .update(updateData)
64
+ .eq("id", id)
65
+ .select()
66
+ .single();
67
+ if (error) {
68
+ return jsonResponse({ error: error.message }, 400);
69
+ }
70
+ return jsonResponse({ data });
71
+ }
72
+ /**
73
+ * DELETE - Supprime un enregistrement de user_prompts
74
+ */
75
+ export async function DELETE(request) {
76
+ const supabase = await getSupabaseServerClient();
77
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
78
+ if (authError || !user) {
79
+ return jsonResponse({ error: "Non authentifié" }, 401);
80
+ }
81
+ const { searchParams } = new URL(request.url);
82
+ const id = searchParams.get("id");
83
+ if (!id) {
84
+ return jsonResponse({ error: "ID requis pour la suppression" }, 400);
85
+ }
86
+ const { error } = await supabase
87
+ .from("user_prompts")
88
+ .delete()
89
+ .eq("id", id);
90
+ if (error) {
91
+ return jsonResponse({ error: error.message }, 400);
92
+ }
93
+ return jsonResponse({ success: true });
94
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * GET - Liste tous les enregistrements de user_token_ledger
3
+ */
4
+ export declare function GET(request: Request): Promise<Response>;
5
+ /**
6
+ * POST - Crée un nouvel enregistrement dans user_token_ledger
7
+ */
8
+ export declare function POST(request: Request): Promise<Response>;
9
+ /**
10
+ * PUT - Met à jour un enregistrement dans user_token_ledger
11
+ */
12
+ export declare function PUT(request: Request): Promise<Response>;
13
+ /**
14
+ * DELETE - Supprime un enregistrement de user_token_ledger
15
+ */
16
+ export declare function DELETE(request: Request): Promise<Response>;
17
+ //# sourceMappingURL=user_token_ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user_token_ledger.d.ts","sourceRoot":"","sources":["../../../src/api/auth/user_token_ledger.ts"],"names":[],"mappings":"AAWA;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,qBAkBzC;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,OAAO,qBAsB1C;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,OAAO,qBA4BzC;AAED;;GAEG;AACH,wBAAsB,MAAM,CAAC,OAAO,EAAE,OAAO,qBA0B5C"}
@@ -0,0 +1,94 @@
1
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
2
+ const jsonResponse = (payload, status = 200) => {
3
+ return new Response(JSON.stringify(payload), {
4
+ headers: {
5
+ "content-type": "application/json"
6
+ },
7
+ status
8
+ });
9
+ };
10
+ /**
11
+ * GET - Liste tous les enregistrements de user_token_ledger
12
+ */
13
+ export async function GET(request) {
14
+ const supabase = await getSupabaseServerClient();
15
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
16
+ if (authError || !user) {
17
+ return jsonResponse({ error: "Non authentifié" }, 401);
18
+ }
19
+ const { data, error } = await supabase
20
+ .from("user_token_ledger")
21
+ .select("*");
22
+ if (error) {
23
+ return jsonResponse({ error: error.message }, 400);
24
+ }
25
+ return jsonResponse({ data });
26
+ }
27
+ /**
28
+ * POST - Crée un nouvel enregistrement dans user_token_ledger
29
+ */
30
+ export async function POST(request) {
31
+ const supabase = await getSupabaseServerClient();
32
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
33
+ if (authError || !user) {
34
+ return jsonResponse({ error: "Non authentifié" }, 401);
35
+ }
36
+ const body = await request.json();
37
+ const { data, error } = await supabase
38
+ .from("user_token_ledger")
39
+ .insert(body)
40
+ .select()
41
+ .single();
42
+ if (error) {
43
+ return jsonResponse({ error: error.message }, 400);
44
+ }
45
+ return jsonResponse({ data }, 201);
46
+ }
47
+ /**
48
+ * PUT - Met à jour un enregistrement dans user_token_ledger
49
+ */
50
+ export async function PUT(request) {
51
+ const supabase = await getSupabaseServerClient();
52
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
53
+ if (authError || !user) {
54
+ return jsonResponse({ error: "Non authentifié" }, 401);
55
+ }
56
+ const body = await request.json();
57
+ const { id, ...updateData } = body;
58
+ if (!id) {
59
+ return jsonResponse({ error: "ID requis pour la mise à jour" }, 400);
60
+ }
61
+ const { data, error } = await supabase
62
+ .from("user_token_ledger")
63
+ .update(updateData)
64
+ .eq("id", id)
65
+ .select()
66
+ .single();
67
+ if (error) {
68
+ return jsonResponse({ error: error.message }, 400);
69
+ }
70
+ return jsonResponse({ data });
71
+ }
72
+ /**
73
+ * DELETE - Supprime un enregistrement de user_token_ledger
74
+ */
75
+ export async function DELETE(request) {
76
+ const supabase = await getSupabaseServerClient();
77
+ const { data: { user }, error: authError } = await supabase.auth.getUser();
78
+ if (authError || !user) {
79
+ return jsonResponse({ error: "Non authentifié" }, 401);
80
+ }
81
+ const { searchParams } = new URL(request.url);
82
+ const id = searchParams.get("id");
83
+ if (!id) {
84
+ return jsonResponse({ error: "ID requis pour la suppression" }, 400);
85
+ }
86
+ const { error } = await supabase
87
+ .from("user_token_ledger")
88
+ .delete()
89
+ .eq("id", id);
90
+ if (error) {
91
+ return jsonResponse({ error: error.message }, 400);
92
+ }
93
+ return jsonResponse({ success: true });
94
+ }
@@ -0,0 +1,2 @@
1
+ export declare function AiModuleDoc(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=Doc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Doc.d.ts","sourceRoot":"","sources":["../../src/components/Doc.tsx"],"names":[],"mappings":"AAUA,wBAAgB,WAAW,4CAqU1B"}
@@ -0,0 +1,5 @@
1
+ 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/auth/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/auth/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/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/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/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');\")" })] }) })] })] })] })] }));
5
+ }
@@ -0,0 +1,10 @@
1
+ export { TextareaGenerative } from "./web/components/TextareaGenerative.js";
2
+ export { ImageGenerative } from "./web/components/ImageGenerative.js";
3
+ export { TokenPage } from "./web/auth/TokenPage.js";
4
+ export { UserTokenPage } from "./web/admin/UserTokenPage.js";
5
+ export { UserTokenIdPage } from "./web/admin/UserTokenIdPage.js";
6
+ export { AiModuleDoc } from "./components/Doc.js";
7
+ export type { GenerativeResponse, TextareaGenerativeProps } from "./web/components/TextareaGenerative.js";
8
+ export type { GenerativeImageResponse, ImageGenerativeProps } from "./web/components/ImageGenerative.js";
9
+ export { default as buildConfig } from "./ai.build.config.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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,EACxB,MAAM,wCAAwC,CAAC;AAEhD,YAAY,EACV,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // Client Components - Composants réutilisables
2
+ export { TextareaGenerative } from "./web/components/TextareaGenerative.js";
3
+ export { ImageGenerative } from "./web/components/ImageGenerative.js";
4
+ // Pages Auth
5
+ export { TokenPage } from "./web/auth/TokenPage.js";
6
+ // Pages Admin
7
+ export { UserTokenPage } from "./web/admin/UserTokenPage.js";
8
+ export { UserTokenIdPage } from "./web/admin/UserTokenIdPage.js";
9
+ // Documentation
10
+ export { AiModuleDoc } from "./components/Doc.js";
11
+ // Configuration de build
12
+ export { default as buildConfig } from "./ai.build.config.js";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Interface pour le résultat d'opération sur les tokens
3
+ */
4
+ export interface TokenOperationResult {
5
+ success: boolean;
6
+ balance: number;
7
+ error?: string;
8
+ }
9
+ /**
10
+ * Interface pour l'historique des tokens
11
+ */
12
+ export interface TokenLedgerEntry {
13
+ id: string;
14
+ owner_id: string;
15
+ ts: string;
16
+ type: 'purchase' | 'gift' | 'use' | 'adjust';
17
+ amount: number;
18
+ model?: string;
19
+ prompt?: string;
20
+ meta: Record<string, any>;
21
+ created_by?: string;
22
+ created_at: string;
23
+ }
24
+ /**
25
+ * Ajoute des tokens au compte d'un utilisateur
26
+ */
27
+ export declare function addTokens(userId: string, amount: number, type?: 'purchase' | 'gift' | 'adjust', meta?: Record<string, any>, createdBy?: string): Promise<TokenOperationResult>;
28
+ /**
29
+ * Utilise des tokens du compte d'un utilisateur
30
+ */
31
+ export declare function useTokens(userId: string, amount: number, model?: string, prompt?: string, meta?: Record<string, any>): Promise<TokenOperationResult>;
32
+ /**
33
+ * Récupère le solde de tokens d'un utilisateur
34
+ */
35
+ export declare function getTokenBalance(userId: string): Promise<number>;
36
+ /**
37
+ * Vérifie si un utilisateur a suffisamment de tokens
38
+ */
39
+ export declare function checkTokenBalance(userId: string, amount: number): Promise<boolean>;
40
+ /**
41
+ * Récupère l'historique des tokens d'un utilisateur
42
+ */
43
+ export declare function getTokenHistory(userId: string, limit?: number, offset?: number): Promise<TokenLedgerEntry[]>;
44
+ /**
45
+ * Récupère les statistiques de tokens d'un utilisateur
46
+ */
47
+ export declare function getTokenStats(userId: string): Promise<{
48
+ balance: number;
49
+ totalPurchased: number;
50
+ totalGifted: number;
51
+ totalUsed: number;
52
+ totalAdjusted: number;
53
+ }>;
54
+ /**
55
+ * Récupère tous les utilisateurs avec leur solde de tokens (admin uniquement)
56
+ */
57
+ export declare function getAllUsersTokenBalance(limit?: number, offset?: number): Promise<{
58
+ owner_id: any;
59
+ balance: any;
60
+ }[]>;
61
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,UAAU,GAAG,MAAM,GAAG,QAAiB,EAC7C,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,EAC9B,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC,CAmB/B;AAED;;GAEG;AACH,wBAAsB,SAAS,CAC7B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,EACd,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAM,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA4C/B;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAoBrE;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGxF;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,GACjB,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAiB7B;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM;;;;;;GA4CjD;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU;;;KAiBnB"}
package/dist/server.js ADDED
@@ -0,0 +1,186 @@
1
+ // Server-only exports (Route Handlers, Server Actions, etc.)
2
+ import { getSupabaseServerClient } from "@lastbrain/core/server";
3
+ /**
4
+ * Ajoute des tokens au compte d'un utilisateur
5
+ */
6
+ export async function addTokens(userId, amount, type = 'gift', meta = {}, createdBy) {
7
+ if (amount <= 0) {
8
+ return { success: false, balance: 0, error: "Le montant doit être positif" };
9
+ }
10
+ const supabase = await getSupabaseServerClient();
11
+ try {
12
+ const { error } = await supabase.from('user_token_ledger').insert({
13
+ owner_id: userId, type, amount, meta, created_by: createdBy
14
+ });
15
+ if (error)
16
+ throw error;
17
+ const balance = await getTokenBalance(userId);
18
+ return { success: true, balance };
19
+ }
20
+ catch (error) {
21
+ console.error('[addTokens] Error:', error);
22
+ return { success: false, balance: 0, error: error.message || 'Erreur lors de l\'ajout de tokens' };
23
+ }
24
+ }
25
+ /**
26
+ * Utilise des tokens du compte d'un utilisateur
27
+ */
28
+ export async function useTokens(userId, amount, model, prompt, meta = {}) {
29
+ if (amount <= 0) {
30
+ return { success: false, balance: 0, error: "Le montant doit être positif" };
31
+ }
32
+ const supabase = await getSupabaseServerClient();
33
+ try {
34
+ const currentBalance = await getTokenBalance(userId);
35
+ if (currentBalance < amount) {
36
+ return {
37
+ success: false,
38
+ balance: currentBalance,
39
+ error: `Solde insuffisant. Disponible: ${currentBalance}, requis: ${amount}`
40
+ };
41
+ }
42
+ const { error } = await supabase.from('user_token_ledger').insert({
43
+ owner_id: userId,
44
+ type: 'use',
45
+ amount: -amount,
46
+ model,
47
+ prompt,
48
+ meta
49
+ });
50
+ if (error) {
51
+ if (error.message?.includes('INSUFFICIENT_TOKEN_BALANCE')) {
52
+ const currentBalance = await getTokenBalance(userId);
53
+ return {
54
+ success: false,
55
+ balance: currentBalance,
56
+ error: `Solde insuffisant. Disponible: ${currentBalance}, requis: ${amount}`
57
+ };
58
+ }
59
+ throw error;
60
+ }
61
+ const balance = await getTokenBalance(userId);
62
+ return { success: true, balance };
63
+ }
64
+ catch (error) {
65
+ console.error('[useTokens] Error:', error);
66
+ return { success: false, balance: 0, error: error.message || 'Erreur lors de l\'utilisation de tokens' };
67
+ }
68
+ }
69
+ /**
70
+ * Récupère le solde de tokens d'un utilisateur
71
+ */
72
+ export async function getTokenBalance(userId) {
73
+ const supabase = await getSupabaseServerClient();
74
+ try {
75
+ const { data, error } = await supabase
76
+ .from('user_token_balance_v')
77
+ .select('balance')
78
+ .eq('owner_id', userId)
79
+ .single();
80
+ if (error) {
81
+ if (error.code === 'PGRST116')
82
+ return 0;
83
+ throw error;
84
+ }
85
+ return data?.balance || 0;
86
+ }
87
+ catch (error) {
88
+ console.error('[getTokenBalance] Error:', error);
89
+ return 0;
90
+ }
91
+ }
92
+ /**
93
+ * Vérifie si un utilisateur a suffisamment de tokens
94
+ */
95
+ export async function checkTokenBalance(userId, amount) {
96
+ const balance = await getTokenBalance(userId);
97
+ return balance >= amount;
98
+ }
99
+ /**
100
+ * Récupère l'historique des tokens d'un utilisateur
101
+ */
102
+ export async function getTokenHistory(userId, limit = 50, offset = 0) {
103
+ const supabase = await getSupabaseServerClient();
104
+ try {
105
+ const { data, error } = await supabase
106
+ .from('user_token_ledger')
107
+ .select('*')
108
+ .eq('owner_id', userId)
109
+ .order('ts', { ascending: false })
110
+ .range(offset, offset + limit - 1);
111
+ if (error)
112
+ throw error;
113
+ return data || [];
114
+ }
115
+ catch (error) {
116
+ console.error('[getTokenHistory] Error:', error);
117
+ return [];
118
+ }
119
+ }
120
+ /**
121
+ * Récupère les statistiques de tokens d'un utilisateur
122
+ */
123
+ export async function getTokenStats(userId) {
124
+ const supabase = await getSupabaseServerClient();
125
+ try {
126
+ const { data, error } = await supabase
127
+ .from('user_token_ledger')
128
+ .select('type, amount')
129
+ .eq('owner_id', userId);
130
+ if (error)
131
+ throw error;
132
+ const stats = {
133
+ balance: 0,
134
+ totalPurchased: 0,
135
+ totalGifted: 0,
136
+ totalUsed: 0,
137
+ totalAdjusted: 0
138
+ };
139
+ data?.forEach((entry) => {
140
+ stats.balance += entry.amount;
141
+ if (entry.type === 'purchase' && entry.amount > 0) {
142
+ stats.totalPurchased += entry.amount;
143
+ }
144
+ else if (entry.type === 'gift' && entry.amount > 0) {
145
+ stats.totalGifted += entry.amount;
146
+ }
147
+ else if (entry.type === 'use' && entry.amount < 0) {
148
+ stats.totalUsed += Math.abs(entry.amount);
149
+ }
150
+ else if (entry.type === 'adjust') {
151
+ stats.totalAdjusted += entry.amount;
152
+ }
153
+ });
154
+ return stats;
155
+ }
156
+ catch (error) {
157
+ console.error('[getTokenStats] Error:', error);
158
+ return {
159
+ balance: 0,
160
+ totalPurchased: 0,
161
+ totalGifted: 0,
162
+ totalUsed: 0,
163
+ totalAdjusted: 0
164
+ };
165
+ }
166
+ }
167
+ /**
168
+ * Récupère tous les utilisateurs avec leur solde de tokens (admin uniquement)
169
+ */
170
+ export async function getAllUsersTokenBalance(limit = 50, offset = 0) {
171
+ const supabase = await getSupabaseServerClient();
172
+ try {
173
+ const { data, error } = await supabase
174
+ .from('user_token_balance_v')
175
+ .select('owner_id, balance')
176
+ .order('balance', { ascending: false })
177
+ .range(offset, offset + limit - 1);
178
+ if (error)
179
+ throw error;
180
+ return data || [];
181
+ }
182
+ catch (error) {
183
+ console.error('[getAllUsersTokenBalance] Error:', error);
184
+ return [];
185
+ }
186
+ }
@@ -0,0 +1,6 @@
1
+ export declare function UserTokenIdPage({ params, }: {
2
+ params: Promise<{
3
+ id: string;
4
+ }>;
5
+ }): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=UserTokenIdPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserTokenIdPage.d.ts","sourceRoot":"","sources":["../../../src/web/admin/UserTokenIdPage.tsx"],"names":[],"mappings":"AAKA,wBAAgB,eAAe,CAAC,EAC9B,MAAM,GACP,EAAE;IACD,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjC,2CAiBA"}