@lastbrain/module-ai 2.0.18 → 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 -3
- package/dist/api/auth/generate-text.d.ts.map +1 -1
- package/dist/api/auth/generate-text.js +133 -59
- 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 +17 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -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/ButtonGenerative.d.ts +24 -0
- package/dist/web/components/ButtonGenerative.d.ts.map +1 -0
- package/dist/web/components/ButtonGenerative.js +91 -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 +240 -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 +12 -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,22 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useCallback } from "react";
|
|
4
|
-
import { useModuleTranslation } from "@lastbrain/core";
|
|
5
|
-
import { Button, Chip, Progress, Card, CardBody, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Textarea, Select, SelectItem, addToast, } from "@lastbrain/ui";
|
|
3
|
+
import { useState, useCallback, useEffect } from "react";
|
|
4
|
+
import { logger, useModuleTranslation } from "@lastbrain/core";
|
|
5
|
+
import { Button, Chip, Progress, Card, CardBody, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Textarea, Select, SelectItem, addToast, AppLink, } from "@lastbrain/ui";
|
|
6
6
|
import { Image as ImageIcon, Loader2, AlertCircle, Download, Wand2, } from "lucide-react";
|
|
7
7
|
import Image from "next/image";
|
|
8
|
-
// Coût en tokens par combinaison modèle/taille(/qualité)
|
|
9
|
-
const TOKENS_PER_IMAGE_KEY = {
|
|
10
|
-
"dall-e-2-256x256": 3000,
|
|
11
|
-
"dall-e-2-512x512": 4000,
|
|
12
|
-
"dall-e-2-1024x1024": 5000,
|
|
13
|
-
"dall-e-3-1024x1024-standard": 6000,
|
|
14
|
-
"dall-e-3-1024x1024-hd": 8000,
|
|
15
|
-
"dall-e-3-1024x1792-standard": 8000,
|
|
16
|
-
"dall-e-3-1024x1792-hd": 10000,
|
|
17
|
-
"dall-e-3-1792x1024-standard": 8000,
|
|
18
|
-
"dall-e-3-1792x1024-hd": 10000,
|
|
19
|
-
};
|
|
20
8
|
const imageStyles = [
|
|
21
9
|
{
|
|
22
10
|
key: "realistic",
|
|
@@ -46,31 +34,151 @@ const imageStyles = [
|
|
|
46
34
|
description: "Peinture à l'aquarelle",
|
|
47
35
|
},
|
|
48
36
|
{ key: "pop-art", label: "Pop Art", description: "Style pop art vibrant" },
|
|
37
|
+
// 🔥 Styles ajoutés
|
|
38
|
+
{
|
|
39
|
+
key: "oil-painting",
|
|
40
|
+
label: "Peinture à l'huile",
|
|
41
|
+
description: "Textures riches, style classique",
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
key: "pixel-art",
|
|
45
|
+
label: "Pixel Art",
|
|
46
|
+
description: "Graphismes rétro pixelisés",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
key: "low-poly",
|
|
50
|
+
label: "Low Poly",
|
|
51
|
+
description: "Modélisation simple en polygones",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: "cyberpunk",
|
|
55
|
+
label: "Cyberpunk",
|
|
56
|
+
description: "Esthétique futuriste néon sombre",
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: "noir",
|
|
60
|
+
label: "Film Noir",
|
|
61
|
+
description: "Ambiance dramatique noir et blanc",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
key: "vaporwave",
|
|
65
|
+
label: "Vaporwave",
|
|
66
|
+
description: "Style rétro futur rose/bleu pastel",
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
key: "fantasy",
|
|
70
|
+
label: "Fantasy",
|
|
71
|
+
description: "Univers magique et héros épiques",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "comic-book",
|
|
75
|
+
label: "Comic Book",
|
|
76
|
+
description: "Style bande dessinée américaine",
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
key: "flat-design",
|
|
80
|
+
label: "Flat Design",
|
|
81
|
+
description: "Illustration minimaliste sans ombres",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
key: "isometric",
|
|
85
|
+
label: "Isométrique",
|
|
86
|
+
description: "Perspective 3D isométrique",
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
key: "steampunk",
|
|
90
|
+
label: "Steampunk",
|
|
91
|
+
description: "Univers mécanique rétro industriel",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "cinematic",
|
|
95
|
+
label: "Cinematic",
|
|
96
|
+
description: "Style film avec mise en scène dramatique",
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
key: "retro",
|
|
100
|
+
label: "Rétro",
|
|
101
|
+
description: "Style années 80/90",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
key: "minimalist",
|
|
105
|
+
label: "Minimaliste",
|
|
106
|
+
description: "Formes simples, ambiance épurée",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
key: "neon",
|
|
110
|
+
label: "Néon",
|
|
111
|
+
description: "Glow électrique et couleurs vibrantes",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
key: "line-art",
|
|
115
|
+
label: "Line Art",
|
|
116
|
+
description: "Illustration en lignes fines",
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
key: "pastel",
|
|
120
|
+
label: "Pastel",
|
|
121
|
+
description: "Couleurs douces et lumineuses",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
key: "dark-fantasy",
|
|
125
|
+
label: "Dark Fantasy",
|
|
126
|
+
description: "Fantasy sombre, atmosphère dramatique",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
key: "surreal",
|
|
130
|
+
label: "Surréaliste",
|
|
131
|
+
description: "Style rêve éveillé, étrange",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
key: "photographic-food",
|
|
135
|
+
label: "Gastronomique",
|
|
136
|
+
description: "Photo culinaire professionnelle",
|
|
137
|
+
},
|
|
49
138
|
];
|
|
50
|
-
export function ImageGenerative({ defaultPrompt = "", model
|
|
139
|
+
export function ImageGenerative({ defaultPrompt = "", model, size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, label, description, defaultStyle = "realistic", hideStyleEditor = false, hideModelSelector = false, uploadPath, preview = true, inline = false, autoGenerate = false, }) {
|
|
51
140
|
const t = useModuleTranslation("ai");
|
|
52
141
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
53
142
|
const [generatedImage, setGeneratedImage] = useState(null);
|
|
54
143
|
const [error, setError] = useState(null);
|
|
55
144
|
const [tokenBalance, setTokenBalance] = useState(null);
|
|
145
|
+
const [defaultModel, setDefaultModel] = useState(null);
|
|
56
146
|
// Modal state
|
|
57
147
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
58
148
|
const [userPrompt, setUserPrompt] = useState(defaultPrompt);
|
|
59
149
|
const [selectedStyle, setSelectedStyle] = useState(defaultStyle);
|
|
60
|
-
const [selectedModel, setSelectedModel] = useState(model);
|
|
150
|
+
const [selectedModel, setSelectedModel] = useState(model || null);
|
|
61
151
|
const [selectedSize, setSelectedSize] = useState(size);
|
|
62
152
|
const [selectedQuality, setSelectedQuality] = useState(quality);
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
153
|
+
// Fetch global AI settings on mount to get default image model
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
async function fetchDefaultModel() {
|
|
156
|
+
try {
|
|
157
|
+
const res = await fetch("/api/ai/admin/global-ai-settings");
|
|
158
|
+
if (res.ok) {
|
|
159
|
+
const data = await res.json();
|
|
160
|
+
const globalModel = `${data.default_image_provider}/${data.default_image_model}`;
|
|
161
|
+
setDefaultModel(globalModel);
|
|
162
|
+
// Set selected model to global default if no model prop provided
|
|
163
|
+
if (!model) {
|
|
164
|
+
setSelectedModel(globalModel);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
logger.error("[ImageGenerative] Failed to fetch default model", err);
|
|
170
|
+
// Fallback to a hardcoded model if fetch fails
|
|
171
|
+
const fallback = "google/gemini-2.5-flash-image";
|
|
172
|
+
setDefaultModel(fallback);
|
|
173
|
+
if (!model) {
|
|
174
|
+
setSelectedModel(fallback);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
71
177
|
}
|
|
72
|
-
|
|
73
|
-
};
|
|
178
|
+
fetchDefaultModel();
|
|
179
|
+
}, [model]);
|
|
180
|
+
// Le coût réel est calculé par l'API
|
|
181
|
+
const estimatedCostText = () => "Variable (selon le modèle)";
|
|
74
182
|
const handleGenerate = useCallback(async () => {
|
|
75
183
|
if (!userPrompt || isGenerating)
|
|
76
184
|
return;
|
|
@@ -98,9 +206,11 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
98
206
|
"Erreur lors de la génération");
|
|
99
207
|
}
|
|
100
208
|
const data = await response.json();
|
|
209
|
+
// Prefer supabaseImageUrl as the primary imageUrl for usage in the app
|
|
210
|
+
const primaryImageUrl = data.supabaseImageUrl || data.imageUrl || null;
|
|
101
211
|
const imageResponse = {
|
|
102
212
|
supabaseImageUrl: data.supabaseImageUrl,
|
|
103
|
-
imageUrl:
|
|
213
|
+
imageUrl: primaryImageUrl,
|
|
104
214
|
tokensUsed: data.tokensUsed || 0,
|
|
105
215
|
tokensRemaining: data.tokensRemaining || 0,
|
|
106
216
|
model: data.model || selectedModel,
|
|
@@ -115,7 +225,9 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
115
225
|
description: `${imageResponse.tokensUsed} ${t("toast.tokens_used") || "tokens utilisés"}, ${imageResponse.tokensRemaining} ${t("toast.remaining") || "restants"}`,
|
|
116
226
|
});
|
|
117
227
|
if (onChange) {
|
|
118
|
-
|
|
228
|
+
// Pass supabaseImageUrl first when available, otherwise fallback to imageUrl
|
|
229
|
+
const changeSrc = data.supabaseImageUrl || data.imageUrl || null;
|
|
230
|
+
onChange(changeSrc, imageResponse);
|
|
119
231
|
}
|
|
120
232
|
}
|
|
121
233
|
catch (err) {
|
|
@@ -146,30 +258,80 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
146
258
|
onError,
|
|
147
259
|
isGenerating,
|
|
148
260
|
]);
|
|
261
|
+
// Reset local prompt when defaultPrompt changes (e.g. recipe loaded)
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
setUserPrompt(defaultPrompt || "");
|
|
264
|
+
}, [defaultPrompt]);
|
|
149
265
|
const handleDownload = useCallback(async () => {
|
|
150
|
-
|
|
266
|
+
const imageSrc = generatedImage?.imageUrl || generatedImage?.supabaseImageUrl || null;
|
|
267
|
+
if (!imageSrc)
|
|
151
268
|
return;
|
|
152
269
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
270
|
+
if (typeof imageSrc === "string" && imageSrc.startsWith("data:")) {
|
|
271
|
+
// data URL -> convert base64 to blob
|
|
272
|
+
const parts = imageSrc.split(",");
|
|
273
|
+
const meta = parts[0];
|
|
274
|
+
const b64 = parts[1] || "";
|
|
275
|
+
const contentTypeMatch = meta.match(/data:(.*);base64/);
|
|
276
|
+
const contentType = contentTypeMatch
|
|
277
|
+
? contentTypeMatch[1]
|
|
278
|
+
: "image/png";
|
|
279
|
+
const byteCharacters = atob(b64);
|
|
280
|
+
const byteNumbers = new Array(byteCharacters.length);
|
|
281
|
+
for (let i = 0; i < byteCharacters.length; i++) {
|
|
282
|
+
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
|
283
|
+
}
|
|
284
|
+
const byteArray = new Uint8Array(byteNumbers);
|
|
285
|
+
const blob = new Blob([byteArray], { type: contentType });
|
|
286
|
+
const url = window.URL.createObjectURL(blob);
|
|
287
|
+
const link = document.createElement("a");
|
|
288
|
+
link.href = url;
|
|
289
|
+
link.download = `generated-image-${Date.now()}.png`;
|
|
290
|
+
document.body.appendChild(link);
|
|
291
|
+
link.click();
|
|
292
|
+
document.body.removeChild(link);
|
|
293
|
+
window.URL.revokeObjectURL(url);
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
// If imageSrc is a storage path (e.g. "app/.../file.png"), fetch via proxy
|
|
297
|
+
const fetchUrl = typeof imageSrc === "string" &&
|
|
298
|
+
!imageSrc.startsWith("http") &&
|
|
299
|
+
!imageSrc.startsWith("/")
|
|
300
|
+
? `/api/storage/${imageSrc}`
|
|
301
|
+
: imageSrc;
|
|
302
|
+
const response = await fetch(fetchUrl);
|
|
303
|
+
const blob = await response.blob();
|
|
304
|
+
const url = window.URL.createObjectURL(blob);
|
|
305
|
+
const link = document.createElement("a");
|
|
306
|
+
link.href = url;
|
|
307
|
+
link.download = `generated-image-${Date.now()}.png`;
|
|
308
|
+
document.body.appendChild(link);
|
|
309
|
+
link.click();
|
|
310
|
+
document.body.removeChild(link);
|
|
311
|
+
window.URL.revokeObjectURL(url);
|
|
312
|
+
}
|
|
163
313
|
}
|
|
164
314
|
catch (error) {
|
|
165
|
-
|
|
315
|
+
logger.error(t("error.download_failed") || "Erreur lors du téléchargement:", error);
|
|
166
316
|
}
|
|
167
317
|
}, [generatedImage]);
|
|
168
|
-
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && _jsx("p", { className: "text-sm text-gray-500", children: description }), generatedImage && !isGenerating && preview && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children:
|
|
318
|
+
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-4", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && _jsx("p", { className: "text-sm text-gray-500", children: description }), generatedImage && !isGenerating && preview && (_jsx(Card, { children: _jsxs(CardBody, { children: [_jsx("div", { className: "relative w-full aspect-square", children: (() => {
|
|
319
|
+
const imageSrc = generatedImage.imageUrl ||
|
|
320
|
+
generatedImage.supabaseImageUrl ||
|
|
321
|
+
"";
|
|
322
|
+
if (typeof imageSrc === "string" &&
|
|
323
|
+
imageSrc.startsWith("data:")) {
|
|
324
|
+
return (_jsx("img", { src: imageSrc, alt: generatedImage.prompt, className: "object-contain rounded-lg w-full h-full", style: { objectFit: "contain" } }));
|
|
325
|
+
}
|
|
326
|
+
if (imageSrc) {
|
|
327
|
+
return (_jsx(Image, { src: imageSrc, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }));
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
})() }), _jsxs("div", { className: "mt-4", children: [_jsxs("p", { className: "text-sm text-default-600", children: [_jsx("strong", { children: t("image.prompt_label") || "Prompt:" }), " ", generatedImage.prompt] }), _jsxs("p", { className: "text-xs text-default-400 mt-1", children: [selectedSize, " \u2022 ", selectedQuality] })] })] }) })), inline ? (_jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: t("image.description_label") || "Description de l'image", placeholder: t("image.description_placeholder") ||
|
|
169
331
|
"Décrivez l'image que vous souhaitez générer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: t("image.description_help") ||
|
|
170
332
|
"Soyez précis et détaillé pour de meilleurs résultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: t("image.style_label") || "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: t("image.style_description") ||
|
|
171
333
|
"Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: t(`image.style_${style.key}`) || style.label }), _jsx("span", { className: "text-xs text-gray-500", children: t(`image.style_${style.key}_desc`) ||
|
|
172
|
-
style.description })] }) }, style.key))) }), _jsxs(Select, { label: t("image.model_label") || "Modèle IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
|
|
334
|
+
style.description })] }) }, style.key))) }), !hideModelSelector && (_jsxs(Select, { label: t("image.model_label") || "Modèle IA", selectedKeys: selectedModel ? [selectedModel] : [], onSelectionChange: (keys) => {
|
|
173
335
|
const newModel = Array.from(keys)[0];
|
|
174
336
|
setSelectedModel(newModel);
|
|
175
337
|
if (newModel === "dall-e-2") {
|
|
@@ -179,16 +341,29 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
179
341
|
}
|
|
180
342
|
}
|
|
181
343
|
}, description: t("image.model_description") ||
|
|
182
|
-
"DALL-E 3 offre la meilleure qualité", children: [_jsx(SelectItem, { children: t("image.
|
|
183
|
-
|
|
184
|
-
"
|
|
344
|
+
"DALL-E 3 offre la meilleure qualité", children: [_jsx(SelectItem, { children: t("image.model_imagen") ||
|
|
345
|
+
"Google Imagen 4.0 (Rapide & économique)" }, "google/imagen-4.0-fast-generate-001"), _jsx(SelectItem, { children: t("image.model_gptimage1") || "GPT Image 1 (Nouveau)" }, "gpt-image-1"), _jsx(SelectItem, { children: t("image.model_dalle3") || "DALL-E 3 (Haute qualité)" }, "dall-e-3"), _jsx(SelectItem, { children: t("image.model_dalle2") || "DALL-E 2 (Standard)" }, "dall-e-2")] })), _jsx(Select, { label: t("image.size_label") || "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: t("image.size_description") ||
|
|
346
|
+
"Tailles disponibles selon le modèle", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3k tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4k tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5k tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6k-8k tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8k-10k tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8k-10k tokens)" }, "1792x1024")] })) }), (selectedModel === "dall-e-3" ||
|
|
347
|
+
selectedModel === "gpt-image-1") && (_jsxs(Select, { label: t("image.quality_label") || "Qualité", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: t("image.quality_description") ||
|
|
348
|
+
"HD offre plus de détails mais coûte plus de tokens", children: [_jsx(SelectItem, { children: t("image.quality_standard") || "Standard" }, "standard"), _jsx(SelectItem, { children: t("image.quality_hd") || "HD (Haute Définition)" }, "hd")] })), _jsx("div", { className: "p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: _jsxs("p", { className: "text-sm font-medium text-primary-700 dark:text-primary-300", children: [t("image.estimated_cost") || "Coût:", " ", estimatedCostText()] }) })] })), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || disabled || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Wand2, { size: 18 })), className: "w-full", children: isGenerating
|
|
185
349
|
? t("button.generating") || "Génération..."
|
|
186
|
-
: t("button.generate") || "Générer" })] })) : (_jsx(Button, { color: "primary",
|
|
350
|
+
: t("button.generate") || "Générer" })] })) : (_jsx(Button, { color: "primary", onPress: () => {
|
|
351
|
+
if (autoGenerate) {
|
|
352
|
+
void handleGenerate();
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
setIsModalOpen(true);
|
|
356
|
+
}
|
|
357
|
+
}, isDisabled: disabled || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Wand2, { size: 18 })), className: "w-full", children: isGenerating
|
|
187
358
|
? t("button.generating") || "Génération..."
|
|
188
|
-
:
|
|
189
|
-
|
|
359
|
+
: autoGenerate
|
|
360
|
+
? t("button.generate") || "Générer"
|
|
361
|
+
: t("button.generate_image") || "Générer une image" })), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance &&
|
|
362
|
+
tokenBalance !== null &&
|
|
363
|
+
tokenBalance !== undefined && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " ", t("chip.tokens_remaining") || "tokens restants"] })) }), generatedImage && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [generatedImage.tokensUsed, " ", t("chip.tokens_used") || "tokens utilisés"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: generatedImage.model }), preview && (_jsx(Button, { size: "sm", variant: "flat", onClick: handleDownload, startContent: _jsx(Download, { size: 16 }), children: t("button.download") || "Télécharger" }))] }))] }), isGenerating && (_jsxs("div", { className: "space-y-2", children: [_jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": t("progress.generating_image") || "Génération en cours...", className: "max-w-md" }), _jsx("p", { className: "text-sm text-default-500", children: t("progress.image_generation_message") ||
|
|
364
|
+
"Génération de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-3 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }), _jsx(Button, { as: AppLink, href: `/auth/ai/token`, size: "sm", color: "warning", children: t("button.manage_tokens") || "Gérer les tokens" })] }))] }), _jsx(Modal, { backdrop: "blur", isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ImageIcon, { size: 20 }), _jsx("span", { children: t("modal.image_title") || "Générer une image avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: t("image.description_label") || "Description de l'image", placeholder: t("image.description_placeholder") ||
|
|
190
365
|
"Décrivez l'image que vous souhaitez générer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: t("image.description_help") ||
|
|
191
|
-
"Soyez précis et détaillé pour de meilleurs résultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), _jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => {
|
|
366
|
+
"Soyez précis et détaillé pour de meilleurs résultats" }), !hideStyleEditor && (_jsxs(_Fragment, { children: [_jsx(Select, { label: "Style artistique", selectedKeys: [selectedStyle], onSelectionChange: (keys) => setSelectedStyle(Array.from(keys)[0]), description: "Le style influence l'apparence finale de l'image", children: imageStyles.map((style) => (_jsx(SelectItem, { textValue: style.label, children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium", children: style.label }), _jsx("span", { className: "text-xs text-gray-500", children: style.description })] }) }, style.key))) }), !hideModelSelector && (_jsxs(Select, { label: "Mod\u00E8le IA", selectedKeys: selectedModel ? [selectedModel] : [], onSelectionChange: (keys) => {
|
|
192
367
|
const newModel = Array.from(keys)[0];
|
|
193
368
|
setSelectedModel(newModel);
|
|
194
369
|
if (newModel === "dall-e-2") {
|
|
@@ -197,8 +372,21 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
197
372
|
setSelectedSize("1024x1024");
|
|
198
373
|
}
|
|
199
374
|
}
|
|
200
|
-
}, description: "DALL-E 3 offre la meilleure qualit\u00E9", children: [_jsx(SelectItem, { children: "DALL-E 3 (Haute qualit\u00E9)" }, "dall-e-3"), _jsx(SelectItem, { children: "DALL-E 2 (Standard)" }, "dall-e-2")] }), _jsx(Select, { label: "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: "Tailles disponibles selon le mod\u00E8le", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (
|
|
201
|
-
|
|
375
|
+
}, description: "DALL-E 3 offre la meilleure qualit\u00E9", children: [_jsx(SelectItem, { children: "Google Imagen 4.0 (Rapide & \u00E9conomique)" }, "google/imagen-4.0-fast-generate-001"), _jsx(SelectItem, { children: "GPT Image 1 (Nouveau)" }, "gpt-image-1"), _jsx(SelectItem, { children: "DALL-E 3 (Haute qualit\u00E9)" }, "dall-e-3"), _jsx(SelectItem, { children: "DALL-E 2 (Standard)" }, "dall-e-2")] })), _jsx(Select, { label: "Taille de l'image", selectedKeys: [selectedSize], onSelectionChange: (keys) => setSelectedSize(Array.from(keys)[0]), description: "Tailles disponibles selon le mod\u00E8le", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3k tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4k tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5k tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6k-8k tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8k-10k tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8k-10k tokens)" }, "1792x1024")] })) }), (selectedModel === "dall-e-3" ||
|
|
376
|
+
selectedModel === "gpt-image-1") && (_jsxs(Select, { label: "Qualit\u00E9", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: "HD offre plus de d\u00E9tails mais co\u00FBte plus de tokens", children: [_jsx(SelectItem, { children: "Standard" }, "standard"), _jsx(SelectItem, { children: "HD (Haute D\u00E9finition)" }, "hd")] })), _jsx("div", { className: "p-3 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: _jsxs("p", { className: "text-sm font-medium text-primary-700 dark:text-primary-300", children: ["Co\u00FBt: ", estimatedCostText()] }) })] })), generatedImage && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-2", children: t("image.current_preview") ||
|
|
377
|
+
"Aperçu de l'image actuelle:" }), _jsx("div", { className: "relative w-full", children: (() => {
|
|
378
|
+
const imageSrc = generatedImage.imageUrl ||
|
|
379
|
+
generatedImage.supabaseImageUrl ||
|
|
380
|
+
"";
|
|
381
|
+
if (typeof imageSrc === "string" &&
|
|
382
|
+
imageSrc.startsWith("data:")) {
|
|
383
|
+
return (_jsx("img", { src: imageSrc, alt: "Current", className: "object-cover rounded w-full h-auto", style: { maxHeight: 480 } }));
|
|
384
|
+
}
|
|
385
|
+
if (imageSrc) {
|
|
386
|
+
return (_jsx(Image, { src: imageSrc, alt: "Current", fill: true, className: "object-cover rounded" }));
|
|
387
|
+
}
|
|
388
|
+
return null;
|
|
389
|
+
})() })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: t("button.cancel") || "Annuler" }), _jsx(Button, { color: "primary", onClick: handleGenerate, disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(ImageIcon, { size: 16 })), children: isGenerating
|
|
202
390
|
? t("button.generating") || "Génération..."
|
|
203
391
|
: t("button.generate") || "Générer" })] })] }) })] }));
|
|
204
392
|
}
|
|
@@ -22,6 +22,8 @@ export interface TextareaGenerativeProps {
|
|
|
22
22
|
description?: string;
|
|
23
23
|
hidePromptEditor?: boolean;
|
|
24
24
|
showStructuredResponse?: boolean;
|
|
25
|
+
maxLength?: number;
|
|
26
|
+
actionType?: "generate-text" | "generate-recipe-text" | "autocomplete";
|
|
25
27
|
}
|
|
26
|
-
export declare function TextareaGenerative({ defaultPrompt, model, placeholder, defaultValue, onChange, onError, className, disabled, minRows, maxRows, apiEndpoint, showTokenBalance, label, description, hidePromptEditor, showStructuredResponse, }: TextareaGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export declare function TextareaGenerative({ defaultPrompt, model, placeholder, defaultValue, onChange, onError, className, disabled, minRows, maxRows, apiEndpoint, showTokenBalance, label, description, hidePromptEditor, maxLength, showStructuredResponse, actionType, }: TextareaGenerativeProps): import("react/jsx-runtime").JSX.Element;
|
|
27
29
|
//# sourceMappingURL=TextareaGenerative.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TextareaGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/TextareaGenerative.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TextareaGenerative.d.ts","sourceRoot":"","sources":["../../../src/web/components/TextareaGenerative.tsx"],"names":[],"mappings":"AAsBA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAClE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,eAAe,GAAG,sBAAsB,GAAG,cAAc,CAAC;CACxE;AAED,wBAAgB,kBAAkB,CAAC,EACjC,aAAkB,EAClB,KAAqB,EACrB,WAA6D,EAC7D,YAAiB,EACjB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,QAAgB,EAChB,OAAW,EACX,OAAY,EACZ,WAAqC,EACrC,gBAAuB,EACvB,KAAK,EACL,WAAW,EACX,gBAAwB,EACxB,SAAgB,EAChB,sBAA8B,EAC9B,UAAU,GACX,EAAE,uBAAuB,2CA0UzB"}
|
|
@@ -1,10 +1,11 @@
|
|
|
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, useCallback } from "react";
|
|
4
4
|
import { useModuleTranslation } from "@lastbrain/core";
|
|
5
|
-
import { Textarea, Button, Chip, Progress, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem, Skeleton, addToast, } from "@lastbrain/ui";
|
|
5
|
+
import { Textarea, Button, Chip, Progress, Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Select, SelectItem, Skeleton, addToast, AppLink, } from "@lastbrain/ui";
|
|
6
6
|
import { Sparkles, Loader2, AlertCircle, Wand2 } from "lucide-react";
|
|
7
|
-
export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini", placeholder = "Saisissez votre texte ou générez avec l'IA...", defaultValue = "", onChange, onError, className, disabled = false, minRows = 4, maxRows = 20, apiEndpoint = "/api/ai/generate-text", showTokenBalance = true, label, description, hidePromptEditor = false, showStructuredResponse = false,
|
|
7
|
+
export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini", placeholder = "Saisissez votre texte ou générez avec l'IA...", defaultValue = "", onChange, onError, className, disabled = false, minRows = 4, maxRows = 20, apiEndpoint = "/api/ai/generate-text", showTokenBalance = true, label, description, hidePromptEditor = false, maxLength = 3000, showStructuredResponse = false, actionType, // Type d'action pour le billing
|
|
8
|
+
}) {
|
|
8
9
|
const t = useModuleTranslation("ai");
|
|
9
10
|
const [userInput, setUserInput] = useState(defaultValue);
|
|
10
11
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
@@ -36,7 +37,9 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
36
37
|
headers: { "Content-Type": "application/json" },
|
|
37
38
|
body: JSON.stringify({
|
|
38
39
|
prompt: fullPrompt,
|
|
40
|
+
promptUser: defaultValue,
|
|
39
41
|
model: hidePromptEditor ? model : selectedModel,
|
|
42
|
+
actionType, // Pass actionType for billing
|
|
40
43
|
}),
|
|
41
44
|
});
|
|
42
45
|
if (!response.ok) {
|
|
@@ -100,7 +103,7 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
100
103
|
onChange(newValue);
|
|
101
104
|
}
|
|
102
105
|
};
|
|
103
|
-
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-2", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && (_jsx("p", { className: "text-sm text-gray-500 mb-2", children: description })), isGenerating && (_jsxs("div", { className: "w-full border border-default-200 p-5 rounded-xl", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Skeleton, { className: "w-
|
|
106
|
+
return (_jsxs("div", { className: className, children: [_jsxs("div", { className: "flex flex-col gap-2", children: [label && _jsx("label", { className: "text-sm font-medium", children: label }), description && (_jsx("p", { className: "text-sm text-gray-500 mb-2", children: description })), isGenerating && (_jsxs("div", { className: "w-full border border-default-200 p-5 rounded-xl", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Skeleton, { className: "w-[30%] bg-content-2 h-8 rounded-lg" }), _jsx(Skeleton, { className: "w-[50%] bg-content-2 h-4 rounded-lg" }), _jsx(Skeleton, { className: "w-[40%] bg-content-2 h-4 rounded-lg" })] }), _jsx("div", { className: "mt-2", children: _jsx(Progress, { size: "sm", isIndeterminate: true, "aria-label": t("progress.generating") || "Génération en cours...", label: t("progress.generating") || "Génération en cours..." }) })] })), !isGenerating && (_jsx(Textarea, { maxLength: maxLength, value: userInput, onChange: (e) => handleInputChange(e.target.value), placeholder: placeholder ||
|
|
104
107
|
t("textarea.placeholder") ||
|
|
105
108
|
"Saisissez votre texte ou générez avec l'IA...", disabled: disabled || isGenerating, minRows: minRows, maxRows: maxRows, endContent: _jsx(Button, { isIconOnly: true, size: "sm", color: "primary", variant: "light", onPress: () => {
|
|
106
109
|
if (hidePromptEditor) {
|
|
@@ -109,7 +112,11 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
109
112
|
else {
|
|
110
113
|
setIsModalOpen(true);
|
|
111
114
|
}
|
|
112
|
-
}, disabled: disabled ||
|
|
115
|
+
}, disabled: disabled ||
|
|
116
|
+
isGenerating ||
|
|
117
|
+
(hidePromptEditor && !userInput.trim()), title: t("button.generate_with_ai") || "Générer avec l'IA", children: _jsx(Wand2, { size: 18 }) }) })), _jsxs("div", { className: "flex items-center justify-between gap-2 flex-wrap", children: [_jsx("div", { className: "flex items-center gap-2", children: showTokenBalance &&
|
|
118
|
+
tokenBalance !== null &&
|
|
119
|
+
tokenBalance !== undefined && (_jsxs(Chip, { size: "sm", variant: "flat", color: "success", children: [tokenBalance.toLocaleString(), " ", t("chip.tokens_remaining") || "tokens restants"] })) }), lastResponse && (_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsxs(Chip, { size: "sm", variant: "flat", children: [lastResponse.tokensUsed, " ", t("chip.tokens_used") || "tokens utilisés"] }), _jsx(Chip, { size: "sm", variant: "flat", color: "primary", children: lastResponse.model }), lastResponse.cost && (_jsxs(Chip, { size: "sm", variant: "flat", color: "warning", children: ["$", lastResponse.cost.toFixed(4)] }))] }))] }), error && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex items-center gap-2 text-danger text-sm p-2 bg-danger-50 rounded-md", children: [_jsx(AlertCircle, { size: 16 }), _jsx("span", { children: error })] }), _jsx(Button, { as: AppLink, href: `/purchase-tokens`, size: "sm", color: "warning", children: t("button.manage_tokens") || "Gérer les tokens" })] }))] }), !hidePromptEditor && (_jsx(Modal, { isOpen: isModalOpen, onClose: () => setIsModalOpen(false), size: "2xl", backdrop: "blur", children: _jsxs(ModalContent, { children: [_jsx(ModalHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Sparkles, { size: 20 }), _jsx("span", { children: t("modal.title") || "Générer du texte avec l'IA" })] }) }), _jsx(ModalBody, { children: _jsxs("div", { className: "space-y-4", children: [_jsx(Textarea, { label: t("modal.prompt_label") || "Prompt de génération", placeholder: t("modal.prompt_placeholder") ||
|
|
113
120
|
"Décrivez ce que vous souhaitez générer...", value: userPrompt, onChange: (e) => setUserPrompt(e.target.value), minRows: 3, description: t("modal.prompt_description") ||
|
|
114
121
|
"Soyez précis pour obtenir de meilleurs résultats" }), _jsxs(Select, { label: t("modal.model_label") || "Modèle IA", selectedKeys: [selectedModel], onSelectionChange: (keys) => setSelectedModel(Array.from(keys)[0]), description: t("modal.model_description") ||
|
|
115
122
|
"GPT-4o-mini est plus rapide et économique", children: [_jsx(SelectItem, { children: t("modal.model_gpt4o_mini") || "GPT-4o Mini (Rapide)" }, "gpt-4o-mini"), _jsx(SelectItem, { children: t("modal.model_gpt4o") || "GPT-4o (Avancé)" }, "gpt-4o"), _jsx(SelectItem, { children: t("modal.model_gpt35") || "GPT-3.5 Turbo (Économique)" }, "gpt-3.5-turbo")] }), userInput && (_jsxs("div", { className: "p-3 bg-gray-50 dark:bg-gray-800 rounded-lg", children: [_jsx("p", { className: "text-sm font-medium mb-1", children: t("modal.current_context") || "Contexte actuel:" }), _jsx("p", { className: "text-xs text-gray-600 dark:text-gray-400 line-clamp-3", children: userInput })] }))] }) }), _jsxs(ModalFooter, { children: [_jsx(Button, { variant: "light", onClick: () => setIsModalOpen(false), children: t("button.cancel") || "Annuler" }), _jsx(Button, { color: "primary", onClick: () => handleGenerate(userPrompt), disabled: !userPrompt.trim() || isGenerating, startContent: isGenerating ? (_jsx(Loader2, { className: "animate-spin", size: 16 })) : (_jsx(Sparkles, { size: 16 })), children: isGenerating
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PromptDetailPage - View detailed public AI prompt with view tracking
|
|
3
|
+
*/
|
|
4
|
+
interface PromptDetail {
|
|
5
|
+
id: string;
|
|
6
|
+
slug: string;
|
|
7
|
+
title: string;
|
|
8
|
+
content: string;
|
|
9
|
+
type: "text" | "image";
|
|
10
|
+
tags: string[];
|
|
11
|
+
username?: string;
|
|
12
|
+
avatar_url?: string;
|
|
13
|
+
lang: string;
|
|
14
|
+
views: number;
|
|
15
|
+
used_count: number;
|
|
16
|
+
picked_count: number;
|
|
17
|
+
created_at: string;
|
|
18
|
+
}
|
|
19
|
+
interface PromptDetailPageProps {
|
|
20
|
+
initialPrompt?: PromptDetail;
|
|
21
|
+
slug?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function PromptDetailPage({ initialPrompt, slug: directSlug, }: PromptDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=PromptDetailPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PromptDetailPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/PromptDetailPage.tsx"],"names":[],"mappings":"AAAA;;GAEG;AA2BH,UAAU,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,qBAAqB;IAC7B,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,aAAa,EACb,IAAI,EAAE,UAAU,GACjB,EAAE,qBAAqB,2CAiLvB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PromptDetailPage - View detailed public AI prompt with view tracking
|
|
3
|
+
*/
|
|
4
|
+
"use client";
|
|
5
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
6
|
+
import { useState, useEffect } from "react";
|
|
7
|
+
import { Card, CardHeader, CardBody, Button, Chip, Spinner, } from "@lastbrain/ui";
|
|
8
|
+
import { Sparkles, MessageSquare, Image as ImageIcon, Copy, Eye, Target, ArrowLeft, } from "lucide-react";
|
|
9
|
+
import { useRouter } from "next/navigation";
|
|
10
|
+
import confetti from "canvas-confetti";
|
|
11
|
+
import { UserAvatar } from "./UserAvatar";
|
|
12
|
+
export function PromptDetailPage({ initialPrompt, slug: directSlug, }) {
|
|
13
|
+
const router = useRouter();
|
|
14
|
+
const [prompt, setPrompt] = useState(initialPrompt || null);
|
|
15
|
+
const [loading, setLoading] = useState(!initialPrompt);
|
|
16
|
+
const [copied, setCopied] = useState(false);
|
|
17
|
+
// Extract slug from direct prop
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (!directSlug || initialPrompt)
|
|
20
|
+
return;
|
|
21
|
+
// Only fetch if we don't have initial data
|
|
22
|
+
const fetchPromptDetail = async () => {
|
|
23
|
+
try {
|
|
24
|
+
setLoading(true);
|
|
25
|
+
const response = await fetch(`/api/ai/public/prompts/slug/${directSlug}`);
|
|
26
|
+
const data = await response.json();
|
|
27
|
+
if (response.ok) {
|
|
28
|
+
setPrompt(data.prompt);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.error("Error fetching prompt:", error);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
setLoading(false);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
fetchPromptDetail();
|
|
39
|
+
}, [directSlug, initialPrompt]);
|
|
40
|
+
const handleCopy = async () => {
|
|
41
|
+
if (!prompt)
|
|
42
|
+
return;
|
|
43
|
+
await navigator.clipboard.writeText(prompt.content);
|
|
44
|
+
setCopied(true);
|
|
45
|
+
// Confetti effect
|
|
46
|
+
confetti({
|
|
47
|
+
particleCount: 100,
|
|
48
|
+
spread: 70,
|
|
49
|
+
origin: { y: 0.6 },
|
|
50
|
+
});
|
|
51
|
+
// Increment picked stat
|
|
52
|
+
try {
|
|
53
|
+
await fetch(`/api/ai/public/prompts/${prompt.id}/stats`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
headers: { "Content-Type": "application/json" },
|
|
56
|
+
body: JSON.stringify({ stat_type: "picked" }),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
console.error("Error incrementing stat:", error);
|
|
61
|
+
}
|
|
62
|
+
setTimeout(() => setCopied(false), 2000);
|
|
63
|
+
};
|
|
64
|
+
if (loading) {
|
|
65
|
+
return (_jsx("div", { className: "flex items-center justify-center min-h-screen", children: _jsx(Spinner, { size: "lg" }) }));
|
|
66
|
+
}
|
|
67
|
+
if (!prompt) {
|
|
68
|
+
return (_jsx("div", { className: "container mx-auto px-4 py-8 mt-16", children: _jsx(Card, { children: _jsx(CardBody, { children: _jsx("p", { className: "text-center text-default-500", children: "Prompt not found" }) }) }) }));
|
|
69
|
+
}
|
|
70
|
+
return (_jsxs("div", { className: "container mx-auto px-4 py-8 max-w-4xl mt-16", children: [_jsx(Button, { variant: "light", startContent: _jsx(ArrowLeft, { className: "w-4 h-4" }), onPress: () => router.back(), className: "mb-4", children: "Back to Prompts" }), _jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex-col items-start gap-3", children: [_jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsx("h1", { className: "text-2xl font-bold", children: prompt.title }), _jsx(Chip, { startContent: prompt.type === "image" ? (_jsx(ImageIcon, { className: "ps-1 w-4 h-4" })) : (_jsx(MessageSquare, { className: "ps-1 w-4 h-4" })), color: prompt.type === "image" ? "secondary" : "primary", variant: "flat", children: prompt.type })] }), prompt.username && (_jsx(UserAvatar, { username: prompt.username, avatarUrl: prompt.avatar_url, size: "md" })), prompt.tags && prompt.tags.length > 0 && (_jsx("div", { className: "flex gap-2 flex-wrap", children: prompt.tags.map((tag) => (_jsx(Chip, { size: "sm", variant: "flat", children: tag }, tag))) })), _jsxs("div", { className: "flex gap-4 text-sm text-default-500", children: [_jsxs("span", { className: `flex items-center gap-1 ${prompt.views > 0 ? "text-primary" : ""}`, children: [_jsx(Eye, { className: "w-4 h-4" }), prompt.views] }), _jsxs("span", { className: `flex items-center gap-1 ${prompt.used_count > 0 ? "text-secondary" : ""}`, children: [_jsx(Sparkles, { className: "w-4 h-4" }), prompt.used_count] }), _jsxs("span", { className: `flex items-center gap-1 ${prompt.picked_count > 0 ? "text-warning" : ""}`, children: [_jsx(Target, { className: "w-4 h-4" }), prompt.picked_count] })] })] }), _jsxs(CardBody, { className: "gap-4", children: [_jsx("div", { className: "rounded-lg border-2 border-default-200 p-4 bg-default-50", children: _jsx("p", { className: "text-sm whitespace-pre-wrap leading-relaxed", children: prompt.content }) }), _jsx(Button, { color: copied ? "success" : "primary", startContent: _jsx(Copy, { className: "w-4 h-4" }), onPress: handleCopy, className: "mx-auto my-4", size: "lg", children: copied ? "Copied!" : "Copy Prompt" })] })] })] }));
|
|
71
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PromptDetailPageServer - Server component for SEO
|
|
3
|
+
*/
|
|
4
|
+
import { Metadata } from "next";
|
|
5
|
+
interface PromptDetailPageServerProps {
|
|
6
|
+
params: Promise<{
|
|
7
|
+
lang: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
}>;
|
|
10
|
+
localeConfig?: any;
|
|
11
|
+
}
|
|
12
|
+
export declare function generateMetadata({ params, localeConfig, }: PromptDetailPageServerProps): Promise<Metadata>;
|
|
13
|
+
export default function PromptDetailPageServer({ params, }: PromptDetailPageServerProps): Promise<import("react/jsx-runtime").JSX.Element>;
|
|
14
|
+
export { PromptDetailPageServer };
|
|
15
|
+
//# sourceMappingURL=PromptDetailPageServer.d.ts.map
|