@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,12 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useCallback } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
4
|
+
import { useSearchParams } from "next/navigation";
|
|
5
|
+
import confetti from "canvas-confetti";
|
|
6
|
+
import { Card, CardBody, CardHeader, Spinner, Chip, Button, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, addToast, Progress, Alert, } from "@lastbrain/ui";
|
|
7
|
+
import { Coins, TrendingUp, TrendingDown, Calendar, ShoppingCart, AlertCircle, Crown, } from "lucide-react";
|
|
8
|
+
import { logger, useAuth, useModuleTranslation, useLocalizedRouter, } from "@lastbrain/core";
|
|
9
|
+
import { formatDateFr } from "../../utils/date";
|
|
7
10
|
export function TokenPage() {
|
|
8
11
|
const t = useModuleTranslation("ai");
|
|
9
12
|
const { user } = useAuth();
|
|
13
|
+
const router = useLocalizedRouter();
|
|
14
|
+
const searchParams = useSearchParams();
|
|
10
15
|
const [loading, setLoading] = useState(true);
|
|
11
16
|
const [balance, setBalance] = useState(null);
|
|
12
17
|
const [transactions, setTransactions] = useState([]);
|
|
@@ -14,6 +19,31 @@ export function TokenPage() {
|
|
|
14
19
|
const [monthlyStats, setMonthlyStats] = useState(null);
|
|
15
20
|
const [tokenPacks, setTokenPacks] = useState([]);
|
|
16
21
|
const [checkoutLoading, setCheckoutLoading] = useState(null);
|
|
22
|
+
const [successMessage, setSuccessMessage] = useState(null);
|
|
23
|
+
// Check for success/canceled params from Stripe redirect
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (searchParams.get("token_success") === "true") {
|
|
26
|
+
setSuccessMessage(t("tokens.purchase_success") || "🎉 Tokens achetés avec succès !");
|
|
27
|
+
// Trigger confetti
|
|
28
|
+
confetti({
|
|
29
|
+
particleCount: 100,
|
|
30
|
+
spread: 600,
|
|
31
|
+
origin: { y: 0.4 },
|
|
32
|
+
});
|
|
33
|
+
setTimeout(() => {
|
|
34
|
+
router.replace("/auth/ai/token");
|
|
35
|
+
}, 100);
|
|
36
|
+
}
|
|
37
|
+
if (searchParams.get("token_canceled") === "true") {
|
|
38
|
+
addToast({
|
|
39
|
+
title: t("tokens.purchase_canceled") || "Achat annulé",
|
|
40
|
+
description: t("tokens.purchase_canceled_desc") ||
|
|
41
|
+
"L'achat de tokens a été annulé.",
|
|
42
|
+
color: "warning",
|
|
43
|
+
});
|
|
44
|
+
router.replace("/auth/ai/token");
|
|
45
|
+
}
|
|
46
|
+
}, [searchParams, router, t]);
|
|
17
47
|
// Générer les 12 derniers mois
|
|
18
48
|
const availableMonths = Array.from({ length: 12 }, (_, i) => {
|
|
19
49
|
const date = new Date();
|
|
@@ -25,31 +55,30 @@ export function TokenPage() {
|
|
|
25
55
|
return;
|
|
26
56
|
try {
|
|
27
57
|
setLoading(true);
|
|
28
|
-
// Récupérer le solde et l'historique
|
|
29
|
-
const
|
|
30
|
-
|
|
58
|
+
// Récupérer le solde combiné (quota + achetés) et l'historique
|
|
59
|
+
const [balanceResponse, historyResponse] = await Promise.all([
|
|
60
|
+
fetch("/api/ai/auth/token-balance"),
|
|
61
|
+
fetch("/api/ai/user/tokens"),
|
|
62
|
+
]);
|
|
63
|
+
if (!balanceResponse.ok || !historyResponse.ok) {
|
|
31
64
|
throw new Error(t("tokens.error.loading") || "Erreur lors du chargement des données");
|
|
32
65
|
}
|
|
33
|
-
const
|
|
66
|
+
const balanceData = await balanceResponse.json();
|
|
67
|
+
const historyData = await historyResponse.json();
|
|
34
68
|
// Historique complet pour calculs globaux
|
|
35
|
-
const allTransactions =
|
|
36
|
-
|
|
37
|
-
const totalGifted = allTransactions
|
|
38
|
-
.filter((t) => t.type === "adjust" && t.amount > 0)
|
|
39
|
-
.reduce((sum, t) => sum + t.amount, 0);
|
|
40
|
-
setBalance({
|
|
41
|
-
balance: data.balance || 0,
|
|
42
|
-
totalAdded: data.stats?.totalPurchased + data.stats?.totalGifted || 0,
|
|
43
|
-
totalUsed: data.stats?.totalUsed || 0,
|
|
44
|
-
totalGifted,
|
|
45
|
-
});
|
|
69
|
+
const allTransactions = historyData.history || [];
|
|
70
|
+
setBalance(balanceData);
|
|
46
71
|
// Filtrer les transactions par mois
|
|
47
72
|
const filtered = allTransactions.filter((t) => t.created_at.startsWith(selectedMonth));
|
|
48
|
-
// Calculer le running balance
|
|
49
|
-
let runningBalance =
|
|
73
|
+
// Calculer le running balance (tokens achetés uniquement, pas quota)
|
|
74
|
+
let runningBalance = balanceData.purchased?.balance || 0;
|
|
50
75
|
const withBalance = filtered.map((t) => {
|
|
51
76
|
const txWithBalance = { ...t, running_balance: runningBalance };
|
|
52
|
-
|
|
77
|
+
// Ne décrémenter le running_balance que pour les transactions achetées
|
|
78
|
+
// Les transactions quota ne changent pas le solde des tokens achetés
|
|
79
|
+
if (t.source !== "quota") {
|
|
80
|
+
runningBalance -= t.amount;
|
|
81
|
+
}
|
|
53
82
|
return txWithBalance;
|
|
54
83
|
});
|
|
55
84
|
setTransactions(withBalance);
|
|
@@ -74,7 +103,7 @@ export function TokenPage() {
|
|
|
74
103
|
}
|
|
75
104
|
}
|
|
76
105
|
catch (error) {
|
|
77
|
-
|
|
106
|
+
logger.error(t("tokens.error.loading") || "Erreur:", error);
|
|
78
107
|
addToast({
|
|
79
108
|
color: "danger",
|
|
80
109
|
title: t("tokens.error.loading") || "Erreur lors du chargement des données",
|
|
@@ -96,21 +125,31 @@ export function TokenPage() {
|
|
|
96
125
|
minute: "2-digit",
|
|
97
126
|
});
|
|
98
127
|
};
|
|
99
|
-
const getTypeLabel = (type) => {
|
|
128
|
+
const getTypeLabel = (type, source) => {
|
|
129
|
+
// Différencier les débits quota des débits achat
|
|
130
|
+
if (type === "use" && source === "quota") {
|
|
131
|
+
return t("tokens.type.quota_use") || "Quota utilisé";
|
|
132
|
+
}
|
|
100
133
|
const labels = {
|
|
101
134
|
purchase: t("tokens.type.purchase") || "Achat",
|
|
102
135
|
gift: t("tokens.type.gift") || "Cadeau",
|
|
103
136
|
use: t("tokens.type.use") || "Utilisation",
|
|
104
137
|
adjust: t("tokens.type.adjust") || "Ajustement",
|
|
138
|
+
quota_use: t("tokens.type.quota_use") || "Quota utilisé",
|
|
105
139
|
};
|
|
106
140
|
return labels[type] || type;
|
|
107
141
|
};
|
|
108
|
-
const getTypeColor = (type) => {
|
|
142
|
+
const getTypeColor = (type, source) => {
|
|
143
|
+
// Différencier les débits quota des débits achat
|
|
144
|
+
if (type === "use" && source === "quota") {
|
|
145
|
+
return "danger"; // Bleu pour quota
|
|
146
|
+
}
|
|
109
147
|
const colors = {
|
|
110
148
|
purchase: "success",
|
|
111
149
|
gift: "primary",
|
|
112
150
|
use: "danger",
|
|
113
151
|
adjust: "warning",
|
|
152
|
+
quota_use: "primary",
|
|
114
153
|
};
|
|
115
154
|
return colors[type] || "default";
|
|
116
155
|
};
|
|
@@ -164,12 +203,30 @@ export function TokenPage() {
|
|
|
164
203
|
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
|
|
165
204
|
}
|
|
166
205
|
return (_jsxs("div", { className: " container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("tokens.page.title") || "Mes Tokens IA" }), _jsx("p", { className: "text-gray-500", children: t("tokens.page.description") ||
|
|
167
|
-
"Gérez votre solde et consultez votre historique de consommation" })] }),
|
|
206
|
+
"Gérez votre solde et consultez votre historique de consommation" })] }), successMessage && (_jsx(Alert, { color: "success", title: successMessage, className: "mb-6", onClose: () => setSuccessMessage(null) })), balance?.quota.hasQuota && balance.quota.isActive && (_jsxs(_Fragment, { children: [_jsxs(Card, { className: "mb-6 border-2 border-warning", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Coins, { size: 20, className: "text-primary" }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.quota.title") }), _jsx(Chip, { variant: "flat", color: "warning", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Crown, { size: 14 }), _jsx("span", { children: "PRO" })] }) })] }) }), _jsxs(CardBody, { children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-primary-50 dark:bg-success-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.quota.included") || "Inclus ce mois" }), _jsx("p", { className: "text-3xl font-bold text-primary", children: formatTokensShort(balance.quota.effectiveQuota) })] }), _jsxs("div", { className: "text-center p-4 bg-warning-50 dark:bg-danger-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.quota.used") || "Utilisés" }), _jsx("p", { className: "text-3xl font-bold text-warning dark:text-danger", children: formatTokensShort(balance.quota.usedQuota) })] }), _jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.quota.remaining") || "Restants" }), _jsx("p", { className: "text-3xl font-bold text-success", children: formatTokensShort(balance.quota.remainingQuota) })] })] }), balance.quota.effectiveQuota > 0 && (_jsxs("div", { className: "mt-6", children: [_jsxs("div", { className: "flex justify-between mb-2", children: [_jsx("p", { className: "text-sm font-medium", children: t("tokens.quota.usage") || "Utilisation du quota" }), _jsxs("p", { className: "text-sm font-semibold", children: [Math.round((balance.quota.usedQuota /
|
|
207
|
+
balance.quota.effectiveQuota) *
|
|
208
|
+
100), "%"] })] }), _jsx(Progress, { value: (balance.quota.usedQuota / balance.quota.effectiveQuota) *
|
|
209
|
+
100, color: balance.quota.usedQuota / balance.quota.effectiveQuota >
|
|
210
|
+
0.9
|
|
211
|
+
? "danger"
|
|
212
|
+
: balance.quota.usedQuota /
|
|
213
|
+
balance.quota.effectiveQuota >
|
|
214
|
+
0.7
|
|
215
|
+
? "warning"
|
|
216
|
+
: "success", className: "h-2" })] })), _jsxs("div", { className: "mt-4 text-center text-sm text-gray-500", children: [_jsxs("p", { children: [t("tokens.quota.period") || "Période:", " ", balance.quota.periodStart &&
|
|
217
|
+
formatDateFr(balance.quota.periodStart), " - ", balance.quota.periodEnd &&
|
|
218
|
+
formatDateFr(balance.quota.periodEnd)] }), _jsx("p", { className: "mt-2 text-xs", children: t("tokens.quota.info") ||
|
|
219
|
+
"Le quota se réinitialise automatiquement à chaque renouvellement." })] })] })] }), balance?.quota?.hasQuota && (_jsx(Alert, { color: "primary", title: t("tokens.purchased.priority_info") ||
|
|
220
|
+
"Priorité de débit des tokens", className: "mb-6", startContent: _jsx(Coins, { size: 20 }) }))] })), _jsxs("div", { className: "mb-6", children: [_jsx("h2", { className: "text-xl font-semibold mb-4", children: t("tokens.purchased.title") || "Tokens achetés" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Coins, { size: 24, className: "text-primary" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.purchased.balance") || "Solde actuel" })] }), _jsx("p", { className: `text-4xl font-bold ${Number(balance?.purchased.balance) > 0 ? "text-primary" : "text-danger"}`, children: formatTokensShort(balance?.purchased.balance || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.purchased.available") || "tokens disponibles" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingUp, { size: 24, className: "text-success" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.purchased.total_added") || "Total ajouté" })] }), _jsx("p", { className: "text-4xl font-bold text-success", children: formatTokensShort(balance?.purchased.totalAdded || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.purchased.cumulative") || "cumulés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingDown, { size: 24, className: "text-danger" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.purchased.total_used") || "Total utilisé" })] }), _jsx("p", { className: "text-4xl font-bold text-danger", children: formatTokensShort(balance?.purchased.totalUsed || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.purchased.consumed") || "consommés" })] }) })] }), _jsx("div", { className: "mt-4 text-center text-sm text-gray-500", children: _jsx("p", { children: t("tokens.purchased.info") ||
|
|
221
|
+
"Les tokens achetés restent disponibles jusqu'à consommation complète." }) })] }), monthlyStats && (_jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.stats.monthly_title") || "Statistiques du mois" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": t("tokens.stats.select_month") || "Sélectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
|
|
168
222
|
month: "long",
|
|
169
223
|
year: "numeric",
|
|
170
|
-
}) }, month))) })] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-
|
|
171
|
-
"
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
224
|
+
}) }, month))) })] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.added") || "Ajoutés" }), _jsxs("p", { className: "text-2xl font-bold text-success", children: ["+", formatTokensShort(monthlyStats.added)] })] }), _jsxs("div", { className: "text-center p-4 bg-danger-50 dark:bg-danger-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.used") || "Utilisés" }), _jsxs("p", { className: "text-2xl font-bold text-danger", children: ["-", formatTokensShort(monthlyStats.used)] })] }), _jsxs("div", { className: `text-center p-4 ${monthlyStats.net >= 0 ? "bg-primary-50 dark:bg-success-300/20" : "bg-danger-50 dark:bg-danger-300/20"} rounded-lg`, children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.net_balance") || "Solde net" }), _jsxs("p", { className: `text-2xl font-bold ${monthlyStats.net >= 0 ? "text-success" : "text-danger"}`, children: [monthlyStats.net >= 0 ? "+" : "-", formatTokensShort(Math.abs(monthlyStats.net))] })] })] }) })] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ShoppingCart, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.buy.title") || "Acheter des tokens" })] }) }), _jsx(CardBody, { children: tokenPacks.length === 0 ? (_jsxs("div", { className: "text-center py-8", children: [_jsx(ShoppingCart, { size: 48, className: "mx-auto mb-4 text-gray-300 dark:text-gray-600" }), _jsx("p", { className: "text-gray-500 mb-4", children: t("tokens.buy.no_packs") ||
|
|
225
|
+
"Aucun pack disponible pour le moment" })] })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: tokenPacks.map((pack) => (_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsx("div", { className: "mx-auto", children: _jsx(Coins, { size: 24, className: "mx-auto mb-2 text-gray-400" }) }), _jsx("h4", { className: "text-lg font-bold mb-2", children: pack.name }), pack.description && (_jsx("p", { className: "text-sm text-gray-500 mb-4", children: pack.description })), _jsxs("div", { className: "mb-4", children: [_jsx("p", { className: "text-3xl font-bold text-primary", children: formatTokensShort(pack.tokens) }), _jsx("p", { className: "text-xs text-gray-400", children: t("chip.tokens") || "tokens" })] }), _jsx("p", { className: "text-xl font-semibold mb-4", children: formatPrice(pack.price_cents, pack.currency) }), _jsx(Button, { color: "primary", className: "w-full", onPress: () => handleBuyTokens(pack.id), isLoading: checkoutLoading === pack.id, startContent: checkoutLoading !== pack.id && (_jsx(ShoppingCart, { size: 16 })), children: t("tokens.buy.button") || "Acheter" })] }) }, pack.id))) })) })] }), _jsxs(Card, { className: "my-6", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.history.title") || "Historique des transactions" }) }), _jsx(CardBody, { children: transactions.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Coins, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("tokens.history.no_transactions") ||
|
|
226
|
+
"Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": t("tokens.history.aria_label") || "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { className: "min-w-[120px]", children: t("tokens.history.column_date") || "DATE" }), _jsx(TableColumn, { children: t("tokens.history.column_type") || "TYPE" }), _jsx(TableColumn, { children: t("tokens.history.column_description") || "DESCRIPTION" }), _jsx(TableColumn, { children: t("tokens.history.column_amount") || "MONTANT" }), _jsx(TableColumn, { children: t("tokens.history.column_prompt") || "PROMPT" }), _jsx(TableColumn, { align: "end", children: t("tokens.history.column_balance") || "SOLDE APRÈS" })] }), _jsx(TableBody, { children: transactions.map((transaction) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: " text-sm text-gray-600", children: formatDate(transaction.created_at) }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: getTypeColor(transaction.type, transaction.source), children: getTypeLabel(transaction.type, transaction.source) }) }), _jsx(TableCell, { children: _jsx("div", { className: "max-w-md", children: _jsx("p", { className: "text-sm truncate", children: transaction.description || transaction.model || "-" }) }) }), _jsx(TableCell, { children: _jsxs("span", { className: `font-semibold ${transaction.source === "quota"
|
|
227
|
+
? "text-danger" // Quota toujours en rouge (danger)
|
|
228
|
+
: transaction.amount > 0
|
|
229
|
+
? "text-success" // Vert pour les ajouts (achetés, cadeaux)
|
|
230
|
+
: "text-danger" // Rouge pour les utilisations
|
|
231
|
+
}`, children: [transaction.amount > 0 ? "+" : "", formatTokensShort(Math.abs(transaction.amount))] }) }), _jsx(TableCell, { children: transaction.prompt && (_jsx("div", { className: "flex flex-inline gap-2 items-center", children: _jsx("p", { className: "max-w-xs truncate", children: transaction.prompt?.slice(0, 100) }) })) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-default-700", children: formatTokensShort(transaction.running_balance) }) })] }, transaction.id))) })] })) })] })] }));
|
|
175
232
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UsageAndTokensPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/UsageAndTokensPage.tsx"],"names":[],"mappings":"AAuEA,wBAAgB,kBAAkB,4CAoPjC"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
import { useSearchParams } from "next/navigation";
|
|
5
|
+
import confetti from "canvas-confetti";
|
|
6
|
+
import { Spinner, addToast, Alert } from "@lastbrain/ui";
|
|
7
|
+
import { AlertCircle } from "lucide-react";
|
|
8
|
+
import { logger, useAuth, useModuleTranslation } from "@lastbrain/core";
|
|
9
|
+
import { UsageByModelBarChart } from "./components/UsageByModelBarChart";
|
|
10
|
+
import { ApiKeyFilterSelect } from "./components/ApiKeyFilterSelect";
|
|
11
|
+
import { PurchaseButton } from "./components/PurchaseButton";
|
|
12
|
+
import { UsageByDayChart } from "./components/UsageByDayChart";
|
|
13
|
+
import { WalletStatusCard } from "./components/WalletStatusCard";
|
|
14
|
+
import { ModelUsageTable } from "./components/ModelUsageTable";
|
|
15
|
+
export function UsageAndTokensPage() {
|
|
16
|
+
const t = useModuleTranslation("ai");
|
|
17
|
+
const { user } = useAuth();
|
|
18
|
+
const searchParams = useSearchParams();
|
|
19
|
+
const [loading, setLoading] = useState(true);
|
|
20
|
+
const [loadingUsage, setLoadingUsage] = useState(false);
|
|
21
|
+
const [wallet, setWallet] = useState(null);
|
|
22
|
+
const [modelUsage, setModelUsage] = useState([]);
|
|
23
|
+
const [usageByModel, setUsageByModel] = useState([]);
|
|
24
|
+
const [usageByDay, setUsageByDay] = useState([]);
|
|
25
|
+
const [apiKeys, setApiKeys] = useState([]);
|
|
26
|
+
const [selectedApiKey, setSelectedApiKey] = useState("all");
|
|
27
|
+
const [selectedMonth, setSelectedMonth] = useState("all");
|
|
28
|
+
const [successMessage, setSuccessMessage] = useState(null);
|
|
29
|
+
// Check for success/canceled params from Stripe redirect
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
if (searchParams.get("token_success") === "true") {
|
|
32
|
+
setSuccessMessage(t("tokens.purchase_success") || "🎉 Tokens achetés avec succès !");
|
|
33
|
+
confetti({
|
|
34
|
+
particleCount: 100,
|
|
35
|
+
spread: 600,
|
|
36
|
+
origin: { y: 0.4 },
|
|
37
|
+
});
|
|
38
|
+
// Clean URL
|
|
39
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
40
|
+
}
|
|
41
|
+
if (searchParams.get("token_canceled") === "true") {
|
|
42
|
+
addToast({
|
|
43
|
+
title: t("tokens.purchase_canceled") || "Achat annulé",
|
|
44
|
+
description: t("tokens.purchase_canceled_desc") ||
|
|
45
|
+
"L'achat de tokens a été annulé.",
|
|
46
|
+
color: "warning",
|
|
47
|
+
});
|
|
48
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
49
|
+
}
|
|
50
|
+
}, [searchParams, t]);
|
|
51
|
+
// Fetch initial data (balance, transactions, API keys) - runs once on mount
|
|
52
|
+
const fetchInitialData = useCallback(async () => {
|
|
53
|
+
if (!user)
|
|
54
|
+
return;
|
|
55
|
+
try {
|
|
56
|
+
setLoading(true);
|
|
57
|
+
// Fetch wallet and api keys in parallel
|
|
58
|
+
const [walletResponse, apiKeysResponse] = await Promise.all([
|
|
59
|
+
fetch("/api/ai/auth/wallet"),
|
|
60
|
+
fetch("/api/ai/auth/api-keys"),
|
|
61
|
+
]);
|
|
62
|
+
if (!walletResponse.ok) {
|
|
63
|
+
throw new Error(t("tokens.error.loading") || "Erreur lors du chargement des données");
|
|
64
|
+
}
|
|
65
|
+
const walletData = await walletResponse.json();
|
|
66
|
+
// Process wallet
|
|
67
|
+
setWallet(walletData);
|
|
68
|
+
// Process API keys
|
|
69
|
+
if (apiKeysResponse.ok) {
|
|
70
|
+
const keysData = await apiKeysResponse.json();
|
|
71
|
+
setApiKeys(keysData.data || []);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
logger.error(t("tokens.error.loading") || "Erreur:", error);
|
|
76
|
+
addToast({
|
|
77
|
+
color: "danger",
|
|
78
|
+
title: t("tokens.error.loading") || "Erreur lors du chargement des données",
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
setLoading(false);
|
|
83
|
+
}
|
|
84
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
85
|
+
}, [user]);
|
|
86
|
+
// Fetch usage data (filtered by API key and month) - runs when selectedApiKey or selectedMonth changes
|
|
87
|
+
const fetchUsageData = useCallback(async () => {
|
|
88
|
+
if (!user)
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
setLoadingUsage(true);
|
|
92
|
+
// Build query params
|
|
93
|
+
const params = new URLSearchParams();
|
|
94
|
+
if (selectedApiKey !== "all") {
|
|
95
|
+
params.append("api_key_id", selectedApiKey);
|
|
96
|
+
}
|
|
97
|
+
if (selectedMonth !== "all") {
|
|
98
|
+
params.append("month", selectedMonth);
|
|
99
|
+
}
|
|
100
|
+
// Fetch model usage and charts in parallel
|
|
101
|
+
const [modelUsageResponse, usageResponse] = await Promise.all([
|
|
102
|
+
fetch(`/api/ai/auth/usage-by-model?${params.toString()}`),
|
|
103
|
+
fetch(`/api/ai/auth/usage?${params.toString()}`),
|
|
104
|
+
]);
|
|
105
|
+
if (!modelUsageResponse.ok || !usageResponse.ok) {
|
|
106
|
+
throw new Error(t("tokens.error.loading") || "Erreur lors du chargement des données");
|
|
107
|
+
}
|
|
108
|
+
const modelUsageData = await modelUsageResponse.json();
|
|
109
|
+
const usageDataRaw = await usageResponse.json();
|
|
110
|
+
// L'API retourne {data: {...}} donc on doit extraire la propriété data
|
|
111
|
+
const usageData = usageDataRaw.data;
|
|
112
|
+
// Process model usage (new detailed table)
|
|
113
|
+
setModelUsage(modelUsageData.data || []);
|
|
114
|
+
// Process usage by model (chart)
|
|
115
|
+
if (usageData.usage_by_model) {
|
|
116
|
+
setUsageByModel(usageData.usage_by_model);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
setUsageByModel([]);
|
|
120
|
+
}
|
|
121
|
+
// Process usage by day
|
|
122
|
+
if (usageData.usage_by_day) {
|
|
123
|
+
setUsageByDay(usageData.usage_by_day);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
setUsageByDay([]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
logger.error(t("tokens.error.loading") || "Erreur:", error);
|
|
131
|
+
addToast({
|
|
132
|
+
color: "danger",
|
|
133
|
+
title: t("tokens.error.loading") ||
|
|
134
|
+
"Erreur lors du chargement des statistiques",
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
setLoadingUsage(false);
|
|
139
|
+
}
|
|
140
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
141
|
+
}, [user, selectedApiKey, selectedMonth]);
|
|
142
|
+
// Initial data load (once on mount)
|
|
143
|
+
useEffect(() => {
|
|
144
|
+
fetchInitialData();
|
|
145
|
+
}, [fetchInitialData]);
|
|
146
|
+
// Usage data load (when API key changes)
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
fetchUsageData();
|
|
149
|
+
}, [fetchUsageData]);
|
|
150
|
+
if (!user) {
|
|
151
|
+
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsxs("div", { className: "text-center", children: [_jsx(AlertCircle, { className: "mx-auto mb-4", size: 48 }), _jsx("p", { className: "text-gray-500", children: t("tokens.error.login_required") || "Veuillez vous connecter" })] }) }));
|
|
152
|
+
}
|
|
153
|
+
if (loading) {
|
|
154
|
+
return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
|
|
155
|
+
}
|
|
156
|
+
return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl space-y-6", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center md:justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("usage.page_title") }), _jsx("p", { className: "text-gray-500", children: t("usage.page_subtitle") })] }), _jsxs("div", { className: "flex-1 flex flex-col justify-end sm:flex-row items-start sm:items-center gap-4", children: [_jsx(ApiKeyFilterSelect, { apiKeys: apiKeys, selectedKey: selectedApiKey, onSelectionChange: setSelectedApiKey }), _jsx(PurchaseButton, {})] })] }), successMessage && (_jsx(Alert, { color: "success", title: successMessage, onClose: () => setSuccessMessage(null) })), wallet && (_jsx(WalletStatusCard, { walletSellValueUsd: wallet.walletSellValueUsd, percentRemaining: wallet.percentRemaining, status: wallet.status, loading: loading })), _jsxs("div", { className: "grid grid-cols-1 gap-6", children: [_jsx("div", { className: "col-span-1", children: _jsx(ModelUsageTable, { data: modelUsage, loading: loadingUsage, totalRemaining: wallet?.walletSellValueUsd || 0, onMonthChange: setSelectedMonth, selectedMonth: selectedMonth }) }), _jsx("div", { className: "col-span-1", children: _jsx(UsageByModelBarChart, { data: usageByModel, loading: loadingUsage }) }), _jsx("div", { className: "col-span-1", children: _jsx(UsageByDayChart, { data: usageByDay, loading: loadingUsage }) })] })] }));
|
|
157
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"UsagePage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/UsagePage.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,SAAS,4CA6MxB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { Card, CardBody, CardHeader, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Spinner, addToast, Chip, Select, SelectItem, } from "@lastbrain/ui";
|
|
5
|
+
import { BarChart3, TrendingUp } from "lucide-react";
|
|
6
|
+
import { useAuth, useModuleTranslation } from "@lastbrain/core";
|
|
7
|
+
export function UsagePage() {
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
9
|
+
const { user } = useAuth();
|
|
10
|
+
const [loading, setLoading] = useState(true);
|
|
11
|
+
const [logs, setLogs] = useState([]);
|
|
12
|
+
const [stats, setStats] = useState(null);
|
|
13
|
+
const [filterEndpoint, setFilterEndpoint] = useState("");
|
|
14
|
+
const [filterProvider, setFilterProvider] = useState("");
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
fetchUsage();
|
|
17
|
+
}, []);
|
|
18
|
+
const fetchUsage = async () => {
|
|
19
|
+
try {
|
|
20
|
+
setLoading(true);
|
|
21
|
+
const response = await fetch("/api/ai/auth/usage");
|
|
22
|
+
if (!response.ok)
|
|
23
|
+
throw new Error("Error loading usage data");
|
|
24
|
+
const result = await response.json();
|
|
25
|
+
setLogs(result.data?.logs || []);
|
|
26
|
+
setStats(result.data?.stats || null);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
addToast({ color: "danger", title: error.message });
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
setLoading(false);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const formatDate = (dateString) => {
|
|
36
|
+
return new Date(dateString).toLocaleString();
|
|
37
|
+
};
|
|
38
|
+
const filteredLogs = logs.filter((log) => {
|
|
39
|
+
if (filterEndpoint && log.endpoint !== filterEndpoint)
|
|
40
|
+
return false;
|
|
41
|
+
if (filterProvider && log.provider !== filterProvider)
|
|
42
|
+
return false;
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
const uniqueEndpoints = Array.from(new Set(logs.map((l) => l.endpoint)));
|
|
46
|
+
const uniqueProviders = Array.from(new Set(logs.map((l) => l.provider).filter(Boolean)));
|
|
47
|
+
if (loading) {
|
|
48
|
+
return (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Spinner, {}) }));
|
|
49
|
+
}
|
|
50
|
+
return (_jsxs("div", { className: "container mx-auto py-8 space-y-6", children: [stats && (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4", children: [_jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(BarChart3, { size: 20, className: "text-blue-500" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Total Calls" }), _jsx("p", { className: "text-2xl font-bold", children: stats.total_calls })] })] }) }) }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TrendingUp, { size: 20, className: "text-green-500" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Total Tokens" }), _jsx("p", { className: "text-2xl font-bold", children: stats.total_tokens.toLocaleString() })] })] }) }) }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Success Rate" }), _jsxs("p", { className: "text-2xl font-bold", children: [stats.success_rate.toFixed(1), "%"] })] }) }) }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Avg Latency" }), _jsxs("p", { className: "text-2xl font-bold", children: [logs.length > 0
|
|
51
|
+
? Math.round(logs.reduce((sum, l) => sum + (l.latency_ms || 0), 0) /
|
|
52
|
+
logs.length)
|
|
53
|
+
: 0, "ms"] })] }) }) })] })), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx("h1", { className: "text-2xl font-bold", children: "API Usage Logs" }), _jsx("p", { className: "text-sm text-gray-600", children: "Recent API calls and usage analytics" })] }), _jsxs(CardBody, { children: [_jsxs("div", { className: "flex gap-4 mb-4", children: [_jsx(Select, { label: "Filter by Endpoint", selectedKeys: filterEndpoint ? [filterEndpoint] : [], onSelectionChange: (keys) => {
|
|
54
|
+
const selected = Array.from(keys)[0];
|
|
55
|
+
setFilterEndpoint(selected ? String(selected) : "");
|
|
56
|
+
}, className: "w-48", children: ["all", ...uniqueEndpoints].map((endpoint) => (_jsx(SelectItem, { children: endpoint }, endpoint))) }), _jsx(Select, { label: "Filter by Provider", selectedKeys: filterProvider ? [filterProvider] : [], onSelectionChange: (keys) => {
|
|
57
|
+
const selected = Array.from(keys)[0];
|
|
58
|
+
setFilterProvider(selected ? String(selected) : "");
|
|
59
|
+
}, className: "w-48", children: ["all", ...uniqueProviders].map((provider) => (_jsx(SelectItem, { children: provider }, provider))) })] }), filteredLogs.length === 0 ? (_jsx("div", { className: "text-center py-8", children: _jsx("p", { className: "text-gray-600", children: "No usage data yet" }) })) : (_jsxs(Table, { "aria-label": "Usage logs table", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "DATE" }), _jsx(TableColumn, { children: "ENDPOINT" }), _jsx(TableColumn, { children: "PROVIDER" }), _jsx(TableColumn, { children: "MODEL" }), _jsx(TableColumn, { children: "TOKENS" }), _jsx(TableColumn, { children: "LATENCY" }), _jsx(TableColumn, { children: "STATUS" })] }), _jsx(TableBody, { children: filteredLogs.map((log) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: formatDate(log.created_at) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", children: log.endpoint }) }), _jsx(TableCell, { children: log.provider || "-" }), _jsx(TableCell, { children: _jsx("code", { className: "text-xs", children: log.model || "-" }) }), _jsxs(TableCell, { children: [log.tokens_total
|
|
60
|
+
? log.tokens_total.toLocaleString()
|
|
61
|
+
: "-", log.tokens_in && log.tokens_out && (_jsxs("span", { className: "text-xs text-gray-500", children: [" ", "(", log.tokens_in, "+", log.tokens_out, ")"] }))] }), _jsx(TableCell, { children: log.latency_ms ? `${log.latency_ms}ms` : "-" }), _jsx(TableCell, { children: _jsx(Chip, { color: log.status_code === 200 ? "success" : "danger", variant: "flat", size: "sm", children: log.status_code }) })] }, log.id))) })] }))] })] })] }));
|
|
62
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface ApiKey {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
prefix: string;
|
|
5
|
+
}
|
|
6
|
+
interface ApiKeyFilterSelectProps {
|
|
7
|
+
apiKeys: ApiKey[];
|
|
8
|
+
selectedKey: string;
|
|
9
|
+
onSelectionChange: (key: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export declare function ApiKeyFilterSelect({ apiKeys, selectedKey, onSelectionChange, }: ApiKeyFilterSelectProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=ApiKeyFilterSelect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ApiKeyFilterSelect.d.ts","sourceRoot":"","sources":["../../../../src/web/auth/components/ApiKeyFilterSelect.tsx"],"names":[],"mappings":"AAMA,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAED,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,WAAW,EACX,iBAAiB,GAClB,EAAE,uBAAuB,2CAwBzB"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Select, SelectItem } from "@lastbrain/ui";
|
|
4
|
+
import { Key as KeyIcon } from "lucide-react";
|
|
5
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
6
|
+
export function ApiKeyFilterSelect({ apiKeys, selectedKey, onSelectionChange, }) {
|
|
7
|
+
const t = useModuleTranslation("ai");
|
|
8
|
+
return (_jsx(Select, { label: t("usage.filter.by_api_key"), size: "sm", selectedKeys: selectedKey ? [selectedKey] : ["all"], onSelectionChange: (keys) => {
|
|
9
|
+
const selected = Array.from(keys)[0];
|
|
10
|
+
onSelectionChange(selected ? String(selected) : "all");
|
|
11
|
+
}, className: "max-w-xs", startContent: _jsx(KeyIcon, { size: 16 }), children: [
|
|
12
|
+
{ id: "all", name: t("usage.filter.all_keys") },
|
|
13
|
+
{ id: "web", name: "Interface Web" },
|
|
14
|
+
...apiKeys,
|
|
15
|
+
].map((apiKey) => (_jsx(SelectItem, { children: apiKey.name }, apiKey.id))) }));
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface ModelUsageItem {
|
|
2
|
+
model: string;
|
|
3
|
+
provider?: string;
|
|
4
|
+
endpoint?: string;
|
|
5
|
+
total_cost_usd: number;
|
|
6
|
+
total_sell_usd: number;
|
|
7
|
+
call_count: number;
|
|
8
|
+
avg_cost_per_call: number;
|
|
9
|
+
}
|
|
10
|
+
interface ModelUsageTableProps {
|
|
11
|
+
data: ModelUsageItem[];
|
|
12
|
+
loading?: boolean;
|
|
13
|
+
totalRemaining?: number;
|
|
14
|
+
onMonthChange?: (month: string) => void;
|
|
15
|
+
selectedMonth?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function ModelUsageTable({ data, loading, totalRemaining, onMonthChange, selectedMonth, }: ModelUsageTableProps): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=ModelUsageTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ModelUsageTable.d.ts","sourceRoot":"","sources":["../../../../src/web/auth/components/ModelUsageTable.tsx"],"names":[],"mappings":"AAaA,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,UAAU,oBAAoB;IAC5B,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,OAAO,EACP,cAAkB,EAClB,aAAa,EACb,aAAqB,GACtB,EAAE,oBAAoB,2CA8KtB"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Card, CardBody, CardHeader, Spinner, Select, SelectItem, } from "@lastbrain/ui";
|
|
4
|
+
import { TrendingUp, Calendar } from "lucide-react";
|
|
5
|
+
import { useModuleTranslation } from "@lastbrain/core";
|
|
6
|
+
export function ModelUsageTable({ data, loading, totalRemaining = 0, onMonthChange, selectedMonth = "all", }) {
|
|
7
|
+
const t = useModuleTranslation("ai");
|
|
8
|
+
// Generate last 12 months for filter
|
|
9
|
+
const months = [];
|
|
10
|
+
const now = new Date();
|
|
11
|
+
for (let i = 0; i < 12; i++) {
|
|
12
|
+
const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
|
13
|
+
const value = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
|
|
14
|
+
const label = date.toLocaleDateString("fr-FR", {
|
|
15
|
+
year: "numeric",
|
|
16
|
+
month: "long",
|
|
17
|
+
});
|
|
18
|
+
months.push({ value, label });
|
|
19
|
+
}
|
|
20
|
+
const totalSpent = data.reduce((sum, item) => sum + item.total_sell_usd, 0);
|
|
21
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TrendingUp, { className: "w-5 h-5" }), _jsx("h3", { className: "text-lg font-semibold", children: t("usage.by_model_title") || "Utilisation par modèle" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "w-4 h-4 text-gray-500" }), _jsx(Select, { size: "sm", label: "", placeholder: t("usage.period.label") || "Période", selectedKeys: [selectedMonth], onSelectionChange: (keys) => {
|
|
22
|
+
const key = Array.from(keys)[0];
|
|
23
|
+
onMonthChange?.(key);
|
|
24
|
+
}, className: "min-w-[180px]", children: [
|
|
25
|
+
{
|
|
26
|
+
value: "all",
|
|
27
|
+
label: t("usage.period.all_months") || "Tous les mois",
|
|
28
|
+
},
|
|
29
|
+
...months,
|
|
30
|
+
].map((month) => (_jsx(SelectItem, { children: month.label }, month.value))) })] })] }), _jsx(CardBody, { children: loading ? (_jsx("div", { className: "flex justify-center items-center py-12", children: _jsx(Spinner, { size: "lg" }) })) : data.length === 0 ? (_jsx("div", { className: "text-center py-12 text-gray-500", children: t("usage.no_usage") || "Aucune utilisation enregistrée" })) : (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-950/20 dark:to-indigo-950/20 p-4 rounded-lg border border-blue-200 dark:border-blue-800", children: _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: t("usage.summary.total_spent") ||
|
|
31
|
+
"Total dépensé (période)" }), _jsxs("p", { className: "text-2xl font-bold text-blue-600 dark:text-blue-400", children: ["$", totalSpent.toFixed(2)] })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: t("usage.summary.remaining_credit") || "Crédit restant" }), _jsxs("p", { className: "text-2xl font-bold text-green-600 dark:text-green-400", children: ["$", totalRemaining.toFixed(2)] })] })] }) }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-gray-200 dark:border-gray-700", children: [_jsx("th", { className: "text-left py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.model") || "Modèle" }), _jsx("th", { className: "text-left py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.provider") || "Provider" }), _jsx("th", { className: "text-right py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.total_cost") || "Coût total" }), _jsx("th", { className: "text-right py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.calls") || "Appels" }), _jsx("th", { className: "text-right py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.cost_per_call") || "Coût/appel" })] }) }), _jsx("tbody", { children: data.map((item, index) => {
|
|
32
|
+
const percentOfTotal = totalSpent > 0
|
|
33
|
+
? (item.total_sell_usd / totalSpent) * 100
|
|
34
|
+
: 0;
|
|
35
|
+
return (_jsxs("tr", { className: "border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors", children: [_jsx("td", { className: "py-3 px-2", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium text-sm", children: item.model }), item.endpoint && (_jsx("span", { className: "text-xs text-gray-500", children: item.endpoint }))] }) }), _jsx("td", { className: "py-3 px-2", children: _jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400 capitalize", children: item.provider || "—" }) }), _jsx("td", { className: "py-3 px-2 text-right", children: _jsx("div", { className: "flex flex-col items-end", children: _jsxs("span", { className: "font-semibold text-sm", children: ["$", item.total_sell_usd.toFixed(2)] }) }) }), _jsx("td", { className: "py-3 px-2 text-right", children: _jsx("span", { className: "text-sm font-medium", children: item.call_count.toLocaleString() }) }), _jsx("td", { className: "py-3 px-2 text-right", children: _jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: ["$", item.avg_cost_per_call.toFixed(4)] }) })] }, `${item.model}-${index}`));
|
|
36
|
+
}) })] }) })] })) })] }));
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PurchaseButton.d.ts","sourceRoot":"","sources":["../../../../src/web/auth/components/PurchaseButton.tsx"],"names":[],"mappings":"AAMA,UAAU,mBAAmB;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CAmBtE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { Button } from "@lastbrain/ui";
|
|
4
|
+
import { ShoppingCart } from "lucide-react";
|
|
5
|
+
import { useLocalizedRouter, useModuleTranslation } from "@lastbrain/core";
|
|
6
|
+
export function PurchaseButton({ text, className }) {
|
|
7
|
+
const router = useLocalizedRouter();
|
|
8
|
+
const t = useModuleTranslation("ai");
|
|
9
|
+
const handlePurchase = () => {
|
|
10
|
+
router.push("/purchase-tokens");
|
|
11
|
+
};
|
|
12
|
+
return (_jsx(Button, { color: "primary", size: "lg", startContent: _jsx(ShoppingCart, { size: 20 }), onPress: handlePurchase, className: className, children: text || t("usage.button.buy_tokens") }));
|
|
13
|
+
}
|