@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,42 @@
|
|
|
1
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
2
|
+
import { logger } from "@lastbrain/core";
|
|
3
|
+
/**
|
|
4
|
+
* Get global AI configuration (default models for text and image)
|
|
5
|
+
* This is the single source of truth for all AI generation calls
|
|
6
|
+
*/
|
|
7
|
+
export async function getGlobalAISettings() {
|
|
8
|
+
const supabase = getSupabaseServiceClient();
|
|
9
|
+
const { data, error } = await supabase
|
|
10
|
+
.from("ai_global_settings")
|
|
11
|
+
.select("*")
|
|
12
|
+
.eq("id", 1)
|
|
13
|
+
.single();
|
|
14
|
+
if (error) {
|
|
15
|
+
logger.error("[getGlobalAISettings] Error fetching global settings:", error);
|
|
16
|
+
// Fallback to defaults if DB query fails
|
|
17
|
+
return {
|
|
18
|
+
default_text_provider: "openai",
|
|
19
|
+
default_text_model: "gpt-4.1-mini",
|
|
20
|
+
default_image_provider: "google",
|
|
21
|
+
default_image_model: "gemini-2.5-flash-image",
|
|
22
|
+
max_tokens_per_call: 16384,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return data;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get default text model from global settings
|
|
29
|
+
* Returns "provider/model" format
|
|
30
|
+
*/
|
|
31
|
+
export async function getDefaultTextModel() {
|
|
32
|
+
const settings = await getGlobalAISettings();
|
|
33
|
+
return `${settings.default_text_provider}/${settings.default_text_model}`;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get default image model from global settings
|
|
37
|
+
* Returns "provider/model" format
|
|
38
|
+
*/
|
|
39
|
+
export async function getDefaultImageModel() {
|
|
40
|
+
const settings = await getGlobalAISettings();
|
|
41
|
+
return `${settings.default_image_provider}/${settings.default_image_model}`;
|
|
42
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for model filtering and validation
|
|
3
|
+
*/
|
|
4
|
+
import type { GatewayModel } from "../types/gateway";
|
|
5
|
+
export interface ModelFilterResult {
|
|
6
|
+
allowed: boolean;
|
|
7
|
+
model?: GatewayModel;
|
|
8
|
+
error?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Check if a model is enabled for the given user
|
|
12
|
+
*/
|
|
13
|
+
export declare function isModelEnabled(ownerId: string, modelId: string): Promise<ModelFilterResult>;
|
|
14
|
+
/**
|
|
15
|
+
* Get list of enabled models for a user
|
|
16
|
+
*/
|
|
17
|
+
export declare function getEnabledModels(ownerId: string): Promise<GatewayModel[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Validate model and return error response if not allowed
|
|
20
|
+
*/
|
|
21
|
+
export declare function createModelNotAllowedError(modelId: string, error: string): {
|
|
22
|
+
error: any;
|
|
23
|
+
status: number;
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=model-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-filter.d.ts","sourceRoot":"","sources":["../../src/server/model-filter.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AA2BrD,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,CAAC,CAmL5B;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,EAAE,CAAC,CA2CzB;AAyBD;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ;IAAE,KAAK,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAShC"}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions for model filtering and validation
|
|
3
|
+
*/
|
|
4
|
+
import { logger } from "@lastbrain/core";
|
|
5
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
6
|
+
import { fetchGatewayModels } from "./gateway-service";
|
|
7
|
+
import { getDefaultTextModel, getDefaultImageModel } from "./global-settings";
|
|
8
|
+
// Default models auto-enabled on first use
|
|
9
|
+
const DEFAULT_MODELS = [
|
|
10
|
+
"openai/gpt-4o-mini",
|
|
11
|
+
"google/imagen-4.0-fast-generate-001",
|
|
12
|
+
"google/gemini-2.5-flash-image",
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Get default models from ai_global_settings
|
|
16
|
+
* These models should always be allowed for all users
|
|
17
|
+
*/
|
|
18
|
+
async function getGlobalDefaultModels() {
|
|
19
|
+
try {
|
|
20
|
+
const [textModel, imageModel] = await Promise.all([
|
|
21
|
+
getDefaultTextModel(),
|
|
22
|
+
getDefaultImageModel(),
|
|
23
|
+
]);
|
|
24
|
+
return [textModel, imageModel];
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
logger.error("[ModelFilter] Failed to fetch global default models", err);
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Check if a model is enabled for the given user
|
|
33
|
+
*/
|
|
34
|
+
export async function isModelEnabled(ownerId, modelId) {
|
|
35
|
+
try {
|
|
36
|
+
// 0. Get global default models - these are ALWAYS allowed
|
|
37
|
+
const globalDefaultModels = await getGlobalDefaultModels();
|
|
38
|
+
const isGlobalDefault = globalDefaultModels.includes(modelId);
|
|
39
|
+
if (isGlobalDefault) {
|
|
40
|
+
logger.debug(`[ModelFilter] Model ${modelId} is a global default, auto-allowing`);
|
|
41
|
+
}
|
|
42
|
+
// 1. Get user settings from user_ai_settings
|
|
43
|
+
const supabase = await getSupabaseServiceClient();
|
|
44
|
+
const { data: settings, error } = await supabase
|
|
45
|
+
.from("user_ai_settings")
|
|
46
|
+
.select("allowed_models")
|
|
47
|
+
.eq("owner_id", ownerId)
|
|
48
|
+
.single();
|
|
49
|
+
if (error && error.code !== "PGRST116") {
|
|
50
|
+
// PGRST116 = no rows returned (user has no settings yet)
|
|
51
|
+
logger.error("[ModelFilter] Error fetching settings:", error);
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
// 2. If no settings exist, create default settings with basic models + global defaults
|
|
55
|
+
if (!settings) {
|
|
56
|
+
logger.info(`[ModelFilter] No settings for user ${ownerId}, creating default with gpt-4o-mini and imagen-4.0-fast`);
|
|
57
|
+
// Merge static defaults with global defaults
|
|
58
|
+
const allDefaultModels = Array.from(new Set([...DEFAULT_MODELS, ...globalDefaultModels]));
|
|
59
|
+
const { error: insertError } = await supabase
|
|
60
|
+
.from("user_ai_settings")
|
|
61
|
+
.insert({
|
|
62
|
+
owner_id: ownerId,
|
|
63
|
+
allowed_models: allDefaultModels,
|
|
64
|
+
});
|
|
65
|
+
if (insertError) {
|
|
66
|
+
logger.error("[ModelFilter] Failed to create default settings:", insertError);
|
|
67
|
+
// Continue anyway, model might still be in default list
|
|
68
|
+
}
|
|
69
|
+
// After creating settings, check if requested model is in default list OR global defaults
|
|
70
|
+
if (!DEFAULT_MODELS.includes(modelId) && !isGlobalDefault) {
|
|
71
|
+
return {
|
|
72
|
+
allowed: false,
|
|
73
|
+
error: `Model "${modelId}" is not enabled. Please enable it in your settings at /auth/ai/settings`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Model is in default list, fetch metadata
|
|
77
|
+
const gatewayModels = await fetchGatewayModels();
|
|
78
|
+
const model = findModelById(gatewayModels.providers, modelId);
|
|
79
|
+
if (!model) {
|
|
80
|
+
logger.warn(`[ModelFilter] Model ${modelId} not found in gateway`);
|
|
81
|
+
return {
|
|
82
|
+
allowed: false,
|
|
83
|
+
error: `Model "${modelId}" not found or unavailable`,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
allowed: true,
|
|
88
|
+
model,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Parse allowed_models - can be array of strings or JSONB
|
|
92
|
+
let enabledModels = [];
|
|
93
|
+
if (settings?.allowed_models) {
|
|
94
|
+
if (Array.isArray(settings.allowed_models)) {
|
|
95
|
+
enabledModels = settings.allowed_models;
|
|
96
|
+
}
|
|
97
|
+
else if (typeof settings.allowed_models === "object") {
|
|
98
|
+
// Handle JSONB format: [{"provider":"openai","models":["gpt-4o-mini"]}]
|
|
99
|
+
enabledModels = settings.allowed_models.flatMap((item) => {
|
|
100
|
+
if (item.models && Array.isArray(item.models)) {
|
|
101
|
+
const provider = item.provider;
|
|
102
|
+
return item.models.map((model) => `${provider}/${model}`);
|
|
103
|
+
}
|
|
104
|
+
return [];
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// 3. If settings exist but empty, add default models + global defaults
|
|
109
|
+
if (enabledModels.length === 0) {
|
|
110
|
+
logger.info(`[ModelFilter] Empty allowed_models for user ${ownerId}, adding defaults`);
|
|
111
|
+
const allDefaultModels = Array.from(new Set([...DEFAULT_MODELS, ...globalDefaultModels]));
|
|
112
|
+
const { error: updateError } = await supabase
|
|
113
|
+
.from("user_ai_settings")
|
|
114
|
+
.update({
|
|
115
|
+
allowed_models: allDefaultModels,
|
|
116
|
+
})
|
|
117
|
+
.eq("owner_id", ownerId);
|
|
118
|
+
if (updateError) {
|
|
119
|
+
logger.error("[ModelFilter] Failed to update settings with defaults:", updateError);
|
|
120
|
+
}
|
|
121
|
+
enabledModels = allDefaultModels;
|
|
122
|
+
}
|
|
123
|
+
// 4. Check specific model filter - allow if in user settings OR global defaults
|
|
124
|
+
if (!enabledModels.includes(modelId) && !isGlobalDefault) {
|
|
125
|
+
logger.warn(`[ModelFilter] Model ${modelId} not enabled for user ${ownerId}`);
|
|
126
|
+
return {
|
|
127
|
+
allowed: false,
|
|
128
|
+
error: `Model "${modelId}" is not enabled for your account. Please enable it in your settings.`,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// If model is global default but not in user settings, add it
|
|
132
|
+
if (isGlobalDefault && !enabledModels.includes(modelId)) {
|
|
133
|
+
logger.info(`[ModelFilter] Adding global default model ${modelId} to user ${ownerId} settings`);
|
|
134
|
+
const updatedModels = Array.from(new Set([...enabledModels, modelId]));
|
|
135
|
+
const { error: updateError } = await supabase
|
|
136
|
+
.from("user_ai_settings")
|
|
137
|
+
.update({ allowed_models: updatedModels })
|
|
138
|
+
.eq("owner_id", ownerId);
|
|
139
|
+
if (updateError) {
|
|
140
|
+
logger.error("[ModelFilter] Failed to add global default to user settings:", updateError);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// 5. Model is allowed, fetch metadata
|
|
144
|
+
const gatewayModels = await fetchGatewayModels();
|
|
145
|
+
const model = findModelById(gatewayModels.providers, modelId);
|
|
146
|
+
if (!model) {
|
|
147
|
+
logger.warn(`[ModelFilter] Model ${modelId} not found in gateway`);
|
|
148
|
+
return {
|
|
149
|
+
allowed: false,
|
|
150
|
+
error: `Model "${modelId}" not found or unavailable`,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
logger.debug(`[ModelFilter] Model ${modelId} allowed for user ${ownerId}`);
|
|
154
|
+
return {
|
|
155
|
+
allowed: true,
|
|
156
|
+
model,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
logger.error("[ModelFilter] Error checking model:", error);
|
|
161
|
+
return {
|
|
162
|
+
allowed: false,
|
|
163
|
+
error: `Failed to validate model: ${error.message}`,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Get list of enabled models for a user
|
|
169
|
+
*/
|
|
170
|
+
export async function getEnabledModels(ownerId) {
|
|
171
|
+
try {
|
|
172
|
+
const supabase = await getSupabaseServiceClient();
|
|
173
|
+
const { data: settings } = await supabase
|
|
174
|
+
.from("user_ai_settings")
|
|
175
|
+
.select("allowed_models")
|
|
176
|
+
.eq("owner_id", ownerId)
|
|
177
|
+
.single();
|
|
178
|
+
// Parse allowed_models
|
|
179
|
+
let enabledModelIds = [];
|
|
180
|
+
if (settings?.allowed_models) {
|
|
181
|
+
if (Array.isArray(settings.allowed_models)) {
|
|
182
|
+
enabledModelIds = settings.allowed_models;
|
|
183
|
+
}
|
|
184
|
+
else if (typeof settings.allowed_models === "object") {
|
|
185
|
+
// Handle JSONB format
|
|
186
|
+
enabledModelIds = settings.allowed_models.flatMap((item) => {
|
|
187
|
+
if (item.models && Array.isArray(item.models)) {
|
|
188
|
+
const provider = item.provider;
|
|
189
|
+
return item.models.map((model) => `${provider}/${model}`);
|
|
190
|
+
}
|
|
191
|
+
return [];
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Fetch all models from gateway
|
|
196
|
+
const gatewayModels = await fetchGatewayModels();
|
|
197
|
+
// If no enabled models, return empty array (explicit enable required)
|
|
198
|
+
if (enabledModelIds.length === 0) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
// Filter by enabled models
|
|
202
|
+
const allModels = getAllModels(gatewayModels.providers);
|
|
203
|
+
return allModels.filter((model) => enabledModelIds.includes(model.id));
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
logger.error("[ModelFilter] Error getting enabled models:", error);
|
|
207
|
+
return [];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Find a model by ID across all providers
|
|
212
|
+
*/
|
|
213
|
+
function findModelById(providers, modelId) {
|
|
214
|
+
for (const provider of providers) {
|
|
215
|
+
const model = provider.models.find((m) => m.id === modelId);
|
|
216
|
+
if (model) {
|
|
217
|
+
return model;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get all models from all providers as flat array
|
|
224
|
+
*/
|
|
225
|
+
function getAllModels(providers) {
|
|
226
|
+
return providers.flatMap((p) => p.models);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Validate model and return error response if not allowed
|
|
230
|
+
*/
|
|
231
|
+
export function createModelNotAllowedError(modelId, error) {
|
|
232
|
+
return {
|
|
233
|
+
error: {
|
|
234
|
+
code: "MODEL_NOT_ALLOWED",
|
|
235
|
+
message: error,
|
|
236
|
+
model_id: modelId,
|
|
237
|
+
},
|
|
238
|
+
status: 403,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface ProcessOCRParams {
|
|
2
|
+
userId: string;
|
|
3
|
+
files: Array<{
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
extension: string;
|
|
7
|
+
bucket?: string;
|
|
8
|
+
}>;
|
|
9
|
+
mode: "verify" | "extract" | "verify_and_extract";
|
|
10
|
+
prompt: string;
|
|
11
|
+
extractionSchema?: Record<string, unknown>;
|
|
12
|
+
allowOverdraft?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface OCRResult {
|
|
15
|
+
file_name: string;
|
|
16
|
+
file_path: string;
|
|
17
|
+
verified: boolean;
|
|
18
|
+
verification_message?: string;
|
|
19
|
+
extracted_data?: Record<string, unknown>;
|
|
20
|
+
confidence_score?: number;
|
|
21
|
+
processing_error?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ProcessOCRResponse {
|
|
24
|
+
success: boolean;
|
|
25
|
+
results: OCRResult[];
|
|
26
|
+
tokensUsed: number;
|
|
27
|
+
tokensRemaining?: number;
|
|
28
|
+
debitedFromQuota?: number;
|
|
29
|
+
debitedFromPurchased?: number;
|
|
30
|
+
remainingQuota?: number;
|
|
31
|
+
remainingPurchased?: number;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Process OCR using OpenAI Vision with proper token debit
|
|
36
|
+
* This function can be called directly by other modules
|
|
37
|
+
*/
|
|
38
|
+
export declare function processOCRWithTokens(params: ProcessOCRParams): Promise<ProcessOCRResponse>;
|
|
39
|
+
//# sourceMappingURL=ocr.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ocr.d.ts","sourceRoot":"","sources":["../../src/server/ocr.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;IACH,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,oBAAoB,CAAC;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,SAAS,EAAE,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,kBAAkB,CAAC,CAiU7B"}
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
2
|
+
import { debitTokensWithPriority } from "./quota";
|
|
3
|
+
import { generateText } from "ai";
|
|
4
|
+
import { logger } from "@lastbrain/core";
|
|
5
|
+
/**
|
|
6
|
+
* Process OCR using OpenAI Vision with proper token debit
|
|
7
|
+
* This function can be called directly by other modules
|
|
8
|
+
*/
|
|
9
|
+
export async function processOCRWithTokens(params) {
|
|
10
|
+
const { userId, files, mode, prompt, extractionSchema, allowOverdraft = false, } = params;
|
|
11
|
+
try {
|
|
12
|
+
const supabase = getSupabaseServiceClient();
|
|
13
|
+
if (!files || files.length === 0) {
|
|
14
|
+
return {
|
|
15
|
+
success: false,
|
|
16
|
+
results: [],
|
|
17
|
+
tokensUsed: 0,
|
|
18
|
+
error: "Aucun fichier à traiter",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (!prompt) {
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
results: [],
|
|
25
|
+
tokensUsed: 0,
|
|
26
|
+
error: "Le prompt est requis",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
// Estimate tokens needed (conservative: ~500 tokens per image)
|
|
30
|
+
const estimatedTokensPerFile = 500;
|
|
31
|
+
const estimatedTokensNeeded = files.length * estimatedTokensPerFile;
|
|
32
|
+
// Pre-check balance/quota
|
|
33
|
+
const { data: balanceData } = await supabase
|
|
34
|
+
.from("user_token_balance_v")
|
|
35
|
+
.select("balance")
|
|
36
|
+
.eq("owner_id", userId)
|
|
37
|
+
.single();
|
|
38
|
+
const purchasedBalance = balanceData?.balance || 0;
|
|
39
|
+
const { data: quotaData } = await supabase
|
|
40
|
+
.from("user_token_quota_monthly")
|
|
41
|
+
.select("effective_included_tokens, used_included_tokens, period_start, period_end")
|
|
42
|
+
.eq("owner_id", userId)
|
|
43
|
+
.single();
|
|
44
|
+
let remainingQuota = 0;
|
|
45
|
+
if (quotaData) {
|
|
46
|
+
const now = new Date();
|
|
47
|
+
const periodStart = quotaData.period_start
|
|
48
|
+
? new Date(quotaData.period_start)
|
|
49
|
+
: null;
|
|
50
|
+
const periodEnd = quotaData.period_end
|
|
51
|
+
? new Date(quotaData.period_end)
|
|
52
|
+
: null;
|
|
53
|
+
const isActive = periodStart && periodEnd && now >= periodStart && now <= periodEnd;
|
|
54
|
+
if (isActive) {
|
|
55
|
+
remainingQuota = Math.max(0, quotaData.effective_included_tokens -
|
|
56
|
+
(quotaData.used_included_tokens || 0));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (purchasedBalance < 0 && !allowOverdraft) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
results: [],
|
|
63
|
+
tokensUsed: 0,
|
|
64
|
+
error: "Solde insuffisant (compte en négatif). Veuillez recharger.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (purchasedBalance + remainingQuota < estimatedTokensNeeded &&
|
|
68
|
+
!allowOverdraft) {
|
|
69
|
+
return {
|
|
70
|
+
success: false,
|
|
71
|
+
results: [],
|
|
72
|
+
tokensUsed: 0,
|
|
73
|
+
error: "Solde insuffisant pour traiter les fichiers OCR.",
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Process each file
|
|
77
|
+
const results = [];
|
|
78
|
+
let totalTokensUsed = 0;
|
|
79
|
+
const model = "openai/gpt-4o"; // Use string model ID for Vercel AI Gateway
|
|
80
|
+
for (const file of files) {
|
|
81
|
+
try {
|
|
82
|
+
// Skip non-image files
|
|
83
|
+
const imageExtensions = ["jpg", "jpeg", "png", "webp", "gif"];
|
|
84
|
+
if (!imageExtensions.includes(file.extension.toLowerCase())) {
|
|
85
|
+
results.push({
|
|
86
|
+
file_name: file.name,
|
|
87
|
+
file_path: file.path,
|
|
88
|
+
verified: false,
|
|
89
|
+
processing_error: `Type de fichier non supporté: ${file.extension}. Seules les images sont supportées pour l'OCR.`,
|
|
90
|
+
});
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
// Download file from Supabase Storage
|
|
94
|
+
const bucket = file.bucket || "app";
|
|
95
|
+
const { data: fileData, error: downloadError } = await supabase.storage
|
|
96
|
+
.from(bucket)
|
|
97
|
+
.download(file.path);
|
|
98
|
+
if (downloadError || !fileData) {
|
|
99
|
+
logger.error(`Error downloading file ${file.path}:`, downloadError);
|
|
100
|
+
results.push({
|
|
101
|
+
file_name: file.name,
|
|
102
|
+
file_path: file.path,
|
|
103
|
+
verified: false,
|
|
104
|
+
processing_error: "Impossible de télécharger le fichier",
|
|
105
|
+
});
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Convert file to base64
|
|
109
|
+
const buffer = await fileData.arrayBuffer();
|
|
110
|
+
const base64 = Buffer.from(buffer).toString("base64");
|
|
111
|
+
const imageUrl = `data:image/${file.extension};base64,${base64}`;
|
|
112
|
+
// Build prompt based on mode
|
|
113
|
+
let systemPrompt = "";
|
|
114
|
+
let userPrompt = prompt;
|
|
115
|
+
if (mode === "verify") {
|
|
116
|
+
systemPrompt =
|
|
117
|
+
"Tu es un expert en vérification de documents. Analyse l'image et vérifie si elle correspond aux critères demandés.";
|
|
118
|
+
userPrompt +=
|
|
119
|
+
"\n\nRéponds au format JSON avec les champs: verified (boolean), message (string), confidence (0-1)";
|
|
120
|
+
}
|
|
121
|
+
else if (mode === "extract") {
|
|
122
|
+
systemPrompt =
|
|
123
|
+
"Tu es un expert en extraction de données depuis des documents. Analyse l'image et extrait les informations demandées.";
|
|
124
|
+
userPrompt += `\n\nRéponds au format JSON avec les champs extraits selon ce schéma: ${JSON.stringify(extractionSchema)}`;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// verify_and_extract
|
|
128
|
+
systemPrompt =
|
|
129
|
+
"Tu es un expert en vérification et extraction de données depuis des documents.";
|
|
130
|
+
userPrompt += `\n\nRéponds au format JSON avec:
|
|
131
|
+
- verified (boolean): si le document correspond aux critères
|
|
132
|
+
- message (string): message de vérification
|
|
133
|
+
- confidence (0-1): niveau de confiance
|
|
134
|
+
- data (object): données extraites selon ce schéma: ${JSON.stringify(extractionSchema)}`;
|
|
135
|
+
}
|
|
136
|
+
// Call Vercel AI SDK Vision API with correct message format
|
|
137
|
+
const completion = await generateText({
|
|
138
|
+
model: model,
|
|
139
|
+
messages: [
|
|
140
|
+
{
|
|
141
|
+
role: "system",
|
|
142
|
+
content: systemPrompt,
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
role: "user",
|
|
146
|
+
content: [
|
|
147
|
+
{ type: "text", text: userPrompt },
|
|
148
|
+
{
|
|
149
|
+
type: "image",
|
|
150
|
+
image: imageUrl,
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
temperature: 0.1,
|
|
156
|
+
});
|
|
157
|
+
const content = completion.text;
|
|
158
|
+
const tokensUsed = completion.usage?.totalTokens || 0;
|
|
159
|
+
totalTokensUsed += tokensUsed;
|
|
160
|
+
if (!content) {
|
|
161
|
+
results.push({
|
|
162
|
+
file_name: file.name,
|
|
163
|
+
file_path: file.path,
|
|
164
|
+
verified: false,
|
|
165
|
+
processing_error: "Aucune réponse de l'API",
|
|
166
|
+
});
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
// Parse JSON response
|
|
170
|
+
try {
|
|
171
|
+
const parsed = JSON.parse(content);
|
|
172
|
+
if (mode === "verify") {
|
|
173
|
+
results.push({
|
|
174
|
+
file_name: file.name,
|
|
175
|
+
file_path: file.path,
|
|
176
|
+
verified: parsed.verified || false,
|
|
177
|
+
verification_message: parsed.message,
|
|
178
|
+
confidence_score: parsed.confidence,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else if (mode === "extract") {
|
|
182
|
+
results.push({
|
|
183
|
+
file_name: file.name,
|
|
184
|
+
file_path: file.path,
|
|
185
|
+
verified: true,
|
|
186
|
+
extracted_data: parsed,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// verify_and_extract
|
|
191
|
+
results.push({
|
|
192
|
+
file_name: file.name,
|
|
193
|
+
file_path: file.path,
|
|
194
|
+
verified: parsed.verified || false,
|
|
195
|
+
verification_message: parsed.message,
|
|
196
|
+
confidence_score: parsed.confidence,
|
|
197
|
+
extracted_data: parsed.data || {},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
catch (parseError) {
|
|
202
|
+
logger.error(`Error parsing OCR response for ${file.name}:`, parseError);
|
|
203
|
+
results.push({
|
|
204
|
+
file_name: file.name,
|
|
205
|
+
file_path: file.path,
|
|
206
|
+
verified: false,
|
|
207
|
+
processing_error: "Erreur lors du parsing de la réponse",
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
logger.error(`Error processing OCR for ${file.name}:`, error);
|
|
213
|
+
results.push({
|
|
214
|
+
file_name: file.name,
|
|
215
|
+
file_path: file.path,
|
|
216
|
+
verified: false,
|
|
217
|
+
processing_error: error instanceof Error ? error.message : "Erreur inconnue",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Debit tokens using the same system as generate-text
|
|
222
|
+
if (totalTokensUsed > 0) {
|
|
223
|
+
// Calculate cost for gpt-4o vision
|
|
224
|
+
// gpt-4o: $2.50/1M input, $10/1M output
|
|
225
|
+
const inputTokens = Math.floor(totalTokensUsed * 0.4); // Approximate (images are input)
|
|
226
|
+
const outputTokens = Math.floor(totalTokensUsed * 0.6);
|
|
227
|
+
const cost = (inputTokens / 1_000_000) * 2.5 + (outputTokens / 1_000_000) * 10;
|
|
228
|
+
const tokenResult = await debitTokensWithPriority(userId, totalTokensUsed, "gpt-4o", `OCR processing: ${files.length} files`, {
|
|
229
|
+
inputTokens,
|
|
230
|
+
outputTokens,
|
|
231
|
+
cost,
|
|
232
|
+
generatedText: `OCR results for ${files.length} files`,
|
|
233
|
+
allowOverdraft,
|
|
234
|
+
});
|
|
235
|
+
if (!tokenResult.success) {
|
|
236
|
+
return {
|
|
237
|
+
success: false,
|
|
238
|
+
results,
|
|
239
|
+
tokensUsed: totalTokensUsed,
|
|
240
|
+
error: tokenResult.error ||
|
|
241
|
+
"Erreur lors de la déduction des tokens (quota/achat)",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const tokensRemainingTotal = tokenResult.remainingQuota + tokenResult.remainingPurchased;
|
|
245
|
+
return {
|
|
246
|
+
success: true,
|
|
247
|
+
results,
|
|
248
|
+
tokensUsed: totalTokensUsed,
|
|
249
|
+
tokensRemaining: tokensRemainingTotal,
|
|
250
|
+
debitedFromQuota: tokenResult.debitedFromQuota,
|
|
251
|
+
debitedFromPurchased: tokenResult.debitedFromPurchased,
|
|
252
|
+
remainingQuota: tokenResult.remainingQuota,
|
|
253
|
+
remainingPurchased: tokenResult.remainingPurchased,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
success: true,
|
|
258
|
+
results,
|
|
259
|
+
tokensUsed: 0,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
logger.error("Erreur de traitement OCR:", error);
|
|
264
|
+
const err = error;
|
|
265
|
+
if (err.code === "insufficient_quota") {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
results: [],
|
|
269
|
+
tokensUsed: 0,
|
|
270
|
+
error: "Quota IA dépassé. Contactez l'administrateur.",
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
success: false,
|
|
275
|
+
results: [],
|
|
276
|
+
tokensUsed: 0,
|
|
277
|
+
error: err.message || "Erreur lors du traitement OCR",
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
/**
|
|
3
|
+
* Obtenir le client OpenAI (singleton)
|
|
4
|
+
*/
|
|
5
|
+
export declare function getOpenAIClient(): OpenAI;
|
|
6
|
+
export interface ChatCompletionParams {
|
|
7
|
+
model?: string;
|
|
8
|
+
messages: OpenAI.Chat.ChatCompletionMessageParam[];
|
|
9
|
+
temperature?: number;
|
|
10
|
+
max_tokens?: number;
|
|
11
|
+
response_format?: {
|
|
12
|
+
type: "json_object" | "text";
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Appeler OpenAI Chat Completion avec les paramètres fournis
|
|
17
|
+
*/
|
|
18
|
+
export declare function createChatCompletion(params: ChatCompletionParams): Promise<OpenAI.Chat.ChatCompletion>;
|
|
19
|
+
//# sourceMappingURL=openai-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openai-client.d.ts","sourceRoot":"","sources":["../../src/server/openai-client.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAI5B;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAOxC;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,0BAA0B,EAAE,CAAC;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE;QAAE,IAAI,EAAE,aAAa,GAAG,MAAM,CAAA;KAAE,CAAC;CACpD;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,cAAc,CAAC,CAUrC"}
|