@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.
Files changed (365) hide show
  1. package/README.md +52 -1
  2. package/dist/ai.build.config.d.ts.map +1 -1
  3. package/dist/ai.build.config.js +508 -9
  4. package/dist/api/admin/ai-provider-models/[id].d.ts +18 -0
  5. package/dist/api/admin/ai-provider-models/[id].d.ts.map +1 -0
  6. package/dist/api/admin/ai-provider-models/[id].js +58 -0
  7. package/dist/api/admin/ai-provider-models.d.ts +20 -0
  8. package/dist/api/admin/ai-provider-models.d.ts.map +1 -0
  9. package/dist/api/admin/ai-provider-models.js +26 -0
  10. package/dist/api/admin/ai-providers/[key].d.ts +18 -0
  11. package/dist/api/admin/ai-providers/[key].d.ts.map +1 -0
  12. package/dist/api/admin/ai-providers/[key].js +55 -0
  13. package/dist/api/admin/ai-providers.d.ts +20 -0
  14. package/dist/api/admin/ai-providers.d.ts.map +1 -0
  15. package/dist/api/admin/ai-providers.js +26 -0
  16. package/dist/api/admin/billing-analytics.d.ts +43 -0
  17. package/dist/api/admin/billing-analytics.d.ts.map +1 -0
  18. package/dist/api/admin/billing-analytics.js +144 -0
  19. package/dist/api/admin/global-ai-settings.d.ts +14 -0
  20. package/dist/api/admin/global-ai-settings.d.ts.map +1 -0
  21. package/dist/api/admin/global-ai-settings.js +63 -0
  22. package/dist/api/admin/token-packs/[id].d.ts.map +1 -1
  23. package/dist/api/admin/token-packs/[id].js +3 -2
  24. package/dist/api/admin/token-packs.d.ts.map +1 -1
  25. package/dist/api/admin/token-packs.js +3 -2
  26. package/dist/api/admin/user-monthly-details.d.ts +49 -0
  27. package/dist/api/admin/user-monthly-details.d.ts.map +1 -0
  28. package/dist/api/admin/user-monthly-details.js +140 -0
  29. package/dist/api/admin/user-quota.d.ts +21 -0
  30. package/dist/api/admin/user-quota.d.ts.map +1 -0
  31. package/dist/api/admin/user-quota.js +59 -0
  32. package/dist/api/admin/user-token/[id].d.ts.map +1 -1
  33. package/dist/api/admin/user-token/[id].js +2 -1
  34. package/dist/api/admin/user-token.d.ts +5 -2
  35. package/dist/api/admin/user-token.d.ts.map +1 -1
  36. package/dist/api/admin/user-token.js +91 -17
  37. package/dist/api/admin/user-usage-by-model.d.ts +22 -0
  38. package/dist/api/admin/user-usage-by-model.d.ts.map +1 -0
  39. package/dist/api/admin/user-usage-by-model.js +78 -0
  40. package/dist/api/admin/user-wallet-analytics.d.ts +15 -0
  41. package/dist/api/admin/user-wallet-analytics.d.ts.map +1 -0
  42. package/dist/api/admin/user-wallet-analytics.js +67 -0
  43. package/dist/api/admin/wallet-repair/route.d.ts +30 -0
  44. package/dist/api/admin/wallet-repair/route.d.ts.map +1 -0
  45. package/dist/api/admin/wallet-repair/route.js +63 -0
  46. package/dist/api/auth/ai-model-settings.d.ts +21 -0
  47. package/dist/api/auth/ai-model-settings.d.ts.map +1 -0
  48. package/dist/api/auth/ai-model-settings.js +86 -0
  49. package/dist/api/auth/ai-settings.d.ts +17 -0
  50. package/dist/api/auth/ai-settings.d.ts.map +1 -0
  51. package/dist/api/auth/ai-settings.js +87 -0
  52. package/dist/api/auth/api-keys/[id].d.ts +17 -0
  53. package/dist/api/auth/api-keys/[id].d.ts.map +1 -0
  54. package/dist/api/auth/api-keys/[id].js +66 -0
  55. package/dist/api/auth/api-keys.d.ts +19 -0
  56. package/dist/api/auth/api-keys.d.ts.map +1 -0
  57. package/dist/api/auth/api-keys.js +94 -0
  58. package/dist/api/auth/create-checkout.d.ts +1 -1
  59. package/dist/api/auth/create-checkout.d.ts.map +1 -1
  60. package/dist/api/auth/create-checkout.js +8 -6
  61. package/dist/api/auth/generate-image.d.ts +2 -2
  62. package/dist/api/auth/generate-image.d.ts.map +1 -1
  63. package/dist/api/auth/generate-image.js +404 -104
  64. package/dist/api/auth/generate-text.d.ts +3 -2
  65. package/dist/api/auth/generate-text.d.ts.map +1 -1
  66. package/dist/api/auth/generate-text.js +130 -58
  67. package/dist/api/auth/process-ocr.d.ts +9 -0
  68. package/dist/api/auth/process-ocr.d.ts.map +1 -0
  69. package/dist/api/auth/process-ocr.js +43 -0
  70. package/dist/api/auth/prompts/stats.d.ts +14 -0
  71. package/dist/api/auth/prompts/stats.d.ts.map +1 -0
  72. package/dist/api/auth/prompts/stats.js +46 -0
  73. package/dist/api/auth/prompts.d.ts +26 -0
  74. package/dist/api/auth/prompts.d.ts.map +1 -0
  75. package/dist/api/auth/prompts.js +175 -0
  76. package/dist/api/auth/token-balance.d.ts +26 -0
  77. package/dist/api/auth/token-balance.d.ts.map +1 -0
  78. package/dist/api/auth/token-balance.js +47 -0
  79. package/dist/api/auth/token-checkout.d.ts.map +1 -1
  80. package/dist/api/auth/token-checkout.js +22 -4
  81. package/dist/api/auth/token-packs.d.ts.map +1 -1
  82. package/dist/api/auth/token-packs.js +2 -1
  83. package/dist/api/auth/usage-by-model.d.ts +25 -0
  84. package/dist/api/auth/usage-by-model.d.ts.map +1 -0
  85. package/dist/api/auth/usage-by-model.js +95 -0
  86. package/dist/api/auth/usage.d.ts +26 -0
  87. package/dist/api/auth/usage.d.ts.map +1 -0
  88. package/dist/api/auth/usage.js +127 -0
  89. package/dist/api/auth/user-tokens.d.ts.map +1 -1
  90. package/dist/api/auth/user-tokens.js +36 -2
  91. package/dist/api/auth/wallet/route.d.ts +17 -0
  92. package/dist/api/auth/wallet/route.d.ts.map +1 -0
  93. package/dist/api/auth/wallet/route.js +68 -0
  94. package/dist/api/auth/wallet.d.ts +16 -0
  95. package/dist/api/auth/wallet.d.ts.map +1 -0
  96. package/dist/api/auth/wallet.js +71 -0
  97. package/dist/api/public/gateway-models.d.ts +25 -0
  98. package/dist/api/public/gateway-models.d.ts.map +1 -0
  99. package/dist/api/public/gateway-models.js +49 -0
  100. package/dist/api/public/pricing-summary.d.ts +46 -0
  101. package/dist/api/public/pricing-summary.d.ts.map +1 -0
  102. package/dist/api/public/pricing-summary.js +70 -0
  103. package/dist/api/public/prompts/[id]/stats.d.ts +15 -0
  104. package/dist/api/public/prompts/[id]/stats.d.ts.map +1 -0
  105. package/dist/api/public/prompts/[id]/stats.js +37 -0
  106. package/dist/api/public/prompts/[id]/view.d.ts +15 -0
  107. package/dist/api/public/prompts/[id]/view.d.ts.map +1 -0
  108. package/dist/api/public/prompts/[id]/view.js +41 -0
  109. package/dist/api/public/prompts/slug/[slug].d.ts +15 -0
  110. package/dist/api/public/prompts/slug/[slug].d.ts.map +1 -0
  111. package/dist/api/public/prompts/slug/[slug].js +40 -0
  112. package/dist/api/public/prompts/user/[username].d.ts +17 -0
  113. package/dist/api/public/prompts/user/[username].d.ts.map +1 -0
  114. package/dist/api/public/prompts/user/[username].js +51 -0
  115. package/dist/api/public/prompts.d.ts +15 -0
  116. package/dist/api/public/prompts.d.ts.map +1 -0
  117. package/dist/api/public/prompts.js +45 -0
  118. package/dist/api/public/token-packs.d.ts +11 -0
  119. package/dist/api/public/token-packs.d.ts.map +1 -0
  120. package/dist/api/public/token-packs.js +25 -0
  121. package/dist/api/public/token-pricing.d.ts +44 -0
  122. package/dist/api/public/token-pricing.d.ts.map +1 -0
  123. package/dist/api/public/token-pricing.js +168 -0
  124. package/dist/api/public/v1/_lib/artifacts.d.ts +52 -0
  125. package/dist/api/public/v1/_lib/artifacts.d.ts.map +1 -0
  126. package/dist/api/public/v1/_lib/artifacts.js +217 -0
  127. package/dist/api/public/v1/_lib/auth.d.ts +43 -0
  128. package/dist/api/public/v1/_lib/auth.d.ts.map +1 -0
  129. package/dist/api/public/v1/_lib/auth.js +108 -0
  130. package/dist/api/public/v1/_lib/errors.d.ts +17 -0
  131. package/dist/api/public/v1/_lib/errors.d.ts.map +1 -0
  132. package/dist/api/public/v1/_lib/errors.js +16 -0
  133. package/dist/api/public/v1/_lib/log.d.ts +29 -0
  134. package/dist/api/public/v1/_lib/log.d.ts.map +1 -0
  135. package/dist/api/public/v1/_lib/log.js +68 -0
  136. package/dist/api/public/v1/_lib/quota.d.ts +24 -0
  137. package/dist/api/public/v1/_lib/quota.d.ts.map +1 -0
  138. package/dist/api/public/v1/_lib/quota.js +118 -0
  139. package/dist/api/public/v1/_lib/router.d.ts +54 -0
  140. package/dist/api/public/v1/_lib/router.d.ts.map +1 -0
  141. package/dist/api/public/v1/_lib/router.js +119 -0
  142. package/dist/api/public/v1/connect.d.ts +20 -0
  143. package/dist/api/public/v1/connect.d.ts.map +1 -0
  144. package/dist/api/public/v1/connect.js +119 -0
  145. package/dist/api/public/v1/doc.d.ts +239 -0
  146. package/dist/api/public/v1/doc.d.ts.map +1 -0
  147. package/dist/api/public/v1/doc.js +253 -0
  148. package/dist/api/public/v1/history/[id].d.ts +92 -0
  149. package/dist/api/public/v1/history/[id].d.ts.map +1 -0
  150. package/dist/api/public/v1/history/[id].js +176 -0
  151. package/dist/api/public/v1/history.d.ts +30 -0
  152. package/dist/api/public/v1/history.d.ts.map +1 -0
  153. package/dist/api/public/v1/history.js +142 -0
  154. package/dist/api/public/v1/image-ai.d.ts +24 -0
  155. package/dist/api/public/v1/image-ai.d.ts.map +1 -0
  156. package/dist/api/public/v1/image-ai.js +233 -0
  157. package/dist/api/public/v1/prompts.d.ts +19 -0
  158. package/dist/api/public/v1/prompts.d.ts.map +1 -0
  159. package/dist/api/public/v1/prompts.js +107 -0
  160. package/dist/api/public/v1/provider.d.ts +16 -0
  161. package/dist/api/public/v1/provider.d.ts.map +1 -0
  162. package/dist/api/public/v1/provider.js +130 -0
  163. package/dist/api/public/v1/purchase.d.ts +11 -0
  164. package/dist/api/public/v1/purchase.d.ts.map +1 -0
  165. package/dist/api/public/v1/purchase.js +18 -0
  166. package/dist/api/public/v1/status.d.ts +35 -0
  167. package/dist/api/public/v1/status.d.ts.map +1 -0
  168. package/dist/api/public/v1/status.js +163 -0
  169. package/dist/api/public/v1/text-ai.d.ts +26 -0
  170. package/dist/api/public/v1/text-ai.d.ts.map +1 -0
  171. package/dist/api/public/v1/text-ai.js +239 -0
  172. package/dist/api/public/webhook.d.ts.map +1 -1
  173. package/dist/api/public/webhook.js +50 -39
  174. package/dist/api/track-usage.d.ts +12 -0
  175. package/dist/api/track-usage.d.ts.map +1 -0
  176. package/dist/api/track-usage.js +37 -0
  177. package/dist/components/Doc.d.ts.map +1 -1
  178. package/dist/components/Doc.js +1 -1
  179. package/dist/components/DocUsageCustom.js +6 -6
  180. package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
  181. package/dist/components/admin/UserTokenTab.js +170 -23
  182. package/dist/components/auth/AuthDashboardAi.d.ts +2 -0
  183. package/dist/components/auth/AuthDashboardAi.d.ts.map +1 -0
  184. package/dist/components/auth/AuthDashboardAi.js +53 -0
  185. package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts +277 -0
  186. package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts.map +1 -0
  187. package/dist/docs/REFACTORING_BILLING_GUIDE.js +276 -0
  188. package/dist/index.d.ts +15 -1
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +18 -1
  191. package/dist/scripts/migrate-tokens-to-wallet.d.ts +13 -0
  192. package/dist/scripts/migrate-tokens-to-wallet.d.ts.map +1 -0
  193. package/dist/scripts/migrate-tokens-to-wallet.js +165 -0
  194. package/dist/server/__tests__/billing.test.d.ts +5 -0
  195. package/dist/server/__tests__/billing.test.d.ts.map +1 -0
  196. package/dist/server/__tests__/billing.test.js +223 -0
  197. package/dist/server/ai-client.d.ts +59 -0
  198. package/dist/server/ai-client.d.ts.map +1 -0
  199. package/dist/server/ai-client.js +111 -0
  200. package/dist/server/ai-generation-service.d.ts +66 -0
  201. package/dist/server/ai-generation-service.d.ts.map +1 -0
  202. package/dist/server/ai-generation-service.js +274 -0
  203. package/dist/server/billing.d.ts +200 -0
  204. package/dist/server/billing.d.ts.map +1 -0
  205. package/dist/server/billing.js +488 -0
  206. package/dist/server/gateway-service.d.ts +13 -0
  207. package/dist/server/gateway-service.d.ts.map +1 -0
  208. package/dist/server/gateway-service.js +161 -0
  209. package/dist/server/global-settings.d.ts +16 -0
  210. package/dist/server/global-settings.d.ts.map +1 -0
  211. package/dist/server/global-settings.js +42 -0
  212. package/dist/server/model-filter.d.ts +25 -0
  213. package/dist/server/model-filter.d.ts.map +1 -0
  214. package/dist/server/model-filter.js +240 -0
  215. package/dist/server/ocr.d.ts +39 -0
  216. package/dist/server/ocr.d.ts.map +1 -0
  217. package/dist/server/ocr.js +280 -0
  218. package/dist/server/openai-client.d.ts +19 -0
  219. package/dist/server/openai-client.d.ts.map +1 -0
  220. package/dist/server/openai-client.js +26 -0
  221. package/dist/server/pricing-config.d.ts +18 -0
  222. package/dist/server/pricing-config.d.ts.map +1 -0
  223. package/dist/server/pricing-config.js +94 -0
  224. package/dist/server/pricing-validator.d.ts +41 -0
  225. package/dist/server/pricing-validator.d.ts.map +1 -0
  226. package/dist/server/pricing-validator.js +113 -0
  227. package/dist/server/pricing.d.ts +121 -0
  228. package/dist/server/pricing.d.ts.map +1 -0
  229. package/dist/server/pricing.js +225 -0
  230. package/dist/server/quota.d.ts +66 -0
  231. package/dist/server/quota.d.ts.map +1 -0
  232. package/dist/server/quota.js +538 -0
  233. package/dist/server/wallet-repair.d.ts +32 -0
  234. package/dist/server/wallet-repair.d.ts.map +1 -0
  235. package/dist/server/wallet-repair.js +189 -0
  236. package/dist/server.d.ts +13 -1
  237. package/dist/server.d.ts.map +1 -1
  238. package/dist/server.js +87 -16
  239. package/dist/sitemap/handlers/prompts.d.ts +6 -0
  240. package/dist/sitemap/handlers/prompts.d.ts.map +1 -0
  241. package/dist/sitemap/handlers/prompts.js +72 -0
  242. package/dist/sitemap/handlers/users.d.ts +6 -0
  243. package/dist/sitemap/handlers/users.d.ts.map +1 -0
  244. package/dist/sitemap/handlers/users.js +80 -0
  245. package/dist/sitemap/manifest.d.ts +8 -0
  246. package/dist/sitemap/manifest.d.ts.map +1 -0
  247. package/dist/sitemap/manifest.js +27 -0
  248. package/dist/types/gateway.d.ts +40 -0
  249. package/dist/types/gateway.d.ts.map +1 -0
  250. package/dist/types/gateway.js +4 -0
  251. package/dist/types/quota.d.ts +74 -0
  252. package/dist/types/quota.d.ts.map +1 -0
  253. package/dist/types/quota.js +11 -0
  254. package/dist/utils/date.d.ts +7 -0
  255. package/dist/utils/date.d.ts.map +1 -0
  256. package/dist/utils/date.js +17 -0
  257. package/dist/web/admin/AdminTokenPacksPage.js +1 -1
  258. package/dist/web/admin/BillingAnalyticsPage.d.ts +2 -0
  259. package/dist/web/admin/BillingAnalyticsPage.d.ts.map +1 -0
  260. package/dist/web/admin/BillingAnalyticsPage.js +141 -0
  261. package/dist/web/admin/GlobalAISettingsPage.d.ts +2 -0
  262. package/dist/web/admin/GlobalAISettingsPage.d.ts.map +1 -0
  263. package/dist/web/admin/GlobalAISettingsPage.js +93 -0
  264. package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
  265. package/dist/web/admin/UserTokenPage.js +20 -7
  266. package/dist/web/auth/AISettingsPage.d.ts +2 -0
  267. package/dist/web/auth/AISettingsPage.d.ts.map +1 -0
  268. package/dist/web/auth/AISettingsPage.js +258 -0
  269. package/dist/web/auth/APIKeysPage.d.ts +2 -0
  270. package/dist/web/auth/APIKeysPage.d.ts.map +1 -0
  271. package/dist/web/auth/APIKeysPage.js +154 -0
  272. package/dist/web/auth/HistoryPage.d.ts +2 -0
  273. package/dist/web/auth/HistoryPage.d.ts.map +1 -0
  274. package/dist/web/auth/HistoryPage.js +279 -0
  275. package/dist/web/auth/PromptsPage.d.ts +5 -0
  276. package/dist/web/auth/PromptsPage.d.ts.map +1 -0
  277. package/dist/web/auth/PromptsPage.js +137 -0
  278. package/dist/web/auth/TokenPage.d.ts.map +1 -1
  279. package/dist/web/auth/TokenPage.js +88 -31
  280. package/dist/web/auth/UsageAndTokensPage.d.ts +2 -0
  281. package/dist/web/auth/UsageAndTokensPage.d.ts.map +1 -0
  282. package/dist/web/auth/UsageAndTokensPage.js +157 -0
  283. package/dist/web/auth/UsagePage.d.ts +2 -0
  284. package/dist/web/auth/UsagePage.d.ts.map +1 -0
  285. package/dist/web/auth/UsagePage.js +62 -0
  286. package/dist/web/auth/components/ApiKeyFilterSelect.d.ts +13 -0
  287. package/dist/web/auth/components/ApiKeyFilterSelect.d.ts.map +1 -0
  288. package/dist/web/auth/components/ApiKeyFilterSelect.js +16 -0
  289. package/dist/web/auth/components/ModelUsageTable.d.ts +19 -0
  290. package/dist/web/auth/components/ModelUsageTable.d.ts.map +1 -0
  291. package/dist/web/auth/components/ModelUsageTable.js +37 -0
  292. package/dist/web/auth/components/PurchaseButton.d.ts +7 -0
  293. package/dist/web/auth/components/PurchaseButton.d.ts.map +1 -0
  294. package/dist/web/auth/components/PurchaseButton.js +13 -0
  295. package/dist/web/auth/components/TokenHistoryCard.d.ts +20 -0
  296. package/dist/web/auth/components/TokenHistoryCard.d.ts.map +1 -0
  297. package/dist/web/auth/components/TokenHistoryCard.js +76 -0
  298. package/dist/web/auth/components/TokenKpiGrid.d.ts +24 -0
  299. package/dist/web/auth/components/TokenKpiGrid.d.ts.map +1 -0
  300. package/dist/web/auth/components/TokenKpiGrid.js +38 -0
  301. package/dist/web/auth/components/UsageByDayChart.d.ts +11 -0
  302. package/dist/web/auth/components/UsageByDayChart.d.ts.map +1 -0
  303. package/dist/web/auth/components/UsageByDayChart.js +32 -0
  304. package/dist/web/auth/components/UsageByModelBarChart.d.ts +12 -0
  305. package/dist/web/auth/components/UsageByModelBarChart.d.ts.map +1 -0
  306. package/dist/web/auth/components/UsageByModelBarChart.js +32 -0
  307. package/dist/web/auth/components/WalletStatusCard.d.ts +9 -0
  308. package/dist/web/auth/components/WalletStatusCard.d.ts.map +1 -0
  309. package/dist/web/auth/components/WalletStatusCard.js +50 -0
  310. package/dist/web/components/ImageGenerative.d.ts +3 -1
  311. package/dist/web/components/ImageGenerative.d.ts.map +1 -1
  312. package/dist/web/components/ImageGenerative.js +139 -52
  313. package/dist/web/components/TextareaGenerative.d.ts +3 -1
  314. package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
  315. package/dist/web/components/TextareaGenerative.js +10 -5
  316. package/dist/web/public/PromptDetailPage.d.ts +25 -0
  317. package/dist/web/public/PromptDetailPage.d.ts.map +1 -0
  318. package/dist/web/public/PromptDetailPage.js +71 -0
  319. package/dist/web/public/PromptDetailPageServer.d.ts +15 -0
  320. package/dist/web/public/PromptDetailPageServer.d.ts.map +1 -0
  321. package/dist/web/public/PromptDetailPageServer.js +68 -0
  322. package/dist/web/public/PublicPromptsPage.d.ts +5 -0
  323. package/dist/web/public/PublicPromptsPage.d.ts.map +1 -0
  324. package/dist/web/public/PublicPromptsPage.js +110 -0
  325. package/dist/web/public/PurchaseTokensPage.d.ts +2 -0
  326. package/dist/web/public/PurchaseTokensPage.d.ts.map +1 -0
  327. package/dist/web/public/PurchaseTokensPage.js +98 -0
  328. package/dist/web/public/UserAvatar.d.ts +13 -0
  329. package/dist/web/public/UserAvatar.d.ts.map +1 -0
  330. package/dist/web/public/UserAvatar.js +13 -0
  331. package/dist/web/public/UserDetailPageServer.d.ts +15 -0
  332. package/dist/web/public/UserDetailPageServer.d.ts.map +1 -0
  333. package/dist/web/public/UserDetailPageServer.js +31 -0
  334. package/dist/web/public/UserPromptsPage.d.ts +9 -0
  335. package/dist/web/public/UserPromptsPage.d.ts.map +1 -0
  336. package/dist/web/public/UserPromptsPage.js +112 -0
  337. package/dist/web/public/UserPromptsPageServer.d.ts +15 -0
  338. package/dist/web/public/UserPromptsPageServer.d.ts.map +1 -0
  339. package/dist/web/public/UserPromptsPageServer.js +31 -0
  340. package/package.json +18 -9
  341. package/supabase/migrations/20251125000000_ai_tokens.sql +7 -0
  342. package/supabase/migrations/20260123100002_user_token_quota_monthly copy.sql +173 -0
  343. package/supabase/migrations/20260128100003_update_and_add_table.sql +368 -0
  344. package/supabase/migrations/20260128120000_seed_providers_models.sql +78 -0
  345. package/supabase/migrations/20260128131405_add_api_key_id_to_ledgers.sql +41 -0
  346. package/supabase/migrations/20260128140000_ai_artifacts_storage.sql +99 -0
  347. package/supabase/migrations/20260128140002_ai_user_settings.sql +57 -0
  348. package/supabase/migrations/20260128150000_drop_ai_user_settings.sql +21 -0
  349. package/supabase/migrations/20260128160000_wallet_billing_system.sql +192 -0
  350. package/supabase/migrations/20260128160001_wallet_rpc_functions.sql +165 -0
  351. package/supabase/migrations/20260128170000_add_pack_coef_to_token_packs.sql +30 -0
  352. package/supabase/migrations/20260129120000_wallet_view_rpc.sql +41 -0
  353. package/supabase/migrations/20260129220003_update_pack_margins.sql +31 -0
  354. package/supabase/migrations/20260129330004_ai_user_prompts.sql +151 -0
  355. package/supabase/migrations/20260129330005_ai_prompts_ip_tracking.sql +92 -0
  356. package/supabase/migrations/20260129330006_ai_prompts_slug.sql +64 -0
  357. package/supabase/migrations/20260129330007_ai_prompts_view_slug.sql +26 -0
  358. package/supabase/migrations/20260129440000_ai_prompts_view_username.sql +33 -0
  359. package/supabase/migrations/20260129450000_ai_prompts_add_lang.sql +40 -0
  360. package/supabase/migrations/20260131000000_extract_model_prompt_in_ledger.sql +92 -0
  361. package/supabase/migrations/20260131140000_fix_duplicate_purchases.sql +64 -0
  362. package/supabase/migrations/20260201120000_module-ai_default_models.sql +63 -0
  363. package/supabase/migrations/20260201130000_module-ai_remove_provider_tables.sql +17 -0
  364. package/supabase/migrations-down/20251217120000_user_token_quota_monthly.sql +34 -0
  365. 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 = "dall-e-3", size = "1024x1024", quality = "standard", onChange, onError, className, disabled = false, apiEndpoint = "/api/ai/generate-image", showTokenBalance = true, label, description, defaultStyle = "realistic", hideStyleEditor = false, uploadPath, preview = true, inline = false, }) {
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
- // Calculer le coût estimé
165
- const estimatedCost = () => {
166
- let key = "";
167
- if (selectedModel === "dall-e-2") {
168
- key = `${selectedModel}-${selectedSize}`;
169
- }
170
- else {
171
- key = `${selectedModel}-${selectedSize}-${selectedQuality}`;
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
- return TOKENS_PER_IMAGE_KEY[key] || 0;
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: data.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
- onChange(data.imageUrl, imageResponse);
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
- if (!generatedImage?.imageUrl)
266
+ const imageSrc = generatedImage?.imageUrl || generatedImage?.supabaseImageUrl || null;
267
+ if (!imageSrc)
252
268
  return;
253
269
  try {
254
- const response = await fetch(generatedImage.imageUrl);
255
- const blob = await response.blob();
256
- const url = window.URL.createObjectURL(blob);
257
- const link = document.createElement("a");
258
- link.href = url;
259
- link.download = `generated-image-${Date.now()}.png`;
260
- document.body.appendChild(link);
261
- link.click();
262
- document.body.removeChild(link);
263
- window.URL.revokeObjectURL(url);
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
- console.error(t("error.download_failed") || "Erreur lors du téléchargement:", error);
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: _jsx(Image, { src: generatedImage.imageUrl, alt: generatedImage.prompt, fill: true, className: "object-contain rounded-lg", priority: true }) }), _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") ||
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.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") ||
284
- "Tailles disponibles selon le modèle", children: selectedModel === "dall-e-2" ? (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "256\u00D7256 (3000 tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4000 tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5000 tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6000-8000 tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8000-10000 tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8000-10000 tokens)" }, "1792x1024")] })) }), selectedModel === "dall-e-3" && (_jsxs(Select, { label: t("image.quality_label") || "Qualité", selectedKeys: [selectedQuality], onSelectionChange: (keys) => setSelectedQuality(Array.from(keys)[0]), description: t("image.quality_description") ||
285
- "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 estimé:", " ", estimatedCost().toLocaleString(), " ", t("chip.tokens") || "tokens"] }) })] })), _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
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: () => setIsModalOpen(true), isDisabled: disabled || isGenerating, startContent: _jsx(Wand2, { size: 18 }), className: "w-full", children: isGenerating
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
- : 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 && tokenBalance !== null && (_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") ||
290
- "Génération de l'image en cours... Cela peut prendre quelques instants." })] })), error && (_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(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") ||
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 (3000 tokens)" }, "256x256"), _jsx(SelectItem, { children: "512\u00D7512 (4000 tokens)" }, "512x512"), _jsx(SelectItem, { children: "1024\u00D71024 (5000 tokens)" }, "1024x1024")] })) : (_jsxs(_Fragment, { children: [_jsx(SelectItem, { children: "1024\u00D71024 (6000-8000 tokens)" }, "1024x1024"), _jsx(SelectItem, { children: "1024\u00D71792 Portrait (8000-10000 tokens)" }, "1024x1792"), _jsx(SelectItem, { children: "1792\u00D71024 Paysage (8000-10000 tokens)" }, "1792x1024")] })) }), selectedModel === "dall-e-3" && (_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 estim\u00E9: ", estimatedCost().toLocaleString(), " tokens"] }) })] })), 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") ||
302
- "Aperçu de l'image actuelle:" }), _jsx("div", { className: "relative w-full", children: _jsx(Image, { src: generatedImage.imageUrl, alt: "Current", fill: true, className: "object-cover rounded" }) })] }))] }) }), _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
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":"AAqBA,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;CAClC;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,sBAA8B,GAC/B,EAAE,uBAAuB,2CA2TzB"}
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 && tokenBalance !== null && (_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("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 })] }))] }), !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") ||
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,5 @@
1
+ /**
2
+ * PublicPromptsPage - Browse and discover public AI prompts
3
+ */
4
+ export declare function PublicPromptsPage(): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=PublicPromptsPage.d.ts.map
@@ -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"}