@lastbrain/module-ai 0.1.18 → 0.1.19

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 (37) hide show
  1. package/dist/ai.build.config.d.ts.map +1 -1
  2. package/dist/ai.build.config.js +65 -1
  3. package/dist/api/admin/token-packs/[id].d.ts +28 -0
  4. package/dist/api/admin/token-packs/[id].d.ts.map +1 -0
  5. package/dist/api/admin/token-packs/[id].js +44 -0
  6. package/dist/api/admin/token-packs.d.ts +20 -0
  7. package/dist/api/admin/token-packs.d.ts.map +1 -0
  8. package/dist/api/admin/token-packs.js +57 -0
  9. package/dist/api/admin/user-token.js +2 -1
  10. package/dist/api/auth/create-checkout.d.ts +31 -0
  11. package/dist/api/auth/create-checkout.d.ts.map +1 -0
  12. package/dist/api/auth/create-checkout.js +93 -0
  13. package/dist/api/auth/generate-image.d.ts +3 -3
  14. package/dist/api/auth/generate-image.d.ts.map +1 -1
  15. package/dist/api/auth/generate-image.js +68 -27
  16. package/dist/api/auth/token-checkout.d.ts +12 -0
  17. package/dist/api/auth/token-checkout.d.ts.map +1 -0
  18. package/dist/api/auth/token-checkout.js +79 -0
  19. package/dist/api/auth/token-packs.d.ts +11 -0
  20. package/dist/api/auth/token-packs.d.ts.map +1 -0
  21. package/dist/api/auth/token-packs.js +29 -0
  22. package/dist/api/public/webhook.d.ts +2 -0
  23. package/dist/api/public/webhook.d.ts.map +1 -0
  24. package/dist/api/public/webhook.js +171 -0
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/web/admin/AdminTokenPacksPage.d.ts +2 -0
  29. package/dist/web/admin/AdminTokenPacksPage.d.ts.map +1 -0
  30. package/dist/web/admin/AdminTokenPacksPage.js +127 -0
  31. package/dist/web/auth/TokenPage.d.ts.map +1 -1
  32. package/dist/web/auth/TokenPage.js +51 -3
  33. package/dist/web/components/ImageGenerative.d.ts +5 -2
  34. package/dist/web/components/ImageGenerative.d.ts.map +1 -1
  35. package/dist/web/components/ImageGenerative.js +51 -7
  36. package/package.json +2 -1
  37. 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,iBAsGlB,CAAC;AAEF,eAAe,WAAW,CAAC"}
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"}
@@ -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;AAoCxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IA4H9C"}
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;AA0CxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IAyL9C"}
@@ -17,55 +17,96 @@ const IMAGE_COSTS = {
17
17
  "dall-e-3-1792x1024-standard": 0.08,
18
18
  "dall-e-3-1792x1024-hd": 0.12,
19
19
  };
20
- // Équivalent en tokens (arbitraire pour notre système de comptage)
21
- const TOKENS_PER_IMAGE = {
22
- "dall-e-2": 1000,
23
- "dall-e-3-standard": 2000,
24
- "dall-e-3-hd": 4000,
20
+ // Coût en tokens par combinaison modèle/taille(/qualité)
21
+ const TOKENS_PER_IMAGE_KEY = {
22
+ "dall-e-2-256x256": 3000,
23
+ "dall-e-2-512x512": 4000,
24
+ "dall-e-2-1024x1024": 5000,
25
+ "dall-e-3-1024x1024-standard": 6000,
26
+ "dall-e-3-1024x1024-hd": 8000,
27
+ "dall-e-3-1024x1792-standard": 8000,
28
+ "dall-e-3-1024x1792-hd": 10000,
29
+ "dall-e-3-1792x1024-standard": 8000,
30
+ "dall-e-3-1792x1024-hd": 10000,
25
31
  };
26
32
  export async function POST(request) {
27
33
  try {
28
34
  const supabase = await getSupabaseServerClient();
29
35
  // Vérifier l'authentification
30
36
  const { data: { user }, } = await supabase.auth.getUser();
37
+ if (!user) {
38
+ return NextResponse.json({ error: "Utilisateur non authentifié" }, { status: 401 });
39
+ }
31
40
  // L'utilisateur est déjà authentifié grâce au middleware
32
41
  const body = await request.json();
33
- const { prompt, model = "dall-e-3", size = "1024x1024", quality = "standard", } = body;
42
+ const { prompt, model = "dall-e-3", size = "1024x1024", quality = "standard", uploadPath, } = body;
34
43
  if (!prompt) {
35
44
  return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
36
45
  }
37
46
  // Calculer le coût et les tokens
38
47
  const costKey = model === "dall-e-3" ? `${model}-${size}-${quality}` : `${model}-${size}`;
39
48
  const cost = IMAGE_COSTS[costKey] || 0.04;
40
- const tokenKey = model === "dall-e-3" ? `${model}-${quality}` : model;
41
- const tokensRequired = TOKENS_PER_IMAGE[tokenKey] || 1000;
49
+ const tokensRequired = TOKENS_PER_IMAGE_KEY[costKey] ?? 6000; // fallback safe
42
50
  // Vérifier le solde de tokens
43
51
  const currentBalance = await getTokenBalance(user.id);
44
- if (currentBalance < tokensRequired) {
52
+ // Autoriser la génération tant que le solde est positif, même si la déduction rend le solde négatif
53
+ if (currentBalance <= 0) {
45
54
  return NextResponse.json({
46
- error: `Solde insuffisant. Disponible: ${currentBalance}, requis: ${tokensRequired}`,
55
+ error: "Solde insuffisant. Votre solde de tokens est nul ou négatif.",
47
56
  }, { status: 402 });
48
57
  }
49
- // Paramètres pour DALL-E 3
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
58
+ let imageParams;
57
59
  if (model === "dall-e-3") {
58
- imageParams.size = size;
59
- imageParams.quality = quality;
60
+ imageParams = {
61
+ model: "dall-e-3",
62
+ prompt,
63
+ n: 1,
64
+ response_format: "url",
65
+ size: size,
66
+ quality: quality,
67
+ };
60
68
  }
61
69
  else {
62
- // DALL-E 2 ne supporte que certaines tailles
70
+ // DALL-E 2
63
71
  const validSizes = ["256x256", "512x512", "1024x1024"];
64
- imageParams.size = validSizes.includes(size) ? size : "1024x1024";
72
+ imageParams = {
73
+ model: "dall-e-2",
74
+ prompt,
75
+ n: 1,
76
+ response_format: "url",
77
+ size: validSizes.includes(size)
78
+ ? size
79
+ : "1024x1024",
80
+ };
65
81
  }
66
82
  // Appeler OpenAI
67
83
  const response = await openai.images.generate(imageParams);
68
84
  const imageUrl = response.data?.[0]?.url;
85
+ let supabaseImageUrl = null;
86
+ if (imageUrl && uploadPath) {
87
+ // Générer un nom de fichier si uploadPath se termine par '/'
88
+ let finalPath = uploadPath;
89
+ if (uploadPath.endsWith("/")) {
90
+ const ext = (imageUrl.split(".").pop() || "png").split("?")[0];
91
+ const filename = `recipe-${Date.now()}.${ext}`;
92
+ finalPath = uploadPath + filename;
93
+ }
94
+ // 1. Télécharger l'image depuis OpenAI
95
+ const imageRes = await fetch(imageUrl);
96
+ if (!imageRes.ok)
97
+ throw new Error("Erreur lors du téléchargement de l'image OpenAI");
98
+ const arrayBuffer = await imageRes.arrayBuffer();
99
+ const contentType = imageRes.headers.get("content-type") || "image/png";
100
+ // 2. Uploader dans Supabase Storage (bucket à adapter si besoin)
101
+ const bucket = finalPath.split("/")[0] || "app";
102
+ const { error: uploadError } = await supabase.storage
103
+ .from(bucket)
104
+ .upload(finalPath.split("/").slice(1).join("/"), new Uint8Array(arrayBuffer), { contentType, upsert: true });
105
+ if (uploadError)
106
+ throw new Error("Erreur upload Supabase: " + uploadError.message);
107
+ // 3. Générer l'URL publique
108
+ supabaseImageUrl = finalPath;
109
+ }
69
110
  if (!imageUrl) {
70
111
  throw new Error("Aucune image générée");
71
112
  }
@@ -83,20 +124,20 @@ export async function POST(request) {
83
124
  }
84
125
  return NextResponse.json({
85
126
  imageUrl,
127
+ supabaseImageUrl,
86
128
  tokensUsed: tokensRequired,
87
129
  tokensRemaining: tokenResult.balance,
88
130
  model,
89
- cost,
90
131
  });
91
132
  }
92
133
  catch (error) {
93
- console.error("Erreur de génération d'image:", error);
94
- if (error.code === "insufficient_quota") {
134
+ const err = error;
135
+ if (err.code === "insufficient_quota") {
95
136
  return NextResponse.json({ error: "Quota OpenAI dépassé. Contactez l'administrateur." }, { status: 503 });
96
137
  }
97
- if (error.code === "content_policy_violation") {
138
+ if (err.code === "content_policy_violation") {
98
139
  return NextResponse.json({ error: "Le prompt viole la politique de contenu d'OpenAI." }, { status: 400 });
99
140
  }
100
- return NextResponse.json({ error: error.message || "Erreur lors de la génération de l'image" }, { status: 500 });
141
+ return NextResponse.json({ error: err.message || "Erreur lors de la génération de l'image" }, { status: 500 });
101
142
  }
102
143
  }
@@ -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"}
@@ -0,0 +1,79 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServerClient, fetchInternalApi, } from "@lastbrain/core/server";
3
+ /**
4
+ * POST /api/ai/auth/token-checkout
5
+ * Create a Stripe checkout session for token purchase
6
+ */
7
+ export async function POST(request) {
8
+ try {
9
+ const supabase = await getSupabaseServerClient();
10
+ // Verify authentication
11
+ const { data: { user }, } = await supabase.auth.getUser();
12
+ if (!user) {
13
+ return NextResponse.json({ error: "Non authentifié" }, { status: 401 });
14
+ }
15
+ const body = await request.json();
16
+ const { pack_id } = body;
17
+ if (!pack_id) {
18
+ return NextResponse.json({ error: "Pack ID requis" }, { status: 400 });
19
+ }
20
+ // Get token pack details
21
+ const { data: pack, error: packError } = await supabase
22
+ .from("token_packs")
23
+ .select("*")
24
+ .eq("id", pack_id)
25
+ .eq("is_active", true)
26
+ .single();
27
+ if (packError || !pack) {
28
+ return NextResponse.json({ error: "Pack non trouvé" }, { status: 404 });
29
+ }
30
+ const baseUrl = process.env.APP_PRINCIPAL_DOMAIN ||
31
+ process.env.NEXT_PUBLIC_APP_URL ||
32
+ "http://localhost:3000";
33
+ const successUrl = `${baseUrl}${"/cart/success"}`;
34
+ const cancelUrl = `${baseUrl}${"/cart/cancel"}`;
35
+ // Create checkout session via payment API
36
+ const checkoutResponse = await fetchInternalApi("/api/auth/payment/checkout", {
37
+ method: "POST",
38
+ headers: {
39
+ "Content-Type": "application/json",
40
+ },
41
+ body: JSON.stringify({
42
+ items: [
43
+ {
44
+ name: pack.name,
45
+ description: pack.description || `${pack.tokens} tokens`,
46
+ price_cents: pack.price_cents,
47
+ quantity: 1,
48
+ },
49
+ ],
50
+ currency: pack.currency,
51
+ mode: "one_time",
52
+ success_url: successUrl,
53
+ cancel_url: cancelUrl,
54
+ metadata: {
55
+ purpose: "token_purchase",
56
+ module: "@lastbrain-labs/module-ai",
57
+ token_pack_id: pack.id,
58
+ tokens_amount: pack.tokens.toString(),
59
+ user_id: user.id,
60
+ },
61
+ }),
62
+ }, request);
63
+ if (!checkoutResponse.ok) {
64
+ const error = await checkoutResponse.json();
65
+ throw new Error(error.error || "Erreur lors de la création du checkout");
66
+ }
67
+ const checkoutData = await checkoutResponse.json();
68
+ const url = checkoutData.url || checkoutData.checkoutUrl || checkoutData.checkout_url;
69
+ const sessionId = checkoutData.session_id || checkoutData.sessionId;
70
+ return NextResponse.json({
71
+ checkout_url: url,
72
+ session_id: sessionId,
73
+ }, { status: 200 });
74
+ }
75
+ catch (error) {
76
+ console.error("[Token Checkout] Error:", error);
77
+ return NextResponse.json({ error: error.message || "Erreur lors du checkout" }, { status: 500 });
78
+ }
79
+ }