@lastbrain/module-ai 2.0.26 → 2.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +52 -1
- package/dist/ai.build.config.d.ts.map +1 -1
- package/dist/ai.build.config.js +508 -9
- package/dist/api/admin/ai-provider-models/[id].d.ts +18 -0
- package/dist/api/admin/ai-provider-models/[id].d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models/[id].js +58 -0
- package/dist/api/admin/ai-provider-models.d.ts +20 -0
- package/dist/api/admin/ai-provider-models.d.ts.map +1 -0
- package/dist/api/admin/ai-provider-models.js +26 -0
- package/dist/api/admin/ai-providers/[key].d.ts +18 -0
- package/dist/api/admin/ai-providers/[key].d.ts.map +1 -0
- package/dist/api/admin/ai-providers/[key].js +55 -0
- package/dist/api/admin/ai-providers.d.ts +20 -0
- package/dist/api/admin/ai-providers.d.ts.map +1 -0
- package/dist/api/admin/ai-providers.js +26 -0
- package/dist/api/admin/billing-analytics.d.ts +43 -0
- package/dist/api/admin/billing-analytics.d.ts.map +1 -0
- package/dist/api/admin/billing-analytics.js +144 -0
- package/dist/api/admin/global-ai-settings.d.ts +14 -0
- package/dist/api/admin/global-ai-settings.d.ts.map +1 -0
- package/dist/api/admin/global-ai-settings.js +63 -0
- package/dist/api/admin/token-packs/[id].d.ts.map +1 -1
- package/dist/api/admin/token-packs/[id].js +3 -2
- package/dist/api/admin/token-packs.d.ts.map +1 -1
- package/dist/api/admin/token-packs.js +3 -2
- package/dist/api/admin/user-monthly-details.d.ts +49 -0
- package/dist/api/admin/user-monthly-details.d.ts.map +1 -0
- package/dist/api/admin/user-monthly-details.js +140 -0
- package/dist/api/admin/user-quota.d.ts +21 -0
- package/dist/api/admin/user-quota.d.ts.map +1 -0
- package/dist/api/admin/user-quota.js +59 -0
- package/dist/api/admin/user-token/[id].d.ts.map +1 -1
- package/dist/api/admin/user-token/[id].js +2 -1
- package/dist/api/admin/user-token.d.ts +5 -2
- package/dist/api/admin/user-token.d.ts.map +1 -1
- package/dist/api/admin/user-token.js +91 -17
- package/dist/api/admin/user-usage-by-model.d.ts +22 -0
- package/dist/api/admin/user-usage-by-model.d.ts.map +1 -0
- package/dist/api/admin/user-usage-by-model.js +78 -0
- package/dist/api/admin/user-wallet-analytics.d.ts +15 -0
- package/dist/api/admin/user-wallet-analytics.d.ts.map +1 -0
- package/dist/api/admin/user-wallet-analytics.js +67 -0
- package/dist/api/admin/wallet-repair/route.d.ts +30 -0
- package/dist/api/admin/wallet-repair/route.d.ts.map +1 -0
- package/dist/api/admin/wallet-repair/route.js +63 -0
- package/dist/api/auth/ai-model-settings.d.ts +21 -0
- package/dist/api/auth/ai-model-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-model-settings.js +86 -0
- package/dist/api/auth/ai-settings.d.ts +17 -0
- package/dist/api/auth/ai-settings.d.ts.map +1 -0
- package/dist/api/auth/ai-settings.js +87 -0
- package/dist/api/auth/api-keys/[id].d.ts +17 -0
- package/dist/api/auth/api-keys/[id].d.ts.map +1 -0
- package/dist/api/auth/api-keys/[id].js +66 -0
- package/dist/api/auth/api-keys.d.ts +19 -0
- package/dist/api/auth/api-keys.d.ts.map +1 -0
- package/dist/api/auth/api-keys.js +94 -0
- package/dist/api/auth/create-checkout.d.ts +1 -1
- package/dist/api/auth/create-checkout.d.ts.map +1 -1
- package/dist/api/auth/create-checkout.js +8 -6
- package/dist/api/auth/generate-image.d.ts +2 -2
- package/dist/api/auth/generate-image.d.ts.map +1 -1
- package/dist/api/auth/generate-image.js +404 -104
- package/dist/api/auth/generate-text.d.ts +3 -2
- package/dist/api/auth/generate-text.d.ts.map +1 -1
- package/dist/api/auth/generate-text.js +130 -58
- package/dist/api/auth/process-ocr.d.ts +9 -0
- package/dist/api/auth/process-ocr.d.ts.map +1 -0
- package/dist/api/auth/process-ocr.js +43 -0
- package/dist/api/auth/prompts/stats.d.ts +14 -0
- package/dist/api/auth/prompts/stats.d.ts.map +1 -0
- package/dist/api/auth/prompts/stats.js +46 -0
- package/dist/api/auth/prompts.d.ts +26 -0
- package/dist/api/auth/prompts.d.ts.map +1 -0
- package/dist/api/auth/prompts.js +175 -0
- package/dist/api/auth/token-balance.d.ts +26 -0
- package/dist/api/auth/token-balance.d.ts.map +1 -0
- package/dist/api/auth/token-balance.js +47 -0
- package/dist/api/auth/token-checkout.d.ts.map +1 -1
- package/dist/api/auth/token-checkout.js +22 -4
- package/dist/api/auth/token-packs.d.ts.map +1 -1
- package/dist/api/auth/token-packs.js +2 -1
- package/dist/api/auth/usage-by-model.d.ts +25 -0
- package/dist/api/auth/usage-by-model.d.ts.map +1 -0
- package/dist/api/auth/usage-by-model.js +95 -0
- package/dist/api/auth/usage.d.ts +26 -0
- package/dist/api/auth/usage.d.ts.map +1 -0
- package/dist/api/auth/usage.js +127 -0
- package/dist/api/auth/user-tokens.d.ts.map +1 -1
- package/dist/api/auth/user-tokens.js +36 -2
- package/dist/api/auth/wallet/route.d.ts +17 -0
- package/dist/api/auth/wallet/route.d.ts.map +1 -0
- package/dist/api/auth/wallet/route.js +68 -0
- package/dist/api/auth/wallet.d.ts +16 -0
- package/dist/api/auth/wallet.d.ts.map +1 -0
- package/dist/api/auth/wallet.js +71 -0
- package/dist/api/public/gateway-models.d.ts +25 -0
- package/dist/api/public/gateway-models.d.ts.map +1 -0
- package/dist/api/public/gateway-models.js +49 -0
- package/dist/api/public/pricing-summary.d.ts +46 -0
- package/dist/api/public/pricing-summary.d.ts.map +1 -0
- package/dist/api/public/pricing-summary.js +70 -0
- package/dist/api/public/prompts/[id]/stats.d.ts +15 -0
- package/dist/api/public/prompts/[id]/stats.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/stats.js +37 -0
- package/dist/api/public/prompts/[id]/view.d.ts +15 -0
- package/dist/api/public/prompts/[id]/view.d.ts.map +1 -0
- package/dist/api/public/prompts/[id]/view.js +41 -0
- package/dist/api/public/prompts/slug/[slug].d.ts +15 -0
- package/dist/api/public/prompts/slug/[slug].d.ts.map +1 -0
- package/dist/api/public/prompts/slug/[slug].js +40 -0
- package/dist/api/public/prompts/user/[username].d.ts +17 -0
- package/dist/api/public/prompts/user/[username].d.ts.map +1 -0
- package/dist/api/public/prompts/user/[username].js +51 -0
- package/dist/api/public/prompts.d.ts +15 -0
- package/dist/api/public/prompts.d.ts.map +1 -0
- package/dist/api/public/prompts.js +45 -0
- package/dist/api/public/token-packs.d.ts +11 -0
- package/dist/api/public/token-packs.d.ts.map +1 -0
- package/dist/api/public/token-packs.js +25 -0
- package/dist/api/public/token-pricing.d.ts +44 -0
- package/dist/api/public/token-pricing.d.ts.map +1 -0
- package/dist/api/public/token-pricing.js +168 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts +52 -0
- package/dist/api/public/v1/_lib/artifacts.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/artifacts.js +217 -0
- package/dist/api/public/v1/_lib/auth.d.ts +43 -0
- package/dist/api/public/v1/_lib/auth.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/auth.js +108 -0
- package/dist/api/public/v1/_lib/errors.d.ts +17 -0
- package/dist/api/public/v1/_lib/errors.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/errors.js +16 -0
- package/dist/api/public/v1/_lib/log.d.ts +29 -0
- package/dist/api/public/v1/_lib/log.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/log.js +68 -0
- package/dist/api/public/v1/_lib/quota.d.ts +24 -0
- package/dist/api/public/v1/_lib/quota.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/quota.js +118 -0
- package/dist/api/public/v1/_lib/router.d.ts +54 -0
- package/dist/api/public/v1/_lib/router.d.ts.map +1 -0
- package/dist/api/public/v1/_lib/router.js +119 -0
- package/dist/api/public/v1/connect.d.ts +20 -0
- package/dist/api/public/v1/connect.d.ts.map +1 -0
- package/dist/api/public/v1/connect.js +119 -0
- package/dist/api/public/v1/doc.d.ts +239 -0
- package/dist/api/public/v1/doc.d.ts.map +1 -0
- package/dist/api/public/v1/doc.js +253 -0
- package/dist/api/public/v1/history/[id].d.ts +92 -0
- package/dist/api/public/v1/history/[id].d.ts.map +1 -0
- package/dist/api/public/v1/history/[id].js +176 -0
- package/dist/api/public/v1/history.d.ts +30 -0
- package/dist/api/public/v1/history.d.ts.map +1 -0
- package/dist/api/public/v1/history.js +142 -0
- package/dist/api/public/v1/image-ai.d.ts +24 -0
- package/dist/api/public/v1/image-ai.d.ts.map +1 -0
- package/dist/api/public/v1/image-ai.js +233 -0
- package/dist/api/public/v1/prompts.d.ts +19 -0
- package/dist/api/public/v1/prompts.d.ts.map +1 -0
- package/dist/api/public/v1/prompts.js +107 -0
- package/dist/api/public/v1/provider.d.ts +16 -0
- package/dist/api/public/v1/provider.d.ts.map +1 -0
- package/dist/api/public/v1/provider.js +130 -0
- package/dist/api/public/v1/purchase.d.ts +11 -0
- package/dist/api/public/v1/purchase.d.ts.map +1 -0
- package/dist/api/public/v1/purchase.js +18 -0
- package/dist/api/public/v1/status.d.ts +35 -0
- package/dist/api/public/v1/status.d.ts.map +1 -0
- package/dist/api/public/v1/status.js +163 -0
- package/dist/api/public/v1/text-ai.d.ts +26 -0
- package/dist/api/public/v1/text-ai.d.ts.map +1 -0
- package/dist/api/public/v1/text-ai.js +239 -0
- package/dist/api/public/webhook.d.ts.map +1 -1
- package/dist/api/public/webhook.js +50 -39
- package/dist/api/track-usage.d.ts +12 -0
- package/dist/api/track-usage.d.ts.map +1 -0
- package/dist/api/track-usage.js +37 -0
- package/dist/components/Doc.d.ts.map +1 -1
- package/dist/components/Doc.js +1 -1
- package/dist/components/DocUsageCustom.js +6 -6
- package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
- package/dist/components/admin/UserTokenTab.js +170 -23
- package/dist/components/auth/AuthDashboardAi.d.ts +2 -0
- package/dist/components/auth/AuthDashboardAi.d.ts.map +1 -0
- package/dist/components/auth/AuthDashboardAi.js +53 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts +277 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts.map +1 -0
- package/dist/docs/REFACTORING_BILLING_GUIDE.js +276 -0
- package/dist/index.d.ts +15 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/scripts/migrate-tokens-to-wallet.d.ts +13 -0
- package/dist/scripts/migrate-tokens-to-wallet.d.ts.map +1 -0
- package/dist/scripts/migrate-tokens-to-wallet.js +165 -0
- package/dist/server/__tests__/billing.test.d.ts +5 -0
- package/dist/server/__tests__/billing.test.d.ts.map +1 -0
- package/dist/server/__tests__/billing.test.js +223 -0
- package/dist/server/ai-client.d.ts +59 -0
- package/dist/server/ai-client.d.ts.map +1 -0
- package/dist/server/ai-client.js +111 -0
- package/dist/server/ai-generation-service.d.ts +66 -0
- package/dist/server/ai-generation-service.d.ts.map +1 -0
- package/dist/server/ai-generation-service.js +274 -0
- package/dist/server/billing.d.ts +200 -0
- package/dist/server/billing.d.ts.map +1 -0
- package/dist/server/billing.js +488 -0
- package/dist/server/gateway-service.d.ts +13 -0
- package/dist/server/gateway-service.d.ts.map +1 -0
- package/dist/server/gateway-service.js +161 -0
- package/dist/server/global-settings.d.ts +16 -0
- package/dist/server/global-settings.d.ts.map +1 -0
- package/dist/server/global-settings.js +42 -0
- package/dist/server/model-filter.d.ts +25 -0
- package/dist/server/model-filter.d.ts.map +1 -0
- package/dist/server/model-filter.js +240 -0
- package/dist/server/ocr.d.ts +39 -0
- package/dist/server/ocr.d.ts.map +1 -0
- package/dist/server/ocr.js +280 -0
- package/dist/server/openai-client.d.ts +19 -0
- package/dist/server/openai-client.d.ts.map +1 -0
- package/dist/server/openai-client.js +26 -0
- package/dist/server/pricing-config.d.ts +18 -0
- package/dist/server/pricing-config.d.ts.map +1 -0
- package/dist/server/pricing-config.js +94 -0
- package/dist/server/pricing-validator.d.ts +41 -0
- package/dist/server/pricing-validator.d.ts.map +1 -0
- package/dist/server/pricing-validator.js +113 -0
- package/dist/server/pricing.d.ts +121 -0
- package/dist/server/pricing.d.ts.map +1 -0
- package/dist/server/pricing.js +225 -0
- package/dist/server/quota.d.ts +66 -0
- package/dist/server/quota.d.ts.map +1 -0
- package/dist/server/quota.js +538 -0
- package/dist/server/wallet-repair.d.ts +32 -0
- package/dist/server/wallet-repair.d.ts.map +1 -0
- package/dist/server/wallet-repair.js +189 -0
- package/dist/server.d.ts +13 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +87 -16
- package/dist/sitemap/handlers/prompts.d.ts +6 -0
- package/dist/sitemap/handlers/prompts.d.ts.map +1 -0
- package/dist/sitemap/handlers/prompts.js +72 -0
- package/dist/sitemap/handlers/users.d.ts +6 -0
- package/dist/sitemap/handlers/users.d.ts.map +1 -0
- package/dist/sitemap/handlers/users.js +80 -0
- package/dist/sitemap/manifest.d.ts +8 -0
- package/dist/sitemap/manifest.d.ts.map +1 -0
- package/dist/sitemap/manifest.js +27 -0
- package/dist/types/gateway.d.ts +40 -0
- package/dist/types/gateway.d.ts.map +1 -0
- package/dist/types/gateway.js +4 -0
- package/dist/types/quota.d.ts +74 -0
- package/dist/types/quota.d.ts.map +1 -0
- package/dist/types/quota.js +11 -0
- package/dist/utils/date.d.ts +7 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +17 -0
- package/dist/web/admin/AdminTokenPacksPage.js +1 -1
- package/dist/web/admin/BillingAnalyticsPage.d.ts +2 -0
- package/dist/web/admin/BillingAnalyticsPage.d.ts.map +1 -0
- package/dist/web/admin/BillingAnalyticsPage.js +141 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts +2 -0
- package/dist/web/admin/GlobalAISettingsPage.d.ts.map +1 -0
- package/dist/web/admin/GlobalAISettingsPage.js +93 -0
- package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
- package/dist/web/admin/UserTokenPage.js +20 -7
- package/dist/web/auth/AISettingsPage.d.ts +2 -0
- package/dist/web/auth/AISettingsPage.d.ts.map +1 -0
- package/dist/web/auth/AISettingsPage.js +258 -0
- package/dist/web/auth/APIKeysPage.d.ts +2 -0
- package/dist/web/auth/APIKeysPage.d.ts.map +1 -0
- package/dist/web/auth/APIKeysPage.js +154 -0
- package/dist/web/auth/HistoryPage.d.ts +2 -0
- package/dist/web/auth/HistoryPage.d.ts.map +1 -0
- package/dist/web/auth/HistoryPage.js +279 -0
- package/dist/web/auth/PromptsPage.d.ts +5 -0
- package/dist/web/auth/PromptsPage.d.ts.map +1 -0
- package/dist/web/auth/PromptsPage.js +137 -0
- package/dist/web/auth/TokenPage.d.ts.map +1 -1
- package/dist/web/auth/TokenPage.js +88 -31
- package/dist/web/auth/UsageAndTokensPage.d.ts +2 -0
- package/dist/web/auth/UsageAndTokensPage.d.ts.map +1 -0
- package/dist/web/auth/UsageAndTokensPage.js +157 -0
- package/dist/web/auth/UsagePage.d.ts +2 -0
- package/dist/web/auth/UsagePage.d.ts.map +1 -0
- package/dist/web/auth/UsagePage.js +62 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts +13 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.d.ts.map +1 -0
- package/dist/web/auth/components/ApiKeyFilterSelect.js +16 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts +19 -0
- package/dist/web/auth/components/ModelUsageTable.d.ts.map +1 -0
- package/dist/web/auth/components/ModelUsageTable.js +37 -0
- package/dist/web/auth/components/PurchaseButton.d.ts +7 -0
- package/dist/web/auth/components/PurchaseButton.d.ts.map +1 -0
- package/dist/web/auth/components/PurchaseButton.js +13 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts +20 -0
- package/dist/web/auth/components/TokenHistoryCard.d.ts.map +1 -0
- package/dist/web/auth/components/TokenHistoryCard.js +76 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts +24 -0
- package/dist/web/auth/components/TokenKpiGrid.d.ts.map +1 -0
- package/dist/web/auth/components/TokenKpiGrid.js +38 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts +11 -0
- package/dist/web/auth/components/UsageByDayChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByDayChart.js +32 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts +12 -0
- package/dist/web/auth/components/UsageByModelBarChart.d.ts.map +1 -0
- package/dist/web/auth/components/UsageByModelBarChart.js +32 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts +9 -0
- package/dist/web/auth/components/WalletStatusCard.d.ts.map +1 -0
- package/dist/web/auth/components/WalletStatusCard.js +50 -0
- package/dist/web/components/ImageGenerative.d.ts +3 -1
- package/dist/web/components/ImageGenerative.d.ts.map +1 -1
- package/dist/web/components/ImageGenerative.js +139 -52
- package/dist/web/components/TextareaGenerative.d.ts +3 -1
- package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
- package/dist/web/components/TextareaGenerative.js +10 -5
- package/dist/web/public/PromptDetailPage.d.ts +25 -0
- package/dist/web/public/PromptDetailPage.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPage.js +71 -0
- package/dist/web/public/PromptDetailPageServer.d.ts +15 -0
- package/dist/web/public/PromptDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/PromptDetailPageServer.js +68 -0
- package/dist/web/public/PublicPromptsPage.d.ts +5 -0
- package/dist/web/public/PublicPromptsPage.d.ts.map +1 -0
- package/dist/web/public/PublicPromptsPage.js +110 -0
- package/dist/web/public/PurchaseTokensPage.d.ts +2 -0
- package/dist/web/public/PurchaseTokensPage.d.ts.map +1 -0
- package/dist/web/public/PurchaseTokensPage.js +98 -0
- package/dist/web/public/UserAvatar.d.ts +13 -0
- package/dist/web/public/UserAvatar.d.ts.map +1 -0
- package/dist/web/public/UserAvatar.js +13 -0
- package/dist/web/public/UserDetailPageServer.d.ts +15 -0
- package/dist/web/public/UserDetailPageServer.d.ts.map +1 -0
- package/dist/web/public/UserDetailPageServer.js +31 -0
- package/dist/web/public/UserPromptsPage.d.ts +9 -0
- package/dist/web/public/UserPromptsPage.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPage.js +112 -0
- package/dist/web/public/UserPromptsPageServer.d.ts +15 -0
- package/dist/web/public/UserPromptsPageServer.d.ts.map +1 -0
- package/dist/web/public/UserPromptsPageServer.js +31 -0
- package/package.json +18 -9
- package/supabase/migrations/20251125000000_ai_tokens.sql +7 -0
- package/supabase/migrations/20260123100002_user_token_quota_monthly copy.sql +173 -0
- package/supabase/migrations/20260128100003_update_and_add_table.sql +368 -0
- package/supabase/migrations/20260128120000_seed_providers_models.sql +78 -0
- package/supabase/migrations/20260128131405_add_api_key_id_to_ledgers.sql +41 -0
- package/supabase/migrations/20260128140000_ai_artifacts_storage.sql +99 -0
- package/supabase/migrations/20260128140002_ai_user_settings.sql +57 -0
- package/supabase/migrations/20260128150000_drop_ai_user_settings.sql +21 -0
- package/supabase/migrations/20260128160000_wallet_billing_system.sql +192 -0
- package/supabase/migrations/20260128160001_wallet_rpc_functions.sql +165 -0
- package/supabase/migrations/20260128170000_add_pack_coef_to_token_packs.sql +30 -0
- package/supabase/migrations/20260129120000_wallet_view_rpc.sql +41 -0
- package/supabase/migrations/20260129220003_update_pack_margins.sql +31 -0
- package/supabase/migrations/20260129330004_ai_user_prompts.sql +151 -0
- package/supabase/migrations/20260129330005_ai_prompts_ip_tracking.sql +92 -0
- package/supabase/migrations/20260129330006_ai_prompts_slug.sql +64 -0
- package/supabase/migrations/20260129330007_ai_prompts_view_slug.sql +26 -0
- package/supabase/migrations/20260129440000_ai_prompts_view_username.sql +33 -0
- package/supabase/migrations/20260129450000_ai_prompts_add_lang.sql +40 -0
- package/supabase/migrations/20260131000000_extract_model_prompt_in_ledger.sql +92 -0
- package/supabase/migrations/20260131140000_fix_duplicate_purchases.sql +64 -0
- package/supabase/migrations/20260201120000_module-ai_default_models.sql +63 -0
- package/supabase/migrations/20260201130000_module-ai_remove_provider_tables.sql +17 -0
- package/supabase/migrations-down/20251217120000_user_token_quota_monthly.sql +34 -0
- package/supabase/migrations-down/20260128131405_add_api_key_id_to_ledgers.sql +25 -0
|
@@ -1,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",
|
|
@@ -148,30 +136,49 @@ const imageStyles = [
|
|
|
148
136
|
description: "Photo culinaire professionnelle",
|
|
149
137
|
},
|
|
150
138
|
];
|
|
151
|
-
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, }) {
|
|
152
140
|
const t = useModuleTranslation("ai");
|
|
153
141
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
154
142
|
const [generatedImage, setGeneratedImage] = useState(null);
|
|
155
143
|
const [error, setError] = useState(null);
|
|
156
144
|
const [tokenBalance, setTokenBalance] = useState(null);
|
|
145
|
+
const [defaultModel, setDefaultModel] = useState(null);
|
|
157
146
|
// Modal state
|
|
158
147
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
159
148
|
const [userPrompt, setUserPrompt] = useState(defaultPrompt);
|
|
160
149
|
const [selectedStyle, setSelectedStyle] = useState(defaultStyle);
|
|
161
|
-
const [selectedModel, setSelectedModel] = useState(model);
|
|
150
|
+
const [selectedModel, setSelectedModel] = useState(model || null);
|
|
162
151
|
const [selectedSize, setSelectedSize] = useState(size);
|
|
163
152
|
const [selectedQuality, setSelectedQuality] = useState(quality);
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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
|
+
}
|
|
172
177
|
}
|
|
173
|
-
|
|
174
|
-
};
|
|
178
|
+
fetchDefaultModel();
|
|
179
|
+
}, [model]);
|
|
180
|
+
// Le coût réel est calculé par l'API
|
|
181
|
+
const estimatedCostText = () => "Variable (selon le modèle)";
|
|
175
182
|
const handleGenerate = useCallback(async () => {
|
|
176
183
|
if (!userPrompt || isGenerating)
|
|
177
184
|
return;
|
|
@@ -199,9 +206,11 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
199
206
|
"Erreur lors de la génération");
|
|
200
207
|
}
|
|
201
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;
|
|
202
211
|
const imageResponse = {
|
|
203
212
|
supabaseImageUrl: data.supabaseImageUrl,
|
|
204
|
-
imageUrl:
|
|
213
|
+
imageUrl: primaryImageUrl,
|
|
205
214
|
tokensUsed: data.tokensUsed || 0,
|
|
206
215
|
tokensRemaining: data.tokensRemaining || 0,
|
|
207
216
|
model: data.model || selectedModel,
|
|
@@ -216,7 +225,9 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
216
225
|
description: `${imageResponse.tokensUsed} ${t("toast.tokens_used") || "tokens utilisés"}, ${imageResponse.tokensRemaining} ${t("toast.remaining") || "restants"}`,
|
|
217
226
|
});
|
|
218
227
|
if (onChange) {
|
|
219
|
-
|
|
228
|
+
// Pass supabaseImageUrl first when available, otherwise fallback to imageUrl
|
|
229
|
+
const changeSrc = data.supabaseImageUrl || data.imageUrl || null;
|
|
230
|
+
onChange(changeSrc, imageResponse);
|
|
220
231
|
}
|
|
221
232
|
}
|
|
222
233
|
catch (err) {
|
|
@@ -247,30 +258,80 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
247
258
|
onError,
|
|
248
259
|
isGenerating,
|
|
249
260
|
]);
|
|
261
|
+
// Reset local prompt when defaultPrompt changes (e.g. recipe loaded)
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
setUserPrompt(defaultPrompt || "");
|
|
264
|
+
}, [defaultPrompt]);
|
|
250
265
|
const handleDownload = useCallback(async () => {
|
|
251
|
-
|
|
266
|
+
const imageSrc = generatedImage?.imageUrl || generatedImage?.supabaseImageUrl || null;
|
|
267
|
+
if (!imageSrc)
|
|
252
268
|
return;
|
|
253
269
|
try {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
+
}
|
|
264
313
|
}
|
|
265
314
|
catch (error) {
|
|
266
|
-
|
|
315
|
+
logger.error(t("error.download_failed") || "Erreur lors du téléchargement:", error);
|
|
267
316
|
}
|
|
268
317
|
}, [generatedImage]);
|
|
269
|
-
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") ||
|
|
270
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") ||
|
|
271
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") ||
|
|
272
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`) ||
|
|
273
|
-
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) => {
|
|
274
335
|
const newModel = Array.from(keys)[0];
|
|
275
336
|
setSelectedModel(newModel);
|
|
276
337
|
if (newModel === "dall-e-2") {
|
|
@@ -280,16 +341,29 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
280
341
|
}
|
|
281
342
|
}
|
|
282
343
|
}, description: t("image.model_description") ||
|
|
283
|
-
"DALL-E 3 offre la meilleure qualité", children: [_jsx(SelectItem, { children: t("image.
|
|
284
|
-
|
|
285
|
-
"
|
|
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
|
|
286
349
|
? t("button.generating") || "Génération..."
|
|
287
|
-
: t("button.generate") || "Générer" })] })) : (_jsx(Button, { color: "primary", onPress: () =>
|
|
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
|
|
288
358
|
? t("button.generating") || "Génération..."
|
|
289
|
-
:
|
|
290
|
-
|
|
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") ||
|
|
291
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") ||
|
|
292
|
-
"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) => {
|
|
293
367
|
const newModel = Array.from(keys)[0];
|
|
294
368
|
setSelectedModel(newModel);
|
|
295
369
|
if (newModel === "dall-e-2") {
|
|
@@ -298,8 +372,21 @@ export function ImageGenerative({ defaultPrompt = "", model = "dall-e-3", size =
|
|
|
298
372
|
setSelectedSize("1024x1024");
|
|
299
373
|
}
|
|
300
374
|
}
|
|
301
|
-
}, 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 (
|
|
302
|
-
|
|
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
|
|
303
390
|
? t("button.generating") || "Génération..."
|
|
304
391
|
: t("button.generate") || "Générer" })] })] }) })] }));
|
|
305
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-[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, { value: userInput, onChange: (e) => handleInputChange(e.target.value), placeholder: placeholder ||
|
|
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) {
|
|
@@ -111,7 +114,9 @@ export function TextareaGenerative({ defaultPrompt = "", model = "gpt-4o-mini",
|
|
|
111
114
|
}
|
|
112
115
|
}, disabled: disabled ||
|
|
113
116
|
isGenerating ||
|
|
114
|
-
(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 &&
|
|
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") ||
|
|
115
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") ||
|
|
116
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") ||
|
|
117
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PromptDetailPageServer.d.ts","sourceRoot":"","sources":["../../../src/web/public/PromptDetailPageServer.tsx"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAMhC,UAAU,2BAA2B;IACnC,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,YAAY,CAAC,EAAE,GAAG,CAAC;CACpB;AAED,wBAAsB,gBAAgB,CAAC,EACrC,MAAM,EACN,YAAY,GACb,EAAE,2BAA2B,GAAG,OAAO,CAAC,QAAQ,CAAC,CAgCjD;AAED,wBAA8B,sBAAsB,CAAC,EACnD,MAAM,GACP,EAAE,2BAA2B,oDA4C7B;AAGD,OAAO,EAAE,sBAAsB,EAAE,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { getSupabaseServiceClient } from "@lastbrain/core/server";
|
|
3
|
+
import { PromptDetailPage } from "./PromptDetailPage";
|
|
4
|
+
import { notFound } from "next/navigation";
|
|
5
|
+
import { headers } from "next/headers";
|
|
6
|
+
export async function generateMetadata({ params, localeConfig, }) {
|
|
7
|
+
if (!params) {
|
|
8
|
+
return {
|
|
9
|
+
title: "Prompt Not Found",
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
const { slug } = await params;
|
|
13
|
+
const supabase = getSupabaseServiceClient();
|
|
14
|
+
const { data: prompt } = await supabase
|
|
15
|
+
.from("public_prompts_with_stats")
|
|
16
|
+
.select("*")
|
|
17
|
+
.eq("slug", slug)
|
|
18
|
+
.single();
|
|
19
|
+
if (!prompt) {
|
|
20
|
+
return {
|
|
21
|
+
title: "Prompt Not Found",
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
title: `${prompt.title} - AI Prompt Library`,
|
|
26
|
+
description: prompt.content.substring(0, 160),
|
|
27
|
+
keywords: prompt.tags?.join(", ") || "",
|
|
28
|
+
openGraph: {
|
|
29
|
+
title: prompt.title,
|
|
30
|
+
description: prompt.content.substring(0, 160),
|
|
31
|
+
type: "article",
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export default async function PromptDetailPageServer({ params, }) {
|
|
36
|
+
console.log("[PromptDetailServer] Starting, params:", params);
|
|
37
|
+
if (!params) {
|
|
38
|
+
console.log("[PromptDetailServer] No params, calling notFound");
|
|
39
|
+
notFound();
|
|
40
|
+
}
|
|
41
|
+
const { slug } = await params;
|
|
42
|
+
console.log("[PromptDetailServer] Slug extracted:", slug);
|
|
43
|
+
const supabase = getSupabaseServiceClient();
|
|
44
|
+
const { data: prompt, error } = await supabase
|
|
45
|
+
.from("public_prompts_with_stats")
|
|
46
|
+
.select("*")
|
|
47
|
+
.eq("slug", slug)
|
|
48
|
+
.single();
|
|
49
|
+
console.log("[PromptDetailServer] Query result:", { prompt, error });
|
|
50
|
+
if (error || !prompt) {
|
|
51
|
+
console.log("[PromptDetailServer] Prompt not found or error:", error);
|
|
52
|
+
notFound();
|
|
53
|
+
}
|
|
54
|
+
// Increment view count with IP tracking
|
|
55
|
+
const headersList = await headers();
|
|
56
|
+
const ip = headersList.get("x-forwarded-for") ||
|
|
57
|
+
headersList.get("x-real-ip") ||
|
|
58
|
+
"unknown";
|
|
59
|
+
await supabase.rpc("increment_prompt_stat_with_ip", {
|
|
60
|
+
p_prompt_id: prompt.id,
|
|
61
|
+
p_stat_type: "views",
|
|
62
|
+
p_ip_address: ip,
|
|
63
|
+
});
|
|
64
|
+
console.log("[PromptDetailServer] Returning component with prompt:", prompt.id);
|
|
65
|
+
return _jsx(PromptDetailPage, { initialPrompt: prompt, slug: slug });
|
|
66
|
+
}
|
|
67
|
+
// Export nommé pour la génération de page du build system
|
|
68
|
+
export { PromptDetailPageServer };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublicPromptsPage.d.ts","sourceRoot":"","sources":["../../../src/web/public/PublicPromptsPage.tsx"],"names":[],"mappings":"AAAA;;GAEG;AA6CH,wBAAgB,iBAAiB,4CA0ShC"}
|