@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,66 @@
1
+ /**
2
+ * Server-side quota management functions
3
+ * Handles monthly token quota (separate from purchased tokens)
4
+ */
5
+ import type { TokenQuotaStatus, DebitTokensResponse, UpdateQuotaRequest } from "../types/quota";
6
+ /**
7
+ * Get user's current quota status
8
+ */
9
+ export declare function getUserQuotaStatus(userId: string): Promise<TokenQuotaStatus>;
10
+ /**
11
+ * Debit tokens with priority: quota first, then purchased
12
+ * This is the CENTRAL function for token consumption
13
+ */
14
+ export declare function debitTokensWithPriority(userId: string, tokensToDebit: number, model?: string, prompt?: string, meta?: Record<string, unknown>): Promise<DebitTokensResponse>;
15
+ /**
16
+ * Update or create user quota (for subscription updates/renewals)
17
+ */
18
+ export declare function updateUserQuota(request: UpdateQuotaRequest): Promise<{
19
+ success: boolean;
20
+ error?: string;
21
+ }>;
22
+ /**
23
+ * Handle subscription update from billing-pro webhook
24
+ * This function is called by billing-pro when subscription events occur
25
+ *
26
+ * @param params Subscription parameters from Stripe
27
+ * @returns Success status
28
+ */
29
+ export declare function handleSubscriptionQuotaUpdate(params: {
30
+ owner_id: string;
31
+ plan_slug: string;
32
+ subscription_id: string | null;
33
+ current_period_start: string;
34
+ current_period_end: string;
35
+ }): Promise<{
36
+ success: boolean;
37
+ error?: string;
38
+ }>;
39
+ /**
40
+ * Update custom quota for a user (admin override)
41
+ */
42
+ export declare function updateCustomQuota(userId: string, customTokens: number | null): Promise<{
43
+ success: boolean;
44
+ error?: string;
45
+ }>;
46
+ /**
47
+ * Get quota usage history for a user
48
+ */
49
+ export declare function getQuotaUsageHistory(userId: string, limit?: number, offset?: number): Promise<any[]>;
50
+ /**
51
+ * Refund/credit tokens back to user in case of error after debit
52
+ * This ensures users don't lose tokens if the operation fails after AI generation
53
+ *
54
+ * @param userId User ID
55
+ * @param tokensToRefund Number of tokens to credit back
56
+ * @param model Model that was used (for logging)
57
+ * @param reason Reason for the refund (for logging)
58
+ * @returns Success status with refund details
59
+ */
60
+ export declare function refundTokens(userId: string, tokensToRefund: number, model: string, reason: string): Promise<{
61
+ success: boolean;
62
+ error?: string;
63
+ refundedToQuota: number;
64
+ refundedToPurchased: number;
65
+ }>;
66
+ //# sourceMappingURL=quota.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quota.d.ts","sourceRoot":"","sources":["../../src/server/quota.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAEV,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,EAEnB,MAAM,gBAAgB,CAAC;AAWxB;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,gBAAgB,CAAC,CA0E3B;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA+Q9B;AAED;;GAEG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAwC/C;AAED;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CAAC,MAAM,EAAE;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;CAC5B,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0DhD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0C/C;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAW,EAClB,MAAM,GAAE,MAAU,kBAkBnB;AAED;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,cAAc,EAAE,MAAM,EACtB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;CAC7B,CAAC,CAgHD"}
@@ -0,0 +1,538 @@
1
+ /**
2
+ * Server-side quota management functions
3
+ * Handles monthly token quota (separate from purchased tokens)
4
+ */
5
+ import { getSupabaseServiceClient } from "@lastbrain/core/server";
6
+ import { logger } from "@lastbrain/core";
7
+ // Import default quotas
8
+ const PLAN_DEFAULTS = {
9
+ free: 0,
10
+ pro: 50000,
11
+ premium: 100000,
12
+ enterprise: 150000,
13
+ };
14
+ /**
15
+ * Get user's current quota status
16
+ */
17
+ export async function getUserQuotaStatus(userId) {
18
+ const supabase = await getSupabaseServiceClient();
19
+ try {
20
+ // Get the most recent quota record (not .single() to avoid PGRST116 error)
21
+ const { data, error } = await supabase
22
+ .from("user_token_quota_monthly")
23
+ .select("*")
24
+ .eq("owner_id", userId)
25
+ .order("created_at", { ascending: false })
26
+ .limit(1);
27
+ if (error) {
28
+ logger.debug(`[getUserQuotaStatus] Query error: ${error.code} - ${error.message}`);
29
+ }
30
+ // Take the first (most recent) record
31
+ const record = data?.[0];
32
+ if (!record) {
33
+ return {
34
+ hasQuota: false,
35
+ plan: "free",
36
+ effectiveQuota: 0,
37
+ usedQuota: 0,
38
+ remainingQuota: 0,
39
+ periodStart: null,
40
+ periodEnd: null,
41
+ isActive: false,
42
+ };
43
+ }
44
+ const quota = record;
45
+ const now = new Date();
46
+ const periodStart = quota.period_start
47
+ ? new Date(quota.period_start)
48
+ : null;
49
+ const periodEnd = quota.period_end ? new Date(quota.period_end) : null;
50
+ // Check if within period
51
+ const isActive = periodStart && periodEnd && now >= periodStart && now <= periodEnd;
52
+ const result = {
53
+ hasQuota: quota.effective_included_tokens > 0,
54
+ plan: quota.plan,
55
+ effectiveQuota: quota.effective_included_tokens,
56
+ usedQuota: quota.used_included_tokens,
57
+ remainingQuota: Math.max(0, quota.effective_included_tokens - quota.used_included_tokens),
58
+ periodStart: quota.period_start,
59
+ periodEnd: quota.period_end,
60
+ isActive: !!isActive,
61
+ };
62
+ return result;
63
+ }
64
+ catch (error) {
65
+ logger.error("[getUserQuotaStatus] Error:", error);
66
+ return {
67
+ hasQuota: false,
68
+ plan: "free",
69
+ effectiveQuota: 0,
70
+ usedQuota: 0,
71
+ remainingQuota: 0,
72
+ periodStart: null,
73
+ periodEnd: null,
74
+ isActive: false,
75
+ };
76
+ }
77
+ }
78
+ /**
79
+ * Debit tokens with priority: quota first, then purchased
80
+ * This is the CENTRAL function for token consumption
81
+ */
82
+ export async function debitTokensWithPriority(userId, tokensToDebit, model, prompt, meta = {}) {
83
+ logger.debug(`[debitTokensWithPriority] START for user ${userId}: tokensToDebit=${tokensToDebit}, model=${model}`);
84
+ if (tokensToDebit <= 0) {
85
+ logger.warn(`[debitTokensWithPriority] Invalid amount: ${tokensToDebit}`);
86
+ return {
87
+ success: false,
88
+ debitedFromQuota: 0,
89
+ debitedFromPurchased: 0,
90
+ remainingQuota: 0,
91
+ remainingPurchased: 0,
92
+ error: "Le montant doit être positif",
93
+ };
94
+ }
95
+ const supabase = await getSupabaseServiceClient();
96
+ try {
97
+ // Step 1: Get quota status
98
+ const quotaStatus = await getUserQuotaStatus(userId);
99
+ logger.debug(`[debitTokensWithPriority] Quota status:`, {
100
+ hasQuota: quotaStatus.hasQuota,
101
+ plan: quotaStatus.plan,
102
+ effectiveQuota: quotaStatus.effectiveQuota,
103
+ usedQuota: quotaStatus.usedQuota,
104
+ remainingQuota: quotaStatus.remainingQuota,
105
+ isActive: quotaStatus.isActive,
106
+ periodStart: quotaStatus.periodStart,
107
+ periodEnd: quotaStatus.periodEnd,
108
+ });
109
+ let debitedFromQuota = 0;
110
+ let debitedFromPurchased = 0;
111
+ let tokensRemaining = tokensToDebit;
112
+ // Step 2: Try to debit from quota first (if active and available)
113
+ if (quotaStatus.isActive && quotaStatus.remainingQuota > 0) {
114
+ debitedFromQuota = Math.min(tokensRemaining, quotaStatus.remainingQuota);
115
+ tokensRemaining -= debitedFromQuota;
116
+ logger.debug(`[debitTokensWithPriority] Debiting from quota: ${debitedFromQuota} tokens (remaining to debit: ${tokensRemaining})`);
117
+ // Update quota usage
118
+ const { data: quotaData, error: quotaError } = await supabase
119
+ .from("user_token_quota_monthly")
120
+ .update({
121
+ used_included_tokens: quotaStatus.usedQuota + debitedFromQuota,
122
+ })
123
+ .eq("owner_id", userId)
124
+ .select()
125
+ .single();
126
+ if (quotaError) {
127
+ logger.error("[debitTokensWithPriority] Quota update error:", quotaError);
128
+ throw new Error("Erreur lors de la mise à jour du quota");
129
+ }
130
+ logger.debug(`[debitTokensWithPriority] Quota updated successfully:`, quotaData);
131
+ // Log quota usage
132
+ if (debitedFromQuota > 0 &&
133
+ quotaStatus.periodStart &&
134
+ quotaStatus.periodEnd) {
135
+ // Truncate prompt to 32 characters for GDPR compliance
136
+ const truncatedPrompt = prompt ? prompt.substring(0, 32) : null;
137
+ logger.debug(`[debitTokensWithPriority] Inserting to user_token_quota_ledger:`, {
138
+ owner_id: userId,
139
+ tokens_used: debitedFromQuota,
140
+ model,
141
+ period_start: quotaStatus.periodStart,
142
+ period_end: quotaStatus.periodEnd,
143
+ });
144
+ const { data: ledgerData, error: ledgerInsertError } = await supabase
145
+ .from("user_token_quota_ledger")
146
+ .insert({
147
+ owner_id: userId,
148
+ tokens_used: debitedFromQuota,
149
+ model,
150
+ prompt: truncatedPrompt,
151
+ period_start: quotaStatus.periodStart,
152
+ period_end: quotaStatus.periodEnd,
153
+ meta,
154
+ })
155
+ .select();
156
+ if (ledgerInsertError) {
157
+ logger.error("[debitTokensWithPriority] Ledger insert error:", ledgerInsertError);
158
+ throw new Error("Erreur lors de l'insertion en ledger quota");
159
+ }
160
+ logger.debug(`[debitTokensWithPriority] Ledger insertion successful:`, ledgerData);
161
+ }
162
+ else {
163
+ logger.debug(`[debitTokensWithPriority] Skipped ledger insertion: debitedFromQuota=${debitedFromQuota}, periodStart=${quotaStatus.periodStart}, periodEnd=${quotaStatus.periodEnd}`);
164
+ }
165
+ }
166
+ else {
167
+ logger.debug(`[debitTokensWithPriority] Skipped quota debit: isActive=${quotaStatus.isActive}, remainingQuota=${quotaStatus.remainingQuota}`);
168
+ }
169
+ // Step 3: Debit remaining from purchased tokens (if any)
170
+ if (tokensRemaining > 0) {
171
+ logger.debug(`[debitTokensWithPriority] Need to debit from purchased: ${tokensRemaining} tokens`);
172
+ // Check purchased balance
173
+ const { data: balanceData } = await supabase
174
+ .from("user_token_balance_v")
175
+ .select("balance")
176
+ .eq("owner_id", userId)
177
+ .single();
178
+ const purchasedBalance = balanceData?.balance || 0;
179
+ logger.debug(`[debitTokensWithPriority] Purchased balance: ${purchasedBalance}`);
180
+ if (purchasedBalance < tokensRemaining) {
181
+ logger.warn(`[debitTokensWithPriority] Purchased tokens insufficient: balance=${purchasedBalance}, required=${tokensRemaining}`);
182
+ // If the caller explicitly allows an overdraft (one-time negative balance), proceed
183
+ const allowOverdraft = Boolean(meta?.allowOverdraft);
184
+ if (!allowOverdraft) {
185
+ return {
186
+ success: false,
187
+ debitedFromQuota,
188
+ debitedFromPurchased: 0,
189
+ remainingQuota: quotaStatus.remainingQuota - debitedFromQuota,
190
+ remainingPurchased: purchasedBalance,
191
+ error: `Tokens insuffisants. Quota: ${quotaStatus.remainingQuota}, Achetés: ${purchasedBalance}, Requis: ${tokensToDebit}`,
192
+ };
193
+ }
194
+ logger.debug(`[debitTokensWithPriority] allowOverdraft=true, proceeding to insert negative ledger entry`);
195
+ // continue and insert the ledger entry below which will make the balance negative
196
+ }
197
+ // Debit from purchased
198
+ logger.debug(`[debitTokensWithPriority] Inserting to user_token_ledger: ${tokensRemaining} tokens`);
199
+ // Truncate prompt to 32 characters for GDPR compliance
200
+ const truncatedPrompt = prompt ? prompt.substring(0, 32) : null;
201
+ // Insert ledger entry for purchased tokens. Use try/catch and fallback
202
+ // to a minimal insert if the full insert fails (to avoid blocking generation).
203
+ let insertData = null;
204
+ try {
205
+ const res = await supabase
206
+ .from("user_token_ledger")
207
+ .insert({
208
+ owner_id: userId,
209
+ type: "use",
210
+ amount: -tokensRemaining,
211
+ model,
212
+ prompt: truncatedPrompt,
213
+ meta,
214
+ })
215
+ .select();
216
+ insertData = res.data;
217
+ if (res.error)
218
+ throw res.error;
219
+ }
220
+ catch (err) {
221
+ logger.error("[debitTokensWithPriority] Ledger insert failed (full payload):", err);
222
+ // Retry with minimal payload (avoid meta or potentially invalid fields)
223
+ try {
224
+ const res2 = await supabase
225
+ .from("user_token_ledger")
226
+ .insert({
227
+ owner_id: userId,
228
+ type: "use",
229
+ amount: -tokensRemaining,
230
+ model,
231
+ })
232
+ .select();
233
+ insertData = res2.data;
234
+ if (res2.error)
235
+ throw res2.error;
236
+ logger.debug("[debitTokensWithPriority] Purchased tokens debited with fallback insert:", insertData);
237
+ }
238
+ catch (err2) {
239
+ logger.error("[debitTokensWithPriority] Ledger insert fallback failed:", err2);
240
+ return {
241
+ success: false,
242
+ debitedFromQuota,
243
+ debitedFromPurchased: 0,
244
+ remainingQuota: quotaStatus.remainingQuota - debitedFromQuota,
245
+ remainingPurchased: purchasedBalance,
246
+ error: (err2 && err2.message) ||
247
+ "Erreur lors du débit des tokens achetés (ledger insert)",
248
+ };
249
+ }
250
+ }
251
+ logger.debug(`[debitTokensWithPriority] Purchased tokens debited successfully:`, insertData);
252
+ debitedFromPurchased = tokensRemaining;
253
+ }
254
+ else {
255
+ logger.debug(`[debitTokensWithPriority] No purchased tokens to debit`);
256
+ }
257
+ // Step 4: Get final balances
258
+ const finalQuotaStatus = await getUserQuotaStatus(userId);
259
+ const { data: finalBalanceData } = await supabase
260
+ .from("user_token_balance_v")
261
+ .select("balance")
262
+ .eq("owner_id", userId)
263
+ .single();
264
+ const result = {
265
+ success: true,
266
+ debitedFromQuota,
267
+ debitedFromPurchased,
268
+ remainingQuota: finalQuotaStatus.remainingQuota,
269
+ remainingPurchased: finalBalanceData?.balance || 0,
270
+ };
271
+ logger.debug(`[debitTokensWithPriority] SUCCESS:`, result);
272
+ return result;
273
+ }
274
+ catch (error) {
275
+ logger.error("[debitTokensWithPriority] FATAL ERROR:", error);
276
+ return {
277
+ success: false,
278
+ debitedFromQuota: 0,
279
+ debitedFromPurchased: 0,
280
+ remainingQuota: 0,
281
+ remainingPurchased: 0,
282
+ error: error.message || "Erreur lors du débit des tokens",
283
+ };
284
+ }
285
+ }
286
+ /**
287
+ * Update or create user quota (for subscription updates/renewals)
288
+ */
289
+ export async function updateUserQuota(request) {
290
+ const supabase = await getSupabaseServiceClient();
291
+ try {
292
+ const defaultTokens = PLAN_DEFAULTS[request.plan] || 0;
293
+ const effectiveTokens = request.custom_included_tokens ?? defaultTokens;
294
+ const quotaData = {
295
+ owner_id: request.owner_id,
296
+ plan: request.plan,
297
+ default_included_tokens: defaultTokens,
298
+ custom_included_tokens: request.custom_included_tokens ?? null,
299
+ effective_included_tokens: effectiveTokens,
300
+ period_start: request.period_start,
301
+ period_end: request.period_end,
302
+ subscription_id: request.subscription_id || null,
303
+ source: "stripe",
304
+ };
305
+ // Reset usage if requested
306
+ if (request.reset_usage) {
307
+ quotaData.used_included_tokens = 0;
308
+ }
309
+ const { error } = await supabase
310
+ .from("user_token_quota_monthly")
311
+ .upsert(quotaData, {
312
+ onConflict: "owner_id",
313
+ });
314
+ if (error) {
315
+ logger.error("[updateUserQuota] Error:", error);
316
+ return { success: false, error: error.message };
317
+ }
318
+ return { success: true };
319
+ }
320
+ catch (error) {
321
+ logger.error("[updateUserQuota] Error:", error);
322
+ return { success: false, error: error.message };
323
+ }
324
+ }
325
+ /**
326
+ * Handle subscription update from billing-pro webhook
327
+ * This function is called by billing-pro when subscription events occur
328
+ *
329
+ * @param params Subscription parameters from Stripe
330
+ * @returns Success status
331
+ */
332
+ export async function handleSubscriptionQuotaUpdate(params) {
333
+ const { owner_id, plan_slug, subscription_id, current_period_start, current_period_end, } = params;
334
+ logger.debug(`[handleSubscriptionQuotaUpdate] Processing for user ${owner_id}, plan ${plan_slug}`);
335
+ try {
336
+ // Map plan slug to plan type
337
+ // Note: Starter plan receives same 100K token quota as Pro plan
338
+ const planMapping = {
339
+ pro: "pro",
340
+ premium: "premium",
341
+ enterprise: "enterprise",
342
+ starter: "pro", // Starter plans receive Pro quota (100K tokens/month)
343
+ free: "free",
344
+ };
345
+ const planType = planMapping[plan_slug.toLowerCase()] || "free";
346
+ // Check if there's an existing custom quota
347
+ const supabase = await getSupabaseServiceClient();
348
+ const { data: existingQuota } = await supabase
349
+ .from("user_token_quota_monthly")
350
+ .select("custom_included_tokens")
351
+ .eq("owner_id", owner_id)
352
+ .single();
353
+ // Update quota with period reset
354
+ const result = await updateUserQuota({
355
+ owner_id,
356
+ plan: planType,
357
+ custom_included_tokens: existingQuota?.custom_included_tokens ?? null,
358
+ period_start: current_period_start,
359
+ period_end: current_period_end,
360
+ subscription_id,
361
+ reset_usage: true, // Reset used tokens on renewal
362
+ });
363
+ if (!result.success) {
364
+ logger.error(`[handleSubscriptionQuotaUpdate] Failed: ${result.error}`);
365
+ return result;
366
+ }
367
+ logger.debug(`[handleSubscriptionQuotaUpdate] Success for user ${owner_id}`);
368
+ return { success: true };
369
+ }
370
+ catch (error) {
371
+ logger.error("[handleSubscriptionQuotaUpdate] Error:", error);
372
+ return { success: false, error: error.message };
373
+ }
374
+ }
375
+ /**
376
+ * Update custom quota for a user (admin override)
377
+ */
378
+ export async function updateCustomQuota(userId, customTokens) {
379
+ const supabase = await getSupabaseServiceClient();
380
+ try {
381
+ // Get current quota
382
+ const { data: currentQuota, error: fetchError } = await supabase
383
+ .from("user_token_quota_monthly")
384
+ .select("*")
385
+ .eq("owner_id", userId)
386
+ .single();
387
+ if (fetchError && fetchError.code !== "PGRST116") {
388
+ return { success: false, error: fetchError.message };
389
+ }
390
+ if (!currentQuota) {
391
+ return {
392
+ success: false,
393
+ error: "Aucun quota trouvé pour cet utilisateur",
394
+ };
395
+ }
396
+ const defaultTokens = PLAN_DEFAULTS[currentQuota.plan] || 0;
397
+ const effectiveTokens = customTokens ?? defaultTokens;
398
+ const { error } = await supabase
399
+ .from("user_token_quota_monthly")
400
+ .update({
401
+ custom_included_tokens: customTokens,
402
+ effective_included_tokens: effectiveTokens,
403
+ })
404
+ .eq("owner_id", userId);
405
+ if (error) {
406
+ return { success: false, error: error.message };
407
+ }
408
+ return { success: true };
409
+ }
410
+ catch (error) {
411
+ logger.error("[updateCustomQuota] Error:", error);
412
+ return { success: false, error: error.message };
413
+ }
414
+ }
415
+ /**
416
+ * Get quota usage history for a user
417
+ */
418
+ export async function getQuotaUsageHistory(userId, limit = 50, offset = 0) {
419
+ const supabase = await getSupabaseServiceClient();
420
+ try {
421
+ const { data, error } = await supabase
422
+ .from("user_token_quota_ledger")
423
+ .select("*")
424
+ .eq("owner_id", userId)
425
+ .order("created_at", { ascending: false })
426
+ .range(offset, offset + limit - 1);
427
+ if (error)
428
+ throw error;
429
+ return data || [];
430
+ }
431
+ catch (error) {
432
+ logger.error("[getQuotaUsageHistory] Error:", error);
433
+ return [];
434
+ }
435
+ }
436
+ /**
437
+ * Refund/credit tokens back to user in case of error after debit
438
+ * This ensures users don't lose tokens if the operation fails after AI generation
439
+ *
440
+ * @param userId User ID
441
+ * @param tokensToRefund Number of tokens to credit back
442
+ * @param model Model that was used (for logging)
443
+ * @param reason Reason for the refund (for logging)
444
+ * @returns Success status with refund details
445
+ */
446
+ export async function refundTokens(userId, tokensToRefund, model, reason) {
447
+ logger.debug(`[refundTokens] START for user ${userId}: tokensToRefund=${tokensToRefund}, model=${model}, reason=${reason}`);
448
+ if (tokensToRefund <= 0) {
449
+ return {
450
+ success: true,
451
+ refundedToQuota: 0,
452
+ refundedToPurchased: 0,
453
+ };
454
+ }
455
+ const supabase = await getSupabaseServiceClient();
456
+ try {
457
+ let refundedToQuota = 0;
458
+ let refundedToPurchased = 0;
459
+ let remainingToRefund = tokensToRefund;
460
+ // 1. Check if user has active quota that was debited
461
+ const quotaStatus = await getUserQuotaStatus(userId);
462
+ if (quotaStatus.hasQuota && quotaStatus.isActive) {
463
+ const { data: quotaData } = await supabase
464
+ .from("user_token_quota_monthly")
465
+ .select("used_included_tokens, effective_included_tokens")
466
+ .eq("owner_id", userId)
467
+ .single();
468
+ if (quotaData && quotaData.used_included_tokens > 0) {
469
+ // Calculate how much to refund to quota (cannot exceed what was used)
470
+ const maxQuotaRefund = Math.min(remainingToRefund, quotaData.used_included_tokens);
471
+ if (maxQuotaRefund > 0) {
472
+ const newUsed = quotaData.used_included_tokens - maxQuotaRefund;
473
+ const { error: quotaError } = await supabase
474
+ .from("user_token_quota_monthly")
475
+ .update({ used_included_tokens: newUsed })
476
+ .eq("owner_id", userId);
477
+ if (quotaError) {
478
+ logger.error("[refundTokens] Quota refund error:", quotaError);
479
+ }
480
+ else {
481
+ refundedToQuota = maxQuotaRefund;
482
+ remainingToRefund -= maxQuotaRefund;
483
+ logger.debug(`[refundTokens] Refunded ${maxQuotaRefund} tokens to quota`);
484
+ // Log quota refund
485
+ await supabase.from("user_token_quota_ledger").insert({
486
+ owner_id: userId,
487
+ tokens_used: -maxQuotaRefund, // Negative = refund
488
+ model,
489
+ prompt: `REFUND: ${reason.substring(0, 32)}`,
490
+ period_start: quotaStatus.periodStart,
491
+ period_end: quotaStatus.periodEnd,
492
+ meta: { refund: true, reason },
493
+ });
494
+ }
495
+ }
496
+ }
497
+ }
498
+ // 2. Refund remaining to purchased tokens
499
+ if (remainingToRefund > 0) {
500
+ const { error: ledgerError } = await supabase
501
+ .from("user_token_ledger")
502
+ .insert({
503
+ owner_id: userId,
504
+ type: "refund",
505
+ amount: remainingToRefund, // Positive = credit
506
+ model,
507
+ prompt: `REFUND: ${reason.substring(0, 32)}`,
508
+ meta: { refund: true, reason },
509
+ });
510
+ if (ledgerError) {
511
+ logger.error("[refundTokens] Ledger refund error:", ledgerError);
512
+ }
513
+ else {
514
+ refundedToPurchased = remainingToRefund;
515
+ logger.debug(`[refundTokens] Refunded ${remainingToRefund} tokens to purchased balance`);
516
+ }
517
+ }
518
+ logger.debug("[refundTokens] SUCCESS:", {
519
+ refundedToQuota,
520
+ refundedToPurchased,
521
+ total: refundedToQuota + refundedToPurchased,
522
+ });
523
+ return {
524
+ success: true,
525
+ refundedToQuota,
526
+ refundedToPurchased,
527
+ };
528
+ }
529
+ catch (error) {
530
+ logger.error("[refundTokens] Error:", error);
531
+ return {
532
+ success: false,
533
+ error: error.message,
534
+ refundedToQuota: 0,
535
+ refundedToPurchased: 0,
536
+ };
537
+ }
538
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Wallet Repair Utilities
3
+ * For fixing wallets that were not initialized correctly (legacy credits, manual edits, etc.)
4
+ */
5
+ /**
6
+ * Recalculate wallet budget from ledger history
7
+ * Useful for users who have tokens but wallet = 0 USD
8
+ */
9
+ export declare function repairUserWallet(userId: string, tokenBalance?: number): Promise<{
10
+ success: boolean;
11
+ error?: string;
12
+ before?: {
13
+ balance: number;
14
+ providerBudget: number;
15
+ sellValue: number;
16
+ };
17
+ after?: {
18
+ balance: number;
19
+ providerBudget: number;
20
+ sellValue: number;
21
+ };
22
+ }>;
23
+ /**
24
+ * Repair all wallets in the system
25
+ */
26
+ export declare function repairAllWallets(): Promise<{
27
+ success: boolean;
28
+ repaired: number;
29
+ failed: number;
30
+ errors: string[];
31
+ }>;
32
+ //# sourceMappingURL=wallet-repair.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wallet-repair.d.ts","sourceRoot":"","sources":["../../src/server/wallet-repair.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAcH;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC;IACT,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACxE,CAAC,CAgKD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC,CA4DD"}