n9router 0.3.89 → 0.3.90

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 (286) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/server/app/(dashboard)/dashboard/basic-chat/page_client-reference-manifest.js +1 -1
  4. package/.next/standalone/.next/server/app/(dashboard)/dashboard/cli-tools/page.js.nft.json +1 -1
  5. package/.next/standalone/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/(dashboard)/dashboard/mitm/page.js.nft.json +1 -1
  10. package/.next/standalone/.next/server/app/(dashboard)/dashboard/mitm/page_client-reference-manifest.js +1 -1
  11. package/.next/standalone/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
  13. package/.next/standalone/.next/server/app/(dashboard)/dashboard/providers/[id]/page.js.nft.json +1 -1
  14. package/.next/standalone/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
  15. package/.next/standalone/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
  19. package/.next/standalone/.next/server/app/(dashboard)/dashboard/translator/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  22. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  23. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  24. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  28. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  29. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  30. package/.next/standalone/.next/server/app/_not-found.rsc +11 -11
  31. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +11 -11
  32. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  33. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +6 -6
  34. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  35. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  36. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  37. package/.next/standalone/.next/server/app/api/oauth/[provider]/[action]/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/translator/send/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/translator/translate/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/usage/[connectionId]/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/usage/chart/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/usage/history/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/api/usage/logs/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/usage/request-details/route.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/api/usage/request-logs/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/usage/stats/route.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/api/usage/stream/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/v1/api/chat/route.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/api/v1/chat/completions/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/api/v1/embeddings/route.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/api/v1/messages/route.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/api/v1/responses/route.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/api/v1beta/models/[...path]/route.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/callback.html +1 -1
  56. package/.next/standalone/.next/server/app/callback.rsc +13 -13
  57. package/.next/standalone/.next/server/app/callback.segments/_full.segment.rsc +13 -13
  58. package/.next/standalone/.next/server/app/callback.segments/_head.segment.rsc +4 -4
  59. package/.next/standalone/.next/server/app/callback.segments/_index.segment.rsc +6 -6
  60. package/.next/standalone/.next/server/app/callback.segments/_tree.segment.rsc +2 -2
  61. package/.next/standalone/.next/server/app/callback.segments/callback/__PAGE__.segment.rsc +4 -4
  62. package/.next/standalone/.next/server/app/callback.segments/callback.segment.rsc +3 -3
  63. package/.next/standalone/.next/server/app/dashboard/basic-chat.html +1 -1
  64. package/.next/standalone/.next/server/app/dashboard/basic-chat.rsc +14 -14
  65. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat/__PAGE__.segment.rsc +3 -3
  66. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat.segment.rsc +3 -3
  67. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  68. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  69. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_full.segment.rsc +14 -14
  70. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_head.segment.rsc +4 -4
  71. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_index.segment.rsc +6 -6
  72. package/.next/standalone/.next/server/app/dashboard/basic-chat.segments/_tree.segment.rsc +2 -2
  73. package/.next/standalone/.next/server/app/dashboard/cli-tools.html +1 -1
  74. package/.next/standalone/.next/server/app/dashboard/cli-tools.rsc +14 -14
  75. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +3 -3
  76. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools.segment.rsc +3 -3
  77. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  78. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  79. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_full.segment.rsc +14 -14
  80. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_head.segment.rsc +4 -4
  81. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_index.segment.rsc +6 -6
  82. package/.next/standalone/.next/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +2 -2
  83. package/.next/standalone/.next/server/app/dashboard/combos.html +1 -1
  84. package/.next/standalone/.next/server/app/dashboard/combos.rsc +15 -15
  85. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +4 -4
  86. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos.segment.rsc +3 -3
  87. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  88. package/.next/standalone/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  89. package/.next/standalone/.next/server/app/dashboard/combos.segments/_full.segment.rsc +15 -15
  90. package/.next/standalone/.next/server/app/dashboard/combos.segments/_head.segment.rsc +4 -4
  91. package/.next/standalone/.next/server/app/dashboard/combos.segments/_index.segment.rsc +6 -6
  92. package/.next/standalone/.next/server/app/dashboard/combos.segments/_tree.segment.rsc +2 -2
  93. package/.next/standalone/.next/server/app/dashboard/endpoint.html +1 -1
  94. package/.next/standalone/.next/server/app/dashboard/endpoint.rsc +14 -14
  95. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +3 -3
  96. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint.segment.rsc +3 -3
  97. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  98. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  99. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_full.segment.rsc +14 -14
  100. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_head.segment.rsc +4 -4
  101. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_index.segment.rsc +6 -6
  102. package/.next/standalone/.next/server/app/dashboard/endpoint.segments/_tree.segment.rsc +2 -2
  103. package/.next/standalone/.next/server/app/dashboard/mitm.html +1 -1
  104. package/.next/standalone/.next/server/app/dashboard/mitm.rsc +14 -14
  105. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard/mitm/__PAGE__.segment.rsc +3 -3
  106. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard/mitm.segment.rsc +3 -3
  107. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  108. package/.next/standalone/.next/server/app/dashboard/mitm.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  109. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_full.segment.rsc +14 -14
  110. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_head.segment.rsc +4 -4
  111. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_index.segment.rsc +6 -6
  112. package/.next/standalone/.next/server/app/dashboard/mitm.segments/_tree.segment.rsc +2 -2
  113. package/.next/standalone/.next/server/app/dashboard/profile.html +1 -1
  114. package/.next/standalone/.next/server/app/dashboard/profile.rsc +15 -15
  115. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +4 -4
  116. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile.segment.rsc +3 -3
  117. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  118. package/.next/standalone/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  119. package/.next/standalone/.next/server/app/dashboard/profile.segments/_full.segment.rsc +15 -15
  120. package/.next/standalone/.next/server/app/dashboard/profile.segments/_head.segment.rsc +4 -4
  121. package/.next/standalone/.next/server/app/dashboard/profile.segments/_index.segment.rsc +6 -6
  122. package/.next/standalone/.next/server/app/dashboard/profile.segments/_tree.segment.rsc +2 -2
  123. package/.next/standalone/.next/server/app/dashboard/providers/new.html +1 -1
  124. package/.next/standalone/.next/server/app/dashboard/providers/new.rsc +15 -15
  125. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +4 -4
  126. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new.segment.rsc +3 -3
  127. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers.segment.rsc +3 -3
  128. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  129. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  130. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_full.segment.rsc +15 -15
  131. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_head.segment.rsc +4 -4
  132. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_index.segment.rsc +6 -6
  133. package/.next/standalone/.next/server/app/dashboard/providers/new.segments/_tree.segment.rsc +2 -2
  134. package/.next/standalone/.next/server/app/dashboard/providers.html +1 -1
  135. package/.next/standalone/.next/server/app/dashboard/providers.rsc +15 -15
  136. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +4 -4
  137. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers.segment.rsc +3 -3
  138. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  139. package/.next/standalone/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  140. package/.next/standalone/.next/server/app/dashboard/providers.segments/_full.segment.rsc +15 -15
  141. package/.next/standalone/.next/server/app/dashboard/providers.segments/_head.segment.rsc +4 -4
  142. package/.next/standalone/.next/server/app/dashboard/providers.segments/_index.segment.rsc +6 -6
  143. package/.next/standalone/.next/server/app/dashboard/providers.segments/_tree.segment.rsc +2 -2
  144. package/.next/standalone/.next/server/app/dashboard/proxy-pools.html +1 -1
  145. package/.next/standalone/.next/server/app/dashboard/proxy-pools.rsc +15 -15
  146. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +4 -4
  147. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools.segment.rsc +3 -3
  148. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  149. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  150. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +15 -15
  151. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_head.segment.rsc +4 -4
  152. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_index.segment.rsc +6 -6
  153. package/.next/standalone/.next/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +2 -2
  154. package/.next/standalone/.next/server/app/dashboard/quota.html +2 -2
  155. package/.next/standalone/.next/server/app/dashboard/quota.rsc +15 -15
  156. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +4 -4
  157. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota.segment.rsc +3 -3
  158. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  159. package/.next/standalone/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  160. package/.next/standalone/.next/server/app/dashboard/quota.segments/_full.segment.rsc +15 -15
  161. package/.next/standalone/.next/server/app/dashboard/quota.segments/_head.segment.rsc +4 -4
  162. package/.next/standalone/.next/server/app/dashboard/quota.segments/_index.segment.rsc +6 -6
  163. package/.next/standalone/.next/server/app/dashboard/quota.segments/_tree.segment.rsc +2 -2
  164. package/.next/standalone/.next/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  165. package/.next/standalone/.next/server/app/dashboard/settings/pricing.html +1 -1
  166. package/.next/standalone/.next/server/app/dashboard/settings/pricing.rsc +13 -13
  167. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +13 -13
  168. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_head.segment.rsc +4 -4
  169. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_index.segment.rsc +6 -6
  170. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +2 -2
  171. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing/__PAGE__.segment.rsc +4 -4
  172. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing.segment.rsc +3 -3
  173. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings.segment.rsc +3 -3
  174. package/.next/standalone/.next/server/app/dashboard/settings/pricing.segments/dashboard.segment.rsc +3 -3
  175. package/.next/standalone/.next/server/app/dashboard/translator.html +1 -1
  176. package/.next/standalone/.next/server/app/dashboard/translator.rsc +15 -15
  177. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard/translator/__PAGE__.segment.rsc +4 -4
  178. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard/translator.segment.rsc +3 -3
  179. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  180. package/.next/standalone/.next/server/app/dashboard/translator.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  181. package/.next/standalone/.next/server/app/dashboard/translator.segments/_full.segment.rsc +15 -15
  182. package/.next/standalone/.next/server/app/dashboard/translator.segments/_head.segment.rsc +4 -4
  183. package/.next/standalone/.next/server/app/dashboard/translator.segments/_index.segment.rsc +6 -6
  184. package/.next/standalone/.next/server/app/dashboard/translator.segments/_tree.segment.rsc +2 -2
  185. package/.next/standalone/.next/server/app/dashboard/usage.html +1 -1
  186. package/.next/standalone/.next/server/app/dashboard/usage.rsc +15 -15
  187. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +4 -4
  188. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage.segment.rsc +3 -3
  189. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  190. package/.next/standalone/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  191. package/.next/standalone/.next/server/app/dashboard/usage.segments/_full.segment.rsc +15 -15
  192. package/.next/standalone/.next/server/app/dashboard/usage.segments/_head.segment.rsc +4 -4
  193. package/.next/standalone/.next/server/app/dashboard/usage.segments/_index.segment.rsc +6 -6
  194. package/.next/standalone/.next/server/app/dashboard/usage.segments/_tree.segment.rsc +2 -2
  195. package/.next/standalone/.next/server/app/dashboard.html +1 -1
  196. package/.next/standalone/.next/server/app/dashboard.rsc +14 -14
  197. package/.next/standalone/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +3 -3
  198. package/.next/standalone/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard.segment.rsc +3 -3
  199. package/.next/standalone/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk.segment.rsc +4 -4
  200. package/.next/standalone/.next/server/app/dashboard.segments/_full.segment.rsc +14 -14
  201. package/.next/standalone/.next/server/app/dashboard.segments/_head.segment.rsc +4 -4
  202. package/.next/standalone/.next/server/app/dashboard.segments/_index.segment.rsc +6 -6
  203. package/.next/standalone/.next/server/app/dashboard.segments/_tree.segment.rsc +2 -2
  204. package/.next/standalone/.next/server/app/index.html +1 -1
  205. package/.next/standalone/.next/server/app/index.rsc +11 -11
  206. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  207. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +11 -11
  208. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  209. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +6 -6
  210. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  211. package/.next/standalone/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  212. package/.next/standalone/.next/server/app/landing.html +1 -1
  213. package/.next/standalone/.next/server/app/landing.rsc +13 -13
  214. package/.next/standalone/.next/server/app/landing.segments/_full.segment.rsc +13 -13
  215. package/.next/standalone/.next/server/app/landing.segments/_head.segment.rsc +4 -4
  216. package/.next/standalone/.next/server/app/landing.segments/_index.segment.rsc +6 -6
  217. package/.next/standalone/.next/server/app/landing.segments/_tree.segment.rsc +2 -2
  218. package/.next/standalone/.next/server/app/landing.segments/landing/__PAGE__.segment.rsc +4 -4
  219. package/.next/standalone/.next/server/app/landing.segments/landing.segment.rsc +3 -3
  220. package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
  221. package/.next/standalone/.next/server/app/login.html +1 -1
  222. package/.next/standalone/.next/server/app/login.rsc +13 -13
  223. package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +13 -13
  224. package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +4 -4
  225. package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +6 -6
  226. package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  227. package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +4 -4
  228. package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +3 -3
  229. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  230. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0.yvcis._.js +1 -1
  231. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0148t9m._.js +1 -1
  232. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0b802lh._.js +1 -1
  233. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0kpc-o6._.js +1 -1
  234. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lc~.sk._.js +1 -1
  235. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0lp_pm5._.js +1 -1
  236. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0pwrfp1._.js +1 -1
  237. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0uw3x8s._.js +1 -1
  238. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0y.reft._.js +1 -1
  239. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0y5ae.7._.js +1 -1
  240. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z-kiuj._.js +1 -1
  241. package/.next/standalone/.next/server/chunks/_0amvca6._.js +1 -1
  242. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0415gym._.js +1 -1
  243. package/.next/standalone/.next/server/chunks/ssr/_00c58qj._.js +1 -1
  244. package/.next/standalone/.next/server/chunks/ssr/_049j5t_._.js +3 -0
  245. package/.next/standalone/.next/server/chunks/ssr/_0ss815q._.js +3 -0
  246. package/.next/standalone/.next/server/chunks/ssr/_0tlv512._.js +3 -0
  247. package/.next/standalone/.next/server/chunks/ssr/src_0_xhepz._.js +1 -1
  248. package/.next/standalone/.next/server/chunks/ssr/src_shared_constants_cliTools_09iru_o.js +1 -1
  249. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  250. package/.next/standalone/.next/server/pages/404.html +1 -1
  251. package/.next/standalone/.next/server/pages/500.html +1 -1
  252. package/.next/standalone/.next/static/chunks/026p3rpdycbyo.css +1 -0
  253. package/.next/standalone/.next/static/chunks/{08kho~qudyqxf.js → 06l6ck_q1o~m9.js} +3 -3
  254. package/.next/standalone/.next/static/chunks/{00~ibgir0ac-0.js → 0pwbnjvdm7lt_.js} +2 -2
  255. package/.next/{static/chunks/0hgyabetclndg.js → standalone/.next/static/chunks/0~p0472sbmye6.js} +1 -1
  256. package/.next/standalone/.next/static/chunks/{0a84s1t0dt8-h.js → 148_2lu2ra629.js} +1 -1
  257. package/.next/standalone/.next/static/chunks/{0f56a6ti-axtc.js → 17~_6lx17xr8z.js} +3 -3
  258. package/.next/standalone/mitm/server.js +8 -8
  259. package/.next/standalone/mitm/tokenPool.js +79 -17
  260. package/.next/standalone/package-lock.json +2 -2
  261. package/.next/standalone/package.json +1 -1
  262. package/.next/standalone/src/app/(dashboard)/dashboard/cli-tools/components/TokenSwapPoolCard.js +331 -50
  263. package/.next/standalone/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +11 -4
  264. package/.next/standalone/src/app/api/providers/[id]/route.js +4 -0
  265. package/.next/standalone/src/lib/localDb.js +3 -1
  266. package/.next/standalone/src/mitm/server.js +8 -8
  267. package/.next/standalone/src/mitm/tokenPool.js +79 -17
  268. package/.next/standalone/tests/unit/tokenPool.test.js +55 -14
  269. package/.next/static/chunks/026p3rpdycbyo.css +1 -0
  270. package/.next/static/chunks/{08kho~qudyqxf.js → 06l6ck_q1o~m9.js} +3 -3
  271. package/.next/static/chunks/{00~ibgir0ac-0.js → 0pwbnjvdm7lt_.js} +2 -2
  272. package/.next/{standalone/.next/static/chunks/0hgyabetclndg.js → static/chunks/0~p0472sbmye6.js} +1 -1
  273. package/.next/static/chunks/{0a84s1t0dt8-h.js → 148_2lu2ra629.js} +1 -1
  274. package/.next/static/chunks/{0f56a6ti-axtc.js → 17~_6lx17xr8z.js} +3 -3
  275. package/package.json +1 -1
  276. package/.next/standalone/.next/server/chunks/ssr/_0wr~fjn._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/ssr/_0z7gqwv._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/ssr/_12us7s0._.js +0 -3
  279. package/.next/standalone/.next/static/chunks/0lkbvidbb15ld.css +0 -1
  280. package/.next/static/chunks/0lkbvidbb15ld.css +0 -1
  281. /package/.next/standalone/.next/static/{XSoZ0BJFg1b0W2IGyybf7 → eALv8q_pKhx9-bE-9KiDB}/_buildManifest.js +0 -0
  282. /package/.next/standalone/.next/static/{XSoZ0BJFg1b0W2IGyybf7 → eALv8q_pKhx9-bE-9KiDB}/_clientMiddlewareManifest.js +0 -0
  283. /package/.next/standalone/.next/static/{XSoZ0BJFg1b0W2IGyybf7 → eALv8q_pKhx9-bE-9KiDB}/_ssgManifest.js +0 -0
  284. /package/.next/static/{XSoZ0BJFg1b0W2IGyybf7 → eALv8q_pKhx9-bE-9KiDB}/_buildManifest.js +0 -0
  285. /package/.next/static/{XSoZ0BJFg1b0W2IGyybf7 → eALv8q_pKhx9-bE-9KiDB}/_clientMiddlewareManifest.js +0 -0
  286. /package/.next/static/{XSoZ0BJFg1b0W2IGyybf7 → eALv8q_pKhx9-bE-9KiDB}/_ssgManifest.js +0 -0
@@ -110,6 +110,52 @@ function getActiveConnections(provider) {
110
110
  } catch { return []; }
111
111
  }
112
112
 
113
+ function getConnectionById(connId) {
114
+ try {
115
+ if (!fs.existsSync(DB_FILE)) return null;
116
+ const db = JSON.parse(fs.readFileSync(DB_FILE, "utf-8"));
117
+ const connections = db.providerConnections || [];
118
+ return connections.find(c => c.id === connId) || null;
119
+ } catch {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ function maskEmail(email) {
125
+ if (!email || typeof email !== "string") return email;
126
+ const atIndex = email.indexOf("@");
127
+ if (atIndex <= 0 || atIndex === email.length - 1) return email;
128
+
129
+ const local = email.slice(0, atIndex);
130
+ const domain = email.slice(atIndex + 1);
131
+
132
+ if (local.length === 1) return `${local[0]}**@${domain}`;
133
+ if (local.length === 2) return `${local[0]}**${local[1]}@${domain}`;
134
+
135
+ return `${local[0]}**${local[local.length - 1]}@${domain}`;
136
+ }
137
+
138
+ function isAccountEmailMaskEnabled() {
139
+ try {
140
+ if (!fs.existsSync(DB_FILE)) return false;
141
+ const db = JSON.parse(fs.readFileSync(DB_FILE, "utf-8"));
142
+ return !!db.settings?.tokenSwapMaskEmails;
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ function getConnectionLabel(connection) {
149
+ if (!connection) return "";
150
+
151
+ const shouldMask = isAccountEmailMaskEnabled();
152
+ const email = shouldMask ? maskEmail(connection.email) : connection.email;
153
+
154
+ if (connection.name && email) return `${connection.name} <${email}>`;
155
+ if (email) return email;
156
+ return connection.name || connection.id.slice(0, 8);
157
+ }
158
+
113
159
  function isTokenSwapEnabled(provider) {
114
160
  try {
115
161
  if (!fs.existsSync(DB_FILE)) return false;
@@ -131,13 +177,13 @@ function getNextConnection(provider) {
131
177
  const connections = getActiveConnections(provider);
132
178
  if (connections.length === 0) return null;
133
179
  if (connections.length === 1) {
134
- log(`🎯 [token-pool] selected: "${connections[0].name || connections[0].email || connections[0].id.slice(0, 8)}" (only account)`);
180
+ log(`🎯 [token-pool] selected: "${getConnectionLabel(connections[0])}" (only account)`);
135
181
  return connections[0];
136
182
  }
137
183
  const idx = (rrState[provider] || 0) % connections.length;
138
184
  rrState[provider] = idx + 1;
139
185
  const selected = connections[idx];
140
- log(`🎯 [token-pool] round-robin[${idx}/${connections.length}]: "${selected.name || selected.email || selected.id.slice(0, 8)}"`);
186
+ log(`🎯 [token-pool] round-robin[${idx}/${connections.length}]: "${getConnectionLabel(selected)}"`);
141
187
  return selected;
142
188
  }
143
189
 
@@ -236,24 +282,38 @@ function markAccountUsed(connId) {
236
282
  }
237
283
  }
238
284
 
239
- // ── Token refresh trigger (fire-and-forget via HTTP) ─────────
285
+ // ── Token refresh trigger (refresh + reload persisted token)
240
286
  // Calls 9Router's existing POST /api/providers/:id/test which
241
- // checks expiry and refreshes token automatically.
287
+ // checks expiry, refreshes token automatically, then returns the
288
+ // latest persisted connection snapshot for the current request.
242
289
 
243
- function triggerRefreshIfNeeded(connection) {
244
- if (!connection.expiresAt) return;
290
+ async function triggerRefreshIfNeeded(connection) {
291
+ if (!connection?.expiresAt || !connection?.refreshToken) return connection;
245
292
  const expiresAt = new Date(connection.expiresAt).getTime();
246
- if (Date.now() + TOKEN_EXPIRY_BUFFER_MS < expiresAt) return;
247
-
248
- log(`🔄 [token-pool] near-expiry refresh → ${(connection.name || connection.email || connection.id).slice(0, 20)}`);
249
- try {
250
- const req = http.request(
251
- { hostname: "127.0.0.1", port: ROUTER_PORT, path: `/api/providers/${connection.id}/test`, method: "POST" },
252
- () => {} // ignore response
253
- );
254
- req.on("error", () => {}); // swallow
255
- req.end();
256
- } catch { /* ignore */ }
293
+ if (Date.now() + TOKEN_EXPIRY_BUFFER_MS < expiresAt) return connection;
294
+
295
+ log(`🔄 [token-pool] near-expiry refresh → ${getConnectionLabel(connection).slice(0, 20)}`);
296
+ return await new Promise((resolve) => {
297
+ try {
298
+ const req = http.request(
299
+ { hostname: "127.0.0.1", port: ROUTER_PORT, path: `/api/providers/${connection.id}/test`, method: "POST" },
300
+ (res) => {
301
+ res.on("data", () => {});
302
+ res.on("end", () => {
303
+ const refreshed = getConnectionById(connection.id);
304
+ if (refreshed?.accessToken && refreshed.accessToken !== connection.accessToken) {
305
+ log(`♻️ [token-pool] refreshed token applied → ${getConnectionLabel(connection).slice(0, 20)}`);
306
+ }
307
+ resolve(refreshed || connection);
308
+ });
309
+ }
310
+ );
311
+ req.on("error", () => resolve(connection));
312
+ req.end();
313
+ } catch {
314
+ resolve(connection);
315
+ }
316
+ });
257
317
  }
258
318
 
259
319
  module.exports = {
@@ -267,4 +327,6 @@ module.exports = {
267
327
  getTokenSwapStrategy,
268
328
  parseQuotaCooldown,
269
329
  markAccountUsed,
330
+ getConnectionLabel,
331
+ maskEmail,
270
332
  };
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "n9router",
3
- "version": "0.3.89",
3
+ "version": "0.3.90",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "n9router",
9
- "version": "0.3.89",
9
+ "version": "0.3.90",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@monaco-editor/react": "^4.7.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n9router",
3
- "version": "0.3.89",
3
+ "version": "0.3.90",
4
4
  "description": "Self-hosted AI routing gateway — local proxy for Claude, Gemini, OpenAI and 40+ providers",
5
5
  "keywords": [
6
6
  "ai",
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
 
3
3
  import { useState, useEffect, useCallback, useRef } from "react";
4
- import { Card, Badge } from "@/shared/components";
4
+ import { Card, Badge, Toggle } from "@/shared/components";
5
5
  import Link from "next/link";
6
- import { parseQuotaData } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils";
6
+ import { parseQuotaData, formatResetTime } from "@/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils";
7
7
 
8
8
  // Model to highlight in the quota summary
9
9
  const HIGHLIGHT_MODEL = "claude-sonnet-4-6";
@@ -26,14 +26,103 @@ function getQuotaBg(pct) {
26
26
  return "bg-red-500";
27
27
  }
28
28
 
29
+ function formatResetTimeDisplay(resetTime) {
30
+ if (!resetTime) return null;
31
+
32
+ try {
33
+ const resetDate = new Date(resetTime);
34
+ const now = new Date();
35
+ const isToday = resetDate.toDateString() === now.toDateString();
36
+ const isTomorrow = resetDate.toDateString() === new Date(now.getTime() + 86400000).toDateString();
37
+
38
+ const timeStr = resetDate.toLocaleTimeString(undefined, {
39
+ hour: "2-digit",
40
+ minute: "2-digit",
41
+ hour12: true,
42
+ });
43
+
44
+ if (isToday) return `Today, ${timeStr}`;
45
+ if (isTomorrow) return `Tomorrow, ${timeStr}`;
46
+
47
+ return resetDate.toLocaleString(undefined, {
48
+ month: "short",
49
+ day: "numeric",
50
+ hour: "2-digit",
51
+ minute: "2-digit",
52
+ hour12: true,
53
+ });
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+
59
+ function maskEmail(email) {
60
+ if (!email || typeof email !== "string") return email;
61
+ const atIndex = email.indexOf("@");
62
+ if (atIndex <= 0 || atIndex === email.length - 1) return email;
63
+
64
+ const local = email.slice(0, atIndex);
65
+ const domain = email.slice(atIndex + 1);
66
+
67
+ if (local.length === 1) return `${local[0]}**@${domain}`;
68
+ if (local.length === 2) return `${local[0]}**${local[1]}@${domain}`;
69
+
70
+ return `${local[0]}**${local[local.length - 1]}@${domain}`;
71
+ }
72
+
73
+ function getAccountDisplay(acc, maskEmails) {
74
+ if (acc.email) return maskEmails ? maskEmail(acc.email) : acc.email;
75
+ return acc.name || acc.id.slice(0, 16);
76
+ }
77
+
78
+ function getStickyLimitForTool(tool) {
79
+ return tool?.stickyRoundRobinLimit || 3;
80
+ }
81
+
82
+ function getPreferredAccountId(accounts, strategy, stickyLimit) {
83
+ if (!accounts || accounts.length === 0) return null;
84
+ if (accounts.length === 1) return accounts[0].id;
85
+
86
+ const byNewest = [...accounts].sort((a, b) => {
87
+ if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
88
+ if (!a.lastUsedAt) return 1;
89
+ if (!b.lastUsedAt) return -1;
90
+ return new Date(b.lastUsedAt) - new Date(a.lastUsedAt);
91
+ });
92
+
93
+ if (strategy === "sticky") {
94
+ return byNewest[0]?.id || null;
95
+ }
96
+
97
+ const current = byNewest[0];
98
+ const currentCount = current?.consecutiveUseCount || 0;
99
+ if (current?.lastUsedAt && currentCount < stickyLimit) {
100
+ return current.id;
101
+ }
102
+
103
+ const byOldest = [...accounts].sort((a, b) => {
104
+ if (!a.lastUsedAt && !b.lastUsedAt) return (a.priority || 999) - (b.priority || 999);
105
+ if (!a.lastUsedAt) return -1;
106
+ if (!b.lastUsedAt) return 1;
107
+ return new Date(a.lastUsedAt) - new Date(b.lastUsedAt);
108
+ });
109
+
110
+ return byOldest[0]?.id || null;
111
+ }
112
+
29
113
  /**
30
114
  * Token Swap Pool Card — standalone card for token rotation mode.
31
115
  */
32
- export default function TokenSwapPoolCard({ tool, connections = [], serverRunning, dnsActive, onToggle }) {
116
+ export default function TokenSwapPoolCard({ tool, connections = [], serverRunning, dnsActive, onToggle, onRefreshConnections }) {
33
117
  const [enabled, setEnabled] = useState(false);
34
118
  const [toggling, setToggling] = useState(false);
35
119
  const [strategy, setStrategy] = useState("round-robin"); // "round-robin" | "sticky"
36
120
  const [togglingStrategy, setTogglingStrategy] = useState(false);
121
+ const [maskEmails, setMaskEmails] = useState(false);
122
+ const [togglingMaskEmails, setTogglingMaskEmails] = useState(false);
123
+ const [togglingAccountId, setTogglingAccountId] = useState(null);
124
+ const [resettingAccountId, setResettingAccountId] = useState(null);
125
+ const [resettingAll, setResettingAll] = useState(false);
37
126
  const [quotas, setQuotas] = useState({}); // { [connId]: { quotas: [], error: string|null, loading: bool } }
38
127
  const quotaCacheRef = useRef({}); // { [connId]: { data: parsed, error, ts: number } }
39
128
 
@@ -44,6 +133,7 @@ export default function TokenSwapPoolCard({ tool, connections = [], serverRunnin
44
133
  const data = await res.json();
45
134
  setEnabled(!!data.tokenSwapEnabled);
46
135
  setStrategy(data.tokenSwapStrategy || "round-robin");
136
+ setMaskEmails(!!data.tokenSwapMaskEmails);
47
137
  }
48
138
  } catch { /* ignore */ }
49
139
  }, []);
@@ -149,18 +239,39 @@ export default function TokenSwapPoolCard({ tool, connections = [], serverRunnin
149
239
  setTogglingStrategy(false);
150
240
  };
151
241
 
152
- const poolAccounts = connections.filter(
153
- (c) => c.provider === tool.tokenSwapProvider && c.isActive !== false
242
+ const toggleMaskEmails = async () => {
243
+ if (togglingMaskEmails) return;
244
+ setTogglingMaskEmails(true);
245
+ const newVal = !maskEmails;
246
+ try {
247
+ const res = await fetch("/api/settings", {
248
+ method: "PATCH",
249
+ headers: { "Content-Type": "application/json" },
250
+ body: JSON.stringify({ tokenSwapMaskEmails: newVal }),
251
+ });
252
+ if (res.ok) setMaskEmails(newVal);
253
+ } catch { /* ignore */ }
254
+ setTogglingMaskEmails(false);
255
+ };
256
+
257
+ const providerAccounts = connections.filter(
258
+ (c) => c.provider === tool.tokenSwapProvider
154
259
  );
155
- const activeCount = poolAccounts.length;
260
+ const activeAccounts = providerAccounts.filter(
261
+ (c) => c.isActive !== false
262
+ );
263
+ const activeCount = activeAccounts.length;
264
+ const activeAccountsKey = activeAccounts.map((acc) => acc.id).join("|");
265
+ const stickyLimit = getStickyLimitForTool(tool);
266
+ const preferredAccountId = getPreferredAccountId(activeAccounts, strategy, stickyLimit);
156
267
 
157
268
  // Auto-fetch quotas when enabled and accounts available
158
269
  // eslint-disable-next-line react-hooks/rules-of-hooks
159
270
  useEffect(() => {
160
271
  if (enabled && activeCount > 0) {
161
- fetchQuotas(poolAccounts);
272
+ fetchQuotas(activeAccounts);
162
273
  }
163
- }, [enabled, activeCount]); // eslint-disable-line react-hooks/exhaustive-deps
274
+ }, [enabled, activeCount, activeAccountsKey, fetchQuotas]); // eslint-disable-line react-hooks/exhaustive-deps
164
275
 
165
276
  // Prerequisites check
166
277
  const prereqsMet = serverRunning && dnsActive;
@@ -169,31 +280,19 @@ export default function TokenSwapPoolCard({ tool, connections = [], serverRunnin
169
280
  /**
170
281
  * Render inline quota info for a single account
171
282
  */
172
- const renderAccountQuota = (accId) => {
283
+ const getAccountQuotaMeta = (accId) => {
173
284
  const q = quotas[accId];
174
- if (!q) return null;
175
- if (q.loading) {
176
- return (
177
- <span className="text-[10px] text-text-muted animate-pulse shrink-0">loading…</span>
178
- );
179
- }
180
- if (q.error) {
181
- return (
182
- <span className="text-[10px] text-red-400 shrink-0" title={q.error}>⛔ bad account</span>
183
- );
184
- }
185
- if (!q.quotas || q.quotas.length === 0) {
186
- return (
187
- <span className="text-[10px] text-text-muted shrink-0">no quota data</span>
188
- );
189
- }
285
+ if (!q) return { state: "empty" };
286
+ if (q.loading) return { state: "loading" };
287
+ if (q.error) return { state: "error", error: q.error };
288
+ if (!q.quotas || q.quotas.length === 0) return { state: "no-data" };
190
289
 
191
290
  // Find highlight model, fallback to first quota with data
192
291
  const highlight = q.quotas.find(m =>
193
292
  m.modelKey?.includes(HIGHLIGHT_MODEL) || m.name?.toLowerCase().includes("opus")
194
293
  ) || q.quotas[0];
195
294
 
196
- if (!highlight) return null;
295
+ if (!highlight) return { state: "no-data" };
197
296
 
198
297
  const pct = highlight.remainingPercentage !== undefined
199
298
  ? Math.round(highlight.remainingPercentage)
@@ -201,19 +300,113 @@ export default function TokenSwapPoolCard({ tool, connections = [], serverRunnin
201
300
  ? Math.round(((highlight.total - highlight.used) / highlight.total) * 100)
202
301
  : null;
203
302
 
204
- if (pct === null) return null;
303
+ const nextResetAt = [...q.quotas]
304
+ .map((quota) => quota.resetAt)
305
+ .filter(Boolean)
306
+ .filter((resetAt) => new Date(resetAt).getTime() > Date.now())
307
+ .sort((a, b) => new Date(a).getTime() - new Date(b).getTime())[0] || highlight.resetAt || null;
308
+
309
+ return {
310
+ state: pct === null ? "no-data" : "ready",
311
+ highlight,
312
+ pct,
313
+ nextResetAt,
314
+ resetCountdown: formatResetTime(nextResetAt),
315
+ resetDisplay: formatResetTimeDisplay(nextResetAt),
316
+ };
317
+ };
205
318
 
319
+ const renderAccountQuota = (accId) => {
320
+ const meta = getAccountQuotaMeta(accId);
321
+ if (!meta || meta.state === "empty") {
322
+ return <span className="text-[10px] text-text-muted">Enable to load quota data</span>;
323
+ }
324
+ if (meta.state === "loading") {
325
+ return <span className="text-[10px] text-text-muted animate-pulse">Loading quota…</span>;
326
+ }
327
+ if (meta.state === "error") {
328
+ return <span className="text-[10px] text-red-400" title={meta.error}>Quota unavailable</span>;
329
+ }
330
+ if (meta.state === "no-data") {
331
+ return <span className="text-[10px] text-text-muted">No quota data</span>;
332
+ }
333
+
334
+ const { highlight, pct, resetCountdown, resetDisplay } = meta;
206
335
  return (
207
- <div className="flex items-center gap-1.5 shrink-0">
208
- <span className="text-[10px] text-text-muted truncate max-w-[80px]">{highlight.name}</span>
209
- <div className="w-12 h-1.5 rounded-full bg-surface-alt overflow-hidden shrink-0">
210
- <div className={`h-full rounded-full ${getQuotaBg(pct)}`} style={{ width: `${Math.min(pct, 100)}%` }} />
336
+ <div className="flex flex-col gap-1.5 min-w-0">
337
+ <div className="flex items-center gap-2 min-w-0">
338
+ <span className="text-[10px] text-text-muted truncate">{highlight.name}</span>
339
+ <div className="flex-1 h-1.5 rounded-full bg-surface-alt overflow-hidden min-w-[56px]">
340
+ <div className={`h-full rounded-full ${getQuotaBg(pct)}`} style={{ width: `${Math.min(pct, 100)}%` }} />
341
+ </div>
342
+ <span className={`text-[10px] font-medium shrink-0 ${getQuotaColor(pct)}`}>{pct}%</span>
211
343
  </div>
212
- <span className={`text-[10px] font-medium shrink-0 ${getQuotaColor(pct)}`}>{pct}%</span>
344
+ {resetCountdown !== "-" && resetDisplay ? (
345
+ <div className="text-[10px] text-text-muted">
346
+ Reset in <span className="text-text-main">{resetCountdown}</span>
347
+ <span className="text-text-muted/70"> • {resetDisplay}</span>
348
+ </div>
349
+ ) : (
350
+ <div className="text-[10px] text-text-muted">Reset time unavailable</div>
351
+ )}
213
352
  </div>
214
353
  );
215
354
  };
216
355
 
356
+ const toggleAccountActive = async (accountId, nextActive) => {
357
+ if (!accountId || togglingAccountId || resettingAll || resettingAccountId) return;
358
+ setTogglingAccountId(accountId);
359
+ try {
360
+ const res = await fetch(`/api/providers/${accountId}`, {
361
+ method: "PUT",
362
+ headers: { "Content-Type": "application/json" },
363
+ body: JSON.stringify({ isActive: nextActive }),
364
+ });
365
+ if (res.ok) {
366
+ await onRefreshConnections?.();
367
+ }
368
+ } catch { /* ignore */ }
369
+ setTogglingAccountId(null);
370
+ };
371
+
372
+ const resetAccountStreak = async (accountId) => {
373
+ if (!accountId || resettingAccountId || resettingAll || togglingAccountId) return;
374
+ setResettingAccountId(accountId);
375
+ try {
376
+ const res = await fetch(`/api/providers/${accountId}`, {
377
+ method: "PUT",
378
+ headers: { "Content-Type": "application/json" },
379
+ body: JSON.stringify({
380
+ lastUsedAt: null,
381
+ consecutiveUseCount: 0,
382
+ }),
383
+ });
384
+ if (res.ok) {
385
+ await onRefreshConnections?.();
386
+ }
387
+ } catch { /* ignore */ }
388
+ setResettingAccountId(null);
389
+ };
390
+
391
+ const resetAllStreaks = async () => {
392
+ if (resettingAll || resettingAccountId || togglingAccountId || providerAccounts.length === 0) return;
393
+ setResettingAll(true);
394
+ try {
395
+ await Promise.all(providerAccounts.map((acc) => (
396
+ fetch(`/api/providers/${acc.id}`, {
397
+ method: "PUT",
398
+ headers: { "Content-Type": "application/json" },
399
+ body: JSON.stringify({
400
+ lastUsedAt: null,
401
+ consecutiveUseCount: 0,
402
+ }),
403
+ })
404
+ )));
405
+ await onRefreshConnections?.();
406
+ } catch { /* ignore */ }
407
+ setResettingAll(false);
408
+ };
409
+
217
410
  return (
218
411
  <Card padding="xs" className="overflow-hidden">
219
412
  {/* ── Header ────────────────────────────────── */}
@@ -334,7 +527,7 @@ export default function TokenSwapPoolCard({ tool, connections = [], serverRunnin
334
527
  <div className="flex items-center gap-2">
335
528
  <p className="text-[10px] uppercase tracking-wider text-text-muted font-semibold">Pool Accounts</p>
336
529
  <span className="text-[10px] text-text-muted">
337
- {activeCount > 0 ? `${activeCount} active` : "none"}
530
+ {providerAccounts.length > 0 ? `${activeCount}/${providerAccounts.length} active` : "none"}
338
531
  </span>
339
532
  {activeCount > 1 && (
340
533
  <span className="text-[9px] text-text-muted bg-surface border border-border px-1 py-0.5 rounded">
@@ -342,26 +535,114 @@ export default function TokenSwapPoolCard({ tool, connections = [], serverRunnin
342
535
  </span>
343
536
  )}
344
537
  </div>
345
- {activeCount > 0 && (
346
- <button
347
- onClick={() => fetchQuotas(poolAccounts, true)}
348
- className="text-[10px] text-text-muted hover:text-primary flex items-center gap-0.5 transition-colors"
349
- title="Refresh quotas"
350
- >
351
- <span className="material-symbols-outlined text-[12px]">refresh</span>
352
- </button>
353
- )}
538
+ <div className="flex items-center gap-2">
539
+ {activeCount > 0 && (
540
+ <button
541
+ onClick={() => fetchQuotas(activeAccounts, true)}
542
+ className="text-[10px] text-text-muted hover:text-primary flex items-center gap-0.5 transition-colors"
543
+ title="Refresh quotas"
544
+ >
545
+ <span className="material-symbols-outlined text-[12px]">refresh</span>
546
+ </button>
547
+ )}
548
+ {providerAccounts.length > 0 && (
549
+ <button
550
+ onClick={resetAllStreaks}
551
+ disabled={resettingAll || !!resettingAccountId}
552
+ className="text-[10px] text-text-muted hover:text-primary disabled:opacity-50 flex items-center gap-0.5 transition-colors"
553
+ title="Reset all streak counts"
554
+ >
555
+ <span className="material-symbols-outlined text-[12px]">restart_alt</span>
556
+ Reset all
557
+ </button>
558
+ )}
559
+ </div>
560
+ </div>
561
+ <p className="text-[10px] text-text-muted px-0.5">
562
+ Sticky round robin keeps the current account until its streak reaches {stickyLimit}, then rotates to the least recently used account.
563
+ </p>
564
+ <div className="flex items-center justify-between gap-3 px-2 py-2 rounded-lg border border-border bg-surface-alt/40">
565
+ <div className="min-w-0">
566
+ <p className="text-[11px] font-medium text-text-main">Mask account emails</p>
567
+ <p className="text-[10px] text-text-muted">
568
+ Hide pool account emails in token swap logs and this panel. Example: {maskEmail("email@gmail.com")}
569
+ </p>
570
+ </div>
571
+ <button
572
+ onClick={toggleMaskEmails}
573
+ disabled={togglingMaskEmails}
574
+ className={`relative inline-flex h-5 w-9 items-center rounded-full transition-colors shrink-0 ${
575
+ maskEmails ? "bg-violet-500" : "bg-surface border border-border"
576
+ } ${togglingMaskEmails ? "opacity-50" : "cursor-pointer"}`}
577
+ title={maskEmails ? "Disable email masking" : "Enable email masking"}
578
+ >
579
+ <span
580
+ className={`inline-block h-3.5 w-3.5 transform rounded-full bg-white transition-transform shadow-sm ${
581
+ maskEmails ? "translate-x-4" : "translate-x-0.5"
582
+ }`}
583
+ />
584
+ </button>
354
585
  </div>
355
586
 
356
- {activeCount > 0 ? (
587
+ {providerAccounts.length > 0 ? (
357
588
  <>
358
- {poolAccounts.map((acc) => (
359
- <div key={acc.id} className="flex items-center gap-2 px-1 py-1 rounded hover:bg-surface-alt/50 transition-colors">
360
- <span className="material-symbols-outlined text-[14px] shrink-0 text-green-500">check_circle</span>
361
- <span className="flex-1 text-xs text-text-main truncate min-w-0">
362
- {acc.email || acc.name || acc.id.slice(0, 16)}
363
- </span>
364
- {renderAccountQuota(acc.id)}
589
+ {providerAccounts.map((acc) => (
590
+ <div
591
+ key={acc.id}
592
+ className={`rounded-xl border border-border bg-surface-alt/30 px-3 py-2.5 transition-colors ${
593
+ acc.isActive === false ? "opacity-65" : "hover:bg-surface-alt/50"
594
+ }`}
595
+ >
596
+ <div className="flex items-start justify-between gap-3">
597
+ <div className="min-w-0 flex-1">
598
+ <div className="flex items-center gap-2 min-w-0">
599
+ <span className={`material-symbols-outlined text-[14px] shrink-0 ${acc.isActive === false ? "text-text-muted" : "text-green-500"}`}>
600
+ {acc.isActive === false ? "pause_circle" : "check_circle"}
601
+ </span>
602
+ <span className="text-xs font-medium text-text-main truncate">
603
+ {getAccountDisplay(acc, maskEmails)}
604
+ </span>
605
+ {preferredAccountId === acc.id && acc.isActive !== false && (
606
+ <span className="text-[9px] text-violet-300 bg-violet-500/10 border border-violet-500/20 px-1 py-0.5 rounded shrink-0">
607
+ next
608
+ </span>
609
+ )}
610
+ <Badge variant={acc.isActive === false ? "default" : "success"} size="sm">
611
+ {acc.isActive === false ? "disabled" : "active"}
612
+ </Badge>
613
+ </div>
614
+ <div className="mt-1 flex items-center gap-2 flex-wrap text-[10px] text-text-muted">
615
+ <span>Priority #{acc.priority ?? "-"}</span>
616
+ <span>Streak {acc.consecutiveUseCount || 0}/{stickyLimit}</span>
617
+ {acc.lastUsedAt && <span>Last used {new Date(acc.lastUsedAt).toLocaleTimeString(undefined, { hour: "2-digit", minute: "2-digit" })}</span>}
618
+ </div>
619
+ </div>
620
+ <div className="flex items-center gap-3 shrink-0">
621
+ <button
622
+ onClick={() => resetAccountStreak(acc.id)}
623
+ disabled={resettingAll || togglingAccountId === acc.id || resettingAccountId === acc.id}
624
+ className="text-[10px] text-text-muted hover:text-primary disabled:opacity-50 transition-colors"
625
+ title="Reset this account streak"
626
+ >
627
+ {resettingAccountId === acc.id ? "..." : "Reset Streak"}
628
+ </button>
629
+ <Toggle
630
+ size="sm"
631
+ checked={acc.isActive !== false}
632
+ disabled={resettingAll || resettingAccountId === acc.id || togglingAccountId === acc.id}
633
+ onChange={(nextChecked) => toggleAccountActive(acc.id, nextChecked)}
634
+ />
635
+ </div>
636
+ </div>
637
+ <div className="mt-2 pl-6">
638
+ {acc.isActive === false ? (
639
+ <div className="text-[10px] text-text-muted">
640
+ Enable this account to include it in token rotation and load quota reset info.
641
+ </div>
642
+ ) : (
643
+ renderAccountQuota(acc.id)
644
+ )}
645
+ </div>
365
646
  </div>
366
647
  ))}
367
648
  <Link
@@ -57,10 +57,16 @@ export default function MitmPageClient() {
57
57
  };
58
58
 
59
59
  useEffect(() => {
60
- fetchConnections();
61
- fetchApiKeys();
62
- fetchAliases();
63
- fetchCloudSettings();
60
+ const loadInitialData = async () => {
61
+ await Promise.all([
62
+ fetchConnections(),
63
+ fetchApiKeys(),
64
+ fetchAliases(),
65
+ fetchCloudSettings(),
66
+ ]);
67
+ };
68
+
69
+ loadInitialData();
64
70
  }, []);
65
71
 
66
72
  const getActiveProviders = () => connections.filter(c => c.isActive !== false);
@@ -111,6 +117,7 @@ export default function MitmPageClient() {
111
117
  serverRunning={mitmStatus.running}
112
118
  dnsActive={mitmStatus.dnsStatus?.[toolId] || false}
113
119
  onToggle={(val) => setTokenSwapActive(val)}
120
+ onRefreshConnections={fetchConnections}
114
121
  />
115
122
  )}
116
123
  </Fragment>
@@ -98,6 +98,8 @@ export async function PUT(request, { params }) {
98
98
  testStatus,
99
99
  lastError,
100
100
  lastErrorAt,
101
+ lastUsedAt,
102
+ consecutiveUseCount,
101
103
  providerSpecificData
102
104
  } = body;
103
105
 
@@ -126,6 +128,8 @@ export async function PUT(request, { params }) {
126
128
  if (testStatus !== undefined) updateData.testStatus = testStatus;
127
129
  if (lastError !== undefined) updateData.lastError = lastError;
128
130
  if (lastErrorAt !== undefined) updateData.lastErrorAt = lastErrorAt;
131
+ if (lastUsedAt !== undefined) updateData.lastUsedAt = lastUsedAt;
132
+ if (consecutiveUseCount !== undefined) updateData.consecutiveUseCount = consecutiveUseCount;
129
133
 
130
134
  if (
131
135
  shouldMergeProviderSpecificData(