@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.
- package/dist/ai.build.config.d.ts +4 -0
- package/dist/ai.build.config.d.ts.map +1 -0
- package/dist/ai.build.config.js +86 -0
- package/dist/api/admin/user-token/[id].d.ts +29 -0
- package/dist/api/admin/user-token/[id].d.ts.map +1 -0
- package/dist/api/admin/user-token/[id].js +57 -0
- package/dist/api/admin/user-token.d.ts +20 -0
- package/dist/api/admin/user-token.d.ts.map +1 -0
- package/dist/api/admin/user-token.js +92 -0
- package/dist/api/admin/user_prompts.d.ts +17 -0
- package/dist/api/admin/user_prompts.d.ts.map +1 -0
- package/dist/api/admin/user_prompts.js +94 -0
- package/dist/api/admin/user_token_ledger.d.ts +17 -0
- package/dist/api/admin/user_token_ledger.d.ts.map +1 -0
- package/dist/api/admin/user_token_ledger.js +94 -0
- package/dist/api/auth/generate-image.d.ts +11 -0
- package/dist/api/auth/generate-image.d.ts.map +1 -0
- package/dist/api/auth/generate-image.js +104 -0
- package/dist/api/auth/generate-text.d.ts +11 -0
- package/dist/api/auth/generate-text.d.ts.map +1 -0
- package/dist/api/auth/generate-text.js +96 -0
- package/dist/api/auth/user_prompts.d.ts +17 -0
- package/dist/api/auth/user_prompts.d.ts.map +1 -0
- package/dist/api/auth/user_prompts.js +94 -0
- package/dist/api/auth/user_token_ledger.d.ts +17 -0
- package/dist/api/auth/user_token_ledger.d.ts.map +1 -0
- package/dist/api/auth/user_token_ledger.js +94 -0
- package/dist/components/Doc.d.ts +2 -0
- package/dist/components/Doc.d.ts.map +1 -0
- package/dist/components/Doc.js +5 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/server.d.ts +61 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +186 -0
- package/dist/web/admin/UserTokenIdPage.d.ts +6 -0
- package/dist/web/admin/UserTokenIdPage.d.ts.map +1 -0
- package/dist/web/admin/UserTokenIdPage.js +8 -0
- package/dist/web/admin/UserTokenPage.d.ts +2 -0
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -0
- package/dist/web/admin/UserTokenPage.js +6 -0
- package/dist/web/auth/TokenPage.d.ts +2 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -0
- package/dist/web/auth/TokenPage.js +6 -0
- package/dist/web/components/ImageGenerative.d.ts +22 -0
- package/dist/web/components/ImageGenerative.d.ts.map +1 -0
- package/dist/web/components/ImageGenerative.js +87 -0
- package/dist/web/components/TextareaGenerative.d.ts +23 -0
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -0
- package/dist/web/components/TextareaGenerative.js +60 -0
- package/package.json +72 -0
- package/supabase/migrations/20251121000000_ai_tokens.sql +189 -0
- package/supabase/migrations/20251121093113_module-ai_init.sql +122 -0
- package/supabase/migrations-down/20251121000000_ai_tokens.sql +23 -0
- 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 @@
|
|
|
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
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -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";
|
package/dist/server.d.ts
ADDED
|
@@ -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 @@
|
|
|
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"}
|