@lastbrain/module-ai 2.0.26 → 2.0.30
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/README.md +52 -1
- package/dist/ai.build.config.d.ts.map +1 -1
- package/dist/ai.build.config.js +508 -9
- package/dist/api/admin/ai-provider-models/[id].d.ts +18 -0
- package/dist/api/admin/ai-provider-models/[id].d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models/[id].js +58 -0
- package/dist/api/admin/ai-provider-models.d.ts +20 -0
- package/dist/api/admin/ai-provider-models.d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models.js +26 -0
- package/dist/api/admin/ai-providers/[key].d.ts +18 -0
- package/dist/api/admin/ai-providers/[key].d.ts.map +1 -0
- package/dist/api/admin/ai-providers/[key].js +55 -0
- package/dist/api/admin/ai-providers.d.ts +20 -0
- package/dist/api/admin/ai-providers.d.ts.map +1 -0
- package/dist/api/admin/ai-providers.js +26 -0
- package/dist/api/admin/billing-analytics.d.ts +43 -0
- package/dist/api/admin/billing-analytics.d.ts.map +1 -0
- package/dist/api/admin/billing-analytics.js +144 -0
- package/dist/api/admin/global-ai-settings.d.ts +14 -0
- package/dist/api/admin/global-ai-settings.d.ts.map +1 -0
- package/dist/api/admin/global-ai-settings.js +63 -0
- package/dist/api/admin/token-packs/[id].d.ts.map +1 -1
- package/dist/api/admin/token-packs/[id].js +3 -2
- package/dist/api/admin/token-packs.d.ts.map +1 -1
- package/dist/api/admin/token-packs.js +3 -2
- package/dist/api/admin/user-monthly-details.d.ts +49 -0
- package/dist/api/admin/user-monthly-details.d.ts.map +1 -0
- package/dist/api/admin/user-monthly-details.js +140 -0
- package/dist/api/admin/user-quota.d.ts +21 -0
- package/dist/api/admin/user-quota.d.ts.map +1 -0
- package/dist/api/admin/user-quota.js +59 -0
- package/dist/api/admin/user-token/[id].d.ts.map +1 -1
- package/dist/api/admin/user-token/[id].js +2 -1
- package/dist/api/admin/user-token.d.ts +5 -2
- package/dist/api/admin/user-token.d.ts.map +1 -1
- package/dist/api/admin/user-token.js +91 -17
- package/dist/api/admin/user-usage-by-model.d.ts +22 -0
- package/dist/api/admin/user-usage-by-model.d.ts.map +1 -0
- package/dist/api/admin/user-usage-by-model.js +78 -0
- package/dist/api/admin/user-wallet-analytics.d.ts +15 -0
- package/dist/api/admin/user-wallet-analytics.d.ts.map +1 -0
- package/dist/api/admin/user-wallet-analytics.js +67 -0
- package/dist/api/admin/wallet-repair/route.d.ts +30 -0
- package/dist/api/admin/wallet-repair/route.d.ts.map +1 -0
- package/dist/api/admin/wallet-repair/route.js +63 -0
- package/dist/api/auth/ai-model-settings.d.ts +21 -0
- package/dist/api/auth/ai-model-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-model-settings.js +86 -0
- package/dist/api/auth/ai-settings.d.ts +17 -0
- package/dist/api/auth/ai-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-settings.js +87 -0
- package/dist/api/auth/api-keys/[id].d.ts +17 -0
- package/dist/api/auth/api-keys/[id].d.ts.map +1 -0
- package/dist/api/auth/api-keys/[id].js +66 -0
- package/dist/api/auth/api-keys.d.ts +19 -0
- package/dist/api/auth/api-keys.d.ts.map +1 -0
- package/dist/api/auth/api-keys.js +94 -0
- package/dist/api/auth/create-checkout.d.ts +1 -1
- package/dist/api/auth/create-checkout.d.ts.map +1 -1
- package/dist/api/auth/create-checkout.js +8 -6
- package/dist/api/auth/generate-image.d.ts +2 -2
- package/dist/api/auth/generate-image.d.ts.map +1 -1
- package/dist/api/auth/generate-image.js +404 -104
- package/dist/api/auth/generate-text.d.ts +3 -2
- package/dist/api/auth/generate-text.d.ts.map +1 -1
- package/dist/api/auth/generate-text.js +130 -58
- package/dist/api/auth/process-ocr.d.ts +9 -0
- package/dist/api/auth/process-ocr.d.ts.map +1 -0
- package/dist/api/auth/process-ocr.js +43 -0
- package/dist/api/auth/prompts/stats.d.ts +14 -0
- package/dist/api/auth/prompts/stats.d.ts.map +1 -0
- package/dist/api/auth/prompts/stats.js +46 -0
- package/dist/api/auth/prompts.d.ts +26 -0
- package/dist/api/auth/prompts.d.ts.map +1 -0
- package/dist/api/auth/prompts.js +175 -0
- package/dist/api/auth/token-balance.d.ts +26 -0
- package/dist/api/auth/token-balance.d.ts.map +1 -0
- package/dist/api/auth/token-balance.js +47 -0
- package/dist/api/auth/token-checkout.d.ts.map +1 -1
- package/dist/api/auth/token-checkout.js +22 -4
- package/dist/api/auth/token-packs.d.ts.map +1 -1
- package/dist/api/auth/token-packs.js +2 -1
- package/dist/api/auth/usage-by-model.d.ts +25 -0
- package/dist/api/auth/usage-by-model.d.ts.map +1 -0
- package/dist/api/auth/usage-by-model.js +95 -0
- package/dist/api/auth/usage.d.ts +26 -0
- package/dist/api/auth/usage.d.ts.map +1 -0
- package/dist/api/auth/usage.js +127 -0
- package/dist/api/auth/user-tokens.d.ts.map +1 -1
- package/dist/api/auth/user-tokens.js +36 -2
- package/dist/api/auth/wallet/route.d.ts +17 -0
- package/dist/api/auth/wallet/route.d.ts.map +1 -0
- package/dist/api/auth/wallet/route.js +68 -0
- package/dist/api/auth/wallet.d.ts +16 -0
- package/dist/api/auth/wallet.d.ts.map +1 -0
- package/dist/api/auth/wallet.js +71 -0
- package/dist/api/public/gateway-models.d.ts +25 -0
- package/dist/api/public/gateway-models.d.ts.map +1 -0
- package/dist/api/public/gateway-models.js +49 -0
- package/dist/api/public/pricing-summary.d.ts +46 -0
- package/dist/api/public/pricing-summary.d.ts.map +1 -0
- package/dist/api/public/pricing-summary.js +70 -0
- package/dist/api/public/prompts/[id]/stats.d.ts +15 -0
- package/dist/api/public/prompts/[id]/stats.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/stats.js +37 -0
- package/dist/api/public/prompts/[id]/view.d.ts +15 -0
- package/dist/api/public/prompts/[id]/view.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/view.js +41 -0
- package/dist/api/public/prompts/slug/[slug].d.ts +15 -0
- package/dist/api/public/prompts/slug/[slug].d.ts.map +1 -0
- package/dist/api/public/prompts/slug/[slug].js +40 -0
- package/dist/api/public/prompts/user/[username].d.ts +17 -0
- package/dist/api/public/prompts/user/[username].d.ts.map +1 -0
- package/dist/api/public/prompts/user/[username].js +51 -0
- package/dist/api/public/prompts.d.ts +15 -0
- package/dist/api/public/prompts.d.ts.map +1 -0
- package/dist/api/public/prompts.js +45 -0
- package/dist/api/public/token-packs.d.ts +11 -0
- package/dist/api/public/token-packs.d.ts.map +1 -0
- package/dist/api/public/token-packs.js +25 -0
- package/dist/api/public/token-pricing.d.ts +44 -0
- package/dist/api/public/token-pricing.d.ts.map +1 -0
- package/dist/api/public/token-pricing.js +168 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts +52 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/artifacts.js +217 -0
- package/dist/api/public/v1/_lib/auth.d.ts +43 -0
- package/dist/api/public/v1/_lib/auth.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/auth.js +108 -0
- package/dist/api/public/v1/_lib/errors.d.ts +17 -0
- package/dist/api/public/v1/_lib/errors.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/errors.js +16 -0
- package/dist/api/public/v1/_lib/log.d.ts +29 -0
- package/dist/api/public/v1/_lib/log.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/log.js +68 -0
- package/dist/api/public/v1/_lib/quota.d.ts +24 -0
- package/dist/api/public/v1/_lib/quota.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/quota.js +118 -0
- package/dist/api/public/v1/_lib/router.d.ts +54 -0
- package/dist/api/public/v1/_lib/router.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/router.js +119 -0
- package/dist/api/public/v1/connect.d.ts +20 -0
- package/dist/api/public/v1/connect.d.ts.map +1 -0
- package/dist/api/public/v1/connect.js +119 -0
- package/dist/api/public/v1/doc.d.ts +239 -0
- package/dist/api/public/v1/doc.d.ts.map +1 -0
- package/dist/api/public/v1/doc.js +253 -0
- package/dist/api/public/v1/history/[id].d.ts +92 -0
- package/dist/api/public/v1/history/[id].d.ts.map +1 -0
- package/dist/api/public/v1/history/[id].js +176 -0
- package/dist/api/public/v1/history.d.ts +30 -0
- package/dist/api/public/v1/history.d.ts.map +1 -0
- package/dist/api/public/v1/history.js +142 -0
- package/dist/api/public/v1/image-ai.d.ts +24 -0
- package/dist/api/public/v1/image-ai.d.ts.map +1 -0
- package/dist/api/public/v1/image-ai.js +233 -0
- package/dist/api/public/v1/prompts.d.ts +19 -0
- package/dist/api/public/v1/prompts.d.ts.map +1 -0
- package/dist/api/public/v1/prompts.js +107 -0
- package/dist/api/public/v1/provider.d.ts +16 -0
- package/dist/api/public/v1/provider.d.ts.map +1 -0
- package/dist/api/public/v1/provider.js +130 -0
- package/dist/api/public/v1/purchase.d.ts +11 -0
- package/dist/api/public/v1/purchase.d.ts.map +1 -0
- package/dist/api/public/v1/purchase.js +18 -0
- package/dist/api/public/v1/status.d.ts +35 -0
- package/dist/api/public/v1/status.d.ts.map +1 -0
- package/dist/api/public/v1/status.js +163 -0
- package/dist/api/public/v1/text-ai.d.ts +26 -0
- package/dist/api/public/v1/text-ai.d.ts.map +1 -0
- package/dist/api/public/v1/text-ai.js +239 -0
- package/dist/api/public/webhook.d.ts.map +1 -1
- package/dist/api/public/webhook.js +50 -39
- package/dist/api/track-usage.d.ts +12 -0
- package/dist/api/track-usage.d.ts.map +1 -0
- package/dist/api/track-usage.js +37 -0
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/DocUsageCustom.js +6 -6
- package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
- package/dist/components/admin/UserTokenTab.js +170 -23
- package/dist/components/auth/AuthDashboardAi.d.ts +2 -0
- package/dist/components/auth/AuthDashboardAi.d.ts.map +1 -0
- package/dist/components/auth/AuthDashboardAi.js +53 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts +277 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts.map +1 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.js +276 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/scripts/migrate-tokens-to-wallet.d.ts +13 -0
- package/dist/scripts/migrate-tokens-to-wallet.d.ts.map +1 -0
- package/dist/scripts/migrate-tokens-to-wallet.js +165 -0
- package/dist/server/__tests__/billing.test.d.ts +5 -0
- package/dist/server/__tests__/billing.test.d.ts.map +1 -0
- package/dist/server/__tests__/billing.test.js +223 -0
- package/dist/server/ai-client.d.ts +59 -0
- package/dist/server/ai-client.d.ts.map +1 -0
- package/dist/server/ai-client.js +111 -0
- package/dist/server/ai-generation-service.d.ts +66 -0
- package/dist/server/ai-generation-service.d.ts.map +1 -0
- package/dist/server/ai-generation-service.js +274 -0
- package/dist/server/billing.d.ts +200 -0
- package/dist/server/billing.d.ts.map +1 -0
- package/dist/server/billing.js +488 -0
- package/dist/server/gateway-service.d.ts +13 -0
- package/dist/server/gateway-service.d.ts.map +1 -0
- package/dist/server/gateway-service.js +161 -0
- package/dist/server/global-settings.d.ts +16 -0
- package/dist/server/global-settings.d.ts.map +1 -0
- package/dist/server/global-settings.js +42 -0
- package/dist/server/model-filter.d.ts +25 -0
- package/dist/server/model-filter.d.ts.map +1 -0
- package/dist/server/model-filter.js +240 -0
- package/dist/server/ocr.d.ts +39 -0
- package/dist/server/ocr.d.ts.map +1 -0
- package/dist/server/ocr.js +280 -0
- package/dist/server/openai-client.d.ts +19 -0
- package/dist/server/openai-client.d.ts.map +1 -0
- package/dist/server/openai-client.js +26 -0
- package/dist/server/pricing-config.d.ts +18 -0
- package/dist/server/pricing-config.d.ts.map +1 -0
- package/dist/server/pricing-config.js +94 -0
- package/dist/server/pricing-validator.d.ts +41 -0
- package/dist/server/pricing-validator.d.ts.map +1 -0
- package/dist/server/pricing-validator.js +113 -0
- package/dist/server/pricing.d.ts +121 -0
- package/dist/server/pricing.d.ts.map +1 -0
- package/dist/server/pricing.js +225 -0
- package/dist/server/quota.d.ts +66 -0
- package/dist/server/quota.d.ts.map +1 -0
- package/dist/server/quota.js +538 -0
- package/dist/server/wallet-repair.d.ts +32 -0
- package/dist/server/wallet-repair.d.ts.map +1 -0
- package/dist/server/wallet-repair.js +189 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +87 -16
- package/dist/sitemap/handlers/prompts.d.ts +6 -0
- package/dist/sitemap/handlers/prompts.d.ts.map +1 -0
- package/dist/sitemap/handlers/prompts.js +72 -0
- package/dist/sitemap/handlers/users.d.ts +6 -0
- package/dist/sitemap/handlers/users.d.ts.map +1 -0
- package/dist/sitemap/handlers/users.js +80 -0
- package/dist/sitemap/manifest.d.ts +8 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +27 -0
- package/dist/types/gateway.d.ts +40 -0
- package/dist/types/gateway.d.ts.map +1 -0
- package/dist/types/gateway.js +4 -0
- package/dist/types/quota.d.ts +74 -0
- package/dist/types/quota.d.ts.map +1 -0
- package/dist/types/quota.js +11 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +17 -0
- package/dist/web/admin/AdminTokenPacksPage.js +1 -1
- package/dist/web/admin/BillingAnalyticsPage.d.ts +2 -0
- package/dist/web/admin/BillingAnalyticsPage.d.ts.map +1 -0
- package/dist/web/admin/BillingAnalyticsPage.js +141 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts +2 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts.map +1 -0
- package/dist/web/admin/GlobalAISettingsPage.js +93 -0
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
- package/dist/web/admin/UserTokenPage.js +20 -7
- package/dist/web/auth/AISettingsPage.d.ts +2 -0
- package/dist/web/auth/AISettingsPage.d.ts.map +1 -0
- package/dist/web/auth/AISettingsPage.js +258 -0
- package/dist/web/auth/APIKeysPage.d.ts +2 -0
- package/dist/web/auth/APIKeysPage.d.ts.map +1 -0
- package/dist/web/auth/APIKeysPage.js +154 -0
- package/dist/web/auth/HistoryPage.d.ts +2 -0
- package/dist/web/auth/HistoryPage.d.ts.map +1 -0
- package/dist/web/auth/HistoryPage.js +279 -0
- package/dist/web/auth/PromptsPage.d.ts +5 -0
- package/dist/web/auth/PromptsPage.d.ts.map +1 -0
- package/dist/web/auth/PromptsPage.js +137 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +88 -31
- package/dist/web/auth/UsageAndTokensPage.d.ts +2 -0
- package/dist/web/auth/UsageAndTokensPage.d.ts.map +1 -0
- package/dist/web/auth/UsageAndTokensPage.js +157 -0
- package/dist/web/auth/UsagePage.d.ts +2 -0
- package/dist/web/auth/UsagePage.d.ts.map +1 -0
- package/dist/web/auth/UsagePage.js +62 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts +13 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts.map +1 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.js +16 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts +19 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts.map +1 -0
- package/dist/web/auth/components/ModelUsageTable.js +37 -0
- package/dist/web/auth/components/PurchaseButton.d.ts +7 -0
- package/dist/web/auth/components/PurchaseButton.d.ts.map +1 -0
- package/dist/web/auth/components/PurchaseButton.js +13 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts +20 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts.map +1 -0
- package/dist/web/auth/components/TokenHistoryCard.js +76 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts +24 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts.map +1 -0
- package/dist/web/auth/components/TokenKpiGrid.js +38 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts +11 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByDayChart.js +32 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts +12 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByModelBarChart.js +32 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts +9 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts.map +1 -0
- package/dist/web/auth/components/WalletStatusCard.js +50 -0
- package/dist/web/components/ImageGenerative.d.ts +3 -1
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +139 -52
- package/dist/web/components/TextareaGenerative.d.ts +3 -1
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
- package/dist/web/components/TextareaGenerative.js +10 -5
- package/dist/web/public/PromptDetailPage.d.ts +25 -0
- package/dist/web/public/PromptDetailPage.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPage.js +71 -0
- package/dist/web/public/PromptDetailPageServer.d.ts +15 -0
- package/dist/web/public/PromptDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPageServer.js +68 -0
- package/dist/web/public/PublicPromptsPage.d.ts +5 -0
- package/dist/web/public/PublicPromptsPage.d.ts.map +1 -0
- package/dist/web/public/PublicPromptsPage.js +110 -0
- package/dist/web/public/PurchaseTokensPage.d.ts +2 -0
- package/dist/web/public/PurchaseTokensPage.d.ts.map +1 -0
- package/dist/web/public/PurchaseTokensPage.js +98 -0
- package/dist/web/public/UserAvatar.d.ts +13 -0
- package/dist/web/public/UserAvatar.d.ts.map +1 -0
- package/dist/web/public/UserAvatar.js +13 -0
- package/dist/web/public/UserDetailPageServer.d.ts +15 -0
- package/dist/web/public/UserDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/UserDetailPageServer.js +31 -0
- package/dist/web/public/UserPromptsPage.d.ts +9 -0
- package/dist/web/public/UserPromptsPage.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPage.js +112 -0
- package/dist/web/public/UserPromptsPageServer.d.ts +15 -0
- package/dist/web/public/UserPromptsPageServer.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPageServer.js +31 -0
- package/package.json +18 -9
- package/supabase/migrations/20251125000000_ai_tokens.sql +7 -0
- package/supabase/migrations/20260123100002_user_token_quota_monthly copy.sql +173 -0
- package/supabase/migrations/20260128100003_update_and_add_table.sql +368 -0
- package/supabase/migrations/20260128120000_seed_providers_models.sql +78 -0
- package/supabase/migrations/20260128131405_add_api_key_id_to_ledgers.sql +41 -0
- package/supabase/migrations/20260128140000_ai_artifacts_storage.sql +99 -0
- package/supabase/migrations/20260128140002_ai_user_settings.sql +57 -0
- package/supabase/migrations/20260128150000_drop_ai_user_settings.sql +21 -0
- package/supabase/migrations/20260128160000_wallet_billing_system.sql +192 -0
- package/supabase/migrations/20260128160001_wallet_rpc_functions.sql +165 -0
- package/supabase/migrations/20260128170000_add_pack_coef_to_token_packs.sql +30 -0
- package/supabase/migrations/20260129120000_wallet_view_rpc.sql +41 -0
- package/supabase/migrations/20260129220003_update_pack_margins.sql +31 -0
- package/supabase/migrations/20260129330004_ai_user_prompts.sql +151 -0
- package/supabase/migrations/20260129330005_ai_prompts_ip_tracking.sql +92 -0
- package/supabase/migrations/20260129330006_ai_prompts_slug.sql +64 -0
- package/supabase/migrations/20260129330007_ai_prompts_view_slug.sql +26 -0
- package/supabase/migrations/20260129440000_ai_prompts_view_username.sql +33 -0
- package/supabase/migrations/20260129450000_ai_prompts_add_lang.sql +40 -0
- package/supabase/migrations/20260131000000_extract_model_prompt_in_ledger.sql +92 -0
- package/supabase/migrations/20260131140000_fix_duplicate_purchases.sql +64 -0
- package/supabase/migrations/20260201120000_module-ai_default_models.sql +63 -0
- package/supabase/migrations/20260201130000_module-ai_remove_provider_tables.sql +17 -0
- package/supabase/migrations-down/20251217120000_user_token_quota_monthly.sql +34 -0
- package/supabase/migrations-down/20260128131405_add_api_key_id_to_ledgers.sql +25 -0
|
@@ -1,40 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import { getSupabaseServerClient } from "@lastbrain/core/server";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
apiKey: process.env.OPENAI_API_KEY,
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
return openai;
|
|
13
|
-
}
|
|
14
|
-
// Coûts des images selon le modèle et la taille
|
|
15
|
-
const IMAGE_COSTS = {
|
|
16
|
-
"dall-e-2-256x256": 0.016,
|
|
17
|
-
"dall-e-2-512x512": 0.018,
|
|
18
|
-
"dall-e-2-1024x1024": 0.02,
|
|
19
|
-
"dall-e-3-1024x1024-standard": 0.04,
|
|
20
|
-
"dall-e-3-1024x1024-hd": 0.08,
|
|
21
|
-
"dall-e-3-1024x1792-standard": 0.08,
|
|
22
|
-
"dall-e-3-1024x1792-hd": 0.12,
|
|
23
|
-
"dall-e-3-1792x1024-standard": 0.08,
|
|
24
|
-
"dall-e-3-1792x1024-hd": 0.12,
|
|
25
|
-
};
|
|
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,
|
|
37
|
-
};
|
|
2
|
+
import { getSupabaseServerClient, getSupabaseServiceClient, } from "@lastbrain/core/server";
|
|
3
|
+
import { imageAI } from "../../server/ai-client";
|
|
4
|
+
import { logger } from "@lastbrain/core";
|
|
5
|
+
import { computeAndDebitAIUsage, computeProviderCostUsd, getWalletState, } from "../../server/billing";
|
|
6
|
+
import { isModelEnabled } from "../../server/model-filter";
|
|
7
|
+
import { validateImagePricing } from "../../server/pricing-validator";
|
|
8
|
+
import { getDefaultImageModel } from "../../server/global-settings";
|
|
38
9
|
export async function POST(request) {
|
|
39
10
|
try {
|
|
40
11
|
const supabase = await getSupabaseServerClient();
|
|
@@ -45,97 +16,426 @@ export async function POST(request) {
|
|
|
45
16
|
}
|
|
46
17
|
// L'utilisateur est déjà authentifié grâce au middleware
|
|
47
18
|
const body = await request.json();
|
|
48
|
-
|
|
19
|
+
// Get default image model from global settings if not provided
|
|
20
|
+
const defaultModel = await getDefaultImageModel();
|
|
21
|
+
const { prompt, model = defaultModel, // Use global default instead of hardcoded
|
|
22
|
+
size = "1024x1024", quality = "standard", uploadPath, recipeId, isPublic, } = body;
|
|
49
23
|
if (!prompt) {
|
|
50
24
|
return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
|
|
51
25
|
}
|
|
52
|
-
//
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
return NextResponse.json({
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
26
|
+
// Vérifier que l'utilisateur n'est pas déjà en solde négatif
|
|
27
|
+
const { data: balanceData } = await supabase
|
|
28
|
+
.from("user_token_balance_v")
|
|
29
|
+
.select("balance")
|
|
30
|
+
.eq("owner_id", user.id)
|
|
31
|
+
.single();
|
|
32
|
+
const purchasedBalance = balanceData?.balance || 0;
|
|
33
|
+
if (purchasedBalance < 0) {
|
|
34
|
+
return NextResponse.json({ error: "Solde insuffisant (compte en négatif). Veuillez recharger." }, { status: 402 });
|
|
35
|
+
}
|
|
36
|
+
// Build full model string
|
|
37
|
+
const fullModel = model.includes("/") ? model : `openai/${model}`;
|
|
38
|
+
// Parse provider/model from fullModel
|
|
39
|
+
const [provider, modelName] = fullModel.includes("/")
|
|
40
|
+
? fullModel.split("/", 2)
|
|
41
|
+
: ["openai", fullModel];
|
|
42
|
+
// Get real model pricing from gateway for accurate billing
|
|
43
|
+
// Images are always allowed (bypass user settings) but we need real pricing
|
|
44
|
+
const filterResult = await isModelEnabled(user.id, fullModel);
|
|
45
|
+
// If model not in user settings or not found, still allow but use fallback pricing
|
|
46
|
+
if (!filterResult.allowed || !filterResult.model?.pricing?.image) {
|
|
47
|
+
logger.warn(`[generate-image] Model ${fullModel} not found in gateway or missing pricing, using fallback`);
|
|
48
|
+
filterResult.allowed = true;
|
|
49
|
+
filterResult.model = {
|
|
50
|
+
pricing: {
|
|
51
|
+
input: 0,
|
|
52
|
+
output: 0,
|
|
53
|
+
image: 0.02, // Fallback: $0.02 per image (dall-e-3 standard)
|
|
54
|
+
},
|
|
73
55
|
};
|
|
74
56
|
}
|
|
75
57
|
else {
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
58
|
+
// Model found with real pricing from gateway
|
|
59
|
+
filterResult.allowed = true;
|
|
60
|
+
logger.info(`[generate-image] Using real pricing from gateway for ${fullModel}: $${filterResult.model.pricing.image} per image`);
|
|
61
|
+
}
|
|
62
|
+
// Prepare pricing for wallet billing
|
|
63
|
+
const pricingValidation = validateImagePricing(filterResult.model?.pricing, fullModel);
|
|
64
|
+
if (!pricingValidation.valid) {
|
|
65
|
+
return NextResponse.json({ error: pricingValidation.error }, { status: 503 });
|
|
66
|
+
}
|
|
67
|
+
const pricing = pricingValidation.pricing;
|
|
68
|
+
// Get current wallet state
|
|
69
|
+
const wallet = await getWalletState(user.id);
|
|
70
|
+
// PRE-CHECK: Simple check - user must have credits (> 0)
|
|
71
|
+
// The real debit with minimums will be computed post-generation
|
|
72
|
+
if (!wallet.walletProviderBudgetUsd ||
|
|
73
|
+
wallet.walletProviderBudgetUsd <= 0) {
|
|
74
|
+
logger.warn(`[generate-image] No credits for user ${user.id}: wallet=$${wallet.walletProviderBudgetUsd}`);
|
|
75
|
+
return NextResponse.json({ error: "Crédits insuffisants pour cette opération" }, { status: 402 });
|
|
87
76
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const
|
|
91
|
-
|
|
77
|
+
logger.info(`[generate-image] Pre-check passed for user ${user.id}: wallet=$${wallet.walletProviderBudgetUsd.toFixed(6)}`);
|
|
78
|
+
// Call our simplified AI client for image generation
|
|
79
|
+
const imageResult = await imageAI({
|
|
80
|
+
prompt,
|
|
81
|
+
model: fullModel,
|
|
82
|
+
size,
|
|
83
|
+
});
|
|
84
|
+
if (!imageResult.imageUrl) {
|
|
85
|
+
return NextResponse.json({ error: "No image generated" }, { status: 500 });
|
|
86
|
+
}
|
|
87
|
+
const returnedImageUrl = imageResult.imageUrl;
|
|
92
88
|
let supabaseImageUrl = null;
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
let imageBytes = null;
|
|
90
|
+
// If we have a base64 data URL, we can convert and upload
|
|
91
|
+
if (returnedImageUrl.startsWith("data:image")) {
|
|
92
|
+
// Extract base64 from data URL
|
|
93
|
+
const base64Data = returnedImageUrl.split(",")[1] || "";
|
|
94
|
+
if (uploadPath) {
|
|
95
|
+
let finalPath = uploadPath;
|
|
96
|
+
if (uploadPath.endsWith("/")) {
|
|
97
|
+
const filename = `recipe-${Date.now()}.png`;
|
|
98
|
+
finalPath = uploadPath + filename;
|
|
99
|
+
}
|
|
100
|
+
const buffer = Buffer.from(base64Data, "base64");
|
|
101
|
+
imageBytes = buffer;
|
|
102
|
+
const contentType = "image/png";
|
|
103
|
+
const bucket = finalPath.split("/")[0] || "app";
|
|
104
|
+
const { error: uploadError } = await supabase.storage
|
|
105
|
+
.from(bucket)
|
|
106
|
+
.upload(finalPath.split("/").slice(1).join("/"), buffer, {
|
|
107
|
+
contentType,
|
|
108
|
+
upsert: true,
|
|
109
|
+
});
|
|
110
|
+
if (uploadError)
|
|
111
|
+
throw new Error("Erreur upload Supabase: " + uploadError.message);
|
|
112
|
+
supabaseImageUrl = finalPath;
|
|
113
|
+
}
|
|
114
|
+
// returnedImageUrl is already in data URL format
|
|
115
|
+
}
|
|
116
|
+
else if (returnedImageUrl && uploadPath) {
|
|
117
|
+
// Télécharger depuis l'URL (OpenAI ou Supabase) puis uploader vers Supabase
|
|
95
118
|
let finalPath = uploadPath;
|
|
96
119
|
if (uploadPath.endsWith("/")) {
|
|
97
|
-
const ext = (
|
|
120
|
+
const ext = (returnedImageUrl.split(".").pop() || "png").split("?")[0];
|
|
98
121
|
const filename = `recipe-${Date.now()}.${ext}`;
|
|
99
122
|
finalPath = uploadPath + filename;
|
|
100
123
|
}
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
124
|
+
// Si l'URL provient d'un endpoint Supabase Storage, utiliser l'API storage.download
|
|
125
|
+
// Exemple d'URL public: https://<project>.supabase.co/storage/v1/object/public/<bucket>/<path>
|
|
126
|
+
// Exemple d'URL privée: https://<project>.supabase.co/storage/v1/object/<bucket>/<path>
|
|
127
|
+
let arrayBuffer = null;
|
|
128
|
+
let contentType = "image/png";
|
|
129
|
+
try {
|
|
130
|
+
const parsed = new URL(returnedImageUrl);
|
|
131
|
+
const storagePrefix = "/storage/v1/object/";
|
|
132
|
+
if (parsed.pathname.includes(storagePrefix)) {
|
|
133
|
+
// extraire la partie après /storage/v1/object/
|
|
134
|
+
const after = parsed.pathname.split(storagePrefix)[1] || ""; // e.g. "public/recipes/foo.png" or "app/3561/..."
|
|
135
|
+
const parts = after.split("/");
|
|
136
|
+
// support: optional 'public' segment
|
|
137
|
+
let bucket = parts[0] || "";
|
|
138
|
+
let objectPathParts = parts.slice(1);
|
|
139
|
+
if (bucket === "public") {
|
|
140
|
+
// URL like /object/public/<bucket>/<path>
|
|
141
|
+
bucket = parts[1] || "";
|
|
142
|
+
objectPathParts = parts.slice(2);
|
|
143
|
+
}
|
|
144
|
+
const objectPath = objectPathParts.join("/");
|
|
145
|
+
logger.debug("[generate-image] Detected supabase storage URL", {
|
|
146
|
+
url: returnedImageUrl,
|
|
147
|
+
bucket,
|
|
148
|
+
objectPath,
|
|
149
|
+
});
|
|
150
|
+
// tenter d'utiliser le service client (requiert SUPABASE_SERVICE_ROLE_KEY)
|
|
151
|
+
let supabaseService;
|
|
152
|
+
try {
|
|
153
|
+
supabaseService = await getSupabaseServiceClient();
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
logger.error("[generate-image] getSupabaseServiceClient() failed — ensure SUPABASE_SERVICE_ROLE_KEY is set", err);
|
|
157
|
+
throw new Error(JSON.stringify({
|
|
158
|
+
message: "Supabase service client non disponible — vérifiez la variable SUPABASE_SERVICE_ROLE_KEY",
|
|
159
|
+
url: returnedImageUrl,
|
|
160
|
+
bucket,
|
|
161
|
+
objectPath,
|
|
162
|
+
inner: err instanceof Error ? err.message : String(err),
|
|
163
|
+
}));
|
|
164
|
+
}
|
|
165
|
+
const { data: downloaded, error: downloadError } = await supabaseService.storage.from(bucket).download(objectPath);
|
|
166
|
+
if (downloadError || !downloaded) {
|
|
167
|
+
const msg = JSON.stringify({
|
|
168
|
+
message: "Supabase storage download failed (service)",
|
|
169
|
+
bucket,
|
|
170
|
+
objectPath,
|
|
171
|
+
error: downloadError
|
|
172
|
+
? downloadError.message || downloadError
|
|
173
|
+
: null,
|
|
174
|
+
});
|
|
175
|
+
logger.error("[generate-image] " + msg);
|
|
176
|
+
throw new Error(msg);
|
|
177
|
+
}
|
|
178
|
+
arrayBuffer = await downloaded.arrayBuffer();
|
|
179
|
+
contentType = downloaded.type || contentType;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch (e) {
|
|
183
|
+
// Si parsing/échec service client -> laisser tomber au fetch plus bas
|
|
184
|
+
logger.debug("[generate-image] supabase storage handling skipped or failed, will fallback to fetch", {
|
|
185
|
+
url: returnedImageUrl,
|
|
186
|
+
error: e instanceof Error ? e.message : String(e),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// Si on n'a pas réussi à récupérer via storage.download, retomber sur fetch
|
|
190
|
+
if (!arrayBuffer) {
|
|
191
|
+
const imageRes = await fetch(returnedImageUrl);
|
|
192
|
+
if (!imageRes.ok) {
|
|
193
|
+
throw new Error(JSON.stringify({
|
|
194
|
+
message: "Erreur lors du téléchargement",
|
|
195
|
+
url: returnedImageUrl,
|
|
196
|
+
status: imageRes.status,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
arrayBuffer = await imageRes.arrayBuffer();
|
|
200
|
+
contentType = imageRes.headers.get("content-type") || contentType;
|
|
201
|
+
}
|
|
108
202
|
const bucket = finalPath.split("/")[0] || "app";
|
|
203
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
204
|
+
imageBytes = bytes;
|
|
109
205
|
const { error: uploadError } = await supabase.storage
|
|
110
206
|
.from(bucket)
|
|
111
|
-
.upload(finalPath.split("/").slice(1).join("/"),
|
|
207
|
+
.upload(finalPath.split("/").slice(1).join("/"), bytes, {
|
|
208
|
+
contentType,
|
|
209
|
+
upsert: true,
|
|
210
|
+
});
|
|
112
211
|
if (uploadError)
|
|
113
212
|
throw new Error("Erreur upload Supabase: " + uploadError.message);
|
|
114
|
-
// 3. Générer l'URL publique
|
|
115
213
|
supabaseImageUrl = finalPath;
|
|
116
214
|
}
|
|
117
|
-
if (!
|
|
215
|
+
if (!returnedImageUrl) {
|
|
118
216
|
throw new Error("Aucune image générée");
|
|
119
217
|
}
|
|
120
|
-
//
|
|
121
|
-
const tokenResult =
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
218
|
+
// Token debit is done AFTER recipe/image persistence so we never lose the image update.
|
|
219
|
+
const tokenResult = null;
|
|
220
|
+
// Si la requête demande d'associer l'image à une recette, reproduire
|
|
221
|
+
// le comportement du flux de sélection d'image: copier dans
|
|
222
|
+
// app/{userId}/recipe/{parentId}/{filename} et si public copier vers recipes/{...}
|
|
223
|
+
let imagePathForRecipe = null;
|
|
224
|
+
let publicImagePath = null;
|
|
225
|
+
if (recipeId && imageBytes) {
|
|
226
|
+
try {
|
|
227
|
+
// Récupérer la recette (id ou parent)
|
|
228
|
+
const { data: existingRecipe, error: fetchError } = await supabase
|
|
229
|
+
.from("recipes")
|
|
230
|
+
.select("id, image_path, public_image_path, is_public, slug, parent_recipe_id")
|
|
231
|
+
.or(`id.eq.${recipeId},parent_recipe_id.eq.${recipeId}`)
|
|
232
|
+
.eq("owner_id", user.id)
|
|
233
|
+
.limit(1)
|
|
234
|
+
.single();
|
|
235
|
+
if (!fetchError && existingRecipe) {
|
|
236
|
+
const parentId = existingRecipe.parent_recipe_id || existingRecipe.id;
|
|
237
|
+
// 1. Nettoyer le dossier recipe privé
|
|
238
|
+
const recipeFolder = `${user.id}/recipe/${parentId}`;
|
|
239
|
+
try {
|
|
240
|
+
const { data: existingFiles } = await supabase.storage
|
|
241
|
+
.from("app")
|
|
242
|
+
.list(recipeFolder, { limit: 100 });
|
|
243
|
+
if (existingFiles && existingFiles.length > 0) {
|
|
244
|
+
const filesToDelete = existingFiles
|
|
245
|
+
.filter((f) => f.name)
|
|
246
|
+
.map((f) => `${recipeFolder}/${f.name}`);
|
|
247
|
+
if (filesToDelete.length > 0) {
|
|
248
|
+
await supabase.storage.from("app").remove(filesToDelete);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (e) {
|
|
253
|
+
logger.warn("[generate-image] Could not list/remove old recipe files", e);
|
|
254
|
+
}
|
|
255
|
+
// 2. Uploader dans app/{user}/recipe/{parentId}/{filename}
|
|
256
|
+
const filename = (supabaseImageUrl || "").split("/").pop() ||
|
|
257
|
+
`recipe-${Date.now()}.png`;
|
|
258
|
+
const privateDestinationPath = `${user.id}/recipe/${parentId}/${filename}`;
|
|
259
|
+
try {
|
|
260
|
+
const { error: uploadError } = await supabase.storage
|
|
261
|
+
.from("app")
|
|
262
|
+
.upload(privateDestinationPath, imageBytes, {
|
|
263
|
+
contentType: "image/png",
|
|
264
|
+
upsert: true,
|
|
265
|
+
});
|
|
266
|
+
if (!uploadError) {
|
|
267
|
+
imagePathForRecipe = `/recipe/${parentId}/${filename}`;
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
logger.error("[generate-image] Upload to recipe folder failed", uploadError);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
logger.error("[generate-image] Upload to recipe folder exception", e);
|
|
275
|
+
}
|
|
276
|
+
// 3. Si AU MOINS UNE recette de la famille est publique, copier vers recipes bucket
|
|
277
|
+
try {
|
|
278
|
+
const { data: publicRecipes } = await supabase
|
|
279
|
+
.from("recipes")
|
|
280
|
+
.select("id, is_public")
|
|
281
|
+
.or(`id.eq.${parentId},parent_recipe_id.eq.${parentId}`)
|
|
282
|
+
.eq("is_public", true)
|
|
283
|
+
.limit(1);
|
|
284
|
+
const isAnyRecipePublic = existingRecipe.is_public ||
|
|
285
|
+
(publicRecipes && publicRecipes.length > 0);
|
|
286
|
+
if (isAnyRecipePublic) {
|
|
287
|
+
const timestamp = Date.now();
|
|
288
|
+
const publicDestinationPath = `${parentId}-${timestamp}.png`;
|
|
289
|
+
try {
|
|
290
|
+
const { error: publicUploadError } = await supabase.storage
|
|
291
|
+
.from("recipes")
|
|
292
|
+
.upload(publicDestinationPath, imageBytes, {
|
|
293
|
+
contentType: "image/png",
|
|
294
|
+
upsert: true,
|
|
295
|
+
});
|
|
296
|
+
if (!publicUploadError) {
|
|
297
|
+
publicImagePath = publicDestinationPath;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
logger.error("[generate-image] Public upload failed", publicUploadError);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (e) {
|
|
304
|
+
logger.error("[generate-image] Public upload exception", e);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
logger.error("[generate-image] Public recipes check failed", e);
|
|
310
|
+
}
|
|
311
|
+
// 4. Mettre à jour les recipes (parent + traductions) si on a une imagePath
|
|
312
|
+
if (imagePathForRecipe) {
|
|
313
|
+
const updateData = {
|
|
314
|
+
image_path: imagePathForRecipe,
|
|
315
|
+
};
|
|
316
|
+
if (publicImagePath)
|
|
317
|
+
updateData.public_image_path = publicImagePath;
|
|
318
|
+
try {
|
|
319
|
+
const { error: updateError } = await supabase
|
|
320
|
+
.from("recipes")
|
|
321
|
+
.update(updateData)
|
|
322
|
+
.or(`id.eq.${parentId},parent_recipe_id.eq.${parentId}`)
|
|
323
|
+
.eq("owner_id", user.id);
|
|
324
|
+
if (updateError) {
|
|
325
|
+
logger.error("[generate-image] Failed to update recipes rows", updateError);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch (e) {
|
|
329
|
+
logger.error("[generate-image] Exception updating recipes", e);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
logger.debug("[generate-image] Recipe not found or not owned, skipping recipe copy", { recipeId, fetchError });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch (e) {
|
|
338
|
+
logger.error("[generate-image] Error while copying image to recipe flow", e);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
// Déduire les tokens utilisés via wallet-service (calcul basé sur le coût USD réel)
|
|
342
|
+
try {
|
|
343
|
+
// Parse provider/model from fullModel
|
|
344
|
+
const [provider, modelName] = fullModel.includes("/")
|
|
345
|
+
? fullModel.split("/", 2)
|
|
346
|
+
: ["openai", fullModel];
|
|
347
|
+
// Get model pricing
|
|
348
|
+
const filterResult = await isModelEnabled(user.id, fullModel);
|
|
349
|
+
if (!filterResult.allowed) {
|
|
350
|
+
logger.warn(`[generate-image] Model ${fullModel} not allowed for user ${user.id}`);
|
|
351
|
+
}
|
|
352
|
+
// Prepare usage and pricing for wallet billing
|
|
353
|
+
const usage = {
|
|
354
|
+
images: 1,
|
|
355
|
+
};
|
|
356
|
+
const pricing = {
|
|
357
|
+
imageUsdPerImage: filterResult.model?.pricing?.image
|
|
358
|
+
? parseFloat(filterResult.model.pricing.image.toString())
|
|
359
|
+
: undefined,
|
|
360
|
+
};
|
|
361
|
+
// Compute provider cost from actual API usage
|
|
362
|
+
const providerCostUsd = computeProviderCostUsd(usage, pricing);
|
|
363
|
+
// Get current wallet state
|
|
364
|
+
const wallet = await getWalletState(user.id);
|
|
365
|
+
// Compute and debit using centralized billing logic
|
|
366
|
+
const billingResult = computeAndDebitAIUsage({
|
|
367
|
+
userId: user.id,
|
|
368
|
+
actionType: "generate-image",
|
|
369
|
+
providerCostUsd,
|
|
370
|
+
wallet,
|
|
371
|
+
metadata: {
|
|
372
|
+
model: modelName,
|
|
373
|
+
provider,
|
|
374
|
+
endpoint: "generate-image",
|
|
375
|
+
images: 1,
|
|
376
|
+
size,
|
|
377
|
+
quality,
|
|
378
|
+
prompt: prompt.substring(0, 32),
|
|
379
|
+
imageUrl: returnedImageUrl,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
// Check if billing succeeded
|
|
383
|
+
if (!billingResult.success) {
|
|
384
|
+
logger.error(`[generate-image] Billing failed: ${billingResult.error}`, {
|
|
385
|
+
providerCostUsd,
|
|
386
|
+
walletSellValue: wallet.walletSellValueUsd,
|
|
387
|
+
walletProviderBudget: wallet.walletProviderBudgetUsd,
|
|
388
|
+
});
|
|
389
|
+
return NextResponse.json({
|
|
390
|
+
error: billingResult.error ||
|
|
391
|
+
"Crédits insuffisants pour cette opération",
|
|
392
|
+
}, { status: 402 } // Payment Required
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
// Record consumption in database (ledger)
|
|
396
|
+
const { error: ledgerError } = await supabase.rpc("record_token_consumption", {
|
|
397
|
+
p_user_id: user.id,
|
|
398
|
+
p_debit_tokens: 0, // Calculated by wallet system
|
|
399
|
+
p_provider_cost_usd: billingResult.providerCostUsd || 0,
|
|
400
|
+
p_sell_usd: billingResult.sellCostUsd || 0,
|
|
401
|
+
p_margin_usd: billingResult.marginUsd || 0,
|
|
402
|
+
p_meta: {
|
|
403
|
+
model: modelName,
|
|
404
|
+
provider,
|
|
405
|
+
endpoint: "generate-image",
|
|
406
|
+
images: 1,
|
|
407
|
+
size,
|
|
408
|
+
quality,
|
|
409
|
+
prompt: prompt.substring(0, 32),
|
|
410
|
+
imageUrl: returnedImageUrl,
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
if (ledgerError) {
|
|
414
|
+
logger.error("[generate-image] Failed to record in ledger:", ledgerError);
|
|
415
|
+
// Continue anyway - billing logic already validated
|
|
416
|
+
}
|
|
417
|
+
logger.info(`[generate-image] Image generation completed: cost=$${billingResult.providerCostUsd?.toFixed(6)}, sell=$${billingResult.sellCostUsd?.toFixed(6)}, margin=${billingResult.marginPercent?.toFixed(1)}%`);
|
|
418
|
+
// Return with billing information (wallet-based)
|
|
128
419
|
return NextResponse.json({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
420
|
+
imageUrl: returnedImageUrl,
|
|
421
|
+
supabaseImageUrl,
|
|
422
|
+
model,
|
|
423
|
+
tokensUsed: billingResult.tokensDebited || 0,
|
|
424
|
+
tokensRemaining: billingResult.newWallet?.walletSellValueUsd || 0,
|
|
425
|
+
cost: billingResult.sellCostUsd || 0,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
catch (e) {
|
|
429
|
+
logger.error("[generate-image] Billing exception:", e);
|
|
430
|
+
// Return anyway but without billing info
|
|
431
|
+
return NextResponse.json({
|
|
432
|
+
imageUrl: returnedImageUrl,
|
|
433
|
+
supabaseImageUrl,
|
|
434
|
+
model,
|
|
435
|
+
tokensUsed: 0,
|
|
436
|
+
tokensRemaining: 0,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
139
439
|
}
|
|
140
440
|
catch (error) {
|
|
141
441
|
const err = error;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from "next/server";
|
|
2
2
|
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
3
3
|
text: string;
|
|
4
|
-
tokensUsed: number;
|
|
5
|
-
tokensRemaining: number;
|
|
6
4
|
model: string;
|
|
5
|
+
provider: string;
|
|
6
|
+
inputTokens: number;
|
|
7
|
+
outputTokens: number;
|
|
7
8
|
}> | NextResponse<{
|
|
8
9
|
error: any;
|
|
9
10
|
}>>;
|
|
@@ -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;AAwBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW
|
|
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;;;;;;;;IA8P9C"}
|