@lastbrain/module-ai 2.0.26 → 2.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. package/README.md +52 -1
  2. package/dist/ai.build.config.d.ts.map +1 -1
  3. package/dist/ai.build.config.js +508 -9
  4. package/dist/api/admin/ai-provider-models/[id].d.ts +18 -0
  5. package/dist/api/admin/ai-provider-models/[id].d.ts.map +1 -0
  6. package/dist/api/admin/ai-provider-models/[id].js +58 -0
  7. package/dist/api/admin/ai-provider-models.d.ts +20 -0
  8. package/dist/api/admin/ai-provider-models.d.ts.map +1 -0
  9. package/dist/api/admin/ai-provider-models.js +26 -0
  10. package/dist/api/admin/ai-providers/[key].d.ts +18 -0
  11. package/dist/api/admin/ai-providers/[key].d.ts.map +1 -0
  12. package/dist/api/admin/ai-providers/[key].js +55 -0
  13. package/dist/api/admin/ai-providers.d.ts +20 -0
  14. package/dist/api/admin/ai-providers.d.ts.map +1 -0
  15. package/dist/api/admin/ai-providers.js +26 -0
  16. package/dist/api/admin/billing-analytics.d.ts +43 -0
  17. package/dist/api/admin/billing-analytics.d.ts.map +1 -0
  18. package/dist/api/admin/billing-analytics.js +144 -0
  19. package/dist/api/admin/global-ai-settings.d.ts +14 -0
  20. package/dist/api/admin/global-ai-settings.d.ts.map +1 -0
  21. package/dist/api/admin/global-ai-settings.js +63 -0
  22. package/dist/api/admin/token-packs/[id].d.ts.map +1 -1
  23. package/dist/api/admin/token-packs/[id].js +3 -2
  24. package/dist/api/admin/token-packs.d.ts.map +1 -1
  25. package/dist/api/admin/token-packs.js +3 -2
  26. package/dist/api/admin/user-monthly-details.d.ts +49 -0
  27. package/dist/api/admin/user-monthly-details.d.ts.map +1 -0
  28. package/dist/api/admin/user-monthly-details.js +140 -0
  29. package/dist/api/admin/user-quota.d.ts +21 -0
  30. package/dist/api/admin/user-quota.d.ts.map +1 -0
  31. package/dist/api/admin/user-quota.js +59 -0
  32. package/dist/api/admin/user-token/[id].d.ts.map +1 -1
  33. package/dist/api/admin/user-token/[id].js +2 -1
  34. package/dist/api/admin/user-token.d.ts +5 -2
  35. package/dist/api/admin/user-token.d.ts.map +1 -1
  36. package/dist/api/admin/user-token.js +91 -17
  37. package/dist/api/admin/user-usage-by-model.d.ts +22 -0
  38. package/dist/api/admin/user-usage-by-model.d.ts.map +1 -0
  39. package/dist/api/admin/user-usage-by-model.js +78 -0
  40. package/dist/api/admin/user-wallet-analytics.d.ts +15 -0
  41. package/dist/api/admin/user-wallet-analytics.d.ts.map +1 -0
  42. package/dist/api/admin/user-wallet-analytics.js +67 -0
  43. package/dist/api/admin/wallet-repair/route.d.ts +30 -0
  44. package/dist/api/admin/wallet-repair/route.d.ts.map +1 -0
  45. package/dist/api/admin/wallet-repair/route.js +63 -0
  46. package/dist/api/auth/ai-model-settings.d.ts +21 -0
  47. package/dist/api/auth/ai-model-settings.d.ts.map +1 -0
  48. package/dist/api/auth/ai-model-settings.js +86 -0
  49. package/dist/api/auth/ai-settings.d.ts +17 -0
  50. package/dist/api/auth/ai-settings.d.ts.map +1 -0
  51. package/dist/api/auth/ai-settings.js +87 -0
  52. package/dist/api/auth/api-keys/[id].d.ts +17 -0
  53. package/dist/api/auth/api-keys/[id].d.ts.map +1 -0
  54. package/dist/api/auth/api-keys/[id].js +66 -0
  55. package/dist/api/auth/api-keys.d.ts +19 -0
  56. package/dist/api/auth/api-keys.d.ts.map +1 -0
  57. package/dist/api/auth/api-keys.js +94 -0
  58. package/dist/api/auth/create-checkout.d.ts +1 -1
  59. package/dist/api/auth/create-checkout.d.ts.map +1 -1
  60. package/dist/api/auth/create-checkout.js +8 -6
  61. package/dist/api/auth/generate-image.d.ts +2 -2
  62. package/dist/api/auth/generate-image.d.ts.map +1 -1
  63. package/dist/api/auth/generate-image.js +404 -104
  64. package/dist/api/auth/generate-text.d.ts +3 -2
  65. package/dist/api/auth/generate-text.d.ts.map +1 -1
  66. package/dist/api/auth/generate-text.js +130 -58
  67. package/dist/api/auth/process-ocr.d.ts +9 -0
  68. package/dist/api/auth/process-ocr.d.ts.map +1 -0
  69. package/dist/api/auth/process-ocr.js +43 -0
  70. package/dist/api/auth/prompts/stats.d.ts +14 -0
  71. package/dist/api/auth/prompts/stats.d.ts.map +1 -0
  72. package/dist/api/auth/prompts/stats.js +46 -0
  73. package/dist/api/auth/prompts.d.ts +26 -0
  74. package/dist/api/auth/prompts.d.ts.map +1 -0
  75. package/dist/api/auth/prompts.js +175 -0
  76. package/dist/api/auth/token-balance.d.ts +26 -0
  77. package/dist/api/auth/token-balance.d.ts.map +1 -0
  78. package/dist/api/auth/token-balance.js +47 -0
  79. package/dist/api/auth/token-checkout.d.ts.map +1 -1
  80. package/dist/api/auth/token-checkout.js +22 -4
  81. package/dist/api/auth/token-packs.d.ts.map +1 -1
  82. package/dist/api/auth/token-packs.js +2 -1
  83. package/dist/api/auth/usage-by-model.d.ts +25 -0
  84. package/dist/api/auth/usage-by-model.d.ts.map +1 -0
  85. package/dist/api/auth/usage-by-model.js +95 -0
  86. package/dist/api/auth/usage.d.ts +26 -0
  87. package/dist/api/auth/usage.d.ts.map +1 -0
  88. package/dist/api/auth/usage.js +127 -0
  89. package/dist/api/auth/user-tokens.d.ts.map +1 -1
  90. package/dist/api/auth/user-tokens.js +36 -2
  91. package/dist/api/auth/wallet/route.d.ts +17 -0
  92. package/dist/api/auth/wallet/route.d.ts.map +1 -0
  93. package/dist/api/auth/wallet/route.js +68 -0
  94. package/dist/api/auth/wallet.d.ts +16 -0
  95. package/dist/api/auth/wallet.d.ts.map +1 -0
  96. package/dist/api/auth/wallet.js +71 -0
  97. package/dist/api/public/gateway-models.d.ts +25 -0
  98. package/dist/api/public/gateway-models.d.ts.map +1 -0
  99. package/dist/api/public/gateway-models.js +49 -0
  100. package/dist/api/public/pricing-summary.d.ts +46 -0
  101. package/dist/api/public/pricing-summary.d.ts.map +1 -0
  102. package/dist/api/public/pricing-summary.js +70 -0
  103. package/dist/api/public/prompts/[id]/stats.d.ts +15 -0
  104. package/dist/api/public/prompts/[id]/stats.d.ts.map +1 -0
  105. package/dist/api/public/prompts/[id]/stats.js +37 -0
  106. package/dist/api/public/prompts/[id]/view.d.ts +15 -0
  107. package/dist/api/public/prompts/[id]/view.d.ts.map +1 -0
  108. package/dist/api/public/prompts/[id]/view.js +41 -0
  109. package/dist/api/public/prompts/slug/[slug].d.ts +15 -0
  110. package/dist/api/public/prompts/slug/[slug].d.ts.map +1 -0
  111. package/dist/api/public/prompts/slug/[slug].js +40 -0
  112. package/dist/api/public/prompts/user/[username].d.ts +17 -0
  113. package/dist/api/public/prompts/user/[username].d.ts.map +1 -0
  114. package/dist/api/public/prompts/user/[username].js +51 -0
  115. package/dist/api/public/prompts.d.ts +15 -0
  116. package/dist/api/public/prompts.d.ts.map +1 -0
  117. package/dist/api/public/prompts.js +45 -0
  118. package/dist/api/public/token-packs.d.ts +11 -0
  119. package/dist/api/public/token-packs.d.ts.map +1 -0
  120. package/dist/api/public/token-packs.js +25 -0
  121. package/dist/api/public/token-pricing.d.ts +44 -0
  122. package/dist/api/public/token-pricing.d.ts.map +1 -0
  123. package/dist/api/public/token-pricing.js +168 -0
  124. package/dist/api/public/v1/_lib/artifacts.d.ts +52 -0
  125. package/dist/api/public/v1/_lib/artifacts.d.ts.map +1 -0
  126. package/dist/api/public/v1/_lib/artifacts.js +217 -0
  127. package/dist/api/public/v1/_lib/auth.d.ts +43 -0
  128. package/dist/api/public/v1/_lib/auth.d.ts.map +1 -0
  129. package/dist/api/public/v1/_lib/auth.js +108 -0
  130. package/dist/api/public/v1/_lib/errors.d.ts +17 -0
  131. package/dist/api/public/v1/_lib/errors.d.ts.map +1 -0
  132. package/dist/api/public/v1/_lib/errors.js +16 -0
  133. package/dist/api/public/v1/_lib/log.d.ts +29 -0
  134. package/dist/api/public/v1/_lib/log.d.ts.map +1 -0
  135. package/dist/api/public/v1/_lib/log.js +68 -0
  136. package/dist/api/public/v1/_lib/quota.d.ts +24 -0
  137. package/dist/api/public/v1/_lib/quota.d.ts.map +1 -0
  138. package/dist/api/public/v1/_lib/quota.js +118 -0
  139. package/dist/api/public/v1/_lib/router.d.ts +54 -0
  140. package/dist/api/public/v1/_lib/router.d.ts.map +1 -0
  141. package/dist/api/public/v1/_lib/router.js +119 -0
  142. package/dist/api/public/v1/connect.d.ts +20 -0
  143. package/dist/api/public/v1/connect.d.ts.map +1 -0
  144. package/dist/api/public/v1/connect.js +119 -0
  145. package/dist/api/public/v1/doc.d.ts +239 -0
  146. package/dist/api/public/v1/doc.d.ts.map +1 -0
  147. package/dist/api/public/v1/doc.js +253 -0
  148. package/dist/api/public/v1/history/[id].d.ts +92 -0
  149. package/dist/api/public/v1/history/[id].d.ts.map +1 -0
  150. package/dist/api/public/v1/history/[id].js +176 -0
  151. package/dist/api/public/v1/history.d.ts +30 -0
  152. package/dist/api/public/v1/history.d.ts.map +1 -0
  153. package/dist/api/public/v1/history.js +142 -0
  154. package/dist/api/public/v1/image-ai.d.ts +24 -0
  155. package/dist/api/public/v1/image-ai.d.ts.map +1 -0
  156. package/dist/api/public/v1/image-ai.js +233 -0
  157. package/dist/api/public/v1/prompts.d.ts +19 -0
  158. package/dist/api/public/v1/prompts.d.ts.map +1 -0
  159. package/dist/api/public/v1/prompts.js +107 -0
  160. package/dist/api/public/v1/provider.d.ts +16 -0
  161. package/dist/api/public/v1/provider.d.ts.map +1 -0
  162. package/dist/api/public/v1/provider.js +130 -0
  163. package/dist/api/public/v1/purchase.d.ts +11 -0
  164. package/dist/api/public/v1/purchase.d.ts.map +1 -0
  165. package/dist/api/public/v1/purchase.js +18 -0
  166. package/dist/api/public/v1/status.d.ts +35 -0
  167. package/dist/api/public/v1/status.d.ts.map +1 -0
  168. package/dist/api/public/v1/status.js +163 -0
  169. package/dist/api/public/v1/text-ai.d.ts +26 -0
  170. package/dist/api/public/v1/text-ai.d.ts.map +1 -0
  171. package/dist/api/public/v1/text-ai.js +239 -0
  172. package/dist/api/public/webhook.d.ts.map +1 -1
  173. package/dist/api/public/webhook.js +50 -39
  174. package/dist/api/track-usage.d.ts +12 -0
  175. package/dist/api/track-usage.d.ts.map +1 -0
  176. package/dist/api/track-usage.js +37 -0
  177. package/dist/components/Doc.d.ts.map +1 -1
  178. package/dist/components/Doc.js +1 -1
  179. package/dist/components/DocUsageCustom.js +6 -6
  180. package/dist/components/admin/UserTokenTab.d.ts.map +1 -1
  181. package/dist/components/admin/UserTokenTab.js +170 -23
  182. package/dist/components/auth/AuthDashboardAi.d.ts +2 -0
  183. package/dist/components/auth/AuthDashboardAi.d.ts.map +1 -0
  184. package/dist/components/auth/AuthDashboardAi.js +53 -0
  185. package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts +277 -0
  186. package/dist/docs/REFACTORING_BILLING_GUIDE.d.ts.map +1 -0
  187. package/dist/docs/REFACTORING_BILLING_GUIDE.js +276 -0
  188. package/dist/index.d.ts +15 -1
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +18 -1
  191. package/dist/scripts/migrate-tokens-to-wallet.d.ts +13 -0
  192. package/dist/scripts/migrate-tokens-to-wallet.d.ts.map +1 -0
  193. package/dist/scripts/migrate-tokens-to-wallet.js +165 -0
  194. package/dist/server/__tests__/billing.test.d.ts +5 -0
  195. package/dist/server/__tests__/billing.test.d.ts.map +1 -0
  196. package/dist/server/__tests__/billing.test.js +223 -0
  197. package/dist/server/ai-client.d.ts +59 -0
  198. package/dist/server/ai-client.d.ts.map +1 -0
  199. package/dist/server/ai-client.js +111 -0
  200. package/dist/server/ai-generation-service.d.ts +66 -0
  201. package/dist/server/ai-generation-service.d.ts.map +1 -0
  202. package/dist/server/ai-generation-service.js +274 -0
  203. package/dist/server/billing.d.ts +200 -0
  204. package/dist/server/billing.d.ts.map +1 -0
  205. package/dist/server/billing.js +488 -0
  206. package/dist/server/gateway-service.d.ts +13 -0
  207. package/dist/server/gateway-service.d.ts.map +1 -0
  208. package/dist/server/gateway-service.js +161 -0
  209. package/dist/server/global-settings.d.ts +16 -0
  210. package/dist/server/global-settings.d.ts.map +1 -0
  211. package/dist/server/global-settings.js +42 -0
  212. package/dist/server/model-filter.d.ts +25 -0
  213. package/dist/server/model-filter.d.ts.map +1 -0
  214. package/dist/server/model-filter.js +240 -0
  215. package/dist/server/ocr.d.ts +39 -0
  216. package/dist/server/ocr.d.ts.map +1 -0
  217. package/dist/server/ocr.js +280 -0
  218. package/dist/server/openai-client.d.ts +19 -0
  219. package/dist/server/openai-client.d.ts.map +1 -0
  220. package/dist/server/openai-client.js +26 -0
  221. package/dist/server/pricing-config.d.ts +18 -0
  222. package/dist/server/pricing-config.d.ts.map +1 -0
  223. package/dist/server/pricing-config.js +94 -0
  224. package/dist/server/pricing-validator.d.ts +41 -0
  225. package/dist/server/pricing-validator.d.ts.map +1 -0
  226. package/dist/server/pricing-validator.js +113 -0
  227. package/dist/server/pricing.d.ts +121 -0
  228. package/dist/server/pricing.d.ts.map +1 -0
  229. package/dist/server/pricing.js +225 -0
  230. package/dist/server/quota.d.ts +66 -0
  231. package/dist/server/quota.d.ts.map +1 -0
  232. package/dist/server/quota.js +538 -0
  233. package/dist/server/wallet-repair.d.ts +32 -0
  234. package/dist/server/wallet-repair.d.ts.map +1 -0
  235. package/dist/server/wallet-repair.js +189 -0
  236. package/dist/server.d.ts +13 -1
  237. package/dist/server.d.ts.map +1 -1
  238. package/dist/server.js +87 -16
  239. package/dist/sitemap/handlers/prompts.d.ts +6 -0
  240. package/dist/sitemap/handlers/prompts.d.ts.map +1 -0
  241. package/dist/sitemap/handlers/prompts.js +72 -0
  242. package/dist/sitemap/handlers/users.d.ts +6 -0
  243. package/dist/sitemap/handlers/users.d.ts.map +1 -0
  244. package/dist/sitemap/handlers/users.js +80 -0
  245. package/dist/sitemap/manifest.d.ts +8 -0
  246. package/dist/sitemap/manifest.d.ts.map +1 -0
  247. package/dist/sitemap/manifest.js +27 -0
  248. package/dist/types/gateway.d.ts +40 -0
  249. package/dist/types/gateway.d.ts.map +1 -0
  250. package/dist/types/gateway.js +4 -0
  251. package/dist/types/quota.d.ts +74 -0
  252. package/dist/types/quota.d.ts.map +1 -0
  253. package/dist/types/quota.js +11 -0
  254. package/dist/utils/date.d.ts +7 -0
  255. package/dist/utils/date.d.ts.map +1 -0
  256. package/dist/utils/date.js +17 -0
  257. package/dist/web/admin/AdminTokenPacksPage.js +1 -1
  258. package/dist/web/admin/BillingAnalyticsPage.d.ts +2 -0
  259. package/dist/web/admin/BillingAnalyticsPage.d.ts.map +1 -0
  260. package/dist/web/admin/BillingAnalyticsPage.js +141 -0
  261. package/dist/web/admin/GlobalAISettingsPage.d.ts +2 -0
  262. package/dist/web/admin/GlobalAISettingsPage.d.ts.map +1 -0
  263. package/dist/web/admin/GlobalAISettingsPage.js +93 -0
  264. package/dist/web/admin/UserTokenPage.d.ts.map +1 -1
  265. package/dist/web/admin/UserTokenPage.js +20 -7
  266. package/dist/web/auth/AISettingsPage.d.ts +2 -0
  267. package/dist/web/auth/AISettingsPage.d.ts.map +1 -0
  268. package/dist/web/auth/AISettingsPage.js +258 -0
  269. package/dist/web/auth/APIKeysPage.d.ts +2 -0
  270. package/dist/web/auth/APIKeysPage.d.ts.map +1 -0
  271. package/dist/web/auth/APIKeysPage.js +154 -0
  272. package/dist/web/auth/HistoryPage.d.ts +2 -0
  273. package/dist/web/auth/HistoryPage.d.ts.map +1 -0
  274. package/dist/web/auth/HistoryPage.js +279 -0
  275. package/dist/web/auth/PromptsPage.d.ts +5 -0
  276. package/dist/web/auth/PromptsPage.d.ts.map +1 -0
  277. package/dist/web/auth/PromptsPage.js +137 -0
  278. package/dist/web/auth/TokenPage.d.ts.map +1 -1
  279. package/dist/web/auth/TokenPage.js +88 -31
  280. package/dist/web/auth/UsageAndTokensPage.d.ts +2 -0
  281. package/dist/web/auth/UsageAndTokensPage.d.ts.map +1 -0
  282. package/dist/web/auth/UsageAndTokensPage.js +157 -0
  283. package/dist/web/auth/UsagePage.d.ts +2 -0
  284. package/dist/web/auth/UsagePage.d.ts.map +1 -0
  285. package/dist/web/auth/UsagePage.js +62 -0
  286. package/dist/web/auth/components/ApiKeyFilterSelect.d.ts +13 -0
  287. package/dist/web/auth/components/ApiKeyFilterSelect.d.ts.map +1 -0
  288. package/dist/web/auth/components/ApiKeyFilterSelect.js +16 -0
  289. package/dist/web/auth/components/ModelUsageTable.d.ts +19 -0
  290. package/dist/web/auth/components/ModelUsageTable.d.ts.map +1 -0
  291. package/dist/web/auth/components/ModelUsageTable.js +37 -0
  292. package/dist/web/auth/components/PurchaseButton.d.ts +7 -0
  293. package/dist/web/auth/components/PurchaseButton.d.ts.map +1 -0
  294. package/dist/web/auth/components/PurchaseButton.js +13 -0
  295. package/dist/web/auth/components/TokenHistoryCard.d.ts +20 -0
  296. package/dist/web/auth/components/TokenHistoryCard.d.ts.map +1 -0
  297. package/dist/web/auth/components/TokenHistoryCard.js +76 -0
  298. package/dist/web/auth/components/TokenKpiGrid.d.ts +24 -0
  299. package/dist/web/auth/components/TokenKpiGrid.d.ts.map +1 -0
  300. package/dist/web/auth/components/TokenKpiGrid.js +38 -0
  301. package/dist/web/auth/components/UsageByDayChart.d.ts +11 -0
  302. package/dist/web/auth/components/UsageByDayChart.d.ts.map +1 -0
  303. package/dist/web/auth/components/UsageByDayChart.js +32 -0
  304. package/dist/web/auth/components/UsageByModelBarChart.d.ts +12 -0
  305. package/dist/web/auth/components/UsageByModelBarChart.d.ts.map +1 -0
  306. package/dist/web/auth/components/UsageByModelBarChart.js +32 -0
  307. package/dist/web/auth/components/WalletStatusCard.d.ts +9 -0
  308. package/dist/web/auth/components/WalletStatusCard.d.ts.map +1 -0
  309. package/dist/web/auth/components/WalletStatusCard.js +50 -0
  310. package/dist/web/components/ImageGenerative.d.ts +3 -1
  311. package/dist/web/components/ImageGenerative.d.ts.map +1 -1
  312. package/dist/web/components/ImageGenerative.js +139 -52
  313. package/dist/web/components/TextareaGenerative.d.ts +3 -1
  314. package/dist/web/components/TextareaGenerative.d.ts.map +1 -1
  315. package/dist/web/components/TextareaGenerative.js +10 -5
  316. package/dist/web/public/PromptDetailPage.d.ts +25 -0
  317. package/dist/web/public/PromptDetailPage.d.ts.map +1 -0
  318. package/dist/web/public/PromptDetailPage.js +71 -0
  319. package/dist/web/public/PromptDetailPageServer.d.ts +15 -0
  320. package/dist/web/public/PromptDetailPageServer.d.ts.map +1 -0
  321. package/dist/web/public/PromptDetailPageServer.js +68 -0
  322. package/dist/web/public/PublicPromptsPage.d.ts +5 -0
  323. package/dist/web/public/PublicPromptsPage.d.ts.map +1 -0
  324. package/dist/web/public/PublicPromptsPage.js +110 -0
  325. package/dist/web/public/PurchaseTokensPage.d.ts +2 -0
  326. package/dist/web/public/PurchaseTokensPage.d.ts.map +1 -0
  327. package/dist/web/public/PurchaseTokensPage.js +98 -0
  328. package/dist/web/public/UserAvatar.d.ts +13 -0
  329. package/dist/web/public/UserAvatar.d.ts.map +1 -0
  330. package/dist/web/public/UserAvatar.js +13 -0
  331. package/dist/web/public/UserDetailPageServer.d.ts +15 -0
  332. package/dist/web/public/UserDetailPageServer.d.ts.map +1 -0
  333. package/dist/web/public/UserDetailPageServer.js +31 -0
  334. package/dist/web/public/UserPromptsPage.d.ts +9 -0
  335. package/dist/web/public/UserPromptsPage.d.ts.map +1 -0
  336. package/dist/web/public/UserPromptsPage.js +112 -0
  337. package/dist/web/public/UserPromptsPageServer.d.ts +15 -0
  338. package/dist/web/public/UserPromptsPageServer.d.ts.map +1 -0
  339. package/dist/web/public/UserPromptsPageServer.js +31 -0
  340. package/package.json +18 -9
  341. package/supabase/migrations/20251125000000_ai_tokens.sql +7 -0
  342. package/supabase/migrations/20260123100002_user_token_quota_monthly copy.sql +173 -0
  343. package/supabase/migrations/20260128100003_update_and_add_table.sql +368 -0
  344. package/supabase/migrations/20260128120000_seed_providers_models.sql +78 -0
  345. package/supabase/migrations/20260128131405_add_api_key_id_to_ledgers.sql +41 -0
  346. package/supabase/migrations/20260128140000_ai_artifacts_storage.sql +99 -0
  347. package/supabase/migrations/20260128140002_ai_user_settings.sql +57 -0
  348. package/supabase/migrations/20260128150000_drop_ai_user_settings.sql +21 -0
  349. package/supabase/migrations/20260128160000_wallet_billing_system.sql +192 -0
  350. package/supabase/migrations/20260128160001_wallet_rpc_functions.sql +165 -0
  351. package/supabase/migrations/20260128170000_add_pack_coef_to_token_packs.sql +30 -0
  352. package/supabase/migrations/20260129120000_wallet_view_rpc.sql +41 -0
  353. package/supabase/migrations/20260129220003_update_pack_margins.sql +31 -0
  354. package/supabase/migrations/20260129330004_ai_user_prompts.sql +151 -0
  355. package/supabase/migrations/20260129330005_ai_prompts_ip_tracking.sql +92 -0
  356. package/supabase/migrations/20260129330006_ai_prompts_slug.sql +64 -0
  357. package/supabase/migrations/20260129330007_ai_prompts_view_slug.sql +26 -0
  358. package/supabase/migrations/20260129440000_ai_prompts_view_username.sql +33 -0
  359. package/supabase/migrations/20260129450000_ai_prompts_add_lang.sql +40 -0
  360. package/supabase/migrations/20260131000000_extract_model_prompt_in_ledger.sql +92 -0
  361. package/supabase/migrations/20260131140000_fix_duplicate_purchases.sql +64 -0
  362. package/supabase/migrations/20260201120000_module-ai_default_models.sql +63 -0
  363. package/supabase/migrations/20260201130000_module-ai_remove_provider_tables.sql +17 -0
  364. package/supabase/migrations-down/20251217120000_user_token_quota_monthly.sql +34 -0
  365. package/supabase/migrations-down/20260128131405_add_api_key_id_to_ledgers.sql +25 -0
@@ -1,12 +1,17 @@
1
1
  "use client";
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback } from "react";
4
- import { Card, CardBody, CardHeader, Spinner, Chip, Button, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, addToast, } from "@lastbrain/ui";
5
- import { Coins, TrendingUp, TrendingDown, Calendar, ShoppingCart, AlertCircle, Gift, Eye, } from "lucide-react";
6
- import { useAuth, useModuleTranslation } from "@lastbrain/core";
4
+ import { useSearchParams } from "next/navigation";
5
+ import confetti from "canvas-confetti";
6
+ import { Card, CardBody, CardHeader, Spinner, Chip, Button, Select, SelectItem, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, addToast, Progress, Alert, } from "@lastbrain/ui";
7
+ import { Coins, TrendingUp, TrendingDown, Calendar, ShoppingCart, AlertCircle, Crown, } from "lucide-react";
8
+ import { logger, useAuth, useModuleTranslation, useLocalizedRouter, } from "@lastbrain/core";
9
+ import { formatDateFr } from "../../utils/date";
7
10
  export function TokenPage() {
8
11
  const t = useModuleTranslation("ai");
9
12
  const { user } = useAuth();
13
+ const router = useLocalizedRouter();
14
+ const searchParams = useSearchParams();
10
15
  const [loading, setLoading] = useState(true);
11
16
  const [balance, setBalance] = useState(null);
12
17
  const [transactions, setTransactions] = useState([]);
@@ -14,6 +19,31 @@ export function TokenPage() {
14
19
  const [monthlyStats, setMonthlyStats] = useState(null);
15
20
  const [tokenPacks, setTokenPacks] = useState([]);
16
21
  const [checkoutLoading, setCheckoutLoading] = useState(null);
22
+ const [successMessage, setSuccessMessage] = useState(null);
23
+ // Check for success/canceled params from Stripe redirect
24
+ useEffect(() => {
25
+ if (searchParams.get("token_success") === "true") {
26
+ setSuccessMessage(t("tokens.purchase_success") || "🎉 Tokens achetés avec succès !");
27
+ // Trigger confetti
28
+ confetti({
29
+ particleCount: 100,
30
+ spread: 600,
31
+ origin: { y: 0.4 },
32
+ });
33
+ setTimeout(() => {
34
+ router.replace("/auth/ai/token");
35
+ }, 100);
36
+ }
37
+ if (searchParams.get("token_canceled") === "true") {
38
+ addToast({
39
+ title: t("tokens.purchase_canceled") || "Achat annulé",
40
+ description: t("tokens.purchase_canceled_desc") ||
41
+ "L'achat de tokens a été annulé.",
42
+ color: "warning",
43
+ });
44
+ router.replace("/auth/ai/token");
45
+ }
46
+ }, [searchParams, router, t]);
17
47
  // Générer les 12 derniers mois
18
48
  const availableMonths = Array.from({ length: 12 }, (_, i) => {
19
49
  const date = new Date();
@@ -25,31 +55,30 @@ export function TokenPage() {
25
55
  return;
26
56
  try {
27
57
  setLoading(true);
28
- // Récupérer le solde et l'historique
29
- const response = await fetch("/api/ai/user/tokens");
30
- if (!response.ok) {
58
+ // Récupérer le solde combiné (quota + achetés) et l'historique
59
+ const [balanceResponse, historyResponse] = await Promise.all([
60
+ fetch("/api/ai/auth/token-balance"),
61
+ fetch("/api/ai/user/tokens"),
62
+ ]);
63
+ if (!balanceResponse.ok || !historyResponse.ok) {
31
64
  throw new Error(t("tokens.error.loading") || "Erreur lors du chargement des données");
32
65
  }
33
- const data = await response.json();
66
+ const balanceData = await balanceResponse.json();
67
+ const historyData = await historyResponse.json();
34
68
  // Historique complet pour calculs globaux
35
- const allTransactions = data.history || [];
36
- // Total offert/ajusté (tous les temps) via les transactions de type "adjust"
37
- const totalGifted = allTransactions
38
- .filter((t) => t.type === "adjust" && t.amount > 0)
39
- .reduce((sum, t) => sum + t.amount, 0);
40
- setBalance({
41
- balance: data.balance || 0,
42
- totalAdded: data.stats?.totalPurchased + data.stats?.totalGifted || 0,
43
- totalUsed: data.stats?.totalUsed || 0,
44
- totalGifted,
45
- });
69
+ const allTransactions = historyData.history || [];
70
+ setBalance(balanceData);
46
71
  // Filtrer les transactions par mois
47
72
  const filtered = allTransactions.filter((t) => t.created_at.startsWith(selectedMonth));
48
- // Calculer le running balance
49
- let runningBalance = data.balance || 0;
73
+ // Calculer le running balance (tokens achetés uniquement, pas quota)
74
+ let runningBalance = balanceData.purchased?.balance || 0;
50
75
  const withBalance = filtered.map((t) => {
51
76
  const txWithBalance = { ...t, running_balance: runningBalance };
52
- runningBalance -= t.amount;
77
+ // Ne décrémenter le running_balance que pour les transactions achetées
78
+ // Les transactions quota ne changent pas le solde des tokens achetés
79
+ if (t.source !== "quota") {
80
+ runningBalance -= t.amount;
81
+ }
53
82
  return txWithBalance;
54
83
  });
55
84
  setTransactions(withBalance);
@@ -74,7 +103,7 @@ export function TokenPage() {
74
103
  }
75
104
  }
76
105
  catch (error) {
77
- console.error(t("tokens.error.loading") || "Erreur:", error);
106
+ logger.error(t("tokens.error.loading") || "Erreur:", error);
78
107
  addToast({
79
108
  color: "danger",
80
109
  title: t("tokens.error.loading") || "Erreur lors du chargement des données",
@@ -96,21 +125,31 @@ export function TokenPage() {
96
125
  minute: "2-digit",
97
126
  });
98
127
  };
99
- const getTypeLabel = (type) => {
128
+ const getTypeLabel = (type, source) => {
129
+ // Différencier les débits quota des débits achat
130
+ if (type === "use" && source === "quota") {
131
+ return t("tokens.type.quota_use") || "Quota utilisé";
132
+ }
100
133
  const labels = {
101
134
  purchase: t("tokens.type.purchase") || "Achat",
102
135
  gift: t("tokens.type.gift") || "Cadeau",
103
136
  use: t("tokens.type.use") || "Utilisation",
104
137
  adjust: t("tokens.type.adjust") || "Ajustement",
138
+ quota_use: t("tokens.type.quota_use") || "Quota utilisé",
105
139
  };
106
140
  return labels[type] || type;
107
141
  };
108
- const getTypeColor = (type) => {
142
+ const getTypeColor = (type, source) => {
143
+ // Différencier les débits quota des débits achat
144
+ if (type === "use" && source === "quota") {
145
+ return "danger"; // Bleu pour quota
146
+ }
109
147
  const colors = {
110
148
  purchase: "success",
111
149
  gift: "primary",
112
150
  use: "danger",
113
151
  adjust: "warning",
152
+ quota_use: "primary",
114
153
  };
115
154
  return colors[type] || "default";
116
155
  };
@@ -164,12 +203,30 @@ export function TokenPage() {
164
203
  return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
165
204
  }
166
205
  return (_jsxs("div", { className: " container mx-auto p-2 md:p-6 max-w-7xl", children: [_jsxs("div", { className: "mb-6", children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("tokens.page.title") || "Mes Tokens IA" }), _jsx("p", { className: "text-gray-500", children: t("tokens.page.description") ||
167
- "Gérez votre solde et consultez votre historique de consommation" })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4 mb-6", children: [_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Coins, { size: 24, className: "text-primary" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.current") || "Solde actuel" })] }), _jsx("p", { className: "text-4xl font-bold text-primary", children: formatTokensShort(balance?.balance || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.available") || "tokens disponibles" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingUp, { size: 24, className: "text-success" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.total") || "Total" })] }), _jsx("p", { className: "text-4xl font-bold text-success", children: formatTokensShort(balance?.totalAdded || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.purchased") || "tokens acheté" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingDown, { size: 24, className: "text-danger" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.total_used") || "Total utilisé" })] }), _jsx("p", { className: "text-4xl font-bold text-danger", children: formatTokensShort(balance?.totalUsed || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.consumed") || "tokens consommés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Gift, { size: 24, className: "text-warning" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.balance.gifted") || "Tokens offerts / ajustements" })] }), _jsx("p", { className: "text-4xl font-bold text-warning", children: formatTokensShort(balance?.totalGifted || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.balance.credited") || "tokens crédités" })] }) })] }), monthlyStats && (_jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.stats.monthly_title") || "Statistiques du mois" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": t("tokens.stats.select_month") || "Sélectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
206
+ "Gérez votre solde et consultez votre historique de consommation" })] }), successMessage && (_jsx(Alert, { color: "success", title: successMessage, className: "mb-6", onClose: () => setSuccessMessage(null) })), balance?.quota.hasQuota && balance.quota.isActive && (_jsxs(_Fragment, { children: [_jsxs(Card, { className: "mb-6 border-2 border-warning", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Coins, { size: 20, className: "text-primary" }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.quota.title") }), _jsx(Chip, { variant: "flat", color: "warning", children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx(Crown, { size: 14 }), _jsx("span", { children: "PRO" })] }) })] }) }), _jsxs(CardBody, { children: [_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-primary-50 dark:bg-success-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.quota.included") || "Inclus ce mois" }), _jsx("p", { className: "text-3xl font-bold text-primary", children: formatTokensShort(balance.quota.effectiveQuota) })] }), _jsxs("div", { className: "text-center p-4 bg-warning-50 dark:bg-danger-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.quota.used") || "Utilisés" }), _jsx("p", { className: "text-3xl font-bold text-warning dark:text-danger", children: formatTokensShort(balance.quota.usedQuota) })] }), _jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.quota.remaining") || "Restants" }), _jsx("p", { className: "text-3xl font-bold text-success", children: formatTokensShort(balance.quota.remainingQuota) })] })] }), balance.quota.effectiveQuota > 0 && (_jsxs("div", { className: "mt-6", children: [_jsxs("div", { className: "flex justify-between mb-2", children: [_jsx("p", { className: "text-sm font-medium", children: t("tokens.quota.usage") || "Utilisation du quota" }), _jsxs("p", { className: "text-sm font-semibold", children: [Math.round((balance.quota.usedQuota /
207
+ balance.quota.effectiveQuota) *
208
+ 100), "%"] })] }), _jsx(Progress, { value: (balance.quota.usedQuota / balance.quota.effectiveQuota) *
209
+ 100, color: balance.quota.usedQuota / balance.quota.effectiveQuota >
210
+ 0.9
211
+ ? "danger"
212
+ : balance.quota.usedQuota /
213
+ balance.quota.effectiveQuota >
214
+ 0.7
215
+ ? "warning"
216
+ : "success", className: "h-2" })] })), _jsxs("div", { className: "mt-4 text-center text-sm text-gray-500", children: [_jsxs("p", { children: [t("tokens.quota.period") || "Période:", " ", balance.quota.periodStart &&
217
+ formatDateFr(balance.quota.periodStart), " - ", balance.quota.periodEnd &&
218
+ formatDateFr(balance.quota.periodEnd)] }), _jsx("p", { className: "mt-2 text-xs", children: t("tokens.quota.info") ||
219
+ "Le quota se réinitialise automatiquement à chaque renouvellement." })] })] })] }), balance?.quota?.hasQuota && (_jsx(Alert, { color: "primary", title: t("tokens.purchased.priority_info") ||
220
+ "Priorité de débit des tokens", className: "mb-6", startContent: _jsx(Coins, { size: 20 }) }))] })), _jsxs("div", { className: "mb-6", children: [_jsx("h2", { className: "text-xl font-semibold mb-4", children: t("tokens.purchased.title") || "Tokens achetés" }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4", children: [_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(Coins, { size: 24, className: "text-primary" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.purchased.balance") || "Solde actuel" })] }), _jsx("p", { className: `text-4xl font-bold ${Number(balance?.purchased.balance) > 0 ? "text-primary" : "text-danger"}`, children: formatTokensShort(balance?.purchased.balance || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.purchased.available") || "tokens disponibles" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingUp, { size: 24, className: "text-success" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.purchased.total_added") || "Total ajouté" })] }), _jsx("p", { className: "text-4xl font-bold text-success", children: formatTokensShort(balance?.purchased.totalAdded || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.purchased.cumulative") || "cumulés" })] }) }), _jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsxs("div", { className: "flex items-center justify-center gap-2 mb-2", children: [_jsx(TrendingDown, { size: 24, className: "text-danger" }), _jsx("h3", { className: "text-sm font-medium text-gray-500", children: t("tokens.purchased.total_used") || "Total utilisé" })] }), _jsx("p", { className: "text-4xl font-bold text-danger", children: formatTokensShort(balance?.purchased.totalUsed || 0) }), _jsx("p", { className: "text-xs text-gray-400 mt-1", children: t("tokens.purchased.consumed") || "consommés" })] }) })] }), _jsx("div", { className: "mt-4 text-center text-sm text-gray-500", children: _jsx("p", { children: t("tokens.purchased.info") ||
221
+ "Les tokens achetés restent disponibles jusqu'à consommation complète." }) })] }), monthlyStats && (_jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center justify-between w-full", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.stats.monthly_title") || "Statistiques du mois" })] }), _jsx(Select, { size: "sm", selectedKeys: [selectedMonth], onSelectionChange: (keys) => setSelectedMonth(Array.from(keys)[0]), className: "w-48", "aria-label": t("tokens.stats.select_month") || "Sélectionner un mois", children: availableMonths.map((month) => (_jsx(SelectItem, { textValue: month, children: new Date(month + "-01").toLocaleDateString("fr-FR", {
168
222
  month: "long",
169
223
  year: "numeric",
170
- }) }, month))) })] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-900/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.added") || "Ajoutés" }), _jsxs("p", { className: "text-2xl font-bold text-success", children: ["+", formatTokensShort(monthlyStats.added)] })] }), _jsxs("div", { className: "text-center p-4 bg-danger-50 dark:bg-danger-900/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.used") || "Utilisés" }), _jsxs("p", { className: "text-2xl font-bold text-danger", children: ["-", formatTokensShort(monthlyStats.used)] })] }), _jsxs("div", { className: "text-center p-4 bg-primary-50 dark:bg-primary-900/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.net_balance") || "Solde net" }), _jsxs("p", { className: `text-2xl font-bold ${monthlyStats.net >= 0 ? "text-success" : "text-danger"}`, children: [monthlyStats.net >= 0 ? "+" : "", formatTokensShort(Math.abs(monthlyStats.net))] })] })] }) })] })), _jsxs(Card, { className: "mb-6", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.history.title") || "Historique des transactions" }) }), _jsx(CardBody, { children: transactions.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Coins, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("tokens.history.no_transactions") ||
171
- "Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": t("tokens.history.aria_label") || "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { className: "min-w-[120px]", children: t("tokens.history.column_date") || "DATE" }), _jsx(TableColumn, { children: t("tokens.history.column_type") || "TYPE" }), _jsx(TableColumn, { children: t("tokens.history.column_description") || "DESCRIPTION" }), _jsx(TableColumn, { children: t("tokens.history.column_amount") || "MONTANT" }), _jsx(TableColumn, { children: t("tokens.history.column_prompt") || "PROMPT" }), _jsx(TableColumn, { align: "end", children: t("tokens.history.column_balance") || "SOLDE APRÈS" })] }), _jsx(TableBody, { children: transactions.map((transaction) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: " text-sm text-gray-600", children: formatDate(transaction.created_at) }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: getTypeColor(transaction.type), children: getTypeLabel(transaction.type) }) }), _jsx(TableCell, { children: _jsx("div", { className: "max-w-md", children: _jsx("p", { className: "text-sm truncate", children: transaction.description || transaction.model || "-" }) }) }), _jsx(TableCell, { children: _jsxs("span", { className: `font-semibold ${transaction.amount > 0
172
- ? "text-success"
173
- : "text-danger"}`, children: [transaction.amount > 0 ? "+" : "", formatTokensShort(Math.abs(transaction.amount))] }) }), _jsx(TableCell, { children: transaction.prompt && (_jsxs("div", { className: "flex flex-inline gap-2 items-center", children: [_jsx("p", { className: "max-w-xs truncate", children: transaction.prompt?.slice(0, 100) }), _jsx(Button, { size: "sm", variant: "flat", isIconOnly: true, className: "truncate max-w-xs", startContent: _jsx(Eye, { size: 12 }) })] })) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-gray-600", children: formatTokensShort(transaction.running_balance) }) })] }, transaction.id))) })] })) })] }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ShoppingCart, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.buy.title") || "Acheter des tokens" })] }) }), _jsx(CardBody, { children: tokenPacks.length === 0 ? (_jsxs("div", { className: "text-center py-8", children: [_jsx(ShoppingCart, { size: 48, className: "mx-auto mb-4 text-gray-300 dark:text-gray-600" }), _jsx("p", { className: "text-gray-500 mb-4", children: t("tokens.buy.no_packs") ||
174
- "Aucun pack disponible pour le moment" })] })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: tokenPacks.map((pack) => (_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsx("div", { className: "mx-auto", children: _jsx(Coins, { size: 24, className: "mx-auto mb-2 text-gray-400" }) }), _jsx("h4", { className: "text-lg font-bold mb-2", children: pack.name }), pack.description && (_jsx("p", { className: "text-sm text-gray-500 mb-4", children: pack.description })), _jsxs("div", { className: "mb-4", children: [_jsx("p", { className: "text-3xl font-bold text-primary", children: formatTokensShort(pack.tokens) }), _jsx("p", { className: "text-xs text-gray-400", children: t("chip.tokens") || "tokens" })] }), _jsx("p", { className: "text-xl font-semibold mb-4", children: formatPrice(pack.price_cents, pack.currency) }), _jsx(Button, { color: "primary", className: "w-full", onPress: () => handleBuyTokens(pack.id), isLoading: checkoutLoading === pack.id, startContent: checkoutLoading !== pack.id && (_jsx(ShoppingCart, { size: 16 })), children: t("tokens.buy.button") || "Acheter" })] }) }, pack.id))) })) })] })] }));
224
+ }) }, month))) })] }) }), _jsx(CardBody, { children: _jsxs("div", { className: "grid grid-cols-3 gap-4", children: [_jsxs("div", { className: "text-center p-4 bg-success-50 dark:bg-success-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.added") || "Ajoutés" }), _jsxs("p", { className: "text-2xl font-bold text-success", children: ["+", formatTokensShort(monthlyStats.added)] })] }), _jsxs("div", { className: "text-center p-4 bg-danger-50 dark:bg-danger-300/20 rounded-lg", children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.used") || "Utilisés" }), _jsxs("p", { className: "text-2xl font-bold text-danger", children: ["-", formatTokensShort(monthlyStats.used)] })] }), _jsxs("div", { className: `text-center p-4 ${monthlyStats.net >= 0 ? "bg-primary-50 dark:bg-success-300/20" : "bg-danger-50 dark:bg-danger-300/20"} rounded-lg`, children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-1", children: t("tokens.stats.net_balance") || "Solde net" }), _jsxs("p", { className: `text-2xl font-bold ${monthlyStats.net >= 0 ? "text-success" : "text-danger"}`, children: [monthlyStats.net >= 0 ? "+" : "-", formatTokensShort(Math.abs(monthlyStats.net))] })] })] }) })] })), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(ShoppingCart, { size: 20 }), _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.buy.title") || "Acheter des tokens" })] }) }), _jsx(CardBody, { children: tokenPacks.length === 0 ? (_jsxs("div", { className: "text-center py-8", children: [_jsx(ShoppingCart, { size: 48, className: "mx-auto mb-4 text-gray-300 dark:text-gray-600" }), _jsx("p", { className: "text-gray-500 mb-4", children: t("tokens.buy.no_packs") ||
225
+ "Aucun pack disponible pour le moment" })] })) : (_jsx("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4", children: tokenPacks.map((pack) => (_jsx(Card, { children: _jsxs(CardBody, { className: "text-center py-6", children: [_jsx("div", { className: "mx-auto", children: _jsx(Coins, { size: 24, className: "mx-auto mb-2 text-gray-400" }) }), _jsx("h4", { className: "text-lg font-bold mb-2", children: pack.name }), pack.description && (_jsx("p", { className: "text-sm text-gray-500 mb-4", children: pack.description })), _jsxs("div", { className: "mb-4", children: [_jsx("p", { className: "text-3xl font-bold text-primary", children: formatTokensShort(pack.tokens) }), _jsx("p", { className: "text-xs text-gray-400", children: t("chip.tokens") || "tokens" })] }), _jsx("p", { className: "text-xl font-semibold mb-4", children: formatPrice(pack.price_cents, pack.currency) }), _jsx(Button, { color: "primary", className: "w-full", onPress: () => handleBuyTokens(pack.id), isLoading: checkoutLoading === pack.id, startContent: checkoutLoading !== pack.id && (_jsx(ShoppingCart, { size: 16 })), children: t("tokens.buy.button") || "Acheter" })] }) }, pack.id))) })) })] }), _jsxs(Card, { className: "my-6", children: [_jsx(CardHeader, { children: _jsx("h3", { className: "text-lg font-semibold", children: t("tokens.history.title") || "Historique des transactions" }) }), _jsx(CardBody, { children: transactions.length === 0 ? (_jsxs("div", { className: "text-center py-12 text-gray-500", children: [_jsx(Coins, { size: 48, className: "mx-auto mb-4 opacity-20" }), _jsx("p", { children: t("tokens.history.no_transactions") ||
226
+ "Aucune transaction pour ce mois" })] })) : (_jsxs(Table, { "aria-label": t("tokens.history.aria_label") || "Historique des transactions", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { className: "min-w-[120px]", children: t("tokens.history.column_date") || "DATE" }), _jsx(TableColumn, { children: t("tokens.history.column_type") || "TYPE" }), _jsx(TableColumn, { children: t("tokens.history.column_description") || "DESCRIPTION" }), _jsx(TableColumn, { children: t("tokens.history.column_amount") || "MONTANT" }), _jsx(TableColumn, { children: t("tokens.history.column_prompt") || "PROMPT" }), _jsx(TableColumn, { align: "end", children: t("tokens.history.column_balance") || "SOLDE APRÈS" })] }), _jsx(TableBody, { children: transactions.map((transaction) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx("span", { className: " text-sm text-gray-600", children: formatDate(transaction.created_at) }) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", color: getTypeColor(transaction.type, transaction.source), children: getTypeLabel(transaction.type, transaction.source) }) }), _jsx(TableCell, { children: _jsx("div", { className: "max-w-md", children: _jsx("p", { className: "text-sm truncate", children: transaction.description || transaction.model || "-" }) }) }), _jsx(TableCell, { children: _jsxs("span", { className: `font-semibold ${transaction.source === "quota"
227
+ ? "text-danger" // Quota toujours en rouge (danger)
228
+ : transaction.amount > 0
229
+ ? "text-success" // Vert pour les ajouts (achetés, cadeaux)
230
+ : "text-danger" // Rouge pour les utilisations
231
+ }`, children: [transaction.amount > 0 ? "+" : "", formatTokensShort(Math.abs(transaction.amount))] }) }), _jsx(TableCell, { children: transaction.prompt && (_jsx("div", { className: "flex flex-inline gap-2 items-center", children: _jsx("p", { className: "max-w-xs truncate", children: transaction.prompt?.slice(0, 100) }) })) }), _jsx(TableCell, { children: _jsx("span", { className: "text-sm text-default-700", children: formatTokensShort(transaction.running_balance) }) })] }, transaction.id))) })] })) })] })] }));
175
232
  }
@@ -0,0 +1,2 @@
1
+ export declare function UsageAndTokensPage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=UsageAndTokensPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsageAndTokensPage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/UsageAndTokensPage.tsx"],"names":[],"mappings":"AAuEA,wBAAgB,kBAAkB,4CAoPjC"}
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback } from "react";
4
+ import { useSearchParams } from "next/navigation";
5
+ import confetti from "canvas-confetti";
6
+ import { Spinner, addToast, Alert } from "@lastbrain/ui";
7
+ import { AlertCircle } from "lucide-react";
8
+ import { logger, useAuth, useModuleTranslation } from "@lastbrain/core";
9
+ import { UsageByModelBarChart } from "./components/UsageByModelBarChart";
10
+ import { ApiKeyFilterSelect } from "./components/ApiKeyFilterSelect";
11
+ import { PurchaseButton } from "./components/PurchaseButton";
12
+ import { UsageByDayChart } from "./components/UsageByDayChart";
13
+ import { WalletStatusCard } from "./components/WalletStatusCard";
14
+ import { ModelUsageTable } from "./components/ModelUsageTable";
15
+ export function UsageAndTokensPage() {
16
+ const t = useModuleTranslation("ai");
17
+ const { user } = useAuth();
18
+ const searchParams = useSearchParams();
19
+ const [loading, setLoading] = useState(true);
20
+ const [loadingUsage, setLoadingUsage] = useState(false);
21
+ const [wallet, setWallet] = useState(null);
22
+ const [modelUsage, setModelUsage] = useState([]);
23
+ const [usageByModel, setUsageByModel] = useState([]);
24
+ const [usageByDay, setUsageByDay] = useState([]);
25
+ const [apiKeys, setApiKeys] = useState([]);
26
+ const [selectedApiKey, setSelectedApiKey] = useState("all");
27
+ const [selectedMonth, setSelectedMonth] = useState("all");
28
+ const [successMessage, setSuccessMessage] = useState(null);
29
+ // Check for success/canceled params from Stripe redirect
30
+ useEffect(() => {
31
+ if (searchParams.get("token_success") === "true") {
32
+ setSuccessMessage(t("tokens.purchase_success") || "🎉 Tokens achetés avec succès !");
33
+ confetti({
34
+ particleCount: 100,
35
+ spread: 600,
36
+ origin: { y: 0.4 },
37
+ });
38
+ // Clean URL
39
+ window.history.replaceState({}, "", window.location.pathname);
40
+ }
41
+ if (searchParams.get("token_canceled") === "true") {
42
+ addToast({
43
+ title: t("tokens.purchase_canceled") || "Achat annulé",
44
+ description: t("tokens.purchase_canceled_desc") ||
45
+ "L'achat de tokens a été annulé.",
46
+ color: "warning",
47
+ });
48
+ window.history.replaceState({}, "", window.location.pathname);
49
+ }
50
+ }, [searchParams, t]);
51
+ // Fetch initial data (balance, transactions, API keys) - runs once on mount
52
+ const fetchInitialData = useCallback(async () => {
53
+ if (!user)
54
+ return;
55
+ try {
56
+ setLoading(true);
57
+ // Fetch wallet and api keys in parallel
58
+ const [walletResponse, apiKeysResponse] = await Promise.all([
59
+ fetch("/api/ai/auth/wallet"),
60
+ fetch("/api/ai/auth/api-keys"),
61
+ ]);
62
+ if (!walletResponse.ok) {
63
+ throw new Error(t("tokens.error.loading") || "Erreur lors du chargement des données");
64
+ }
65
+ const walletData = await walletResponse.json();
66
+ // Process wallet
67
+ setWallet(walletData);
68
+ // Process API keys
69
+ if (apiKeysResponse.ok) {
70
+ const keysData = await apiKeysResponse.json();
71
+ setApiKeys(keysData.data || []);
72
+ }
73
+ }
74
+ catch (error) {
75
+ logger.error(t("tokens.error.loading") || "Erreur:", error);
76
+ addToast({
77
+ color: "danger",
78
+ title: t("tokens.error.loading") || "Erreur lors du chargement des données",
79
+ });
80
+ }
81
+ finally {
82
+ setLoading(false);
83
+ }
84
+ // eslint-disable-next-line react-hooks/exhaustive-deps
85
+ }, [user]);
86
+ // Fetch usage data (filtered by API key and month) - runs when selectedApiKey or selectedMonth changes
87
+ const fetchUsageData = useCallback(async () => {
88
+ if (!user)
89
+ return;
90
+ try {
91
+ setLoadingUsage(true);
92
+ // Build query params
93
+ const params = new URLSearchParams();
94
+ if (selectedApiKey !== "all") {
95
+ params.append("api_key_id", selectedApiKey);
96
+ }
97
+ if (selectedMonth !== "all") {
98
+ params.append("month", selectedMonth);
99
+ }
100
+ // Fetch model usage and charts in parallel
101
+ const [modelUsageResponse, usageResponse] = await Promise.all([
102
+ fetch(`/api/ai/auth/usage-by-model?${params.toString()}`),
103
+ fetch(`/api/ai/auth/usage?${params.toString()}`),
104
+ ]);
105
+ if (!modelUsageResponse.ok || !usageResponse.ok) {
106
+ throw new Error(t("tokens.error.loading") || "Erreur lors du chargement des données");
107
+ }
108
+ const modelUsageData = await modelUsageResponse.json();
109
+ const usageDataRaw = await usageResponse.json();
110
+ // L'API retourne {data: {...}} donc on doit extraire la propriété data
111
+ const usageData = usageDataRaw.data;
112
+ // Process model usage (new detailed table)
113
+ setModelUsage(modelUsageData.data || []);
114
+ // Process usage by model (chart)
115
+ if (usageData.usage_by_model) {
116
+ setUsageByModel(usageData.usage_by_model);
117
+ }
118
+ else {
119
+ setUsageByModel([]);
120
+ }
121
+ // Process usage by day
122
+ if (usageData.usage_by_day) {
123
+ setUsageByDay(usageData.usage_by_day);
124
+ }
125
+ else {
126
+ setUsageByDay([]);
127
+ }
128
+ }
129
+ catch (error) {
130
+ logger.error(t("tokens.error.loading") || "Erreur:", error);
131
+ addToast({
132
+ color: "danger",
133
+ title: t("tokens.error.loading") ||
134
+ "Erreur lors du chargement des statistiques",
135
+ });
136
+ }
137
+ finally {
138
+ setLoadingUsage(false);
139
+ }
140
+ // eslint-disable-next-line react-hooks/exhaustive-deps
141
+ }, [user, selectedApiKey, selectedMonth]);
142
+ // Initial data load (once on mount)
143
+ useEffect(() => {
144
+ fetchInitialData();
145
+ }, [fetchInitialData]);
146
+ // Usage data load (when API key changes)
147
+ useEffect(() => {
148
+ fetchUsageData();
149
+ }, [fetchUsageData]);
150
+ if (!user) {
151
+ return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsxs("div", { className: "text-center", children: [_jsx(AlertCircle, { className: "mx-auto mb-4", size: 48 }), _jsx("p", { className: "text-gray-500", children: t("tokens.error.login_required") || "Veuillez vous connecter" })] }) }));
152
+ }
153
+ if (loading) {
154
+ return (_jsx("div", { className: "flex justify-center items-center min-h-96", children: _jsx(Spinner, { size: "lg" }) }));
155
+ }
156
+ return (_jsxs("div", { className: "container mx-auto p-2 md:p-6 max-w-7xl space-y-6", children: [_jsxs("div", { className: "flex flex-col md:flex-row md:items-center md:justify-between gap-4", children: [_jsxs("div", { children: [_jsx("h1", { className: "text-3xl font-bold mb-2", children: t("usage.page_title") }), _jsx("p", { className: "text-gray-500", children: t("usage.page_subtitle") })] }), _jsxs("div", { className: "flex-1 flex flex-col justify-end sm:flex-row items-start sm:items-center gap-4", children: [_jsx(ApiKeyFilterSelect, { apiKeys: apiKeys, selectedKey: selectedApiKey, onSelectionChange: setSelectedApiKey }), _jsx(PurchaseButton, {})] })] }), successMessage && (_jsx(Alert, { color: "success", title: successMessage, onClose: () => setSuccessMessage(null) })), wallet && (_jsx(WalletStatusCard, { walletSellValueUsd: wallet.walletSellValueUsd, percentRemaining: wallet.percentRemaining, status: wallet.status, loading: loading })), _jsxs("div", { className: "grid grid-cols-1 gap-6", children: [_jsx("div", { className: "col-span-1", children: _jsx(ModelUsageTable, { data: modelUsage, loading: loadingUsage, totalRemaining: wallet?.walletSellValueUsd || 0, onMonthChange: setSelectedMonth, selectedMonth: selectedMonth }) }), _jsx("div", { className: "col-span-1", children: _jsx(UsageByModelBarChart, { data: usageByModel, loading: loadingUsage }) }), _jsx("div", { className: "col-span-1", children: _jsx(UsageByDayChart, { data: usageByDay, loading: loadingUsage }) })] })] }));
157
+ }
@@ -0,0 +1,2 @@
1
+ export declare function UsagePage(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=UsagePage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UsagePage.d.ts","sourceRoot":"","sources":["../../../src/web/auth/UsagePage.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,SAAS,4CA6MxB"}
@@ -0,0 +1,62 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect } from "react";
4
+ import { Card, CardBody, CardHeader, Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Spinner, addToast, Chip, Select, SelectItem, } from "@lastbrain/ui";
5
+ import { BarChart3, TrendingUp } from "lucide-react";
6
+ import { useAuth, useModuleTranslation } from "@lastbrain/core";
7
+ export function UsagePage() {
8
+ const t = useModuleTranslation("ai");
9
+ const { user } = useAuth();
10
+ const [loading, setLoading] = useState(true);
11
+ const [logs, setLogs] = useState([]);
12
+ const [stats, setStats] = useState(null);
13
+ const [filterEndpoint, setFilterEndpoint] = useState("");
14
+ const [filterProvider, setFilterProvider] = useState("");
15
+ useEffect(() => {
16
+ fetchUsage();
17
+ }, []);
18
+ const fetchUsage = async () => {
19
+ try {
20
+ setLoading(true);
21
+ const response = await fetch("/api/ai/auth/usage");
22
+ if (!response.ok)
23
+ throw new Error("Error loading usage data");
24
+ const result = await response.json();
25
+ setLogs(result.data?.logs || []);
26
+ setStats(result.data?.stats || null);
27
+ }
28
+ catch (error) {
29
+ addToast({ color: "danger", title: error.message });
30
+ }
31
+ finally {
32
+ setLoading(false);
33
+ }
34
+ };
35
+ const formatDate = (dateString) => {
36
+ return new Date(dateString).toLocaleString();
37
+ };
38
+ const filteredLogs = logs.filter((log) => {
39
+ if (filterEndpoint && log.endpoint !== filterEndpoint)
40
+ return false;
41
+ if (filterProvider && log.provider !== filterProvider)
42
+ return false;
43
+ return true;
44
+ });
45
+ const uniqueEndpoints = Array.from(new Set(logs.map((l) => l.endpoint)));
46
+ const uniqueProviders = Array.from(new Set(logs.map((l) => l.provider).filter(Boolean)));
47
+ if (loading) {
48
+ return (_jsx("div", { className: "flex justify-center py-8", children: _jsx(Spinner, {}) }));
49
+ }
50
+ return (_jsxs("div", { className: "container mx-auto py-8 space-y-6", children: [stats && (_jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4", children: [_jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(BarChart3, { size: 20, className: "text-blue-500" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Total Calls" }), _jsx("p", { className: "text-2xl font-bold", children: stats.total_calls })] })] }) }) }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TrendingUp, { size: 20, className: "text-green-500" }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Total Tokens" }), _jsx("p", { className: "text-2xl font-bold", children: stats.total_tokens.toLocaleString() })] })] }) }) }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Success Rate" }), _jsxs("p", { className: "text-2xl font-bold", children: [stats.success_rate.toFixed(1), "%"] })] }) }) }), _jsx(Card, { children: _jsx(CardBody, { children: _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600", children: "Avg Latency" }), _jsxs("p", { className: "text-2xl font-bold", children: [logs.length > 0
51
+ ? Math.round(logs.reduce((sum, l) => sum + (l.latency_ms || 0), 0) /
52
+ logs.length)
53
+ : 0, "ms"] })] }) }) })] })), _jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx("h1", { className: "text-2xl font-bold", children: "API Usage Logs" }), _jsx("p", { className: "text-sm text-gray-600", children: "Recent API calls and usage analytics" })] }), _jsxs(CardBody, { children: [_jsxs("div", { className: "flex gap-4 mb-4", children: [_jsx(Select, { label: "Filter by Endpoint", selectedKeys: filterEndpoint ? [filterEndpoint] : [], onSelectionChange: (keys) => {
54
+ const selected = Array.from(keys)[0];
55
+ setFilterEndpoint(selected ? String(selected) : "");
56
+ }, className: "w-48", children: ["all", ...uniqueEndpoints].map((endpoint) => (_jsx(SelectItem, { children: endpoint }, endpoint))) }), _jsx(Select, { label: "Filter by Provider", selectedKeys: filterProvider ? [filterProvider] : [], onSelectionChange: (keys) => {
57
+ const selected = Array.from(keys)[0];
58
+ setFilterProvider(selected ? String(selected) : "");
59
+ }, className: "w-48", children: ["all", ...uniqueProviders].map((provider) => (_jsx(SelectItem, { children: provider }, provider))) })] }), filteredLogs.length === 0 ? (_jsx("div", { className: "text-center py-8", children: _jsx("p", { className: "text-gray-600", children: "No usage data yet" }) })) : (_jsxs(Table, { "aria-label": "Usage logs table", children: [_jsxs(TableHeader, { children: [_jsx(TableColumn, { children: "DATE" }), _jsx(TableColumn, { children: "ENDPOINT" }), _jsx(TableColumn, { children: "PROVIDER" }), _jsx(TableColumn, { children: "MODEL" }), _jsx(TableColumn, { children: "TOKENS" }), _jsx(TableColumn, { children: "LATENCY" }), _jsx(TableColumn, { children: "STATUS" })] }), _jsx(TableBody, { children: filteredLogs.map((log) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: formatDate(log.created_at) }), _jsx(TableCell, { children: _jsx(Chip, { size: "sm", variant: "flat", children: log.endpoint }) }), _jsx(TableCell, { children: log.provider || "-" }), _jsx(TableCell, { children: _jsx("code", { className: "text-xs", children: log.model || "-" }) }), _jsxs(TableCell, { children: [log.tokens_total
60
+ ? log.tokens_total.toLocaleString()
61
+ : "-", log.tokens_in && log.tokens_out && (_jsxs("span", { className: "text-xs text-gray-500", children: [" ", "(", log.tokens_in, "+", log.tokens_out, ")"] }))] }), _jsx(TableCell, { children: log.latency_ms ? `${log.latency_ms}ms` : "-" }), _jsx(TableCell, { children: _jsx(Chip, { color: log.status_code === 200 ? "success" : "danger", variant: "flat", size: "sm", children: log.status_code }) })] }, log.id))) })] }))] })] })] }));
62
+ }
@@ -0,0 +1,13 @@
1
+ interface ApiKey {
2
+ id: string;
3
+ name: string;
4
+ prefix: string;
5
+ }
6
+ interface ApiKeyFilterSelectProps {
7
+ apiKeys: ApiKey[];
8
+ selectedKey: string;
9
+ onSelectionChange: (key: string) => void;
10
+ }
11
+ export declare function ApiKeyFilterSelect({ apiKeys, selectedKey, onSelectionChange, }: ApiKeyFilterSelectProps): import("react/jsx-runtime").JSX.Element;
12
+ export {};
13
+ //# sourceMappingURL=ApiKeyFilterSelect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ApiKeyFilterSelect.d.ts","sourceRoot":"","sources":["../../../../src/web/auth/components/ApiKeyFilterSelect.tsx"],"names":[],"mappings":"AAMA,UAAU,MAAM;IACd,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,uBAAuB;IAC/B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC1C;AAED,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,WAAW,EACX,iBAAiB,GAClB,EAAE,uBAAuB,2CAwBzB"}
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Select, SelectItem } from "@lastbrain/ui";
4
+ import { Key as KeyIcon } from "lucide-react";
5
+ import { useModuleTranslation } from "@lastbrain/core";
6
+ export function ApiKeyFilterSelect({ apiKeys, selectedKey, onSelectionChange, }) {
7
+ const t = useModuleTranslation("ai");
8
+ return (_jsx(Select, { label: t("usage.filter.by_api_key"), size: "sm", selectedKeys: selectedKey ? [selectedKey] : ["all"], onSelectionChange: (keys) => {
9
+ const selected = Array.from(keys)[0];
10
+ onSelectionChange(selected ? String(selected) : "all");
11
+ }, className: "max-w-xs", startContent: _jsx(KeyIcon, { size: 16 }), children: [
12
+ { id: "all", name: t("usage.filter.all_keys") },
13
+ { id: "web", name: "Interface Web" },
14
+ ...apiKeys,
15
+ ].map((apiKey) => (_jsx(SelectItem, { children: apiKey.name }, apiKey.id))) }));
16
+ }
@@ -0,0 +1,19 @@
1
+ interface ModelUsageItem {
2
+ model: string;
3
+ provider?: string;
4
+ endpoint?: string;
5
+ total_cost_usd: number;
6
+ total_sell_usd: number;
7
+ call_count: number;
8
+ avg_cost_per_call: number;
9
+ }
10
+ interface ModelUsageTableProps {
11
+ data: ModelUsageItem[];
12
+ loading?: boolean;
13
+ totalRemaining?: number;
14
+ onMonthChange?: (month: string) => void;
15
+ selectedMonth?: string;
16
+ }
17
+ export declare function ModelUsageTable({ data, loading, totalRemaining, onMonthChange, selectedMonth, }: ModelUsageTableProps): import("react/jsx-runtime").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=ModelUsageTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelUsageTable.d.ts","sourceRoot":"","sources":["../../../../src/web/auth/components/ModelUsageTable.tsx"],"names":[],"mappings":"AAaA,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,UAAU,oBAAoB;IAC5B,IAAI,EAAE,cAAc,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,EAC9B,IAAI,EACJ,OAAO,EACP,cAAkB,EAClB,aAAa,EACb,aAAqB,GACtB,EAAE,oBAAoB,2CA8KtB"}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Card, CardBody, CardHeader, Spinner, Select, SelectItem, } from "@lastbrain/ui";
4
+ import { TrendingUp, Calendar } from "lucide-react";
5
+ import { useModuleTranslation } from "@lastbrain/core";
6
+ export function ModelUsageTable({ data, loading, totalRemaining = 0, onMonthChange, selectedMonth = "all", }) {
7
+ const t = useModuleTranslation("ai");
8
+ // Generate last 12 months for filter
9
+ const months = [];
10
+ const now = new Date();
11
+ for (let i = 0; i < 12; i++) {
12
+ const date = new Date(now.getFullYear(), now.getMonth() - i, 1);
13
+ const value = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
14
+ const label = date.toLocaleDateString("fr-FR", {
15
+ year: "numeric",
16
+ month: "long",
17
+ });
18
+ months.push({ value, label });
19
+ }
20
+ const totalSpent = data.reduce((sum, item) => sum + item.total_sell_usd, 0);
21
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(TrendingUp, { className: "w-5 h-5" }), _jsx("h3", { className: "text-lg font-semibold", children: t("usage.by_model_title") || "Utilisation par modèle" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Calendar, { className: "w-4 h-4 text-gray-500" }), _jsx(Select, { size: "sm", label: "", placeholder: t("usage.period.label") || "Période", selectedKeys: [selectedMonth], onSelectionChange: (keys) => {
22
+ const key = Array.from(keys)[0];
23
+ onMonthChange?.(key);
24
+ }, className: "min-w-[180px]", children: [
25
+ {
26
+ value: "all",
27
+ label: t("usage.period.all_months") || "Tous les mois",
28
+ },
29
+ ...months,
30
+ ].map((month) => (_jsx(SelectItem, { children: month.label }, month.value))) })] })] }), _jsx(CardBody, { children: loading ? (_jsx("div", { className: "flex justify-center items-center py-12", children: _jsx(Spinner, { size: "lg" }) })) : data.length === 0 ? (_jsx("div", { className: "text-center py-12 text-gray-500", children: t("usage.no_usage") || "Aucune utilisation enregistrée" })) : (_jsxs("div", { className: "space-y-4", children: [_jsx("div", { className: "bg-gradient-to-r from-blue-50 to-indigo-50 dark:from-blue-950/20 dark:to-indigo-950/20 p-4 rounded-lg border border-blue-200 dark:border-blue-800", children: _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: t("usage.summary.total_spent") ||
31
+ "Total dépensé (période)" }), _jsxs("p", { className: "text-2xl font-bold text-blue-600 dark:text-blue-400", children: ["$", totalSpent.toFixed(2)] })] }), _jsxs("div", { children: [_jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400", children: t("usage.summary.remaining_credit") || "Crédit restant" }), _jsxs("p", { className: "text-2xl font-bold text-green-600 dark:text-green-400", children: ["$", totalRemaining.toFixed(2)] })] })] }) }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b border-gray-200 dark:border-gray-700", children: [_jsx("th", { className: "text-left py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.model") || "Modèle" }), _jsx("th", { className: "text-left py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.provider") || "Provider" }), _jsx("th", { className: "text-right py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.total_cost") || "Coût total" }), _jsx("th", { className: "text-right py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.calls") || "Appels" }), _jsx("th", { className: "text-right py-3 px-2 text-sm font-semibold text-gray-700 dark:text-gray-300", children: t("usage.table.cost_per_call") || "Coût/appel" })] }) }), _jsx("tbody", { children: data.map((item, index) => {
32
+ const percentOfTotal = totalSpent > 0
33
+ ? (item.total_sell_usd / totalSpent) * 100
34
+ : 0;
35
+ return (_jsxs("tr", { className: "border-b border-gray-100 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-900/50 transition-colors", children: [_jsx("td", { className: "py-3 px-2", children: _jsxs("div", { className: "flex flex-col", children: [_jsx("span", { className: "font-medium text-sm", children: item.model }), item.endpoint && (_jsx("span", { className: "text-xs text-gray-500", children: item.endpoint }))] }) }), _jsx("td", { className: "py-3 px-2", children: _jsx("span", { className: "text-sm text-gray-600 dark:text-gray-400 capitalize", children: item.provider || "—" }) }), _jsx("td", { className: "py-3 px-2 text-right", children: _jsx("div", { className: "flex flex-col items-end", children: _jsxs("span", { className: "font-semibold text-sm", children: ["$", item.total_sell_usd.toFixed(2)] }) }) }), _jsx("td", { className: "py-3 px-2 text-right", children: _jsx("span", { className: "text-sm font-medium", children: item.call_count.toLocaleString() }) }), _jsx("td", { className: "py-3 px-2 text-right", children: _jsxs("span", { className: "text-sm text-gray-600 dark:text-gray-400", children: ["$", item.avg_cost_per_call.toFixed(4)] }) })] }, `${item.model}-${index}`));
36
+ }) })] }) })] })) })] }));
37
+ }
@@ -0,0 +1,7 @@
1
+ interface PurchaseButtonProps {
2
+ text?: string;
3
+ className?: string;
4
+ }
5
+ export declare function PurchaseButton({ text, className }: PurchaseButtonProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=PurchaseButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PurchaseButton.d.ts","sourceRoot":"","sources":["../../../../src/web/auth/components/PurchaseButton.tsx"],"names":[],"mappings":"AAMA,UAAU,mBAAmB;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CAmBtE"}
@@ -0,0 +1,13 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { Button } from "@lastbrain/ui";
4
+ import { ShoppingCart } from "lucide-react";
5
+ import { useLocalizedRouter, useModuleTranslation } from "@lastbrain/core";
6
+ export function PurchaseButton({ text, className }) {
7
+ const router = useLocalizedRouter();
8
+ const t = useModuleTranslation("ai");
9
+ const handlePurchase = () => {
10
+ router.push("/purchase-tokens");
11
+ };
12
+ return (_jsx(Button, { color: "primary", size: "lg", startContent: _jsx(ShoppingCart, { size: 20 }), onPress: handlePurchase, className: className, children: text || t("usage.button.buy_tokens") }));
13
+ }