@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
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GET /api/ai/admin/user-monthly-details?userId=xxx&month=2026-01
|
|
3
|
+
* Get detailed monthly usage for a specific user
|
|
4
|
+
*/
|
|
5
|
+
import { NextResponse } from "next/server";
|
|
6
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
7
|
+
import { logger } from "@lastbrain/core";
|
|
8
|
+
export async function GET(request) {
|
|
9
|
+
try {
|
|
10
|
+
const { searchParams } = new URL(request.url);
|
|
11
|
+
const userId = searchParams.get("userId");
|
|
12
|
+
const month = searchParams.get("month"); // Format: YYYY-MM
|
|
13
|
+
if (!userId) {
|
|
14
|
+
return NextResponse.json({ error: "userId required" }, { status: 400 });
|
|
15
|
+
}
|
|
16
|
+
if (!month || !/^\d{4}-\d{2}$/.test(month)) {
|
|
17
|
+
return NextResponse.json({ error: "month required in format YYYY-MM" }, { status: 400 });
|
|
18
|
+
}
|
|
19
|
+
const supabase = await getSupabaseServiceClient();
|
|
20
|
+
// Calculate month start/end
|
|
21
|
+
const monthStart = new Date(`${month}-01T00:00:00Z`);
|
|
22
|
+
const monthEnd = new Date(monthStart);
|
|
23
|
+
monthEnd.setMonth(monthEnd.getMonth() + 1);
|
|
24
|
+
// Get all ledger entries for this user in this month
|
|
25
|
+
const { data: ledgerEntries, error: ledgerError } = await supabase
|
|
26
|
+
.from("user_token_ledger")
|
|
27
|
+
.select(`
|
|
28
|
+
created_at,
|
|
29
|
+
type,
|
|
30
|
+
debit_tokens,
|
|
31
|
+
provider_cost_usd,
|
|
32
|
+
sell_usd,
|
|
33
|
+
margin_usd,
|
|
34
|
+
model,
|
|
35
|
+
provider,
|
|
36
|
+
meta,
|
|
37
|
+
provider_budget_added_usd
|
|
38
|
+
`)
|
|
39
|
+
.eq("owner_id", userId)
|
|
40
|
+
.gte("created_at", monthStart.toISOString())
|
|
41
|
+
.lt("created_at", monthEnd.toISOString())
|
|
42
|
+
.order("created_at", { ascending: false });
|
|
43
|
+
if (ledgerError) {
|
|
44
|
+
logger.error("[user-monthly-details] Error fetching ledger:", ledgerError);
|
|
45
|
+
return NextResponse.json({ error: "Failed to fetch ledger entries" }, { status: 500 });
|
|
46
|
+
}
|
|
47
|
+
// Separate entry types
|
|
48
|
+
const purchases = ledgerEntries?.filter((e) => e.type === "purchase") || [];
|
|
49
|
+
const gifts = ledgerEntries?.filter((e) => e.type === "gift" || e.type === "signup_bonus") || [];
|
|
50
|
+
const usages = ledgerEntries?.filter((e) => e.type === "use") || [];
|
|
51
|
+
// CA = uniquement les achats réels (purchases)
|
|
52
|
+
const totalPurchaseUsd = purchases.reduce((sum, p) => sum + (p.sell_usd || 0), 0);
|
|
53
|
+
// Coût fournisseur = toutes les utilisations (use)
|
|
54
|
+
const totalProviderCostUsd = usages.reduce((sum, u) => sum + (u.provider_cost_usd || 0), 0);
|
|
55
|
+
// Budget total offert (gifts/signup_bonus) - PAS une charge tant que non consommé
|
|
56
|
+
const totalGiftBudgetUsd = gifts.reduce((sum, g) => sum + (g.provider_budget_added_usd || 0), 0);
|
|
57
|
+
const totalSellUsd = usages.reduce((sum, u) => sum + (u.sell_usd || 0), 0);
|
|
58
|
+
const totalMarginUsd = usages.reduce((sum, u) => sum + (u.margin_usd || 0), 0);
|
|
59
|
+
// Group usages by action type
|
|
60
|
+
const usageByAction = {};
|
|
61
|
+
usages.forEach((usage) => {
|
|
62
|
+
// Extract action type from meta or infer from model
|
|
63
|
+
let actionType = usage.meta?.action_type || "use";
|
|
64
|
+
// Get model and provider
|
|
65
|
+
const modelFull = usage.model || "unknown";
|
|
66
|
+
let provider = usage.provider || null;
|
|
67
|
+
let modelName = modelFull;
|
|
68
|
+
// If model contains "/" extract provider and model
|
|
69
|
+
if (modelFull.includes("/")) {
|
|
70
|
+
const parts = modelFull.split("/");
|
|
71
|
+
provider = parts[0];
|
|
72
|
+
modelName = parts[1];
|
|
73
|
+
}
|
|
74
|
+
// Infer action type from model if not in meta
|
|
75
|
+
if (actionType === "use") {
|
|
76
|
+
if (modelName.includes("imagen") || modelName.includes("dall-e")) {
|
|
77
|
+
actionType = "generate-image";
|
|
78
|
+
}
|
|
79
|
+
else if (modelName.includes("gpt") || modelName.includes("claude")) {
|
|
80
|
+
actionType = "generate-text";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const key = `${actionType} (${provider || "unknown"}/${modelName})`;
|
|
84
|
+
if (!usageByAction[key]) {
|
|
85
|
+
usageByAction[key] = {
|
|
86
|
+
count: 0,
|
|
87
|
+
providerCostUsd: 0,
|
|
88
|
+
sellUsd: 0,
|
|
89
|
+
marginUsd: 0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
usageByAction[key].count++;
|
|
93
|
+
usageByAction[key].providerCostUsd += usage.provider_cost_usd || 0;
|
|
94
|
+
usageByAction[key].sellUsd += usage.sell_usd || 0;
|
|
95
|
+
usageByAction[key].marginUsd += usage.margin_usd || 0;
|
|
96
|
+
});
|
|
97
|
+
// Get user info
|
|
98
|
+
const { data: userData } = await supabase.auth.admin.getUserById(userId);
|
|
99
|
+
// Calcul du vrai CA et marge
|
|
100
|
+
// CA = Achats uniquement (purchases)
|
|
101
|
+
// Charges = Coût fournisseur utilisation + Coût crédits offerts
|
|
102
|
+
// Charges réelles = UNIQUEMENT ce qui a été consommé (usages API)
|
|
103
|
+
// Les budgets offerts (gifts) ne sont PAS des charges tant qu'ils ne sont pas utilisés
|
|
104
|
+
const totalCharges = totalProviderCostUsd;
|
|
105
|
+
const realMarginUsd = totalPurchaseUsd - totalCharges;
|
|
106
|
+
const realMarginPercent = totalPurchaseUsd > 0 ? (realMarginUsd / totalPurchaseUsd) * 100 : 0;
|
|
107
|
+
return NextResponse.json({
|
|
108
|
+
user: {
|
|
109
|
+
id: userId,
|
|
110
|
+
email: userData?.user?.email || "Unknown",
|
|
111
|
+
},
|
|
112
|
+
month,
|
|
113
|
+
summary: {
|
|
114
|
+
totalPurchases: purchases.length,
|
|
115
|
+
totalGifts: gifts.length,
|
|
116
|
+
totalUsages: usages.length,
|
|
117
|
+
totalPurchaseUsd, // CA réel (achats uniquement)
|
|
118
|
+
totalProviderCostUsd, // Coût utilisation réelle
|
|
119
|
+
totalGiftBudgetUsd, // Budget offert (non consommé = pas une charge)
|
|
120
|
+
totalCharges, // = totalProviderCostUsd uniquement
|
|
121
|
+
realMarginUsd, // Marge réelle = CA - Charges
|
|
122
|
+
realMarginPercent, // % marge réelle
|
|
123
|
+
// Anciennes valeurs pour compatibilité
|
|
124
|
+
totalSellUsd,
|
|
125
|
+
totalMarginUsd,
|
|
126
|
+
marginPercent: totalSellUsd > 0 ? (totalMarginUsd / totalSellUsd) * 100 : 0,
|
|
127
|
+
},
|
|
128
|
+
usageByAction: Object.entries(usageByAction).map(([key, data]) => ({
|
|
129
|
+
actionType: key,
|
|
130
|
+
...data,
|
|
131
|
+
avgCostPerCall: data.providerCostUsd / data.count,
|
|
132
|
+
})),
|
|
133
|
+
recentEntries: ledgerEntries?.slice(0, 50) || [], // Last 50 entries
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
logger.error("[user-monthly-details] Unexpected error:", error);
|
|
138
|
+
return NextResponse.json({ error: "Internal server error" }, { status: 500 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/ai/admin/user-quota
|
|
3
|
+
* Update custom quota for a user (admin only)
|
|
4
|
+
*/
|
|
5
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
6
|
+
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
7
|
+
success: boolean;
|
|
8
|
+
quota: import("../..").TokenQuotaStatus;
|
|
9
|
+
}> | NextResponse<{
|
|
10
|
+
error: any;
|
|
11
|
+
}>>;
|
|
12
|
+
/**
|
|
13
|
+
* GET /api/ai/admin/user-quota?userId=xxx
|
|
14
|
+
* Get quota status for a specific user (admin only)
|
|
15
|
+
*/
|
|
16
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
17
|
+
quota: import("../..").TokenQuotaStatus;
|
|
18
|
+
}> | NextResponse<{
|
|
19
|
+
error: any;
|
|
20
|
+
}>>;
|
|
21
|
+
//# sourceMappingURL=user-quota.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-quota.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-quota.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IA8C9C;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAqB7C"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POST /api/ai/admin/user-quota
|
|
3
|
+
* Update custom quota for a user (admin only)
|
|
4
|
+
*/
|
|
5
|
+
import { NextResponse } from "next/server";
|
|
6
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
7
|
+
import { updateCustomQuota, getUserQuotaStatus } from "../../server/quota";
|
|
8
|
+
import { logger } from "@lastbrain/core";
|
|
9
|
+
export async function POST(request) {
|
|
10
|
+
try {
|
|
11
|
+
const supabase = await getSupabaseServiceClient();
|
|
12
|
+
const body = await request.json();
|
|
13
|
+
const { userId, customTokens } = body;
|
|
14
|
+
if (!userId) {
|
|
15
|
+
return NextResponse.json({ error: "userId requis" }, { status: 400 });
|
|
16
|
+
}
|
|
17
|
+
// Validate customTokens (null or positive number)
|
|
18
|
+
if (customTokens !== null &&
|
|
19
|
+
(typeof customTokens !== "number" || customTokens < 0)) {
|
|
20
|
+
return NextResponse.json({ error: "customTokens doit être null ou un nombre positif" }, { status: 400 });
|
|
21
|
+
}
|
|
22
|
+
// Update custom quota
|
|
23
|
+
const result = await updateCustomQuota(userId, customTokens);
|
|
24
|
+
if (!result.success) {
|
|
25
|
+
return NextResponse.json({ error: result.error || "Erreur lors de la mise à jour" }, { status: 400 });
|
|
26
|
+
}
|
|
27
|
+
// Return updated quota status
|
|
28
|
+
const updatedStatus = await getUserQuotaStatus(userId);
|
|
29
|
+
return NextResponse.json({
|
|
30
|
+
success: true,
|
|
31
|
+
quota: updatedStatus,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
logger.error("[POST /api/ai/admin/user-quota] Error:", error);
|
|
36
|
+
return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* GET /api/ai/admin/user-quota?userId=xxx
|
|
41
|
+
* Get quota status for a specific user (admin only)
|
|
42
|
+
*/
|
|
43
|
+
export async function GET(request) {
|
|
44
|
+
try {
|
|
45
|
+
const searchParams = request.nextUrl.searchParams;
|
|
46
|
+
const userId = searchParams.get("userId");
|
|
47
|
+
if (!userId) {
|
|
48
|
+
return NextResponse.json({ error: "userId requis" }, { status: 400 });
|
|
49
|
+
}
|
|
50
|
+
const quotaStatus = await getUserQuotaStatus(userId);
|
|
51
|
+
return NextResponse.json({
|
|
52
|
+
quota: quotaStatus,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
logger.error("[GET /api/ai/admin/user-quota] Error:", error);
|
|
57
|
+
return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"[id].d.ts","sourceRoot":"","sources":["../../../../src/api/admin/user-token/[id].ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"[id].d.ts","sourceRoot":"","sources":["../../../../src/api/admin/user-token/[id].ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUxD,wBAAsB,GAAG,CACvB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4D7C"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
2
|
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
3
3
|
import { getTokenBalance, getTokenHistory, getTokenStats, } from "../../../server";
|
|
4
|
+
import { logger } from "@lastbrain/core";
|
|
4
5
|
// GET /api/ai/admin/user-token/[id] - Détails d'un utilisateur spécifique
|
|
5
6
|
export async function GET(request, context) {
|
|
6
7
|
const { id: userId } = await context.params;
|
|
@@ -44,7 +45,7 @@ export async function GET(request, context) {
|
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
catch (error) {
|
|
47
|
-
|
|
48
|
+
logger.error("[GET /api/ai/admin/user-token/[id]] Error:", error);
|
|
48
49
|
return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
|
|
49
50
|
}
|
|
50
51
|
}
|
|
@@ -9,15 +9,18 @@ export declare function GET(_request: NextRequest): Promise<NextResponse<{
|
|
|
9
9
|
totalAdded: number;
|
|
10
10
|
totalUsed: number;
|
|
11
11
|
lastActivity: string | undefined;
|
|
12
|
+
includedTokens: any;
|
|
13
|
+
usedIncludedTokens: any;
|
|
14
|
+
remainingIncludedTokens: number;
|
|
12
15
|
}[];
|
|
13
|
-
transactions: any;
|
|
16
|
+
transactions: any[];
|
|
14
17
|
total: number;
|
|
15
18
|
}> | NextResponse<{
|
|
16
19
|
error: any;
|
|
17
20
|
}>>;
|
|
18
21
|
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
19
22
|
success: boolean;
|
|
20
|
-
balance: number;
|
|
23
|
+
balance: number | undefined;
|
|
21
24
|
message: string;
|
|
22
25
|
}> | NextResponse<{
|
|
23
26
|
error: any;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-token.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"user-token.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,wBAAsB,GAAG,CAAC,QAAQ,EAAE,WAAW;;;;;;;;;;;;;;;;;;IA4M9C;AAGD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;IAmE9C"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
3
|
-
import {
|
|
2
|
+
import { getSupabaseServerClient, getSupabaseServiceClient, } from "@lastbrain/core/server";
|
|
3
|
+
import { addWalletCredits } from "../../server/billing";
|
|
4
|
+
import { logger } from "@lastbrain/core";
|
|
4
5
|
// GET /api/ai/admin/user-token - Liste tous les utilisateurs avec leur balance et transactions
|
|
5
6
|
export async function GET(_request) {
|
|
6
7
|
try {
|
|
@@ -13,14 +14,23 @@ export async function GET(_request) {
|
|
|
13
14
|
// Récupérer toutes les transactions pour calculer les balances et stats
|
|
14
15
|
const { data: transactions, error: txError } = await supabase
|
|
15
16
|
.from("user_token_ledger")
|
|
16
|
-
.select("
|
|
17
|
-
.order("ts", { ascending: false })
|
|
18
|
-
|
|
17
|
+
.select("owner_id,ts,type,amount")
|
|
18
|
+
.order("ts", { ascending: false });
|
|
19
|
+
logger.error("transactions", txError);
|
|
19
20
|
if (txError)
|
|
20
21
|
throw txError;
|
|
21
22
|
const txList = transactions || [];
|
|
23
|
+
// Récupérer les usages de quotas (consommation d'abonnement)
|
|
24
|
+
const { data: quotaRows, error: quotaError } = await supabase
|
|
25
|
+
.from("user_token_quota_ledger")
|
|
26
|
+
.select("owner_id,period_start,period_end,tokens_used,model,created_at")
|
|
27
|
+
.order("created_at", { ascending: false });
|
|
28
|
+
if (quotaError)
|
|
29
|
+
throw quotaError;
|
|
30
|
+
const quotaList = quotaRows || [];
|
|
22
31
|
// Calculer les stats par utilisateur
|
|
23
32
|
const userStatsMap = new Map();
|
|
33
|
+
// Transactions (achat/cadeau/usage...) affectent le solde
|
|
24
34
|
txList.forEach((tx) => {
|
|
25
35
|
const stats = userStatsMap.get(tx.owner_id) || {
|
|
26
36
|
balance: 0,
|
|
@@ -39,8 +49,28 @@ export async function GET(_request) {
|
|
|
39
49
|
}
|
|
40
50
|
userStatsMap.set(tx.owner_id, stats);
|
|
41
51
|
});
|
|
52
|
+
// Usages de quota : ne modifient pas le solde, mais comptent dans le totalUsed et l'activité
|
|
53
|
+
quotaList.forEach((quota) => {
|
|
54
|
+
const stats = userStatsMap.get(quota.owner_id) || {
|
|
55
|
+
balance: 0,
|
|
56
|
+
totalAdded: 0,
|
|
57
|
+
totalUsed: 0,
|
|
58
|
+
};
|
|
59
|
+
stats.totalUsed += quota.tokens_used; // toujours positif dans la table
|
|
60
|
+
if (!stats.lastActivity || quota.created_at > stats.lastActivity) {
|
|
61
|
+
stats.lastActivity = quota.created_at;
|
|
62
|
+
}
|
|
63
|
+
userStatsMap.set(quota.owner_id, stats);
|
|
64
|
+
});
|
|
42
65
|
// Récupérer les profils utilisateurs pour avatar et nom
|
|
43
66
|
const userIds = authUsers.users.map((u) => u.id);
|
|
67
|
+
const { data: quotaMonthly, error: quotaMonthlyError } = await supabase
|
|
68
|
+
.from("user_token_quota_monthly")
|
|
69
|
+
.select("owner_id, effective_included_tokens, used_included_tokens")
|
|
70
|
+
.in("owner_id", userIds);
|
|
71
|
+
if (quotaMonthlyError)
|
|
72
|
+
throw quotaMonthlyError;
|
|
73
|
+
const quotaMonthlyMap = new Map((quotaMonthly || []).map((row) => [row.owner_id, row]));
|
|
44
74
|
const { data: profiles } = await supabase
|
|
45
75
|
.from("user_profil")
|
|
46
76
|
.select("id, first_name, last_name, avatar_url")
|
|
@@ -60,6 +90,10 @@ export async function GET(_request) {
|
|
|
60
90
|
totalUsed: 0,
|
|
61
91
|
};
|
|
62
92
|
const profile = profilesMap.get(user.id);
|
|
93
|
+
const quota = quotaMonthlyMap.get(user.id);
|
|
94
|
+
const includedTokens = quota?.effective_included_tokens || 0;
|
|
95
|
+
const usedIncludedTokens = quota?.used_included_tokens || 0;
|
|
96
|
+
const remainingIncludedTokens = Math.max(0, includedTokens - usedIncludedTokens);
|
|
63
97
|
return {
|
|
64
98
|
userId: user.id,
|
|
65
99
|
email: user.email || "Unknown",
|
|
@@ -69,9 +103,12 @@ export async function GET(_request) {
|
|
|
69
103
|
totalAdded: stats.totalAdded,
|
|
70
104
|
totalUsed: stats.totalUsed,
|
|
71
105
|
lastActivity: stats.lastActivity,
|
|
106
|
+
includedTokens,
|
|
107
|
+
usedIncludedTokens,
|
|
108
|
+
remainingIncludedTokens,
|
|
72
109
|
};
|
|
73
110
|
});
|
|
74
|
-
//
|
|
111
|
+
// Transactions classiques (achats / cadeaux / usages)
|
|
75
112
|
const transactionsWithUsers = txList.map((tx) => {
|
|
76
113
|
const user = authUsers.users.find((u) => u.id === tx.owner_id);
|
|
77
114
|
const profile = profilesMap.get(tx.owner_id);
|
|
@@ -88,44 +125,81 @@ export async function GET(_request) {
|
|
|
88
125
|
created_at: tx.ts,
|
|
89
126
|
};
|
|
90
127
|
});
|
|
128
|
+
// Transactions de quota (consommation d'abonnement)
|
|
129
|
+
const quotaTransactions = quotaList.map((quota) => {
|
|
130
|
+
const user = authUsers.users.find((u) => u.id === quota.owner_id);
|
|
131
|
+
const profile = profilesMap.get(quota.owner_id);
|
|
132
|
+
return {
|
|
133
|
+
id: `quota-${quota.id}`,
|
|
134
|
+
userId: quota.owner_id,
|
|
135
|
+
email: user?.email || "Unknown",
|
|
136
|
+
fullName: profile?.fullName,
|
|
137
|
+
avatarUrl: profile?.avatarUrl,
|
|
138
|
+
amount: -quota.tokens_used, // on l'affiche en débit
|
|
139
|
+
type: "quota_use",
|
|
140
|
+
description: quota.meta?.reason ||
|
|
141
|
+
(quota.model
|
|
142
|
+
? `Usage quota ${quota.model}`
|
|
143
|
+
: "Usage quota abonnement"),
|
|
144
|
+
model: quota.model,
|
|
145
|
+
created_at: quota.created_at,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
// Fusionner et trier par date desc
|
|
149
|
+
const allTransactions = [
|
|
150
|
+
...transactionsWithUsers,
|
|
151
|
+
...quotaTransactions,
|
|
152
|
+
].sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
|
|
91
153
|
return NextResponse.json({
|
|
92
154
|
users: usersData,
|
|
93
|
-
transactions:
|
|
155
|
+
transactions: allTransactions,
|
|
94
156
|
total: usersData.length,
|
|
95
157
|
});
|
|
96
158
|
}
|
|
97
159
|
catch (error) {
|
|
98
|
-
|
|
160
|
+
logger.error("[GET /api/ai/admin/user-token] Error:", error);
|
|
99
161
|
return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
|
|
100
162
|
}
|
|
101
163
|
}
|
|
102
|
-
// POST /api/ai/admin/user-token - Ajouter/retirer des
|
|
164
|
+
// POST /api/ai/admin/user-token - Ajouter/retirer des crédits USD à un utilisateur
|
|
103
165
|
export async function POST(request) {
|
|
104
166
|
try {
|
|
105
167
|
// L'authentification et les droits superadmin sont déjà vérifiés par le middleware
|
|
106
168
|
const body = await request.json();
|
|
107
|
-
const { userId, amount,
|
|
169
|
+
const { userId, amount, reason } = body;
|
|
108
170
|
if (!userId || !amount) {
|
|
109
171
|
return NextResponse.json({ error: "userId et amount sont requis" }, { status: 400 });
|
|
110
172
|
}
|
|
111
173
|
if (amount === 0) {
|
|
112
174
|
return NextResponse.json({ error: "Le montant ne peut pas être zéro" }, { status: 400 });
|
|
113
175
|
}
|
|
114
|
-
//
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
176
|
+
// Utiliser le nouveau système de wallet en USD
|
|
177
|
+
const usdAmount = parseFloat(amount);
|
|
178
|
+
if (usdAmount < 0) {
|
|
179
|
+
return NextResponse.json({
|
|
180
|
+
error: "Le débit de crédits n'est pas encore supporté. Utilisez un montant positif pour créditer.",
|
|
181
|
+
}, { status: 400 });
|
|
182
|
+
}
|
|
183
|
+
const result = await addWalletCredits(userId, usdAmount, reason || "Ajustement manuel par admin");
|
|
118
184
|
if (!result.success) {
|
|
119
185
|
return NextResponse.json({ error: result.error }, { status: 500 });
|
|
120
186
|
}
|
|
187
|
+
const supabase = await getSupabaseServerClient();
|
|
188
|
+
await supabase.from("user_notifications").insert({
|
|
189
|
+
owner_id: userId,
|
|
190
|
+
title: `Crédits IA ajoutés`,
|
|
191
|
+
body: `$${usdAmount.toFixed(2)} de crédits IA ont été ajoutés à votre compte. ${reason ? `Raison : ${reason}` : ""}`,
|
|
192
|
+
type: "credit_added",
|
|
193
|
+
read: false,
|
|
194
|
+
});
|
|
121
195
|
return NextResponse.json({
|
|
122
196
|
success: true,
|
|
123
|
-
balance: result.
|
|
124
|
-
message:
|
|
197
|
+
balance: result.newBalance,
|
|
198
|
+
message: `Ajouté $${usdAmount.toFixed(2)} de crédits IA`,
|
|
125
199
|
});
|
|
126
200
|
}
|
|
127
201
|
catch (error) {
|
|
128
|
-
|
|
202
|
+
logger.error("[POST /api/ai/admin/user-token] Error:", error);
|
|
129
203
|
return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
|
|
130
204
|
}
|
|
131
205
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
/**
|
|
3
|
+
* GET /api/ai/admin/user-usage-by-model
|
|
4
|
+
* Returns usage statistics grouped by model with costs in USD for a specific user
|
|
5
|
+
* Supports filtering by month
|
|
6
|
+
*/
|
|
7
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
8
|
+
data: {
|
|
9
|
+
avg_cost_per_call: number;
|
|
10
|
+
model: string;
|
|
11
|
+
provider: string;
|
|
12
|
+
endpoint: string;
|
|
13
|
+
total_cost_usd: number;
|
|
14
|
+
total_sell_usd: number;
|
|
15
|
+
call_count: number;
|
|
16
|
+
}[];
|
|
17
|
+
period: string;
|
|
18
|
+
userId: string;
|
|
19
|
+
}> | NextResponse<{
|
|
20
|
+
error: any;
|
|
21
|
+
}>>;
|
|
22
|
+
//# sourceMappingURL=user-usage-by-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-usage-by-model.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-usage-by-model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;eAgD/B,MAAM;kBACH,MAAM;kBACN,MAAM;wBACA,MAAM;wBACN,MAAM;oBACV,MAAM;;;;;;IAiDzB"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
3
|
+
import { logger } from "@lastbrain/core";
|
|
4
|
+
/**
|
|
5
|
+
* GET /api/ai/admin/user-usage-by-model
|
|
6
|
+
* Returns usage statistics grouped by model with costs in USD for a specific user
|
|
7
|
+
* Supports filtering by month
|
|
8
|
+
*/
|
|
9
|
+
export async function GET(request) {
|
|
10
|
+
try {
|
|
11
|
+
const supabase = getSupabaseServiceClient();
|
|
12
|
+
// Get query params
|
|
13
|
+
const searchParams = request.nextUrl.searchParams;
|
|
14
|
+
const userId = searchParams.get("userId");
|
|
15
|
+
const month = searchParams.get("month"); // Format: YYYY-MM
|
|
16
|
+
if (!userId) {
|
|
17
|
+
return NextResponse.json({ error: "userId parameter required" }, { status: 400 });
|
|
18
|
+
}
|
|
19
|
+
// Build query
|
|
20
|
+
let query = supabase
|
|
21
|
+
.from("user_token_ledger")
|
|
22
|
+
.select("model, provider_cost_usd, sell_usd, meta")
|
|
23
|
+
.eq("owner_id", userId)
|
|
24
|
+
.eq("type", "use")
|
|
25
|
+
.order("ts", { ascending: false });
|
|
26
|
+
// Filter by month if provided
|
|
27
|
+
if (month && month !== "all") {
|
|
28
|
+
const startDate = `${month}-01`;
|
|
29
|
+
const [year, monthNum] = month.split("-");
|
|
30
|
+
const nextMonth = new Date(parseInt(year), parseInt(monthNum), 1);
|
|
31
|
+
const endDate = nextMonth.toISOString().split("T")[0];
|
|
32
|
+
query = query.gte("ts", startDate).lt("ts", endDate);
|
|
33
|
+
}
|
|
34
|
+
const { data: ledgerEntries, error } = await query;
|
|
35
|
+
if (error) {
|
|
36
|
+
logger.error("[admin/user-usage-by-model] Error fetching ledger:", error);
|
|
37
|
+
return NextResponse.json({ error: "Failed to fetch usage data" }, { status: 500 });
|
|
38
|
+
}
|
|
39
|
+
// Group by model + provider + endpoint
|
|
40
|
+
const groupedData = new Map();
|
|
41
|
+
for (const entry of ledgerEntries || []) {
|
|
42
|
+
const model = entry.model || "unknown";
|
|
43
|
+
const provider = entry.meta?.provider || "unknown";
|
|
44
|
+
const endpoint = entry.meta?.endpoint || "unknown";
|
|
45
|
+
const key = `${model}|${provider}|${endpoint}`;
|
|
46
|
+
if (!groupedData.has(key)) {
|
|
47
|
+
groupedData.set(key, {
|
|
48
|
+
model,
|
|
49
|
+
provider,
|
|
50
|
+
endpoint,
|
|
51
|
+
total_cost_usd: 0,
|
|
52
|
+
total_sell_usd: 0,
|
|
53
|
+
call_count: 0,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const group = groupedData.get(key);
|
|
57
|
+
group.total_cost_usd += entry.provider_cost_usd || 0;
|
|
58
|
+
group.total_sell_usd += entry.sell_usd || 0;
|
|
59
|
+
group.call_count += 1;
|
|
60
|
+
}
|
|
61
|
+
// Convert to array and calculate averages
|
|
62
|
+
const result = Array.from(groupedData.values()).map((group) => ({
|
|
63
|
+
...group,
|
|
64
|
+
avg_cost_per_call: group.call_count > 0 ? group.total_sell_usd / group.call_count : 0,
|
|
65
|
+
}));
|
|
66
|
+
// Sort by total_sell_usd descending
|
|
67
|
+
result.sort((a, b) => b.total_sell_usd - a.total_sell_usd);
|
|
68
|
+
return NextResponse.json({
|
|
69
|
+
data: result,
|
|
70
|
+
period: month || "all",
|
|
71
|
+
userId,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger.error("[admin/user-usage-by-model] Error:", error);
|
|
76
|
+
return NextResponse.json({ error: error.message || "Internal server error" }, { status: 500 });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
3
|
+
error: string;
|
|
4
|
+
}> | NextResponse<{
|
|
5
|
+
tokensAdded: any;
|
|
6
|
+
tokensUsed: any;
|
|
7
|
+
totalPurchaseUsd: number;
|
|
8
|
+
totalProviderCostUsd: number;
|
|
9
|
+
totalSellUsd: number;
|
|
10
|
+
totalMarginUsd: number;
|
|
11
|
+
marginPercent: number;
|
|
12
|
+
walletProviderBudgetUsd: number;
|
|
13
|
+
walletSellValueUsd: number;
|
|
14
|
+
}>>;
|
|
15
|
+
//# sourceMappingURL=user-wallet-analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-wallet-analytics.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-wallet-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;IAmG7C"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { NextResponse } from "next/server";
|
|
2
|
+
import { logger } from "@lastbrain/core";
|
|
3
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
4
|
+
export async function GET(request) {
|
|
5
|
+
try {
|
|
6
|
+
const { searchParams } = new URL(request.url);
|
|
7
|
+
const userId = searchParams.get("userId");
|
|
8
|
+
if (!userId) {
|
|
9
|
+
return NextResponse.json({ error: "userId required" }, { status: 400 });
|
|
10
|
+
}
|
|
11
|
+
const supabase = getSupabaseServiceClient();
|
|
12
|
+
// Get ledger for user
|
|
13
|
+
const { data: ledgerRows, error: ledgerError } = await supabase
|
|
14
|
+
.from("user_token_ledger")
|
|
15
|
+
.select("owner_id, type, amount, provider_cost_usd, sell_usd, margin_usd, pack_coef, pack_price_usd")
|
|
16
|
+
.eq("owner_id", userId);
|
|
17
|
+
if (ledgerError) {
|
|
18
|
+
logger.error("[User Wallet Analytics] Failed to fetch ledger:", ledgerError);
|
|
19
|
+
throw ledgerError;
|
|
20
|
+
}
|
|
21
|
+
const ledger = ledgerRows || [];
|
|
22
|
+
// Get wallet state
|
|
23
|
+
const { data: walletRow, error: walletError } = await supabase
|
|
24
|
+
.from("user_token_wallet")
|
|
25
|
+
.select("wallet_provider_budget_usd, wallet_sell_value_usd")
|
|
26
|
+
.eq("user_id", userId)
|
|
27
|
+
.single();
|
|
28
|
+
if (walletError && walletError.code !== "PGRST116") {
|
|
29
|
+
logger.error("[User Wallet Analytics] Failed to fetch wallet:", walletError);
|
|
30
|
+
}
|
|
31
|
+
const wallet = walletRow || {
|
|
32
|
+
wallet_provider_budget_usd: 0,
|
|
33
|
+
wallet_sell_value_usd: 0,
|
|
34
|
+
};
|
|
35
|
+
// Calculate stats
|
|
36
|
+
const purchases = ledger.filter((t) => t.type === "purchase");
|
|
37
|
+
const consumptions = ledger.filter((t) => t.type === "use");
|
|
38
|
+
const totalPurchaseUsd = purchases.reduce((sum, t) => {
|
|
39
|
+
return sum + (parseFloat(t.pack_price_usd) || 0);
|
|
40
|
+
}, 0);
|
|
41
|
+
const tokensAdded = purchases.reduce((sum, t) => sum + t.amount, 0);
|
|
42
|
+
const tokensUsed = consumptions.reduce((sum, t) => sum + Math.abs(t.amount), 0);
|
|
43
|
+
const totalProviderCostUsd = consumptions.reduce((sum, t) => {
|
|
44
|
+
return sum + (parseFloat(t.provider_cost_usd) || 0);
|
|
45
|
+
}, 0);
|
|
46
|
+
const totalSellUsd = consumptions.reduce((sum, t) => {
|
|
47
|
+
return sum + (parseFloat(t.sell_usd) || 0);
|
|
48
|
+
}, 0);
|
|
49
|
+
const totalMarginUsd = totalSellUsd - totalProviderCostUsd;
|
|
50
|
+
const marginPercent = totalSellUsd > 0 ? (totalMarginUsd / totalSellUsd) * 100 : 0;
|
|
51
|
+
return NextResponse.json({
|
|
52
|
+
tokensAdded,
|
|
53
|
+
tokensUsed,
|
|
54
|
+
totalPurchaseUsd: parseFloat(totalPurchaseUsd.toFixed(4)),
|
|
55
|
+
totalProviderCostUsd: parseFloat(totalProviderCostUsd.toFixed(4)),
|
|
56
|
+
totalSellUsd: parseFloat(totalSellUsd.toFixed(4)),
|
|
57
|
+
totalMarginUsd: parseFloat(totalMarginUsd.toFixed(4)),
|
|
58
|
+
marginPercent: parseFloat(marginPercent.toFixed(2)),
|
|
59
|
+
walletProviderBudgetUsd: parseFloat((wallet.wallet_provider_budget_usd || 0).toFixed(4)),
|
|
60
|
+
walletSellValueUsd: parseFloat((wallet.wallet_sell_value_usd || 0).toFixed(4)),
|
|
61
|
+
}, { status: 200 });
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.error("[User Wallet Analytics] Error:", error);
|
|
65
|
+
return NextResponse.json({ error: "Failed to fetch analytics" }, { status: 500 });
|
|
66
|
+
}
|
|
67
|
+
}
|