@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
@@ -0,0 +1,140 @@
1
+ /**
2
+ * GET /api/ai/admin/user-monthly-details?userId=xxx&month=2026-01
3
+ * Get detailed monthly usage for a specific user
4
+ */
5
+ import { NextResponse } from "next/server";
6
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
7
+ import { logger } from "@lastbrain/core";
8
+ export async function GET(request) {
9
+ try {
10
+ const { searchParams } = new URL(request.url);
11
+ const userId = searchParams.get("userId");
12
+ const month = searchParams.get("month"); // Format: YYYY-MM
13
+ if (!userId) {
14
+ return NextResponse.json({ error: "userId required" }, { status: 400 });
15
+ }
16
+ if (!month || !/^\d{4}-\d{2}$/.test(month)) {
17
+ return NextResponse.json({ error: "month required in format YYYY-MM" }, { status: 400 });
18
+ }
19
+ const supabase = await getSupabaseServiceClient();
20
+ // Calculate month start/end
21
+ const monthStart = new Date(`${month}-01T00:00:00Z`);
22
+ const monthEnd = new Date(monthStart);
23
+ monthEnd.setMonth(monthEnd.getMonth() + 1);
24
+ // Get all ledger entries for this user in this month
25
+ const { data: ledgerEntries, error: ledgerError } = await supabase
26
+ .from("user_token_ledger")
27
+ .select(`
28
+ created_at,
29
+ type,
30
+ debit_tokens,
31
+ provider_cost_usd,
32
+ sell_usd,
33
+ margin_usd,
34
+ model,
35
+ provider,
36
+ meta,
37
+ provider_budget_added_usd
38
+ `)
39
+ .eq("owner_id", userId)
40
+ .gte("created_at", monthStart.toISOString())
41
+ .lt("created_at", monthEnd.toISOString())
42
+ .order("created_at", { ascending: false });
43
+ if (ledgerError) {
44
+ logger.error("[user-monthly-details] Error fetching ledger:", ledgerError);
45
+ return NextResponse.json({ error: "Failed to fetch ledger entries" }, { status: 500 });
46
+ }
47
+ // Separate entry types
48
+ const purchases = ledgerEntries?.filter((e) => e.type === "purchase") || [];
49
+ const gifts = ledgerEntries?.filter((e) => e.type === "gift" || e.type === "signup_bonus") || [];
50
+ const usages = ledgerEntries?.filter((e) => e.type === "use") || [];
51
+ // CA = uniquement les achats réels (purchases)
52
+ const totalPurchaseUsd = purchases.reduce((sum, p) => sum + (p.sell_usd || 0), 0);
53
+ // Coût fournisseur = toutes les utilisations (use)
54
+ const totalProviderCostUsd = usages.reduce((sum, u) => sum + (u.provider_cost_usd || 0), 0);
55
+ // Budget total offert (gifts/signup_bonus) - PAS une charge tant que non consommé
56
+ const totalGiftBudgetUsd = gifts.reduce((sum, g) => sum + (g.provider_budget_added_usd || 0), 0);
57
+ const totalSellUsd = usages.reduce((sum, u) => sum + (u.sell_usd || 0), 0);
58
+ const totalMarginUsd = usages.reduce((sum, u) => sum + (u.margin_usd || 0), 0);
59
+ // Group usages by action type
60
+ const usageByAction = {};
61
+ usages.forEach((usage) => {
62
+ // Extract action type from meta or infer from model
63
+ let actionType = usage.meta?.action_type || "use";
64
+ // Get model and provider
65
+ const modelFull = usage.model || "unknown";
66
+ let provider = usage.provider || null;
67
+ let modelName = modelFull;
68
+ // If model contains "/" extract provider and model
69
+ if (modelFull.includes("/")) {
70
+ const parts = modelFull.split("/");
71
+ provider = parts[0];
72
+ modelName = parts[1];
73
+ }
74
+ // Infer action type from model if not in meta
75
+ if (actionType === "use") {
76
+ if (modelName.includes("imagen") || modelName.includes("dall-e")) {
77
+ actionType = "generate-image";
78
+ }
79
+ else if (modelName.includes("gpt") || modelName.includes("claude")) {
80
+ actionType = "generate-text";
81
+ }
82
+ }
83
+ const key = `${actionType} (${provider || "unknown"}/${modelName})`;
84
+ if (!usageByAction[key]) {
85
+ usageByAction[key] = {
86
+ count: 0,
87
+ providerCostUsd: 0,
88
+ sellUsd: 0,
89
+ marginUsd: 0,
90
+ };
91
+ }
92
+ usageByAction[key].count++;
93
+ usageByAction[key].providerCostUsd += usage.provider_cost_usd || 0;
94
+ usageByAction[key].sellUsd += usage.sell_usd || 0;
95
+ usageByAction[key].marginUsd += usage.margin_usd || 0;
96
+ });
97
+ // Get user info
98
+ const { data: userData } = await supabase.auth.admin.getUserById(userId);
99
+ // Calcul du vrai CA et marge
100
+ // CA = Achats uniquement (purchases)
101
+ // Charges = Coût fournisseur utilisation + Coût crédits offerts
102
+ // Charges réelles = UNIQUEMENT ce qui a été consommé (usages API)
103
+ // Les budgets offerts (gifts) ne sont PAS des charges tant qu'ils ne sont pas utilisés
104
+ const totalCharges = totalProviderCostUsd;
105
+ const realMarginUsd = totalPurchaseUsd - totalCharges;
106
+ const realMarginPercent = totalPurchaseUsd > 0 ? (realMarginUsd / totalPurchaseUsd) * 100 : 0;
107
+ return NextResponse.json({
108
+ user: {
109
+ id: userId,
110
+ email: userData?.user?.email || "Unknown",
111
+ },
112
+ month,
113
+ summary: {
114
+ totalPurchases: purchases.length,
115
+ totalGifts: gifts.length,
116
+ totalUsages: usages.length,
117
+ totalPurchaseUsd, // CA réel (achats uniquement)
118
+ totalProviderCostUsd, // Coût utilisation réelle
119
+ totalGiftBudgetUsd, // Budget offert (non consommé = pas une charge)
120
+ totalCharges, // = totalProviderCostUsd uniquement
121
+ realMarginUsd, // Marge réelle = CA - Charges
122
+ realMarginPercent, // % marge réelle
123
+ // Anciennes valeurs pour compatibilité
124
+ totalSellUsd,
125
+ totalMarginUsd,
126
+ marginPercent: totalSellUsd > 0 ? (totalMarginUsd / totalSellUsd) * 100 : 0,
127
+ },
128
+ usageByAction: Object.entries(usageByAction).map(([key, data]) => ({
129
+ actionType: key,
130
+ ...data,
131
+ avgCostPerCall: data.providerCostUsd / data.count,
132
+ })),
133
+ recentEntries: ledgerEntries?.slice(0, 50) || [], // Last 50 entries
134
+ });
135
+ }
136
+ catch (error) {
137
+ logger.error("[user-monthly-details] Unexpected error:", error);
138
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 });
139
+ }
140
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * POST /api/ai/admin/user-quota
3
+ * Update custom quota for a user (admin only)
4
+ */
5
+ import { NextRequest, NextResponse } from "next/server";
6
+ export declare function POST(request: NextRequest): Promise<NextResponse<{
7
+ success: boolean;
8
+ quota: import("../..").TokenQuotaStatus;
9
+ }> | NextResponse<{
10
+ error: any;
11
+ }>>;
12
+ /**
13
+ * GET /api/ai/admin/user-quota?userId=xxx
14
+ * Get quota status for a specific user (admin only)
15
+ */
16
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
17
+ quota: import("../..").TokenQuotaStatus;
18
+ }> | NextResponse<{
19
+ error: any;
20
+ }>>;
21
+ //# sourceMappingURL=user-quota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-quota.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-quota.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IA8C9C;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAqB7C"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * POST /api/ai/admin/user-quota
3
+ * Update custom quota for a user (admin only)
4
+ */
5
+ import { NextResponse } from "next/server";
6
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
7
+ import { updateCustomQuota, getUserQuotaStatus } from "../../server/quota";
8
+ import { logger } from "@lastbrain/core";
9
+ export async function POST(request) {
10
+ try {
11
+ const supabase = await getSupabaseServiceClient();
12
+ const body = await request.json();
13
+ const { userId, customTokens } = body;
14
+ if (!userId) {
15
+ return NextResponse.json({ error: "userId requis" }, { status: 400 });
16
+ }
17
+ // Validate customTokens (null or positive number)
18
+ if (customTokens !== null &&
19
+ (typeof customTokens !== "number" || customTokens < 0)) {
20
+ return NextResponse.json({ error: "customTokens doit être null ou un nombre positif" }, { status: 400 });
21
+ }
22
+ // Update custom quota
23
+ const result = await updateCustomQuota(userId, customTokens);
24
+ if (!result.success) {
25
+ return NextResponse.json({ error: result.error || "Erreur lors de la mise à jour" }, { status: 400 });
26
+ }
27
+ // Return updated quota status
28
+ const updatedStatus = await getUserQuotaStatus(userId);
29
+ return NextResponse.json({
30
+ success: true,
31
+ quota: updatedStatus,
32
+ });
33
+ }
34
+ catch (error) {
35
+ logger.error("[POST /api/ai/admin/user-quota] Error:", error);
36
+ return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
37
+ }
38
+ }
39
+ /**
40
+ * GET /api/ai/admin/user-quota?userId=xxx
41
+ * Get quota status for a specific user (admin only)
42
+ */
43
+ export async function GET(request) {
44
+ try {
45
+ const searchParams = request.nextUrl.searchParams;
46
+ const userId = searchParams.get("userId");
47
+ if (!userId) {
48
+ return NextResponse.json({ error: "userId requis" }, { status: 400 });
49
+ }
50
+ const quotaStatus = await getUserQuotaStatus(userId);
51
+ return NextResponse.json({
52
+ quota: quotaStatus,
53
+ });
54
+ }
55
+ catch (error) {
56
+ logger.error("[GET /api/ai/admin/user-quota] Error:", error);
57
+ return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
58
+ }
59
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"[id].d.ts","sourceRoot":"","sources":["../../../../src/api/admin/user-token/[id].ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,wBAAsB,GAAG,CACvB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4D7C"}
1
+ {"version":3,"file":"[id].d.ts","sourceRoot":"","sources":["../../../../src/api/admin/user-token/[id].ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUxD,wBAAsB,GAAG,CACvB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4D7C"}
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
2
  import { getSupabaseServiceClient } from "@lastbrain/core/server";
3
3
  import { getTokenBalance, getTokenHistory, getTokenStats, } from "../../../server";
4
+ import { logger } from "@lastbrain/core";
4
5
  // GET /api/ai/admin/user-token/[id] - Détails d'un utilisateur spécifique
5
6
  export async function GET(request, context) {
6
7
  const { id: userId } = await context.params;
@@ -44,7 +45,7 @@ export async function GET(request, context) {
44
45
  });
45
46
  }
46
47
  catch (error) {
47
- console.error("[GET /api/ai/admin/user-token/[id]] Error:", error);
48
+ logger.error("[GET /api/ai/admin/user-token/[id]] Error:", error);
48
49
  return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
49
50
  }
50
51
  }
@@ -9,15 +9,18 @@ export declare function GET(_request: NextRequest): Promise<NextResponse<{
9
9
  totalAdded: number;
10
10
  totalUsed: number;
11
11
  lastActivity: string | undefined;
12
+ includedTokens: any;
13
+ usedIncludedTokens: any;
14
+ remainingIncludedTokens: number;
12
15
  }[];
13
- transactions: any;
16
+ transactions: any[];
14
17
  total: number;
15
18
  }> | NextResponse<{
16
19
  error: any;
17
20
  }>>;
18
21
  export declare function POST(request: NextRequest): Promise<NextResponse<{
19
22
  success: boolean;
20
- balance: number;
23
+ balance: number | undefined;
21
24
  message: string;
22
25
  }> | NextResponse<{
23
26
  error: any;
@@ -1 +1 @@
1
- {"version":3,"file":"user-token.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKxD,wBAAsB,GAAG,CAAC,QAAQ,EAAE,WAAW;;;;;;;;;;;;;;;IA6H9C;AAGD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;IA+C9C"}
1
+ {"version":3,"file":"user-token.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-token.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,wBAAsB,GAAG,CAAC,QAAQ,EAAE,WAAW;;;;;;;;;;;;;;;;;;IA4M9C;AAGD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;IAmE9C"}
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from "next/server";
2
- import { getSupabaseServiceClient } from "@lastbrain/core/server";
3
- import { addTokens } from "../../server";
2
+ import { getSupabaseServerClient, getSupabaseServiceClient, } from "@lastbrain/core/server";
3
+ import { addWalletCredits } from "../../server/billing";
4
+ import { logger } from "@lastbrain/core";
4
5
  // GET /api/ai/admin/user-token - Liste tous les utilisateurs avec leur balance et transactions
5
6
  export async function GET(_request) {
6
7
  try {
@@ -13,14 +14,23 @@ export async function GET(_request) {
13
14
  // Récupérer toutes les transactions pour calculer les balances et stats
14
15
  const { data: transactions, error: txError } = await supabase
15
16
  .from("user_token_ledger")
16
- .select("*")
17
- .order("ts", { ascending: false })
18
- .limit(1000);
17
+ .select("owner_id,ts,type,amount")
18
+ .order("ts", { ascending: false });
19
+ logger.error("transactions", txError);
19
20
  if (txError)
20
21
  throw txError;
21
22
  const txList = transactions || [];
23
+ // Récupérer les usages de quotas (consommation d'abonnement)
24
+ const { data: quotaRows, error: quotaError } = await supabase
25
+ .from("user_token_quota_ledger")
26
+ .select("owner_id,period_start,period_end,tokens_used,model,created_at")
27
+ .order("created_at", { ascending: false });
28
+ if (quotaError)
29
+ throw quotaError;
30
+ const quotaList = quotaRows || [];
22
31
  // Calculer les stats par utilisateur
23
32
  const userStatsMap = new Map();
33
+ // Transactions (achat/cadeau/usage...) affectent le solde
24
34
  txList.forEach((tx) => {
25
35
  const stats = userStatsMap.get(tx.owner_id) || {
26
36
  balance: 0,
@@ -39,8 +49,28 @@ export async function GET(_request) {
39
49
  }
40
50
  userStatsMap.set(tx.owner_id, stats);
41
51
  });
52
+ // Usages de quota : ne modifient pas le solde, mais comptent dans le totalUsed et l'activité
53
+ quotaList.forEach((quota) => {
54
+ const stats = userStatsMap.get(quota.owner_id) || {
55
+ balance: 0,
56
+ totalAdded: 0,
57
+ totalUsed: 0,
58
+ };
59
+ stats.totalUsed += quota.tokens_used; // toujours positif dans la table
60
+ if (!stats.lastActivity || quota.created_at > stats.lastActivity) {
61
+ stats.lastActivity = quota.created_at;
62
+ }
63
+ userStatsMap.set(quota.owner_id, stats);
64
+ });
42
65
  // Récupérer les profils utilisateurs pour avatar et nom
43
66
  const userIds = authUsers.users.map((u) => u.id);
67
+ const { data: quotaMonthly, error: quotaMonthlyError } = await supabase
68
+ .from("user_token_quota_monthly")
69
+ .select("owner_id, effective_included_tokens, used_included_tokens")
70
+ .in("owner_id", userIds);
71
+ if (quotaMonthlyError)
72
+ throw quotaMonthlyError;
73
+ const quotaMonthlyMap = new Map((quotaMonthly || []).map((row) => [row.owner_id, row]));
44
74
  const { data: profiles } = await supabase
45
75
  .from("user_profil")
46
76
  .select("id, first_name, last_name, avatar_url")
@@ -60,6 +90,10 @@ export async function GET(_request) {
60
90
  totalUsed: 0,
61
91
  };
62
92
  const profile = profilesMap.get(user.id);
93
+ const quota = quotaMonthlyMap.get(user.id);
94
+ const includedTokens = quota?.effective_included_tokens || 0;
95
+ const usedIncludedTokens = quota?.used_included_tokens || 0;
96
+ const remainingIncludedTokens = Math.max(0, includedTokens - usedIncludedTokens);
63
97
  return {
64
98
  userId: user.id,
65
99
  email: user.email || "Unknown",
@@ -69,9 +103,12 @@ export async function GET(_request) {
69
103
  totalAdded: stats.totalAdded,
70
104
  totalUsed: stats.totalUsed,
71
105
  lastActivity: stats.lastActivity,
106
+ includedTokens,
107
+ usedIncludedTokens,
108
+ remainingIncludedTokens,
72
109
  };
73
110
  });
74
- // Préparer les transactions avec infos utilisateurs
111
+ // Transactions classiques (achats / cadeaux / usages)
75
112
  const transactionsWithUsers = txList.map((tx) => {
76
113
  const user = authUsers.users.find((u) => u.id === tx.owner_id);
77
114
  const profile = profilesMap.get(tx.owner_id);
@@ -88,44 +125,81 @@ export async function GET(_request) {
88
125
  created_at: tx.ts,
89
126
  };
90
127
  });
128
+ // Transactions de quota (consommation d'abonnement)
129
+ const quotaTransactions = quotaList.map((quota) => {
130
+ const user = authUsers.users.find((u) => u.id === quota.owner_id);
131
+ const profile = profilesMap.get(quota.owner_id);
132
+ return {
133
+ id: `quota-${quota.id}`,
134
+ userId: quota.owner_id,
135
+ email: user?.email || "Unknown",
136
+ fullName: profile?.fullName,
137
+ avatarUrl: profile?.avatarUrl,
138
+ amount: -quota.tokens_used, // on l'affiche en débit
139
+ type: "quota_use",
140
+ description: quota.meta?.reason ||
141
+ (quota.model
142
+ ? `Usage quota ${quota.model}`
143
+ : "Usage quota abonnement"),
144
+ model: quota.model,
145
+ created_at: quota.created_at,
146
+ };
147
+ });
148
+ // Fusionner et trier par date desc
149
+ const allTransactions = [
150
+ ...transactionsWithUsers,
151
+ ...quotaTransactions,
152
+ ].sort((a, b) => (a.created_at < b.created_at ? 1 : -1));
91
153
  return NextResponse.json({
92
154
  users: usersData,
93
- transactions: transactionsWithUsers,
155
+ transactions: allTransactions,
94
156
  total: usersData.length,
95
157
  });
96
158
  }
97
159
  catch (error) {
98
- console.error("[GET /api/ai/admin/user-token] Error:", error);
160
+ logger.error("[GET /api/ai/admin/user-token] Error:", error);
99
161
  return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
100
162
  }
101
163
  }
102
- // POST /api/ai/admin/user-token - Ajouter/retirer des tokens à un utilisateur
164
+ // POST /api/ai/admin/user-token - Ajouter/retirer des crédits USD à un utilisateur
103
165
  export async function POST(request) {
104
166
  try {
105
167
  // L'authentification et les droits superadmin sont déjà vérifiés par le middleware
106
168
  const body = await request.json();
107
- const { userId, amount, type, reason } = body;
169
+ const { userId, amount, reason } = body;
108
170
  if (!userId || !amount) {
109
171
  return NextResponse.json({ error: "userId et amount sont requis" }, { status: 400 });
110
172
  }
111
173
  if (amount === 0) {
112
174
  return NextResponse.json({ error: "Le montant ne peut pas être zéro" }, { status: 400 });
113
175
  }
114
- // Déterminer le type d'opération
115
- const operationType = type || (amount > 0 ? "adjust" : "adjust");
116
- const result = await addTokens(userId, amount, operationType, { reason: reason || "Ajustement manuel par admin" }, undefined // created_by undefined pour les opérations admin via service role
117
- );
176
+ // Utiliser le nouveau système de wallet en USD
177
+ const usdAmount = parseFloat(amount);
178
+ if (usdAmount < 0) {
179
+ return NextResponse.json({
180
+ error: "Le débit de crédits n'est pas encore supporté. Utilisez un montant positif pour créditer.",
181
+ }, { status: 400 });
182
+ }
183
+ const result = await addWalletCredits(userId, usdAmount, reason || "Ajustement manuel par admin");
118
184
  if (!result.success) {
119
185
  return NextResponse.json({ error: result.error }, { status: 500 });
120
186
  }
187
+ const supabase = await getSupabaseServerClient();
188
+ await supabase.from("user_notifications").insert({
189
+ owner_id: userId,
190
+ title: `Crédits IA ajoutés`,
191
+ body: `$${usdAmount.toFixed(2)} de crédits IA ont été ajoutés à votre compte. ${reason ? `Raison : ${reason}` : ""}`,
192
+ type: "credit_added",
193
+ read: false,
194
+ });
121
195
  return NextResponse.json({
122
196
  success: true,
123
- balance: result.balance,
124
- message: `${amount > 0 ? "Ajouté" : "Retiré"} ${Math.abs(amount)} tokens`,
197
+ balance: result.newBalance,
198
+ message: `Ajouté $${usdAmount.toFixed(2)} de crédits IA`,
125
199
  });
126
200
  }
127
201
  catch (error) {
128
- console.error("[POST /api/ai/admin/user-token] Error:", error);
202
+ logger.error("[POST /api/ai/admin/user-token] Error:", error);
129
203
  return NextResponse.json({ error: error.message || "Erreur serveur" }, { status: 500 });
130
204
  }
131
205
  }
@@ -0,0 +1,22 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ /**
3
+ * GET /api/ai/admin/user-usage-by-model
4
+ * Returns usage statistics grouped by model with costs in USD for a specific user
5
+ * Supports filtering by month
6
+ */
7
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
8
+ data: {
9
+ avg_cost_per_call: number;
10
+ model: string;
11
+ provider: string;
12
+ endpoint: string;
13
+ total_cost_usd: number;
14
+ total_sell_usd: number;
15
+ call_count: number;
16
+ }[];
17
+ period: string;
18
+ userId: string;
19
+ }> | NextResponse<{
20
+ error: any;
21
+ }>>;
22
+ //# sourceMappingURL=user-usage-by-model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-usage-by-model.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-usage-by-model.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD;;;;GAIG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;eAgD/B,MAAM;kBACH,MAAM;kBACN,MAAM;wBACA,MAAM;wBACN,MAAM;oBACV,MAAM;;;;;;IAiDzB"}
@@ -0,0 +1,78 @@
1
+ import { NextResponse } from "next/server";
2
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
3
+ import { logger } from "@lastbrain/core";
4
+ /**
5
+ * GET /api/ai/admin/user-usage-by-model
6
+ * Returns usage statistics grouped by model with costs in USD for a specific user
7
+ * Supports filtering by month
8
+ */
9
+ export async function GET(request) {
10
+ try {
11
+ const supabase = getSupabaseServiceClient();
12
+ // Get query params
13
+ const searchParams = request.nextUrl.searchParams;
14
+ const userId = searchParams.get("userId");
15
+ const month = searchParams.get("month"); // Format: YYYY-MM
16
+ if (!userId) {
17
+ return NextResponse.json({ error: "userId parameter required" }, { status: 400 });
18
+ }
19
+ // Build query
20
+ let query = supabase
21
+ .from("user_token_ledger")
22
+ .select("model, provider_cost_usd, sell_usd, meta")
23
+ .eq("owner_id", userId)
24
+ .eq("type", "use")
25
+ .order("ts", { ascending: false });
26
+ // Filter by month if provided
27
+ if (month && month !== "all") {
28
+ const startDate = `${month}-01`;
29
+ const [year, monthNum] = month.split("-");
30
+ const nextMonth = new Date(parseInt(year), parseInt(monthNum), 1);
31
+ const endDate = nextMonth.toISOString().split("T")[0];
32
+ query = query.gte("ts", startDate).lt("ts", endDate);
33
+ }
34
+ const { data: ledgerEntries, error } = await query;
35
+ if (error) {
36
+ logger.error("[admin/user-usage-by-model] Error fetching ledger:", error);
37
+ return NextResponse.json({ error: "Failed to fetch usage data" }, { status: 500 });
38
+ }
39
+ // Group by model + provider + endpoint
40
+ const groupedData = new Map();
41
+ for (const entry of ledgerEntries || []) {
42
+ const model = entry.model || "unknown";
43
+ const provider = entry.meta?.provider || "unknown";
44
+ const endpoint = entry.meta?.endpoint || "unknown";
45
+ const key = `${model}|${provider}|${endpoint}`;
46
+ if (!groupedData.has(key)) {
47
+ groupedData.set(key, {
48
+ model,
49
+ provider,
50
+ endpoint,
51
+ total_cost_usd: 0,
52
+ total_sell_usd: 0,
53
+ call_count: 0,
54
+ });
55
+ }
56
+ const group = groupedData.get(key);
57
+ group.total_cost_usd += entry.provider_cost_usd || 0;
58
+ group.total_sell_usd += entry.sell_usd || 0;
59
+ group.call_count += 1;
60
+ }
61
+ // Convert to array and calculate averages
62
+ const result = Array.from(groupedData.values()).map((group) => ({
63
+ ...group,
64
+ avg_cost_per_call: group.call_count > 0 ? group.total_sell_usd / group.call_count : 0,
65
+ }));
66
+ // Sort by total_sell_usd descending
67
+ result.sort((a, b) => b.total_sell_usd - a.total_sell_usd);
68
+ return NextResponse.json({
69
+ data: result,
70
+ period: month || "all",
71
+ userId,
72
+ });
73
+ }
74
+ catch (error) {
75
+ logger.error("[admin/user-usage-by-model] Error:", error);
76
+ return NextResponse.json({ error: error.message || "Internal server error" }, { status: 500 });
77
+ }
78
+ }
@@ -0,0 +1,15 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
3
+ error: string;
4
+ }> | NextResponse<{
5
+ tokensAdded: any;
6
+ tokensUsed: any;
7
+ totalPurchaseUsd: number;
8
+ totalProviderCostUsd: number;
9
+ totalSellUsd: number;
10
+ totalMarginUsd: number;
11
+ marginPercent: number;
12
+ walletProviderBudgetUsd: number;
13
+ walletSellValueUsd: number;
14
+ }>>;
15
+ //# sourceMappingURL=user-wallet-analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-wallet-analytics.d.ts","sourceRoot":"","sources":["../../../src/api/admin/user-wallet-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIxD,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;IAmG7C"}
@@ -0,0 +1,67 @@
1
+ import { NextResponse } from "next/server";
2
+ import { logger } from "@lastbrain/core";
3
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
4
+ export async function GET(request) {
5
+ try {
6
+ const { searchParams } = new URL(request.url);
7
+ const userId = searchParams.get("userId");
8
+ if (!userId) {
9
+ return NextResponse.json({ error: "userId required" }, { status: 400 });
10
+ }
11
+ const supabase = getSupabaseServiceClient();
12
+ // Get ledger for user
13
+ const { data: ledgerRows, error: ledgerError } = await supabase
14
+ .from("user_token_ledger")
15
+ .select("owner_id, type, amount, provider_cost_usd, sell_usd, margin_usd, pack_coef, pack_price_usd")
16
+ .eq("owner_id", userId);
17
+ if (ledgerError) {
18
+ logger.error("[User Wallet Analytics] Failed to fetch ledger:", ledgerError);
19
+ throw ledgerError;
20
+ }
21
+ const ledger = ledgerRows || [];
22
+ // Get wallet state
23
+ const { data: walletRow, error: walletError } = await supabase
24
+ .from("user_token_wallet")
25
+ .select("wallet_provider_budget_usd, wallet_sell_value_usd")
26
+ .eq("user_id", userId)
27
+ .single();
28
+ if (walletError && walletError.code !== "PGRST116") {
29
+ logger.error("[User Wallet Analytics] Failed to fetch wallet:", walletError);
30
+ }
31
+ const wallet = walletRow || {
32
+ wallet_provider_budget_usd: 0,
33
+ wallet_sell_value_usd: 0,
34
+ };
35
+ // Calculate stats
36
+ const purchases = ledger.filter((t) => t.type === "purchase");
37
+ const consumptions = ledger.filter((t) => t.type === "use");
38
+ const totalPurchaseUsd = purchases.reduce((sum, t) => {
39
+ return sum + (parseFloat(t.pack_price_usd) || 0);
40
+ }, 0);
41
+ const tokensAdded = purchases.reduce((sum, t) => sum + t.amount, 0);
42
+ const tokensUsed = consumptions.reduce((sum, t) => sum + Math.abs(t.amount), 0);
43
+ const totalProviderCostUsd = consumptions.reduce((sum, t) => {
44
+ return sum + (parseFloat(t.provider_cost_usd) || 0);
45
+ }, 0);
46
+ const totalSellUsd = consumptions.reduce((sum, t) => {
47
+ return sum + (parseFloat(t.sell_usd) || 0);
48
+ }, 0);
49
+ const totalMarginUsd = totalSellUsd - totalProviderCostUsd;
50
+ const marginPercent = totalSellUsd > 0 ? (totalMarginUsd / totalSellUsd) * 100 : 0;
51
+ return NextResponse.json({
52
+ tokensAdded,
53
+ tokensUsed,
54
+ totalPurchaseUsd: parseFloat(totalPurchaseUsd.toFixed(4)),
55
+ totalProviderCostUsd: parseFloat(totalProviderCostUsd.toFixed(4)),
56
+ totalSellUsd: parseFloat(totalSellUsd.toFixed(4)),
57
+ totalMarginUsd: parseFloat(totalMarginUsd.toFixed(4)),
58
+ marginPercent: parseFloat(marginPercent.toFixed(2)),
59
+ walletProviderBudgetUsd: parseFloat((wallet.wallet_provider_budget_usd || 0).toFixed(4)),
60
+ walletSellValueUsd: parseFloat((wallet.wallet_sell_value_usd || 0).toFixed(4)),
61
+ }, { status: 200 });
62
+ }
63
+ catch (error) {
64
+ logger.error("[User Wallet Analytics] Error:", error);
65
+ return NextResponse.json({ error: "Failed to fetch analytics" }, { status: 500 });
66
+ }
67
+ }