@lastbrain/module-ai 0.1.18 → 0.1.20
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.map +1 -1
- package/dist/ai.build.config.js +65 -1
- package/dist/api/admin/token-packs/[id].d.ts +28 -0
- package/dist/api/admin/token-packs/[id].d.ts.map +1 -0
- package/dist/api/admin/token-packs/[id].js +44 -0
- package/dist/api/admin/token-packs.d.ts +20 -0
- package/dist/api/admin/token-packs.d.ts.map +1 -0
- package/dist/api/admin/token-packs.js +57 -0
- package/dist/api/admin/user-token.js +2 -1
- package/dist/api/auth/create-checkout.d.ts +31 -0
- package/dist/api/auth/create-checkout.d.ts.map +1 -0
- package/dist/api/auth/create-checkout.js +93 -0
- package/dist/api/auth/generate-image.d.ts +3 -3
- package/dist/api/auth/generate-image.d.ts.map +1 -1
- package/dist/api/auth/generate-image.js +79 -31
- package/dist/api/auth/generate-text.d.ts.map +1 -1
- package/dist/api/auth/generate-text.js +11 -4
- package/dist/api/auth/token-checkout.d.ts +12 -0
- package/dist/api/auth/token-checkout.d.ts.map +1 -0
- package/dist/api/auth/token-checkout.js +79 -0
- package/dist/api/auth/token-packs.d.ts +11 -0
- package/dist/api/auth/token-packs.d.ts.map +1 -0
- package/dist/api/auth/token-packs.js +29 -0
- package/dist/api/public/webhook.d.ts +2 -0
- package/dist/api/public/webhook.d.ts.map +1 -0
- package/dist/api/public/webhook.js +171 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/web/admin/AdminTokenPacksPage.d.ts +2 -0
- package/dist/web/admin/AdminTokenPacksPage.d.ts.map +1 -0
- package/dist/web/admin/AdminTokenPacksPage.js +127 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +51 -3
- package/dist/web/components/ImageGenerative.d.ts +5 -2
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +51 -7
- package/package.json +5 -4
- package/supabase/migrations/20251201000000_token_packs.sql +73 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai.build.config.d.ts","sourceRoot":"","sources":["../src/ai.build.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAA,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"ai.build.config.d.ts","sourceRoot":"","sources":["../src/ai.build.config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,QAAA,MAAM,WAAW,EAAE,iBAsKlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
package/dist/ai.build.config.js
CHANGED
|
@@ -11,6 +11,11 @@ const buildConfig = {
|
|
|
11
11
|
path: "/user-token",
|
|
12
12
|
componentExport: "UserTokenPage",
|
|
13
13
|
},
|
|
14
|
+
{
|
|
15
|
+
section: "admin",
|
|
16
|
+
path: "/token-packs",
|
|
17
|
+
componentExport: "AdminTokenPacksPage",
|
|
18
|
+
},
|
|
14
19
|
],
|
|
15
20
|
apis: [
|
|
16
21
|
// Routes de génération IA
|
|
@@ -36,6 +41,50 @@ const buildConfig = {
|
|
|
36
41
|
entryPoint: "api/auth/user-tokens",
|
|
37
42
|
authRequired: true,
|
|
38
43
|
},
|
|
44
|
+
// Token packs - User routes
|
|
45
|
+
{
|
|
46
|
+
method: "GET",
|
|
47
|
+
path: "/api/ai/auth/token-packs",
|
|
48
|
+
handlerExport: "GET",
|
|
49
|
+
entryPoint: "api/auth/token-packs",
|
|
50
|
+
authRequired: true,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
method: "POST",
|
|
54
|
+
path: "/api/ai/auth/token-checkout",
|
|
55
|
+
handlerExport: "POST",
|
|
56
|
+
entryPoint: "api/auth/token-checkout",
|
|
57
|
+
authRequired: true,
|
|
58
|
+
},
|
|
59
|
+
// Token packs - Admin routes
|
|
60
|
+
{
|
|
61
|
+
method: "GET",
|
|
62
|
+
path: "/api/ai/admin/token-packs",
|
|
63
|
+
handlerExport: "GET",
|
|
64
|
+
entryPoint: "api/admin/token-packs",
|
|
65
|
+
authRequired: true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
method: "POST",
|
|
69
|
+
path: "/api/ai/admin/token-packs",
|
|
70
|
+
handlerExport: "POST",
|
|
71
|
+
entryPoint: "api/admin/token-packs",
|
|
72
|
+
authRequired: true,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
method: "PUT",
|
|
76
|
+
path: "/api/ai/admin/token-packs/[id]",
|
|
77
|
+
handlerExport: "PUT",
|
|
78
|
+
entryPoint: "api/admin/token-packs/[id]",
|
|
79
|
+
authRequired: true,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
method: "DELETE",
|
|
83
|
+
path: "/api/ai/admin/token-packs/[id]",
|
|
84
|
+
handlerExport: "DELETE",
|
|
85
|
+
entryPoint: "api/admin/token-packs/[id]",
|
|
86
|
+
authRequired: true,
|
|
87
|
+
},
|
|
39
88
|
// Routes admin pour la gestion des tokens
|
|
40
89
|
{
|
|
41
90
|
method: "GET",
|
|
@@ -58,12 +107,20 @@ const buildConfig = {
|
|
|
58
107
|
entryPoint: "api/admin/user-token/[id]",
|
|
59
108
|
authRequired: true,
|
|
60
109
|
},
|
|
110
|
+
// Webhook for token purchase payments
|
|
111
|
+
{
|
|
112
|
+
method: "POST",
|
|
113
|
+
path: "/api/ai/public/webhook",
|
|
114
|
+
handlerExport: "POST",
|
|
115
|
+
entryPoint: "api/public/webhook",
|
|
116
|
+
authRequired: false,
|
|
117
|
+
},
|
|
61
118
|
],
|
|
62
119
|
migrations: {
|
|
63
120
|
enabled: true,
|
|
64
121
|
priority: 30,
|
|
65
122
|
path: "supabase/migrations",
|
|
66
|
-
files: ["20251125000000_ai_tokens.sql"],
|
|
123
|
+
files: ["20251125000000_ai_tokens.sql", "20251201000000_token_packs.sql"],
|
|
67
124
|
migrationsDownPath: "supabase/migrations-down",
|
|
68
125
|
},
|
|
69
126
|
menu: {
|
|
@@ -88,6 +145,13 @@ const buildConfig = {
|
|
|
88
145
|
shortcut: "cmd+shift+g",
|
|
89
146
|
shortcutDisplay: "⌘⇧G",
|
|
90
147
|
},
|
|
148
|
+
{
|
|
149
|
+
title: "Packs de Tokens",
|
|
150
|
+
description: "Gestion des packs de tokens",
|
|
151
|
+
icon: "Package",
|
|
152
|
+
path: "/admin/ai/token-packs",
|
|
153
|
+
order: 101,
|
|
154
|
+
},
|
|
91
155
|
],
|
|
92
156
|
},
|
|
93
157
|
userTabs: [
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
/**
|
|
3
|
+
* PUT /api/ai/admin/token-packs/[id]
|
|
4
|
+
* Update a token pack
|
|
5
|
+
*/
|
|
6
|
+
export declare function PUT(request: NextRequest, context: {
|
|
7
|
+
params: Promise<{
|
|
8
|
+
id: string;
|
|
9
|
+
}>;
|
|
10
|
+
}): Promise<NextResponse<{
|
|
11
|
+
data: any;
|
|
12
|
+
}> | NextResponse<{
|
|
13
|
+
error: any;
|
|
14
|
+
}>>;
|
|
15
|
+
/**
|
|
16
|
+
* DELETE /api/ai/admin/token-packs/[id]
|
|
17
|
+
* Delete a token pack
|
|
18
|
+
*/
|
|
19
|
+
export declare function DELETE(request: NextRequest, context: {
|
|
20
|
+
params: Promise<{
|
|
21
|
+
id: string;
|
|
22
|
+
}>;
|
|
23
|
+
}): Promise<NextResponse<{
|
|
24
|
+
success: boolean;
|
|
25
|
+
}> | NextResponse<{
|
|
26
|
+
error: any;
|
|
27
|
+
}>>;
|
|
28
|
+
//# sourceMappingURL=%5Bid%5D.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"[id].d.ts","sourceRoot":"","sources":["../../../../src/api/admin/token-packs/[id].ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAcxD;;;GAGG;AACH,wBAAsB,GAAG,CACvB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;;;;IAwB7C;AAED;;;GAGG;AACH,wBAAsB,MAAM,CAC1B,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;;;;IAkB7C"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
3
|
+
/**
|
|
4
|
+
* PUT /api/ai/admin/token-packs/[id]
|
|
5
|
+
* Update a token pack
|
|
6
|
+
*/
|
|
7
|
+
export async function PUT(request, context) {
|
|
8
|
+
try {
|
|
9
|
+
const { id } = await context.params;
|
|
10
|
+
const supabase = await getSupabaseServiceClient();
|
|
11
|
+
const body = await request.json();
|
|
12
|
+
const { data, error } = await supabase
|
|
13
|
+
.from("token_packs")
|
|
14
|
+
.update(body)
|
|
15
|
+
.eq("id", id)
|
|
16
|
+
.select()
|
|
17
|
+
.single();
|
|
18
|
+
if (error)
|
|
19
|
+
throw error;
|
|
20
|
+
return NextResponse.json({ data }, { status: 200 });
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error("[Admin Token Packs] PUT error:", error);
|
|
24
|
+
return NextResponse.json({ error: error.message || "Erreur lors de la mise à jour du pack" }, { status: 500 });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* DELETE /api/ai/admin/token-packs/[id]
|
|
29
|
+
* Delete a token pack
|
|
30
|
+
*/
|
|
31
|
+
export async function DELETE(request, context) {
|
|
32
|
+
try {
|
|
33
|
+
const { id } = await context.params;
|
|
34
|
+
const supabase = await getSupabaseServiceClient();
|
|
35
|
+
const { error } = await supabase.from("token_packs").delete().eq("id", id);
|
|
36
|
+
if (error)
|
|
37
|
+
throw error;
|
|
38
|
+
return NextResponse.json({ success: true }, { status: 200 });
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.error("[Admin Token Packs] DELETE error:", error);
|
|
42
|
+
return NextResponse.json({ error: error.message || "Erreur lors de la suppression du pack" }, { status: 500 });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
/**
|
|
3
|
+
* GET /api/ai/admin/token-packs
|
|
4
|
+
* List all token packs (including inactive)
|
|
5
|
+
*/
|
|
6
|
+
export declare function GET(): Promise<NextResponse<{
|
|
7
|
+
data: any[];
|
|
8
|
+
}> | NextResponse<{
|
|
9
|
+
error: any;
|
|
10
|
+
}>>;
|
|
11
|
+
/**
|
|
12
|
+
* POST /api/ai/admin/token-packs
|
|
13
|
+
* Create a new token pack
|
|
14
|
+
*/
|
|
15
|
+
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
16
|
+
data: any;
|
|
17
|
+
}> | NextResponse<{
|
|
18
|
+
error: any;
|
|
19
|
+
}>>;
|
|
20
|
+
//# sourceMappingURL=token-packs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-packs.d.ts","sourceRoot":"","sources":["../../../src/api/admin/token-packs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAexD;;;GAGG;AACH,wBAAsB,GAAG;;;;IAmBxB;AAED;;;GAGG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IAgD9C"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
3
|
+
/**
|
|
4
|
+
* GET /api/ai/admin/token-packs
|
|
5
|
+
* List all token packs (including inactive)
|
|
6
|
+
*/
|
|
7
|
+
export async function GET() {
|
|
8
|
+
try {
|
|
9
|
+
const supabase = await getSupabaseServiceClient();
|
|
10
|
+
const { data, error } = await supabase
|
|
11
|
+
.from("token_packs")
|
|
12
|
+
.select("*")
|
|
13
|
+
.order("sort_order", { ascending: true });
|
|
14
|
+
if (error)
|
|
15
|
+
throw error;
|
|
16
|
+
return NextResponse.json({ data }, { status: 200 });
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error("[Admin Token Packs] GET error:", error);
|
|
20
|
+
return NextResponse.json({ error: error.message || "Erreur lors de la récupération des packs" }, { status: 500 });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* POST /api/ai/admin/token-packs
|
|
25
|
+
* Create a new token pack
|
|
26
|
+
*/
|
|
27
|
+
export async function POST(request) {
|
|
28
|
+
try {
|
|
29
|
+
const supabase = await getSupabaseServiceClient();
|
|
30
|
+
const body = await request.json();
|
|
31
|
+
const { name, description, tokens, price_cents, currency, stripe_price_id, is_active, sort_order, } = body;
|
|
32
|
+
if (!name || !tokens || !price_cents || !currency) {
|
|
33
|
+
return NextResponse.json({ error: "Champs requis manquants" }, { status: 400 });
|
|
34
|
+
}
|
|
35
|
+
const { data, error } = await supabase
|
|
36
|
+
.from("token_packs")
|
|
37
|
+
.insert({
|
|
38
|
+
name,
|
|
39
|
+
description,
|
|
40
|
+
tokens,
|
|
41
|
+
price_cents,
|
|
42
|
+
currency,
|
|
43
|
+
stripe_price_id,
|
|
44
|
+
is_active: is_active ?? true,
|
|
45
|
+
sort_order: sort_order ?? 0,
|
|
46
|
+
})
|
|
47
|
+
.select()
|
|
48
|
+
.single();
|
|
49
|
+
if (error)
|
|
50
|
+
throw error;
|
|
51
|
+
return NextResponse.json({ data }, { status: 201 });
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error("[Admin Token Packs] POST error:", error);
|
|
55
|
+
return NextResponse.json({ error: error.message || "Erreur lors de la création du pack" }, { status: 500 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -113,7 +113,8 @@ export async function POST(request) {
|
|
|
113
113
|
}
|
|
114
114
|
// Déterminer le type d'opération
|
|
115
115
|
const operationType = type || (amount > 0 ? "adjust" : "adjust");
|
|
116
|
-
const result = await addTokens(userId, amount, operationType, { reason: reason || "Ajustement manuel par admin" }, undefined
|
|
116
|
+
const result = await addTokens(userId, amount, operationType, { reason: reason || "Ajustement manuel par admin" }, undefined // created_by undefined pour les opérations admin via service role
|
|
117
|
+
);
|
|
117
118
|
if (!result.success) {
|
|
118
119
|
return NextResponse.json({ error: result.error }, { status: 500 });
|
|
119
120
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
/**
|
|
3
|
+
* POST /api/ai/create-checkout
|
|
4
|
+
*
|
|
5
|
+
* Creates a Stripe checkout session for purchasing token packs.
|
|
6
|
+
*
|
|
7
|
+
* Body:
|
|
8
|
+
* - token_pack_id: string - ID of the token pack to purchase
|
|
9
|
+
*
|
|
10
|
+
* Returns:
|
|
11
|
+
* - url: string - Stripe checkout URL
|
|
12
|
+
* - sessionId: string - Stripe session ID
|
|
13
|
+
*/
|
|
14
|
+
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
15
|
+
error: string;
|
|
16
|
+
}> | NextResponse<{
|
|
17
|
+
url: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
paymentId: string;
|
|
20
|
+
}>>;
|
|
21
|
+
/**
|
|
22
|
+
* GET /api/ai/create-checkout - Get available token packs
|
|
23
|
+
*
|
|
24
|
+
* Returns list of available token packs for purchase
|
|
25
|
+
*/
|
|
26
|
+
export declare function GET(): Promise<NextResponse<{
|
|
27
|
+
error: string;
|
|
28
|
+
}> | NextResponse<{
|
|
29
|
+
packs: any[];
|
|
30
|
+
}>>;
|
|
31
|
+
//# sourceMappingURL=create-checkout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-checkout.d.ts","sourceRoot":"","sources":["../../../src/api/auth/create-checkout.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGxD;;;;;;;;;;;GAWG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;IAuE9C;AAED;;;;GAIG;AACH,wBAAsB,GAAG;;;;IAuBxB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// POST /api/ai/create-checkout - Create Stripe checkout for token purchase
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// SECURITY: Amount is looked up from token_packs table - NOT from client
|
|
5
|
+
// ============================================================================
|
|
6
|
+
import { NextResponse } from "next/server";
|
|
7
|
+
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
8
|
+
/**
|
|
9
|
+
* POST /api/ai/create-checkout
|
|
10
|
+
*
|
|
11
|
+
* Creates a Stripe checkout session for purchasing token packs.
|
|
12
|
+
*
|
|
13
|
+
* Body:
|
|
14
|
+
* - token_pack_id: string - ID of the token pack to purchase
|
|
15
|
+
*
|
|
16
|
+
* Returns:
|
|
17
|
+
* - url: string - Stripe checkout URL
|
|
18
|
+
* - sessionId: string - Stripe session ID
|
|
19
|
+
*/
|
|
20
|
+
export async function POST(request) {
|
|
21
|
+
try {
|
|
22
|
+
const supabase = await getSupabaseServerClient();
|
|
23
|
+
// Get authenticated user
|
|
24
|
+
const { data: { user }, error: authError, } = await supabase.auth.getUser();
|
|
25
|
+
if (authError || !user) {
|
|
26
|
+
return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
|
|
27
|
+
}
|
|
28
|
+
// Parse request body
|
|
29
|
+
const body = await request.json();
|
|
30
|
+
const { token_pack_id } = body;
|
|
31
|
+
if (!token_pack_id) {
|
|
32
|
+
return NextResponse.json({ error: "token_pack_id est requis" }, { status: 400 });
|
|
33
|
+
}
|
|
34
|
+
// Validate environment
|
|
35
|
+
const frontendUrl = process.env.FRONTEND_URL || process.env.NEXT_PUBLIC_APP_URL;
|
|
36
|
+
if (!frontendUrl) {
|
|
37
|
+
console.error("FRONTEND_URL or NEXT_PUBLIC_APP_URL not set");
|
|
38
|
+
return NextResponse.json({ error: "Configuration serveur manquante" }, { status: 500 });
|
|
39
|
+
}
|
|
40
|
+
// Import central payment service dynamically to avoid circular deps
|
|
41
|
+
const { createOneTimeCheckout } = await import("@lastbrain-labs/module-core-payment-pro/server");
|
|
42
|
+
// Create checkout using central payment service
|
|
43
|
+
// Price is looked up server-side from token_packs table
|
|
44
|
+
const result = await createOneTimeCheckout({
|
|
45
|
+
purpose: "token",
|
|
46
|
+
module: "module-ai",
|
|
47
|
+
mode: "one_time",
|
|
48
|
+
owner_id: user.id,
|
|
49
|
+
user_id: user.id,
|
|
50
|
+
resourceType: "token_packs",
|
|
51
|
+
resourceId: token_pack_id,
|
|
52
|
+
successPath: "/auth/ai/token?success=true",
|
|
53
|
+
cancelPath: "/auth/ai/token?cancelled=true",
|
|
54
|
+
description: "Achat de tokens IA",
|
|
55
|
+
});
|
|
56
|
+
return NextResponse.json({
|
|
57
|
+
url: result.url,
|
|
58
|
+
sessionId: result.sessionId,
|
|
59
|
+
paymentId: result.paymentId,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
console.error("[POST /api/ai/create-checkout] Error:", error);
|
|
64
|
+
const message = error instanceof Error
|
|
65
|
+
? error.message
|
|
66
|
+
: "Erreur lors de la création du paiement";
|
|
67
|
+
return NextResponse.json({ error: message }, { status: 500 });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* GET /api/ai/create-checkout - Get available token packs
|
|
72
|
+
*
|
|
73
|
+
* Returns list of available token packs for purchase
|
|
74
|
+
*/
|
|
75
|
+
export async function GET() {
|
|
76
|
+
try {
|
|
77
|
+
const supabase = await getSupabaseServerClient();
|
|
78
|
+
const { data: packs, error } = await supabase
|
|
79
|
+
.from("token_packs")
|
|
80
|
+
.select("*")
|
|
81
|
+
.eq("is_active", true)
|
|
82
|
+
.order("sort_order", { ascending: true });
|
|
83
|
+
if (error) {
|
|
84
|
+
console.error("[GET /api/ai/create-checkout] Error:", error);
|
|
85
|
+
return NextResponse.json({ error: "Erreur lors de la récupération des packs" }, { status: 500 });
|
|
86
|
+
}
|
|
87
|
+
return NextResponse.json({ packs: packs || [] });
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error("[GET /api/ai/create-checkout] Error:", error);
|
|
91
|
+
return NextResponse.json({ error: "Erreur serveur" }, { status: 500 });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
3
|
+
error: string;
|
|
4
|
+
}> | NextResponse<{
|
|
3
5
|
imageUrl: string;
|
|
6
|
+
supabaseImageUrl: string | null;
|
|
4
7
|
tokensUsed: number;
|
|
5
8
|
tokensRemaining: number;
|
|
6
9
|
model: "dall-e-3" | "dall-e-2";
|
|
7
|
-
cost: number;
|
|
8
|
-
}> | NextResponse<{
|
|
9
|
-
error: any;
|
|
10
10
|
}>>;
|
|
11
11
|
//# sourceMappingURL=generate-image.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-image.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-image.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-image.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiDxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IA0L9C"}
|
|
@@ -2,9 +2,15 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
3
3
|
import { deductTokens, getTokenBalance } from "../../server";
|
|
4
4
|
import OpenAI from "openai";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
let openai = null;
|
|
6
|
+
function getOpenAIClient() {
|
|
7
|
+
if (!openai) {
|
|
8
|
+
openai = new OpenAI({
|
|
9
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return openai;
|
|
13
|
+
}
|
|
8
14
|
// Coûts des images selon le modèle et la taille
|
|
9
15
|
const IMAGE_COSTS = {
|
|
10
16
|
"dall-e-2-256x256": 0.016,
|
|
@@ -17,55 +23,97 @@ const IMAGE_COSTS = {
|
|
|
17
23
|
"dall-e-3-1792x1024-standard": 0.08,
|
|
18
24
|
"dall-e-3-1792x1024-hd": 0.12,
|
|
19
25
|
};
|
|
20
|
-
//
|
|
21
|
-
const
|
|
22
|
-
"dall-e-2":
|
|
23
|
-
"dall-e-
|
|
24
|
-
"dall-e-
|
|
26
|
+
// Coût en tokens par combinaison modèle/taille(/qualité)
|
|
27
|
+
const TOKENS_PER_IMAGE_KEY = {
|
|
28
|
+
"dall-e-2-256x256": 3000,
|
|
29
|
+
"dall-e-2-512x512": 4000,
|
|
30
|
+
"dall-e-2-1024x1024": 5000,
|
|
31
|
+
"dall-e-3-1024x1024-standard": 6000,
|
|
32
|
+
"dall-e-3-1024x1024-hd": 8000,
|
|
33
|
+
"dall-e-3-1024x1792-standard": 8000,
|
|
34
|
+
"dall-e-3-1024x1792-hd": 10000,
|
|
35
|
+
"dall-e-3-1792x1024-standard": 8000,
|
|
36
|
+
"dall-e-3-1792x1024-hd": 10000,
|
|
25
37
|
};
|
|
26
38
|
export async function POST(request) {
|
|
27
39
|
try {
|
|
28
40
|
const supabase = await getSupabaseServerClient();
|
|
29
41
|
// Vérifier l'authentification
|
|
30
42
|
const { data: { user }, } = await supabase.auth.getUser();
|
|
43
|
+
if (!user) {
|
|
44
|
+
return NextResponse.json({ error: "Utilisateur non authentifié" }, { status: 401 });
|
|
45
|
+
}
|
|
31
46
|
// L'utilisateur est déjà authentifié grâce au middleware
|
|
32
47
|
const body = await request.json();
|
|
33
|
-
const { prompt, model = "dall-e-3", size = "1024x1024", quality = "standard", } = body;
|
|
48
|
+
const { prompt, model = "dall-e-3", size = "1024x1024", quality = "standard", uploadPath, } = body;
|
|
34
49
|
if (!prompt) {
|
|
35
50
|
return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
|
|
36
51
|
}
|
|
37
52
|
// Calculer le coût et les tokens
|
|
38
53
|
const costKey = model === "dall-e-3" ? `${model}-${size}-${quality}` : `${model}-${size}`;
|
|
39
54
|
const cost = IMAGE_COSTS[costKey] || 0.04;
|
|
40
|
-
const
|
|
41
|
-
const tokensRequired = TOKENS_PER_IMAGE[tokenKey] || 1000;
|
|
55
|
+
const tokensRequired = TOKENS_PER_IMAGE_KEY[costKey] ?? 6000; // fallback safe
|
|
42
56
|
// Vérifier le solde de tokens
|
|
43
57
|
const currentBalance = await getTokenBalance(user.id);
|
|
44
|
-
|
|
58
|
+
// Autoriser la génération tant que le solde est positif, même si la déduction rend le solde négatif
|
|
59
|
+
if (currentBalance <= 0) {
|
|
45
60
|
return NextResponse.json({
|
|
46
|
-
error:
|
|
61
|
+
error: "Solde insuffisant. Votre solde de tokens est nul ou négatif.",
|
|
47
62
|
}, { status: 402 });
|
|
48
63
|
}
|
|
49
|
-
|
|
50
|
-
const imageParams = {
|
|
51
|
-
model,
|
|
52
|
-
prompt,
|
|
53
|
-
n: 1,
|
|
54
|
-
response_format: "url",
|
|
55
|
-
};
|
|
56
|
-
// DALL-E 3 a des paramètres différents de DALL-E 2
|
|
64
|
+
let imageParams;
|
|
57
65
|
if (model === "dall-e-3") {
|
|
58
|
-
imageParams
|
|
59
|
-
|
|
66
|
+
imageParams = {
|
|
67
|
+
model: "dall-e-3",
|
|
68
|
+
prompt,
|
|
69
|
+
n: 1,
|
|
70
|
+
response_format: "url",
|
|
71
|
+
size: size,
|
|
72
|
+
quality: quality,
|
|
73
|
+
};
|
|
60
74
|
}
|
|
61
75
|
else {
|
|
62
|
-
// DALL-E 2
|
|
76
|
+
// DALL-E 2
|
|
63
77
|
const validSizes = ["256x256", "512x512", "1024x1024"];
|
|
64
|
-
imageParams
|
|
78
|
+
imageParams = {
|
|
79
|
+
model: "dall-e-2",
|
|
80
|
+
prompt,
|
|
81
|
+
n: 1,
|
|
82
|
+
response_format: "url",
|
|
83
|
+
size: validSizes.includes(size)
|
|
84
|
+
? size
|
|
85
|
+
: "1024x1024",
|
|
86
|
+
};
|
|
65
87
|
}
|
|
66
88
|
// Appeler OpenAI
|
|
67
|
-
const
|
|
89
|
+
const openaiClient = getOpenAIClient();
|
|
90
|
+
const response = await openaiClient.images.generate(imageParams);
|
|
68
91
|
const imageUrl = response.data?.[0]?.url;
|
|
92
|
+
let supabaseImageUrl = null;
|
|
93
|
+
if (imageUrl && uploadPath) {
|
|
94
|
+
// Générer un nom de fichier si uploadPath se termine par '/'
|
|
95
|
+
let finalPath = uploadPath;
|
|
96
|
+
if (uploadPath.endsWith("/")) {
|
|
97
|
+
const ext = (imageUrl.split(".").pop() || "png").split("?")[0];
|
|
98
|
+
const filename = `recipe-${Date.now()}.${ext}`;
|
|
99
|
+
finalPath = uploadPath + filename;
|
|
100
|
+
}
|
|
101
|
+
// 1. Télécharger l'image depuis OpenAI
|
|
102
|
+
const imageRes = await fetch(imageUrl);
|
|
103
|
+
if (!imageRes.ok)
|
|
104
|
+
throw new Error("Erreur lors du téléchargement de l'image OpenAI");
|
|
105
|
+
const arrayBuffer = await imageRes.arrayBuffer();
|
|
106
|
+
const contentType = imageRes.headers.get("content-type") || "image/png";
|
|
107
|
+
// 2. Uploader dans Supabase Storage (bucket à adapter si besoin)
|
|
108
|
+
const bucket = finalPath.split("/")[0] || "app";
|
|
109
|
+
const { error: uploadError } = await supabase.storage
|
|
110
|
+
.from(bucket)
|
|
111
|
+
.upload(finalPath.split("/").slice(1).join("/"), new Uint8Array(arrayBuffer), { contentType, upsert: true });
|
|
112
|
+
if (uploadError)
|
|
113
|
+
throw new Error("Erreur upload Supabase: " + uploadError.message);
|
|
114
|
+
// 3. Générer l'URL publique
|
|
115
|
+
supabaseImageUrl = finalPath;
|
|
116
|
+
}
|
|
69
117
|
if (!imageUrl) {
|
|
70
118
|
throw new Error("Aucune image générée");
|
|
71
119
|
}
|
|
@@ -83,20 +131,20 @@ export async function POST(request) {
|
|
|
83
131
|
}
|
|
84
132
|
return NextResponse.json({
|
|
85
133
|
imageUrl,
|
|
134
|
+
supabaseImageUrl,
|
|
86
135
|
tokensUsed: tokensRequired,
|
|
87
136
|
tokensRemaining: tokenResult.balance,
|
|
88
137
|
model,
|
|
89
|
-
cost,
|
|
90
138
|
});
|
|
91
139
|
}
|
|
92
140
|
catch (error) {
|
|
93
|
-
|
|
94
|
-
if (
|
|
141
|
+
const err = error;
|
|
142
|
+
if (err.code === "insufficient_quota") {
|
|
95
143
|
return NextResponse.json({ error: "Quota OpenAI dépassé. Contactez l'administrateur." }, { status: 503 });
|
|
96
144
|
}
|
|
97
|
-
if (
|
|
145
|
+
if (err.code === "content_policy_violation") {
|
|
98
146
|
return NextResponse.json({ error: "Le prompt viole la politique de contenu d'OpenAI." }, { status: 400 });
|
|
99
147
|
}
|
|
100
|
-
return NextResponse.json({ error:
|
|
148
|
+
return NextResponse.json({ error: err.message || "Erreur lors de la génération de l'image" }, { status: 500 });
|
|
101
149
|
}
|
|
102
150
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-text.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-text.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IAqI9C"}
|
|
@@ -2,9 +2,15 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
3
3
|
import { deductTokens, getTokenBalance } from "../../server";
|
|
4
4
|
import OpenAI from "openai";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
let openai = null;
|
|
6
|
+
function getOpenAIClient() {
|
|
7
|
+
if (!openai) {
|
|
8
|
+
openai = new OpenAI({
|
|
9
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
return openai;
|
|
13
|
+
}
|
|
8
14
|
export async function POST(request) {
|
|
9
15
|
try {
|
|
10
16
|
const supabase = await getSupabaseServerClient();
|
|
@@ -42,7 +48,8 @@ export async function POST(request) {
|
|
|
42
48
|
content: prompt,
|
|
43
49
|
});
|
|
44
50
|
// Appeler OpenAI
|
|
45
|
-
const
|
|
51
|
+
const openaiClient = getOpenAIClient();
|
|
52
|
+
const completion = await openaiClient.chat.completions.create({
|
|
46
53
|
model,
|
|
47
54
|
messages,
|
|
48
55
|
temperature,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
/**
|
|
3
|
+
* POST /api/ai/auth/token-checkout
|
|
4
|
+
* Create a Stripe checkout session for token purchase
|
|
5
|
+
*/
|
|
6
|
+
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
7
|
+
checkout_url: any;
|
|
8
|
+
session_id: any;
|
|
9
|
+
}> | NextResponse<{
|
|
10
|
+
error: any;
|
|
11
|
+
}>>;
|
|
12
|
+
//# sourceMappingURL=token-checkout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-checkout.d.ts","sourceRoot":"","sources":["../../../src/api/auth/token-checkout.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAMxD;;;GAGG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IAgG9C"}
|