@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,40 +1,11 @@
1
1
  import { NextResponse } from "next/server";
2
- import { getSupabaseServerClient } from "@lastbrain/core/server";
3
- import { deductTokens, getTokenBalance } from "../../server";
4
- import OpenAI from "openai";
5
- let openai = null;
6
- function getOpenAIClient() {
7
- if (!openai) {
8
- openai = new OpenAI({
9
- apiKey: process.env.OPENAI_API_KEY,
10
- });
11
- }
12
- return openai;
13
- }
14
- // Coûts des images selon le modèle et la taille
15
- const IMAGE_COSTS = {
16
- "dall-e-2-256x256": 0.016,
17
- "dall-e-2-512x512": 0.018,
18
- "dall-e-2-1024x1024": 0.02,
19
- "dall-e-3-1024x1024-standard": 0.04,
20
- "dall-e-3-1024x1024-hd": 0.08,
21
- "dall-e-3-1024x1792-standard": 0.08,
22
- "dall-e-3-1024x1792-hd": 0.12,
23
- "dall-e-3-1792x1024-standard": 0.08,
24
- "dall-e-3-1792x1024-hd": 0.12,
25
- };
26
- // Coût en tokens par combinaison modèle/taille(/qualité)
27
- const TOKENS_PER_IMAGE_KEY = {
28
- "dall-e-2-256x256": 3000,
29
- "dall-e-2-512x512": 4000,
30
- "dall-e-2-1024x1024": 5000,
31
- "dall-e-3-1024x1024-standard": 6000,
32
- "dall-e-3-1024x1024-hd": 8000,
33
- "dall-e-3-1024x1792-standard": 8000,
34
- "dall-e-3-1024x1792-hd": 10000,
35
- "dall-e-3-1792x1024-standard": 8000,
36
- "dall-e-3-1792x1024-hd": 10000,
37
- };
2
+ import { getSupabaseServerClient, getSupabaseServiceClient, } from "@lastbrain/core/server";
3
+ import { imageAI } from "../../server/ai-client";
4
+ import { logger } from "@lastbrain/core";
5
+ import { computeAndDebitAIUsage, computeProviderCostUsd, getWalletState, } from "../../server/billing";
6
+ import { isModelEnabled } from "../../server/model-filter";
7
+ import { validateImagePricing } from "../../server/pricing-validator";
8
+ import { getDefaultImageModel } from "../../server/global-settings";
38
9
  export async function POST(request) {
39
10
  try {
40
11
  const supabase = await getSupabaseServerClient();
@@ -45,97 +16,426 @@ export async function POST(request) {
45
16
  }
46
17
  // L'utilisateur est déjà authentifié grâce au middleware
47
18
  const body = await request.json();
48
- const { prompt, model = "dall-e-3", size = "1024x1024", quality = "standard", uploadPath, } = body;
19
+ // Get default image model from global settings if not provided
20
+ const defaultModel = await getDefaultImageModel();
21
+ const { prompt, model = defaultModel, // Use global default instead of hardcoded
22
+ size = "1024x1024", quality = "standard", uploadPath, recipeId, isPublic, } = body;
49
23
  if (!prompt) {
50
24
  return NextResponse.json({ error: "Le prompt est requis" }, { status: 400 });
51
25
  }
52
- // Calculer le coût et les tokens
53
- const costKey = model === "dall-e-3" ? `${model}-${size}-${quality}` : `${model}-${size}`;
54
- const cost = IMAGE_COSTS[costKey] || 0.04;
55
- const tokensRequired = TOKENS_PER_IMAGE_KEY[costKey] ?? 6000; // fallback safe
56
- // Vérifier le solde de tokens
57
- const currentBalance = await getTokenBalance(user.id);
58
- // Autoriser la génération tant que le solde est positif, même si la déduction rend le solde négatif
59
- if (currentBalance <= 0) {
60
- return NextResponse.json({
61
- error: "Solde insuffisant. Votre solde de tokens est nul ou négatif.",
62
- }, { status: 402 });
63
- }
64
- let imageParams;
65
- if (model === "dall-e-3") {
66
- imageParams = {
67
- model: "dall-e-3",
68
- prompt,
69
- n: 1,
70
- response_format: "url",
71
- size: size,
72
- quality: quality,
26
+ // Vérifier que l'utilisateur n'est pas déjà en solde négatif
27
+ const { data: balanceData } = await supabase
28
+ .from("user_token_balance_v")
29
+ .select("balance")
30
+ .eq("owner_id", user.id)
31
+ .single();
32
+ const purchasedBalance = balanceData?.balance || 0;
33
+ if (purchasedBalance < 0) {
34
+ return NextResponse.json({ error: "Solde insuffisant (compte en négatif). Veuillez recharger." }, { status: 402 });
35
+ }
36
+ // Build full model string
37
+ const fullModel = model.includes("/") ? model : `openai/${model}`;
38
+ // Parse provider/model from fullModel
39
+ const [provider, modelName] = fullModel.includes("/")
40
+ ? fullModel.split("/", 2)
41
+ : ["openai", fullModel];
42
+ // Get real model pricing from gateway for accurate billing
43
+ // Images are always allowed (bypass user settings) but we need real pricing
44
+ const filterResult = await isModelEnabled(user.id, fullModel);
45
+ // If model not in user settings or not found, still allow but use fallback pricing
46
+ if (!filterResult.allowed || !filterResult.model?.pricing?.image) {
47
+ logger.warn(`[generate-image] Model ${fullModel} not found in gateway or missing pricing, using fallback`);
48
+ filterResult.allowed = true;
49
+ filterResult.model = {
50
+ pricing: {
51
+ input: 0,
52
+ output: 0,
53
+ image: 0.02, // Fallback: $0.02 per image (dall-e-3 standard)
54
+ },
73
55
  };
74
56
  }
75
57
  else {
76
- // DALL-E 2
77
- const validSizes = ["256x256", "512x512", "1024x1024"];
78
- imageParams = {
79
- model: "dall-e-2",
80
- prompt,
81
- n: 1,
82
- response_format: "url",
83
- size: validSizes.includes(size)
84
- ? size
85
- : "1024x1024",
86
- };
58
+ // Model found with real pricing from gateway
59
+ filterResult.allowed = true;
60
+ logger.info(`[generate-image] Using real pricing from gateway for ${fullModel}: $${filterResult.model.pricing.image} per image`);
61
+ }
62
+ // Prepare pricing for wallet billing
63
+ const pricingValidation = validateImagePricing(filterResult.model?.pricing, fullModel);
64
+ if (!pricingValidation.valid) {
65
+ return NextResponse.json({ error: pricingValidation.error }, { status: 503 });
66
+ }
67
+ const pricing = pricingValidation.pricing;
68
+ // Get current wallet state
69
+ const wallet = await getWalletState(user.id);
70
+ // PRE-CHECK: Simple check - user must have credits (> 0)
71
+ // The real debit with minimums will be computed post-generation
72
+ if (!wallet.walletProviderBudgetUsd ||
73
+ wallet.walletProviderBudgetUsd <= 0) {
74
+ logger.warn(`[generate-image] No credits for user ${user.id}: wallet=$${wallet.walletProviderBudgetUsd}`);
75
+ return NextResponse.json({ error: "Crédits insuffisants pour cette opération" }, { status: 402 });
87
76
  }
88
- // Appeler OpenAI
89
- const openaiClient = getOpenAIClient();
90
- const response = await openaiClient.images.generate(imageParams);
91
- const imageUrl = response.data?.[0]?.url;
77
+ logger.info(`[generate-image] Pre-check passed for user ${user.id}: wallet=$${wallet.walletProviderBudgetUsd.toFixed(6)}`);
78
+ // Call our simplified AI client for image generation
79
+ const imageResult = await imageAI({
80
+ prompt,
81
+ model: fullModel,
82
+ size,
83
+ });
84
+ if (!imageResult.imageUrl) {
85
+ return NextResponse.json({ error: "No image generated" }, { status: 500 });
86
+ }
87
+ const returnedImageUrl = imageResult.imageUrl;
92
88
  let supabaseImageUrl = null;
93
- if (imageUrl && uploadPath) {
94
- // Générer un nom de fichier si uploadPath se termine par '/'
89
+ let imageBytes = null;
90
+ // If we have a base64 data URL, we can convert and upload
91
+ if (returnedImageUrl.startsWith("data:image")) {
92
+ // Extract base64 from data URL
93
+ const base64Data = returnedImageUrl.split(",")[1] || "";
94
+ if (uploadPath) {
95
+ let finalPath = uploadPath;
96
+ if (uploadPath.endsWith("/")) {
97
+ const filename = `recipe-${Date.now()}.png`;
98
+ finalPath = uploadPath + filename;
99
+ }
100
+ const buffer = Buffer.from(base64Data, "base64");
101
+ imageBytes = buffer;
102
+ const contentType = "image/png";
103
+ const bucket = finalPath.split("/")[0] || "app";
104
+ const { error: uploadError } = await supabase.storage
105
+ .from(bucket)
106
+ .upload(finalPath.split("/").slice(1).join("/"), buffer, {
107
+ contentType,
108
+ upsert: true,
109
+ });
110
+ if (uploadError)
111
+ throw new Error("Erreur upload Supabase: " + uploadError.message);
112
+ supabaseImageUrl = finalPath;
113
+ }
114
+ // returnedImageUrl is already in data URL format
115
+ }
116
+ else if (returnedImageUrl && uploadPath) {
117
+ // Télécharger depuis l'URL (OpenAI ou Supabase) puis uploader vers Supabase
95
118
  let finalPath = uploadPath;
96
119
  if (uploadPath.endsWith("/")) {
97
- const ext = (imageUrl.split(".").pop() || "png").split("?")[0];
120
+ const ext = (returnedImageUrl.split(".").pop() || "png").split("?")[0];
98
121
  const filename = `recipe-${Date.now()}.${ext}`;
99
122
  finalPath = uploadPath + filename;
100
123
  }
101
- // 1. Télécharger l'image depuis OpenAI
102
- const imageRes = await fetch(imageUrl);
103
- if (!imageRes.ok)
104
- throw new Error("Erreur lors du téléchargement de l'image OpenAI");
105
- const arrayBuffer = await imageRes.arrayBuffer();
106
- const contentType = imageRes.headers.get("content-type") || "image/png";
107
- // 2. Uploader dans Supabase Storage (bucket à adapter si besoin)
124
+ // Si l'URL provient d'un endpoint Supabase Storage, utiliser l'API storage.download
125
+ // Exemple d'URL public: https://<project>.supabase.co/storage/v1/object/public/<bucket>/<path>
126
+ // Exemple d'URL privée: https://<project>.supabase.co/storage/v1/object/<bucket>/<path>
127
+ let arrayBuffer = null;
128
+ let contentType = "image/png";
129
+ try {
130
+ const parsed = new URL(returnedImageUrl);
131
+ const storagePrefix = "/storage/v1/object/";
132
+ if (parsed.pathname.includes(storagePrefix)) {
133
+ // extraire la partie après /storage/v1/object/
134
+ const after = parsed.pathname.split(storagePrefix)[1] || ""; // e.g. "public/recipes/foo.png" or "app/3561/..."
135
+ const parts = after.split("/");
136
+ // support: optional 'public' segment
137
+ let bucket = parts[0] || "";
138
+ let objectPathParts = parts.slice(1);
139
+ if (bucket === "public") {
140
+ // URL like /object/public/<bucket>/<path>
141
+ bucket = parts[1] || "";
142
+ objectPathParts = parts.slice(2);
143
+ }
144
+ const objectPath = objectPathParts.join("/");
145
+ logger.debug("[generate-image] Detected supabase storage URL", {
146
+ url: returnedImageUrl,
147
+ bucket,
148
+ objectPath,
149
+ });
150
+ // tenter d'utiliser le service client (requiert SUPABASE_SERVICE_ROLE_KEY)
151
+ let supabaseService;
152
+ try {
153
+ supabaseService = await getSupabaseServiceClient();
154
+ }
155
+ catch (err) {
156
+ logger.error("[generate-image] getSupabaseServiceClient() failed — ensure SUPABASE_SERVICE_ROLE_KEY is set", err);
157
+ throw new Error(JSON.stringify({
158
+ message: "Supabase service client non disponible — vérifiez la variable SUPABASE_SERVICE_ROLE_KEY",
159
+ url: returnedImageUrl,
160
+ bucket,
161
+ objectPath,
162
+ inner: err instanceof Error ? err.message : String(err),
163
+ }));
164
+ }
165
+ const { data: downloaded, error: downloadError } = await supabaseService.storage.from(bucket).download(objectPath);
166
+ if (downloadError || !downloaded) {
167
+ const msg = JSON.stringify({
168
+ message: "Supabase storage download failed (service)",
169
+ bucket,
170
+ objectPath,
171
+ error: downloadError
172
+ ? downloadError.message || downloadError
173
+ : null,
174
+ });
175
+ logger.error("[generate-image] " + msg);
176
+ throw new Error(msg);
177
+ }
178
+ arrayBuffer = await downloaded.arrayBuffer();
179
+ contentType = downloaded.type || contentType;
180
+ }
181
+ }
182
+ catch (e) {
183
+ // Si parsing/échec service client -> laisser tomber au fetch plus bas
184
+ logger.debug("[generate-image] supabase storage handling skipped or failed, will fallback to fetch", {
185
+ url: returnedImageUrl,
186
+ error: e instanceof Error ? e.message : String(e),
187
+ });
188
+ }
189
+ // Si on n'a pas réussi à récupérer via storage.download, retomber sur fetch
190
+ if (!arrayBuffer) {
191
+ const imageRes = await fetch(returnedImageUrl);
192
+ if (!imageRes.ok) {
193
+ throw new Error(JSON.stringify({
194
+ message: "Erreur lors du téléchargement",
195
+ url: returnedImageUrl,
196
+ status: imageRes.status,
197
+ }));
198
+ }
199
+ arrayBuffer = await imageRes.arrayBuffer();
200
+ contentType = imageRes.headers.get("content-type") || contentType;
201
+ }
108
202
  const bucket = finalPath.split("/")[0] || "app";
203
+ const bytes = new Uint8Array(arrayBuffer);
204
+ imageBytes = bytes;
109
205
  const { error: uploadError } = await supabase.storage
110
206
  .from(bucket)
111
- .upload(finalPath.split("/").slice(1).join("/"), new Uint8Array(arrayBuffer), { contentType, upsert: true });
207
+ .upload(finalPath.split("/").slice(1).join("/"), bytes, {
208
+ contentType,
209
+ upsert: true,
210
+ });
112
211
  if (uploadError)
113
212
  throw new Error("Erreur upload Supabase: " + uploadError.message);
114
- // 3. Générer l'URL publique
115
213
  supabaseImageUrl = finalPath;
116
214
  }
117
- if (!imageUrl) {
215
+ if (!returnedImageUrl) {
118
216
  throw new Error("Aucune image générée");
119
217
  }
120
- // Déduire les tokens utilisés
121
- const tokenResult = await deductTokens(user.id, tokensRequired, model, prompt, {
122
- size,
123
- quality,
124
- cost,
125
- imageUrl,
126
- });
127
- if (!tokenResult.success) {
218
+ // Token debit is done AFTER recipe/image persistence so we never lose the image update.
219
+ const tokenResult = null;
220
+ // Si la requête demande d'associer l'image à une recette, reproduire
221
+ // le comportement du flux de sélection d'image: copier dans
222
+ // app/{userId}/recipe/{parentId}/{filename} et si public copier vers recipes/{...}
223
+ let imagePathForRecipe = null;
224
+ let publicImagePath = null;
225
+ if (recipeId && imageBytes) {
226
+ try {
227
+ // Récupérer la recette (id ou parent)
228
+ const { data: existingRecipe, error: fetchError } = await supabase
229
+ .from("recipes")
230
+ .select("id, image_path, public_image_path, is_public, slug, parent_recipe_id")
231
+ .or(`id.eq.${recipeId},parent_recipe_id.eq.${recipeId}`)
232
+ .eq("owner_id", user.id)
233
+ .limit(1)
234
+ .single();
235
+ if (!fetchError && existingRecipe) {
236
+ const parentId = existingRecipe.parent_recipe_id || existingRecipe.id;
237
+ // 1. Nettoyer le dossier recipe privé
238
+ const recipeFolder = `${user.id}/recipe/${parentId}`;
239
+ try {
240
+ const { data: existingFiles } = await supabase.storage
241
+ .from("app")
242
+ .list(recipeFolder, { limit: 100 });
243
+ if (existingFiles && existingFiles.length > 0) {
244
+ const filesToDelete = existingFiles
245
+ .filter((f) => f.name)
246
+ .map((f) => `${recipeFolder}/${f.name}`);
247
+ if (filesToDelete.length > 0) {
248
+ await supabase.storage.from("app").remove(filesToDelete);
249
+ }
250
+ }
251
+ }
252
+ catch (e) {
253
+ logger.warn("[generate-image] Could not list/remove old recipe files", e);
254
+ }
255
+ // 2. Uploader dans app/{user}/recipe/{parentId}/{filename}
256
+ const filename = (supabaseImageUrl || "").split("/").pop() ||
257
+ `recipe-${Date.now()}.png`;
258
+ const privateDestinationPath = `${user.id}/recipe/${parentId}/${filename}`;
259
+ try {
260
+ const { error: uploadError } = await supabase.storage
261
+ .from("app")
262
+ .upload(privateDestinationPath, imageBytes, {
263
+ contentType: "image/png",
264
+ upsert: true,
265
+ });
266
+ if (!uploadError) {
267
+ imagePathForRecipe = `/recipe/${parentId}/${filename}`;
268
+ }
269
+ else {
270
+ logger.error("[generate-image] Upload to recipe folder failed", uploadError);
271
+ }
272
+ }
273
+ catch (e) {
274
+ logger.error("[generate-image] Upload to recipe folder exception", e);
275
+ }
276
+ // 3. Si AU MOINS UNE recette de la famille est publique, copier vers recipes bucket
277
+ try {
278
+ const { data: publicRecipes } = await supabase
279
+ .from("recipes")
280
+ .select("id, is_public")
281
+ .or(`id.eq.${parentId},parent_recipe_id.eq.${parentId}`)
282
+ .eq("is_public", true)
283
+ .limit(1);
284
+ const isAnyRecipePublic = existingRecipe.is_public ||
285
+ (publicRecipes && publicRecipes.length > 0);
286
+ if (isAnyRecipePublic) {
287
+ const timestamp = Date.now();
288
+ const publicDestinationPath = `${parentId}-${timestamp}.png`;
289
+ try {
290
+ const { error: publicUploadError } = await supabase.storage
291
+ .from("recipes")
292
+ .upload(publicDestinationPath, imageBytes, {
293
+ contentType: "image/png",
294
+ upsert: true,
295
+ });
296
+ if (!publicUploadError) {
297
+ publicImagePath = publicDestinationPath;
298
+ }
299
+ else {
300
+ logger.error("[generate-image] Public upload failed", publicUploadError);
301
+ }
302
+ }
303
+ catch (e) {
304
+ logger.error("[generate-image] Public upload exception", e);
305
+ }
306
+ }
307
+ }
308
+ catch (e) {
309
+ logger.error("[generate-image] Public recipes check failed", e);
310
+ }
311
+ // 4. Mettre à jour les recipes (parent + traductions) si on a une imagePath
312
+ if (imagePathForRecipe) {
313
+ const updateData = {
314
+ image_path: imagePathForRecipe,
315
+ };
316
+ if (publicImagePath)
317
+ updateData.public_image_path = publicImagePath;
318
+ try {
319
+ const { error: updateError } = await supabase
320
+ .from("recipes")
321
+ .update(updateData)
322
+ .or(`id.eq.${parentId},parent_recipe_id.eq.${parentId}`)
323
+ .eq("owner_id", user.id);
324
+ if (updateError) {
325
+ logger.error("[generate-image] Failed to update recipes rows", updateError);
326
+ }
327
+ }
328
+ catch (e) {
329
+ logger.error("[generate-image] Exception updating recipes", e);
330
+ }
331
+ }
332
+ }
333
+ else {
334
+ logger.debug("[generate-image] Recipe not found or not owned, skipping recipe copy", { recipeId, fetchError });
335
+ }
336
+ }
337
+ catch (e) {
338
+ logger.error("[generate-image] Error while copying image to recipe flow", e);
339
+ }
340
+ }
341
+ // Déduire les tokens utilisés via wallet-service (calcul basé sur le coût USD réel)
342
+ try {
343
+ // Parse provider/model from fullModel
344
+ const [provider, modelName] = fullModel.includes("/")
345
+ ? fullModel.split("/", 2)
346
+ : ["openai", fullModel];
347
+ // Get model pricing
348
+ const filterResult = await isModelEnabled(user.id, fullModel);
349
+ if (!filterResult.allowed) {
350
+ logger.warn(`[generate-image] Model ${fullModel} not allowed for user ${user.id}`);
351
+ }
352
+ // Prepare usage and pricing for wallet billing
353
+ const usage = {
354
+ images: 1,
355
+ };
356
+ const pricing = {
357
+ imageUsdPerImage: filterResult.model?.pricing?.image
358
+ ? parseFloat(filterResult.model.pricing.image.toString())
359
+ : undefined,
360
+ };
361
+ // Compute provider cost from actual API usage
362
+ const providerCostUsd = computeProviderCostUsd(usage, pricing);
363
+ // Get current wallet state
364
+ const wallet = await getWalletState(user.id);
365
+ // Compute and debit using centralized billing logic
366
+ const billingResult = computeAndDebitAIUsage({
367
+ userId: user.id,
368
+ actionType: "generate-image",
369
+ providerCostUsd,
370
+ wallet,
371
+ metadata: {
372
+ model: modelName,
373
+ provider,
374
+ endpoint: "generate-image",
375
+ images: 1,
376
+ size,
377
+ quality,
378
+ prompt: prompt.substring(0, 32),
379
+ imageUrl: returnedImageUrl,
380
+ },
381
+ });
382
+ // Check if billing succeeded
383
+ if (!billingResult.success) {
384
+ logger.error(`[generate-image] Billing failed: ${billingResult.error}`, {
385
+ providerCostUsd,
386
+ walletSellValue: wallet.walletSellValueUsd,
387
+ walletProviderBudget: wallet.walletProviderBudgetUsd,
388
+ });
389
+ return NextResponse.json({
390
+ error: billingResult.error ||
391
+ "Crédits insuffisants pour cette opération",
392
+ }, { status: 402 } // Payment Required
393
+ );
394
+ }
395
+ // Record consumption in database (ledger)
396
+ const { error: ledgerError } = await supabase.rpc("record_token_consumption", {
397
+ p_user_id: user.id,
398
+ p_debit_tokens: 0, // Calculated by wallet system
399
+ p_provider_cost_usd: billingResult.providerCostUsd || 0,
400
+ p_sell_usd: billingResult.sellCostUsd || 0,
401
+ p_margin_usd: billingResult.marginUsd || 0,
402
+ p_meta: {
403
+ model: modelName,
404
+ provider,
405
+ endpoint: "generate-image",
406
+ images: 1,
407
+ size,
408
+ quality,
409
+ prompt: prompt.substring(0, 32),
410
+ imageUrl: returnedImageUrl,
411
+ },
412
+ });
413
+ if (ledgerError) {
414
+ logger.error("[generate-image] Failed to record in ledger:", ledgerError);
415
+ // Continue anyway - billing logic already validated
416
+ }
417
+ logger.info(`[generate-image] Image generation completed: cost=$${billingResult.providerCostUsd?.toFixed(6)}, sell=$${billingResult.sellCostUsd?.toFixed(6)}, margin=${billingResult.marginPercent?.toFixed(1)}%`);
418
+ // Return with billing information (wallet-based)
128
419
  return NextResponse.json({
129
- error: tokenResult.error || "Erreur lors de la déduction des tokens",
130
- }, { status: 500 });
131
- }
132
- return NextResponse.json({
133
- imageUrl,
134
- supabaseImageUrl,
135
- tokensUsed: tokensRequired,
136
- tokensRemaining: tokenResult.balance,
137
- model,
138
- });
420
+ imageUrl: returnedImageUrl,
421
+ supabaseImageUrl,
422
+ model,
423
+ tokensUsed: billingResult.tokensDebited || 0,
424
+ tokensRemaining: billingResult.newWallet?.walletSellValueUsd || 0,
425
+ cost: billingResult.sellCostUsd || 0,
426
+ });
427
+ }
428
+ catch (e) {
429
+ logger.error("[generate-image] Billing exception:", e);
430
+ // Return anyway but without billing info
431
+ return NextResponse.json({
432
+ imageUrl: returnedImageUrl,
433
+ supabaseImageUrl,
434
+ model,
435
+ tokensUsed: 0,
436
+ tokensRemaining: 0,
437
+ });
438
+ }
139
439
  }
140
440
  catch (error) {
141
441
  const err = error;
@@ -1,9 +1,10 @@
1
1
  import { NextRequest, NextResponse } from "next/server";
2
2
  export declare function POST(request: NextRequest): Promise<NextResponse<{
3
3
  text: string;
4
- tokensUsed: number;
5
- tokensRemaining: number;
6
4
  model: string;
5
+ provider: string;
6
+ inputTokens: number;
7
+ outputTokens: number;
7
8
  }> | NextResponse<{
8
9
  error: any;
9
10
  }>>;
@@ -1 +1 @@
1
- {"version":3,"file":"generate-text.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;IAuI9C"}
1
+ {"version":3,"file":"generate-text.d.ts","sourceRoot":"","sources":["../../../src/api/auth/generate-text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwBxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IA8P9C"}