wyxrouter 0.4.71

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 (763) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/LICENSE +21 -0
  3. package/README.md +96 -0
  4. package/README.zh-CN.md +1311 -0
  5. package/assets/pixel-router-2d.png +0 -0
  6. package/cli/LICENSE +42 -0
  7. package/cli/README.md +125 -0
  8. package/cli/app/package.json +58 -0
  9. package/cli/cli.js +806 -0
  10. package/cli/package.json +48 -0
  11. package/i18n/README.ja-JP.md +1209 -0
  12. package/i18n/README.ru.md +1311 -0
  13. package/i18n/README.vi.md +1310 -0
  14. package/i18n/README.zh-CN.md +1306 -0
  15. package/images/9router.png +0 -0
  16. package/jsconfig.json +12 -0
  17. package/next.config.mjs +71 -0
  18. package/open-sse/config/appConstants.js +203 -0
  19. package/open-sse/config/codexInstructions.js +119 -0
  20. package/open-sse/config/constants.js +4 -0
  21. package/open-sse/config/defaultThinkingSignature.js +12 -0
  22. package/open-sse/config/errorConfig.js +85 -0
  23. package/open-sse/config/googleTtsLanguages.js +62 -0
  24. package/open-sse/config/kiroConstants.js +322 -0
  25. package/open-sse/config/models.js +13 -0
  26. package/open-sse/config/ollamaModels.js +19 -0
  27. package/open-sse/config/providerModels.js +944 -0
  28. package/open-sse/config/providers.js +458 -0
  29. package/open-sse/config/runtimeConfig.js +72 -0
  30. package/open-sse/config/ttsModels.js +129 -0
  31. package/open-sse/executors/antigravity.js +504 -0
  32. package/open-sse/executors/azure.js +57 -0
  33. package/open-sse/executors/base.js +185 -0
  34. package/open-sse/executors/codex.js +469 -0
  35. package/open-sse/executors/commandcode.js +88 -0
  36. package/open-sse/executors/cursor.js +795 -0
  37. package/open-sse/executors/default.js +497 -0
  38. package/open-sse/executors/gemini-cli.js +89 -0
  39. package/open-sse/executors/github.js +379 -0
  40. package/open-sse/executors/grok-web.js +345 -0
  41. package/open-sse/executors/iflow.js +108 -0
  42. package/open-sse/executors/index.js +75 -0
  43. package/open-sse/executors/kiro.js +508 -0
  44. package/open-sse/executors/ollama-local.js +14 -0
  45. package/open-sse/executors/opencode-go.js +41 -0
  46. package/open-sse/executors/opencode.js +32 -0
  47. package/open-sse/executors/perplexity-web.js +507 -0
  48. package/open-sse/executors/qoder.js +450 -0
  49. package/open-sse/executors/qwen.js +129 -0
  50. package/open-sse/executors/vertex.js +131 -0
  51. package/open-sse/executors/xiaomi-tokenplan.js +19 -0
  52. package/open-sse/handlers/chatCore/nonStreamingHandler.js +230 -0
  53. package/open-sse/handlers/chatCore/requestDetail.js +102 -0
  54. package/open-sse/handlers/chatCore/sseToJsonHandler.js +231 -0
  55. package/open-sse/handlers/chatCore/streamingHandler.js +103 -0
  56. package/open-sse/handlers/chatCore.js +287 -0
  57. package/open-sse/handlers/embeddingProviders/_base.js +4 -0
  58. package/open-sse/handlers/embeddingProviders/gemini.js +54 -0
  59. package/open-sse/handlers/embeddingProviders/index.js +23 -0
  60. package/open-sse/handlers/embeddingProviders/openai.js +39 -0
  61. package/open-sse/handlers/embeddingProviders/openaiCompatNode.js +13 -0
  62. package/open-sse/handlers/embeddingsCore.js +126 -0
  63. package/open-sse/handlers/fetch/index.js +237 -0
  64. package/open-sse/handlers/imageGenerationCore.js +189 -0
  65. package/open-sse/handlers/imageProviders/_base.js +31 -0
  66. package/open-sse/handlers/imageProviders/blackForestLabs.js +43 -0
  67. package/open-sse/handlers/imageProviders/cloudflareAi.js +178 -0
  68. package/open-sse/handlers/imageProviders/codex.js +198 -0
  69. package/open-sse/handlers/imageProviders/comfyui.js +8 -0
  70. package/open-sse/handlers/imageProviders/falAi.js +41 -0
  71. package/open-sse/handlers/imageProviders/gemini.js +25 -0
  72. package/open-sse/handlers/imageProviders/huggingface.js +22 -0
  73. package/open-sse/handlers/imageProviders/index.js +40 -0
  74. package/open-sse/handlers/imageProviders/nanobanana.js +58 -0
  75. package/open-sse/handlers/imageProviders/openai.js +40 -0
  76. package/open-sse/handlers/imageProviders/runwayml.js +47 -0
  77. package/open-sse/handlers/imageProviders/sdwebui.js +17 -0
  78. package/open-sse/handlers/imageProviders/stabilityAi.js +34 -0
  79. package/open-sse/handlers/responsesHandler.js +103 -0
  80. package/open-sse/handlers/search/callers.js +371 -0
  81. package/open-sse/handlers/search/chatSearch.js +409 -0
  82. package/open-sse/handlers/search/index.js +201 -0
  83. package/open-sse/handlers/search/normalizers.js +223 -0
  84. package/open-sse/handlers/sttCore.js +194 -0
  85. package/open-sse/handlers/ttsCore.js +74 -0
  86. package/open-sse/handlers/ttsProviders/_base.js +39 -0
  87. package/open-sse/handlers/ttsProviders/edgeTts.js +89 -0
  88. package/open-sse/handlers/ttsProviders/elevenlabs.js +48 -0
  89. package/open-sse/handlers/ttsProviders/gemini.js +117 -0
  90. package/open-sse/handlers/ttsProviders/genericFormats.js +169 -0
  91. package/open-sse/handlers/ttsProviders/googleTts.js +54 -0
  92. package/open-sse/handlers/ttsProviders/index.js +50 -0
  93. package/open-sse/handlers/ttsProviders/localDevice.js +87 -0
  94. package/open-sse/handlers/ttsProviders/minimax.js +59 -0
  95. package/open-sse/handlers/ttsProviders/openai.js +30 -0
  96. package/open-sse/handlers/ttsProviders/openrouter.js +70 -0
  97. package/open-sse/index.js +82 -0
  98. package/open-sse/rtk/applyFilter.js +15 -0
  99. package/open-sse/rtk/autodetect.js +111 -0
  100. package/open-sse/rtk/caveman.js +100 -0
  101. package/open-sse/rtk/cavemanPrompts.js +78 -0
  102. package/open-sse/rtk/constants.js +55 -0
  103. package/open-sse/rtk/filters/buildOutput.js +127 -0
  104. package/open-sse/rtk/filters/dedupLog.js +44 -0
  105. package/open-sse/rtk/filters/find.js +49 -0
  106. package/open-sse/rtk/filters/gitDiff.js +92 -0
  107. package/open-sse/rtk/filters/gitStatus.js +117 -0
  108. package/open-sse/rtk/filters/grep.js +48 -0
  109. package/open-sse/rtk/filters/ls.js +79 -0
  110. package/open-sse/rtk/filters/readNumbered.js +27 -0
  111. package/open-sse/rtk/filters/searchList.js +52 -0
  112. package/open-sse/rtk/filters/smartTruncate.js +15 -0
  113. package/open-sse/rtk/filters/tree.js +32 -0
  114. package/open-sse/rtk/index.js +155 -0
  115. package/open-sse/rtk/registry.js +38 -0
  116. package/open-sse/services/accountFallback.js +238 -0
  117. package/open-sse/services/combo.js +198 -0
  118. package/open-sse/services/compact.js +71 -0
  119. package/open-sse/services/kiroModels.js +332 -0
  120. package/open-sse/services/model.js +261 -0
  121. package/open-sse/services/oauthCredentialManager.js +151 -0
  122. package/open-sse/services/projectId.js +306 -0
  123. package/open-sse/services/provider.js +356 -0
  124. package/open-sse/services/qoderModels.js +214 -0
  125. package/open-sse/services/tokenRefresh.js +939 -0
  126. package/open-sse/services/usage.js +1496 -0
  127. package/open-sse/transformer/responsesTransformer.js +439 -0
  128. package/open-sse/transformer/streamToJsonConverter.js +103 -0
  129. package/open-sse/translator/formats.js +36 -0
  130. package/open-sse/translator/helpers/claudeHelper.js +216 -0
  131. package/open-sse/translator/helpers/geminiHelper.js +372 -0
  132. package/open-sse/translator/helpers/imageHelper.js +34 -0
  133. package/open-sse/translator/helpers/maxTokensHelper.js +27 -0
  134. package/open-sse/translator/helpers/openaiHelper.js +130 -0
  135. package/open-sse/translator/helpers/responsesApiHelper.js +139 -0
  136. package/open-sse/translator/helpers/toolCallHelper.js +148 -0
  137. package/open-sse/translator/index.js +251 -0
  138. package/open-sse/translator/request/antigravity-to-openai.js +229 -0
  139. package/open-sse/translator/request/claude-to-openai.js +232 -0
  140. package/open-sse/translator/request/gemini-to-openai.js +147 -0
  141. package/open-sse/translator/request/openai-responses.js +318 -0
  142. package/open-sse/translator/request/openai-to-claude.js +401 -0
  143. package/open-sse/translator/request/openai-to-commandcode.js +170 -0
  144. package/open-sse/translator/request/openai-to-cursor.js +183 -0
  145. package/open-sse/translator/request/openai-to-gemini.js +470 -0
  146. package/open-sse/translator/request/openai-to-kiro.js +629 -0
  147. package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
  148. package/open-sse/translator/request/openai-to-ollama.js +192 -0
  149. package/open-sse/translator/request/openai-to-vertex.js +42 -0
  150. package/open-sse/translator/response/claude-to-openai.js +206 -0
  151. package/open-sse/translator/response/commandcode-to-openai.js +197 -0
  152. package/open-sse/translator/response/cursor-to-openai.js +30 -0
  153. package/open-sse/translator/response/gemini-to-openai.js +245 -0
  154. package/open-sse/translator/response/kiro-to-openai.js +195 -0
  155. package/open-sse/translator/response/ollama-to-openai.js +152 -0
  156. package/open-sse/translator/response/openai-responses.js +590 -0
  157. package/open-sse/translator/response/openai-to-antigravity.js +122 -0
  158. package/open-sse/translator/response/openai-to-claude.js +266 -0
  159. package/open-sse/utils/bypassHandler.js +298 -0
  160. package/open-sse/utils/claudeCloaking.js +155 -0
  161. package/open-sse/utils/claudeHeaderCache.js +70 -0
  162. package/open-sse/utils/clientDetector.js +63 -0
  163. package/open-sse/utils/cursorChecksum.js +149 -0
  164. package/open-sse/utils/cursorProtobuf.js +904 -0
  165. package/open-sse/utils/debugLog.js +14 -0
  166. package/open-sse/utils/error.js +147 -0
  167. package/open-sse/utils/ollamaTransform.js +85 -0
  168. package/open-sse/utils/proxyFetch.js +368 -0
  169. package/open-sse/utils/reasoningContentInjector.js +79 -0
  170. package/open-sse/utils/requestLogger.js +260 -0
  171. package/open-sse/utils/responsesStreamHelpers.js +49 -0
  172. package/open-sse/utils/sessionManager.js +82 -0
  173. package/open-sse/utils/stream.js +462 -0
  174. package/open-sse/utils/streamHandler.js +250 -0
  175. package/open-sse/utils/streamHelpers.js +122 -0
  176. package/open-sse/utils/toolDeduper.js +49 -0
  177. package/open-sse/utils/usageTracking.js +347 -0
  178. package/package.json +100 -0
  179. package/postcss.config.mjs +12 -0
  180. package/public/favicon.svg +11 -0
  181. package/public/file.svg +1 -0
  182. package/public/globe.svg +1 -0
  183. package/public/i18n/literals/ar.json +195 -0
  184. package/public/i18n/literals/bn.json +195 -0
  185. package/public/i18n/literals/cs.json +195 -0
  186. package/public/i18n/literals/da.json +195 -0
  187. package/public/i18n/literals/de.json +195 -0
  188. package/public/i18n/literals/el.json +195 -0
  189. package/public/i18n/literals/es.json +195 -0
  190. package/public/i18n/literals/fi.json +195 -0
  191. package/public/i18n/literals/fr.json +195 -0
  192. package/public/i18n/literals/he.json +195 -0
  193. package/public/i18n/literals/hi.json +195 -0
  194. package/public/i18n/literals/hu.json +195 -0
  195. package/public/i18n/literals/id.json +195 -0
  196. package/public/i18n/literals/it.json +195 -0
  197. package/public/i18n/literals/ja.json +195 -0
  198. package/public/i18n/literals/ko.json +195 -0
  199. package/public/i18n/literals/nl.json +195 -0
  200. package/public/i18n/literals/no.json +195 -0
  201. package/public/i18n/literals/pl.json +195 -0
  202. package/public/i18n/literals/pt-BR.json +195 -0
  203. package/public/i18n/literals/pt-PT.json +195 -0
  204. package/public/i18n/literals/ro.json +195 -0
  205. package/public/i18n/literals/ru.json +195 -0
  206. package/public/i18n/literals/sv.json +195 -0
  207. package/public/i18n/literals/th.json +195 -0
  208. package/public/i18n/literals/tl.json +195 -0
  209. package/public/i18n/literals/tr.json +195 -0
  210. package/public/i18n/literals/uk.json +195 -0
  211. package/public/i18n/literals/ur.json +195 -0
  212. package/public/i18n/literals/vi.json +195 -0
  213. package/public/i18n/literals/zh-CN.json +772 -0
  214. package/public/i18n/literals/zh-TW.json +195 -0
  215. package/public/icons/discord.svg +4 -0
  216. package/public/icons/icon-192.svg +4 -0
  217. package/public/icons/icon-512.svg +4 -0
  218. package/public/next.svg +1 -0
  219. package/public/providers/alicode-intl.png +0 -0
  220. package/public/providers/alicode.png +0 -0
  221. package/public/providers/amp.png +0 -0
  222. package/public/providers/anthropic-m.png +0 -0
  223. package/public/providers/anthropic.png +0 -0
  224. package/public/providers/antigravity.png +0 -0
  225. package/public/providers/assemblyai.png +0 -0
  226. package/public/providers/aws-polly.png +0 -0
  227. package/public/providers/azure.png +0 -0
  228. package/public/providers/black-forest-labs.png +0 -0
  229. package/public/providers/blackbox.png +0 -0
  230. package/public/providers/brave-search.png +0 -0
  231. package/public/providers/byteplus.png +0 -0
  232. package/public/providers/cartesia.png +0 -0
  233. package/public/providers/cerebras.png +0 -0
  234. package/public/providers/chutes.png +0 -0
  235. package/public/providers/claude.png +0 -0
  236. package/public/providers/cline.png +0 -0
  237. package/public/providers/cloudflare-ai.png +0 -0
  238. package/public/providers/codebuddy.svg +37 -0
  239. package/public/providers/codex.png +0 -0
  240. package/public/providers/cohere.png +0 -0
  241. package/public/providers/comfyui.png +0 -0
  242. package/public/providers/commandcode.png +0 -0
  243. package/public/providers/continue.png +0 -0
  244. package/public/providers/copilot.png +0 -0
  245. package/public/providers/coqui.png +0 -0
  246. package/public/providers/cursor.png +0 -0
  247. package/public/providers/deepgram.png +0 -0
  248. package/public/providers/deepseek-tui.png +0 -0
  249. package/public/providers/deepseek.png +0 -0
  250. package/public/providers/droid.png +0 -0
  251. package/public/providers/edge-tts.png +0 -0
  252. package/public/providers/elevenlabs.png +0 -0
  253. package/public/providers/exa.png +0 -0
  254. package/public/providers/fal-ai.png +0 -0
  255. package/public/providers/firecrawl.png +0 -0
  256. package/public/providers/fireworks.png +0 -0
  257. package/public/providers/gemini-cli.png +0 -0
  258. package/public/providers/gemini.png +0 -0
  259. package/public/providers/github.png +0 -0
  260. package/public/providers/glm-cn.png +0 -0
  261. package/public/providers/glm.png +0 -0
  262. package/public/providers/google-pse.png +0 -0
  263. package/public/providers/google-tts.png +0 -0
  264. package/public/providers/grok-web.png +0 -0
  265. package/public/providers/groq.png +0 -0
  266. package/public/providers/hermes.png +0 -0
  267. package/public/providers/huggingface.png +0 -0
  268. package/public/providers/hyperbolic.png +0 -0
  269. package/public/providers/iflow.png +0 -0
  270. package/public/providers/inworld.png +0 -0
  271. package/public/providers/jcode.png +0 -0
  272. package/public/providers/jina-ai.png +0 -0
  273. package/public/providers/jina-reader.png +0 -0
  274. package/public/providers/kilocode.png +0 -0
  275. package/public/providers/kimi-coding.png +0 -0
  276. package/public/providers/kimi.png +0 -0
  277. package/public/providers/kiro.png +0 -0
  278. package/public/providers/linkup.png +0 -0
  279. package/public/providers/local-device.png +0 -0
  280. package/public/providers/minimax-cn.png +0 -0
  281. package/public/providers/minimax.png +0 -0
  282. package/public/providers/mistral.png +0 -0
  283. package/public/providers/nanobanana.png +0 -0
  284. package/public/providers/nebius.png +0 -0
  285. package/public/providers/nvidia.png +0 -0
  286. package/public/providers/oai-cc.png +0 -0
  287. package/public/providers/oai-r.png +0 -0
  288. package/public/providers/ollama-local.png +0 -0
  289. package/public/providers/ollama.png +0 -0
  290. package/public/providers/openai.png +0 -0
  291. package/public/providers/openclaw.png +0 -0
  292. package/public/providers/opencode-go.png +0 -0
  293. package/public/providers/opencode.png +0 -0
  294. package/public/providers/openrouter.png +0 -0
  295. package/public/providers/perplexity-web.png +0 -0
  296. package/public/providers/perplexity.png +0 -0
  297. package/public/providers/playht.png +0 -0
  298. package/public/providers/qoder.png +0 -0
  299. package/public/providers/qwen.png +0 -0
  300. package/public/providers/recraft.png +0 -0
  301. package/public/providers/roo.png +0 -0
  302. package/public/providers/runwayml.png +0 -0
  303. package/public/providers/sdwebui.png +0 -0
  304. package/public/providers/searchapi.png +0 -0
  305. package/public/providers/searxng.png +0 -0
  306. package/public/providers/serper.png +0 -0
  307. package/public/providers/siliconflow.png +0 -0
  308. package/public/providers/stability-ai.png +0 -0
  309. package/public/providers/tavily.png +0 -0
  310. package/public/providers/together.png +0 -0
  311. package/public/providers/topaz.png +0 -0
  312. package/public/providers/tortoise.png +0 -0
  313. package/public/providers/vertex-partner.png +0 -0
  314. package/public/providers/vertex.png +0 -0
  315. package/public/providers/volcengine-ark.png +0 -0
  316. package/public/providers/voyage-ai.png +0 -0
  317. package/public/providers/xai.png +0 -0
  318. package/public/providers/xiaomi-mimo.png +0 -0
  319. package/public/providers/xiaomi-tokenplan.png +0 -0
  320. package/public/providers/youcom.png +0 -0
  321. package/public/sw.js +22 -0
  322. package/public/vercel.svg +1 -0
  323. package/public/window.svg +1 -0
  324. package/scripts/compact-request-details.mjs +71 -0
  325. package/scripts/import-codex-gptjson.mjs +342 -0
  326. package/scripts/start-standalone.mjs +25 -0
  327. package/scripts/translate-readme.js +201 -0
  328. package/src/app/(dashboard)/dashboard/automation/page.js +294 -0
  329. package/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js +967 -0
  330. package/src/app/(dashboard)/dashboard/basic-chat/page.js +5 -0
  331. package/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js +66 -0
  332. package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +173 -0
  333. package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +11 -0
  334. package/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +481 -0
  335. package/src/app/(dashboard)/dashboard/cli-tools/components/ApiKeySelect.js +66 -0
  336. package/src/app/(dashboard)/dashboard/cli-tools/components/BaseUrlSelect.js +174 -0
  337. package/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +390 -0
  338. package/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js +301 -0
  339. package/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +458 -0
  340. package/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +323 -0
  341. package/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js +640 -0
  342. package/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js +338 -0
  343. package/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js +271 -0
  344. package/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +410 -0
  345. package/src/app/(dashboard)/dashboard/cli-tools/components/EndpointPresetControl.js +128 -0
  346. package/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +317 -0
  347. package/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js +380 -0
  348. package/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js +275 -0
  349. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmLinkCard.js +40 -0
  350. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +329 -0
  351. package/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +318 -0
  352. package/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +388 -0
  353. package/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +500 -0
  354. package/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js +39 -0
  355. package/src/app/(dashboard)/dashboard/cli-tools/components/cliEndpointMatch.js +13 -0
  356. package/src/app/(dashboard)/dashboard/cli-tools/components/index.js +19 -0
  357. package/src/app/(dashboard)/dashboard/cli-tools/page.js +7 -0
  358. package/src/app/(dashboard)/dashboard/combos/page.js +612 -0
  359. package/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js +91 -0
  360. package/src/app/(dashboard)/dashboard/console-log/page.js +8 -0
  361. package/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +1555 -0
  362. package/src/app/(dashboard)/dashboard/endpoint/page.js +7 -0
  363. package/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js +1903 -0
  364. package/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js +289 -0
  365. package/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +410 -0
  366. package/src/app/(dashboard)/dashboard/media-providers/web/page.js +209 -0
  367. package/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +117 -0
  368. package/src/app/(dashboard)/dashboard/mitm/page.js +5 -0
  369. package/src/app/(dashboard)/dashboard/page.js +7 -0
  370. package/src/app/(dashboard)/dashboard/profile/page.js +1140 -0
  371. package/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +389 -0
  372. package/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js +125 -0
  373. package/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js +250 -0
  374. package/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js +299 -0
  375. package/src/app/(dashboard)/dashboard/providers/[id]/CooldownTimer.js +42 -0
  376. package/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js +161 -0
  377. package/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js +95 -0
  378. package/src/app/(dashboard)/dashboard/providers/[id]/PassthroughModelsSection.js +183 -0
  379. package/src/app/(dashboard)/dashboard/providers/[id]/page.js +2053 -0
  380. package/src/app/(dashboard)/dashboard/providers/[id]/page.new.js +1724 -0
  381. package/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js +497 -0
  382. package/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js +185 -0
  383. package/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js +294 -0
  384. package/src/app/(dashboard)/dashboard/providers/new/page.js +220 -0
  385. package/src/app/(dashboard)/dashboard/providers/page.js +1365 -0
  386. package/src/app/(dashboard)/dashboard/proxy-pools/page.js +1092 -0
  387. package/src/app/(dashboard)/dashboard/quota/page.js +11 -0
  388. package/src/app/(dashboard)/dashboard/skills/page.js +112 -0
  389. package/src/app/(dashboard)/dashboard/translator/page.js +303 -0
  390. package/src/app/(dashboard)/dashboard/usage/components/OverviewCards.js +35 -0
  391. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/ProviderLimitCard.js +185 -0
  392. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js +127 -0
  393. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js +259 -0
  394. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +1394 -0
  395. package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +244 -0
  396. package/src/app/(dashboard)/dashboard/usage/components/ProviderTopology.js +327 -0
  397. package/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +433 -0
  398. package/src/app/(dashboard)/dashboard/usage/components/UsageChart.js +141 -0
  399. package/src/app/(dashboard)/dashboard/usage/components/UsageTable.js +247 -0
  400. package/src/app/(dashboard)/dashboard/usage/page.js +75 -0
  401. package/src/app/(dashboard)/layout.js +6 -0
  402. package/src/app/api/auth/login/route.js +76 -0
  403. package/src/app/api/auth/logout/route.js +12 -0
  404. package/src/app/api/auth/oidc/callback/route.js +87 -0
  405. package/src/app/api/auth/oidc/start/route.js +52 -0
  406. package/src/app/api/auth/oidc/test/route.js +84 -0
  407. package/src/app/api/auth/status/route.js +45 -0
  408. package/src/app/api/cli-tools/all-statuses/route.js +46 -0
  409. package/src/app/api/cli-tools/antigravity-mitm/alias/route.js +53 -0
  410. package/src/app/api/cli-tools/antigravity-mitm/route.js +202 -0
  411. package/src/app/api/cli-tools/claude-settings/route.js +203 -0
  412. package/src/app/api/cli-tools/cline-settings/route.js +133 -0
  413. package/src/app/api/cli-tools/codex-gateway/accounts/route.js +16 -0
  414. package/src/app/api/cli-tools/codex-settings/route.js +239 -0
  415. package/src/app/api/cli-tools/copilot-settings/route.js +148 -0
  416. package/src/app/api/cli-tools/cowork-mcp-registry/route.js +77 -0
  417. package/src/app/api/cli-tools/cowork-mcp-tools/route.js +95 -0
  418. package/src/app/api/cli-tools/cowork-settings/route.js +412 -0
  419. package/src/app/api/cli-tools/deepseek-tui-settings/route.js +164 -0
  420. package/src/app/api/cli-tools/droid-settings/route.js +213 -0
  421. package/src/app/api/cli-tools/hermes-settings/route.js +175 -0
  422. package/src/app/api/cli-tools/jcode-settings/route.js +216 -0
  423. package/src/app/api/cli-tools/kilo-settings/route.js +131 -0
  424. package/src/app/api/cli-tools/openclaw-settings/route.js +292 -0
  425. package/src/app/api/cli-tools/opencode-settings/route.js +259 -0
  426. package/src/app/api/combos/[id]/route.js +81 -0
  427. package/src/app/api/combos/route.js +48 -0
  428. package/src/app/api/health/route.js +15 -0
  429. package/src/app/api/init/route.js +4 -0
  430. package/src/app/api/keys/[id]/route.js +58 -0
  431. package/src/app/api/keys/route.js +42 -0
  432. package/src/app/api/locale/route.js +30 -0
  433. package/src/app/api/mcp/[plugin]/message/route.js +21 -0
  434. package/src/app/api/mcp/[plugin]/sse/route.js +37 -0
  435. package/src/app/api/media-providers/tts/deepgram/voices/route.js +65 -0
  436. package/src/app/api/media-providers/tts/elevenlabs/voices/route.js +71 -0
  437. package/src/app/api/media-providers/tts/inworld/voices/route.js +61 -0
  438. package/src/app/api/media-providers/tts/minimax/voices/route.js +113 -0
  439. package/src/app/api/media-providers/tts/voices/route.js +99 -0
  440. package/src/app/api/models/alias/route.js +53 -0
  441. package/src/app/api/models/availability/route.js +103 -0
  442. package/src/app/api/models/custom/route.js +48 -0
  443. package/src/app/api/models/disabled/route.js +50 -0
  444. package/src/app/api/models/route.js +64 -0
  445. package/src/app/api/models/test/ping.js +191 -0
  446. package/src/app/api/models/test/route.js +14 -0
  447. package/src/app/api/oauth/[provider]/[action]/route.js +343 -0
  448. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/cancel/route.js +19 -0
  449. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/manual/[workerId]/route.js +30 -0
  450. package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/route.js +23 -0
  451. package/src/app/api/oauth/codebuddy/bulk-import/latest/route.js +25 -0
  452. package/src/app/api/oauth/codebuddy/bulk-import/route.js +49 -0
  453. package/src/app/api/oauth/codebuddy/quota-cookie/route.js +133 -0
  454. package/src/app/api/oauth/codex/import-token/route.js +96 -0
  455. package/src/app/api/oauth/cursor/auto-import/route.js +258 -0
  456. package/src/app/api/oauth/cursor/import/route.js +100 -0
  457. package/src/app/api/oauth/gitlab/pat/route.js +62 -0
  458. package/src/app/api/oauth/iflow/cookie/route.js +137 -0
  459. package/src/app/api/oauth/kiro/auto-import/route.js +85 -0
  460. package/src/app/api/oauth/kiro/bulk-import/[jobId]/cancel/route.js +18 -0
  461. package/src/app/api/oauth/kiro/bulk-import/[jobId]/manual/[workerId]/route.js +29 -0
  462. package/src/app/api/oauth/kiro/bulk-import/[jobId]/route.js +22 -0
  463. package/src/app/api/oauth/kiro/bulk-import/latest/route.js +25 -0
  464. package/src/app/api/oauth/kiro/bulk-import/route.js +49 -0
  465. package/src/app/api/oauth/kiro/import/route.js +110 -0
  466. package/src/app/api/oauth/kiro/social-authorize/route.js +27 -0
  467. package/src/app/api/oauth/kiro/social-exchange/route.js +41 -0
  468. package/src/app/api/pricing/route.js +134 -0
  469. package/src/app/api/provider-nodes/[id]/route.js +101 -0
  470. package/src/app/api/provider-nodes/route.js +104 -0
  471. package/src/app/api/provider-nodes/validate/route.js +201 -0
  472. package/src/app/api/providers/[id]/models/route.js +526 -0
  473. package/src/app/api/providers/[id]/route.js +189 -0
  474. package/src/app/api/providers/[id]/test/route.js +23 -0
  475. package/src/app/api/providers/[id]/test/testUtils.js +714 -0
  476. package/src/app/api/providers/[id]/test-models/route.js +66 -0
  477. package/src/app/api/providers/client/route.js +126 -0
  478. package/src/app/api/providers/kilo/free-models/route.js +55 -0
  479. package/src/app/api/providers/route.js +206 -0
  480. package/src/app/api/providers/suggested-models/filters.js +20 -0
  481. package/src/app/api/providers/suggested-models/route.js +32 -0
  482. package/src/app/api/providers/test-batch/route.js +131 -0
  483. package/src/app/api/providers/validate/route.js +637 -0
  484. package/src/app/api/proxy-pools/[id]/route.js +123 -0
  485. package/src/app/api/proxy-pools/[id]/test/route.js +70 -0
  486. package/src/app/api/proxy-pools/cloudflare-deploy/route.js +145 -0
  487. package/src/app/api/proxy-pools/deno-deploy/route.js +175 -0
  488. package/src/app/api/proxy-pools/route.js +93 -0
  489. package/src/app/api/proxy-pools/vercel-deploy/route.js +142 -0
  490. package/src/app/api/settings/database/route.js +36 -0
  491. package/src/app/api/settings/proxy-test/route.js +23 -0
  492. package/src/app/api/settings/require-login/route.js +15 -0
  493. package/src/app/api/settings/route.js +100 -0
  494. package/src/app/api/shutdown/route.js +24 -0
  495. package/src/app/api/tags/route.js +18 -0
  496. package/src/app/api/translator/console-logs/route.js +24 -0
  497. package/src/app/api/translator/console-logs/stream/route.js +79 -0
  498. package/src/app/api/translator/load/route.js +45 -0
  499. package/src/app/api/translator/save/route.js +44 -0
  500. package/src/app/api/translator/send/route.js +94 -0
  501. package/src/app/api/translator/translate/route.js +90 -0
  502. package/src/app/api/tunnel/disable/route.js +12 -0
  503. package/src/app/api/tunnel/enable/route.js +16 -0
  504. package/src/app/api/tunnel/status/route.js +13 -0
  505. package/src/app/api/tunnel/tailscale-check/route.js +50 -0
  506. package/src/app/api/tunnel/tailscale-disable/route.js +12 -0
  507. package/src/app/api/tunnel/tailscale-enable/route.js +12 -0
  508. package/src/app/api/tunnel/tailscale-install/route.js +72 -0
  509. package/src/app/api/usage/[connectionId]/route.js +188 -0
  510. package/src/app/api/usage/chart/route.js +21 -0
  511. package/src/app/api/usage/history/route.js +12 -0
  512. package/src/app/api/usage/logs/route.js +12 -0
  513. package/src/app/api/usage/providers/route.js +42 -0
  514. package/src/app/api/usage/request-details/route.js +57 -0
  515. package/src/app/api/usage/request-logs/route.js +13 -0
  516. package/src/app/api/usage/stats/route.js +23 -0
  517. package/src/app/api/usage/stream/route.js +79 -0
  518. package/src/app/api/v1/api/chat/route.js +37 -0
  519. package/src/app/api/v1/audio/speech/route.js +16 -0
  520. package/src/app/api/v1/audio/transcriptions/route.js +19 -0
  521. package/src/app/api/v1/audio/voices/route.js +68 -0
  522. package/src/app/api/v1/chat/completions/route.js +35 -0
  523. package/src/app/api/v1/embeddings/route.js +21 -0
  524. package/src/app/api/v1/images/generations/route.js +16 -0
  525. package/src/app/api/v1/messages/count_tokens/route.js +52 -0
  526. package/src/app/api/v1/messages/route.js +36 -0
  527. package/src/app/api/v1/models/[kind]/route.js +55 -0
  528. package/src/app/api/v1/models/info/route.js +110 -0
  529. package/src/app/api/v1/models/route.js +451 -0
  530. package/src/app/api/v1/responses/compact/route.js +37 -0
  531. package/src/app/api/v1/responses/route.js +30 -0
  532. package/src/app/api/v1/route.js +1 -0
  533. package/src/app/api/v1/search/route.js +21 -0
  534. package/src/app/api/v1/web/fetch/route.js +21 -0
  535. package/src/app/api/v1beta/models/[...path]/route.js +328 -0
  536. package/src/app/api/v1beta/models/route.js +44 -0
  537. package/src/app/api/version/route.js +45 -0
  538. package/src/app/api/version/shutdown/route.js +15 -0
  539. package/src/app/api/version/update/route.js +21 -0
  540. package/src/app/callback/page.js +148 -0
  541. package/src/app/dashboard/settings/pricing/page.js +173 -0
  542. package/src/app/favicon.ico +0 -0
  543. package/src/app/globals.css +496 -0
  544. package/src/app/landing/components/AnimatedBackground.js +57 -0
  545. package/src/app/landing/components/Features.js +133 -0
  546. package/src/app/landing/components/FlowAnimation.js +175 -0
  547. package/src/app/landing/components/Footer.js +61 -0
  548. package/src/app/landing/components/GetStarted.js +97 -0
  549. package/src/app/landing/components/HeroSection.js +47 -0
  550. package/src/app/landing/components/HowItWorks.js +66 -0
  551. package/src/app/landing/components/Navigation.js +72 -0
  552. package/src/app/landing/page.js +106 -0
  553. package/src/app/layout.js +49 -0
  554. package/src/app/login/page.js +197 -0
  555. package/src/app/manifest.js +30 -0
  556. package/src/app/page.js +5 -0
  557. package/src/dashboardGuard.js +242 -0
  558. package/src/i18n/RuntimeI18nProvider.js +27 -0
  559. package/src/i18n/config.js +146 -0
  560. package/src/i18n/runtime.js +162 -0
  561. package/src/lib/appUpdater.js +200 -0
  562. package/src/lib/auth/dashboardSession.js +68 -0
  563. package/src/lib/auth/loginLimiter.js +52 -0
  564. package/src/lib/auth/oidc.js +234 -0
  565. package/src/lib/consoleLogBuffer.js +79 -0
  566. package/src/lib/dataDir.js +29 -0
  567. package/src/lib/db/adapters/betterSqliteAdapter.js +55 -0
  568. package/src/lib/db/adapters/bunSqliteAdapter.js +63 -0
  569. package/src/lib/db/adapters/nodeSqliteAdapter.js +84 -0
  570. package/src/lib/db/adapters/sqljsAdapter.js +115 -0
  571. package/src/lib/db/backup.js +35 -0
  572. package/src/lib/db/driver.js +85 -0
  573. package/src/lib/db/helpers/jsonCol.js +9 -0
  574. package/src/lib/db/helpers/kvStore.js +39 -0
  575. package/src/lib/db/helpers/metaStore.js +22 -0
  576. package/src/lib/db/index.js +171 -0
  577. package/src/lib/db/migrate.js +286 -0
  578. package/src/lib/db/migrations/001-initial.js +14 -0
  579. package/src/lib/db/migrations/index.js +10 -0
  580. package/src/lib/db/paths.js +18 -0
  581. package/src/lib/db/repos/aliasRepo.js +62 -0
  582. package/src/lib/db/repos/apiKeysRepo.js +75 -0
  583. package/src/lib/db/repos/combosRepo.js +73 -0
  584. package/src/lib/db/repos/connectionsRepo.js +226 -0
  585. package/src/lib/db/repos/disabledModelsRepo.js +56 -0
  586. package/src/lib/db/repos/nodesRepo.js +95 -0
  587. package/src/lib/db/repos/pricingRepo.js +108 -0
  588. package/src/lib/db/repos/proxyPoolsRepo.js +103 -0
  589. package/src/lib/db/repos/requestDetailsRepo.js +259 -0
  590. package/src/lib/db/repos/settingsRepo.js +104 -0
  591. package/src/lib/db/repos/usageRepo.js +731 -0
  592. package/src/lib/db/schema.js +157 -0
  593. package/src/lib/db/version.js +21 -0
  594. package/src/lib/disabledModelsDb.js +4 -0
  595. package/src/lib/localDb.js +21 -0
  596. package/src/lib/mcp/stdioSseBridge.js +198 -0
  597. package/src/lib/mitmAliasCache.js +46 -0
  598. package/src/lib/network/connectionProxy.js +160 -0
  599. package/src/lib/network/initOutboundProxy.js +25 -0
  600. package/src/lib/network/outboundProxy.js +68 -0
  601. package/src/lib/network/proxyTest.js +91 -0
  602. package/src/lib/oauth/constants/oauth.js +284 -0
  603. package/src/lib/oauth/constants/xai.js +61 -0
  604. package/src/lib/oauth/providers.js +1506 -0
  605. package/src/lib/oauth/services/antigravity.js +321 -0
  606. package/src/lib/oauth/services/claude.js +136 -0
  607. package/src/lib/oauth/services/codebuddyBulkImportManager.js +454 -0
  608. package/src/lib/oauth/services/codex.js +144 -0
  609. package/src/lib/oauth/services/cursor.js +179 -0
  610. package/src/lib/oauth/services/gemini.js +240 -0
  611. package/src/lib/oauth/services/github.js +225 -0
  612. package/src/lib/oauth/services/iflow.js +202 -0
  613. package/src/lib/oauth/services/index.js +17 -0
  614. package/src/lib/oauth/services/kiro.js +334 -0
  615. package/src/lib/oauth/services/kiroBulkImportManager.js +778 -0
  616. package/src/lib/oauth/services/kiroConnections.js +93 -0
  617. package/src/lib/oauth/services/kiroGoogleAutomation.js +1136 -0
  618. package/src/lib/oauth/services/oauth.js +157 -0
  619. package/src/lib/oauth/services/openai.js +123 -0
  620. package/src/lib/oauth/services/qoder.js +216 -0
  621. package/src/lib/oauth/services/qwen.js +170 -0
  622. package/src/lib/oauth/services/xai.js +238 -0
  623. package/src/lib/oauth/utils/banner.js +63 -0
  624. package/src/lib/oauth/utils/pkce.js +39 -0
  625. package/src/lib/oauth/utils/server.js +415 -0
  626. package/src/lib/oauth/utils/ui.js +48 -0
  627. package/src/lib/providerNormalization.js +45 -0
  628. package/src/lib/qoder/constants.js +64 -0
  629. package/src/lib/qoder/cosy.js +175 -0
  630. package/src/lib/qoder/encoding.js +55 -0
  631. package/src/lib/requestDetailsDb.js +4 -0
  632. package/src/lib/tunnel/cloudflare/cloudflared.js +449 -0
  633. package/src/lib/tunnel/cloudflare/config.js +9 -0
  634. package/src/lib/tunnel/cloudflare/healthCheck.js +29 -0
  635. package/src/lib/tunnel/cloudflare/manager.js +151 -0
  636. package/src/lib/tunnel/cloudflare/pid.js +23 -0
  637. package/src/lib/tunnel/index.js +48 -0
  638. package/src/lib/tunnel/shared/dnsResolver.js +17 -0
  639. package/src/lib/tunnel/shared/internetCheck.js +26 -0
  640. package/src/lib/tunnel/shared/state.js +41 -0
  641. package/src/lib/tunnel/shared/watchdogConfig.js +8 -0
  642. package/src/lib/tunnel/tailscale/config.js +7 -0
  643. package/src/lib/tunnel/tailscale/healthCheck.js +29 -0
  644. package/src/lib/tunnel/tailscale/manager.js +129 -0
  645. package/src/lib/tunnel/tailscale/tailscale.js +790 -0
  646. package/src/lib/updater/updater.js +235 -0
  647. package/src/lib/usage/fetcher.js +208 -0
  648. package/src/lib/usageDb.js +7 -0
  649. package/src/mitm/antigravityIdeVersion.js +50 -0
  650. package/src/mitm/cert/generate.js +32 -0
  651. package/src/mitm/cert/install.js +269 -0
  652. package/src/mitm/cert/rootCA.js +173 -0
  653. package/src/mitm/config.js +87 -0
  654. package/src/mitm/dbReader.js +22 -0
  655. package/src/mitm/dns/dnsConfig.js +266 -0
  656. package/src/mitm/handlers/antigravity.js +33 -0
  657. package/src/mitm/handlers/base.js +226 -0
  658. package/src/mitm/handlers/copilot.js +35 -0
  659. package/src/mitm/handlers/cursor.js +15 -0
  660. package/src/mitm/handlers/kiro.js +526 -0
  661. package/src/mitm/logger.js +106 -0
  662. package/src/mitm/manager.js +851 -0
  663. package/src/mitm/paths.js +32 -0
  664. package/src/mitm/server.js +435 -0
  665. package/src/mitm/winElevated.js +81 -0
  666. package/src/models/index.js +38 -0
  667. package/src/proxy.js +5 -0
  668. package/src/shared/components/AddCustomEmbeddingModal.js +183 -0
  669. package/src/shared/components/Avatar.js +88 -0
  670. package/src/shared/components/Badge.js +54 -0
  671. package/src/shared/components/BulkAccountAutomationModal.js +508 -0
  672. package/src/shared/components/Button.js +56 -0
  673. package/src/shared/components/Card.js +116 -0
  674. package/src/shared/components/ChangelogModal.js +97 -0
  675. package/src/shared/components/CodeBuddyQuotaCookieModal.js +109 -0
  676. package/src/shared/components/ComboFormModal.js +176 -0
  677. package/src/shared/components/CursorAuthModal.js +212 -0
  678. package/src/shared/components/DonateModal.js +136 -0
  679. package/src/shared/components/Drawer.js +82 -0
  680. package/src/shared/components/EditConnectionModal.js +286 -0
  681. package/src/shared/components/Footer.js +132 -0
  682. package/src/shared/components/GitLabAuthModal.js +194 -0
  683. package/src/shared/components/Header.js +380 -0
  684. package/src/shared/components/HeaderLanguage.js +46 -0
  685. package/src/shared/components/HeaderMenu.js +126 -0
  686. package/src/shared/components/IFlowCookieModal.js +132 -0
  687. package/src/shared/components/Input.js +65 -0
  688. package/src/shared/components/KiroAuthModal.js +1171 -0
  689. package/src/shared/components/KiroOAuthWrapper.js +149 -0
  690. package/src/shared/components/KiroSocialOAuthModal.js +205 -0
  691. package/src/shared/components/LanguageSwitcher.js +190 -0
  692. package/src/shared/components/Loading.js +75 -0
  693. package/src/shared/components/ManualConfigModal.js +44 -0
  694. package/src/shared/components/McpMarketplaceModal.js +255 -0
  695. package/src/shared/components/Modal.js +146 -0
  696. package/src/shared/components/ModelSelectModal.js +537 -0
  697. package/src/shared/components/NineRemoteButton.js +23 -0
  698. package/src/shared/components/NineRemotePromoModal.js +99 -0
  699. package/src/shared/components/NoAuthProxyCard.js +86 -0
  700. package/src/shared/components/OAuthModal.js +682 -0
  701. package/src/shared/components/Pagination.js +150 -0
  702. package/src/shared/components/PricingModal.js +208 -0
  703. package/src/shared/components/ProviderIcon.js +63 -0
  704. package/src/shared/components/ProviderInfoCard.js +82 -0
  705. package/src/shared/components/RequestLogger.js +121 -0
  706. package/src/shared/components/SegmentedControl.js +48 -0
  707. package/src/shared/components/Select.js +67 -0
  708. package/src/shared/components/Sidebar.js +441 -0
  709. package/src/shared/components/ThemeProvider.js +15 -0
  710. package/src/shared/components/ThemeToggle.js +42 -0
  711. package/src/shared/components/Toggle.js +69 -0
  712. package/src/shared/components/Tooltip.js +25 -0
  713. package/src/shared/components/UsageStats.js +505 -0
  714. package/src/shared/components/index.js +46 -0
  715. package/src/shared/components/layouts/AuthLayout.js +29 -0
  716. package/src/shared/components/layouts/DashboardLayout.js +104 -0
  717. package/src/shared/components/layouts/index.js +4 -0
  718. package/src/shared/constants/cliTools.js +397 -0
  719. package/src/shared/constants/colors.js +77 -0
  720. package/src/shared/constants/config.js +99 -0
  721. package/src/shared/constants/coworkPlugins.js +75 -0
  722. package/src/shared/constants/index.js +4 -0
  723. package/src/shared/constants/locales.js +36 -0
  724. package/src/shared/constants/mitmToolHosts.js +12 -0
  725. package/src/shared/constants/models.js +38 -0
  726. package/src/shared/constants/pricing.js +303 -0
  727. package/src/shared/constants/providers.js +289 -0
  728. package/src/shared/constants/skills.js +78 -0
  729. package/src/shared/constants/ttsProviders.js +138 -0
  730. package/src/shared/hooks/index.js +2 -0
  731. package/src/shared/hooks/useCopyToClipboard.js +43 -0
  732. package/src/shared/hooks/useTheme.js +60 -0
  733. package/src/shared/services/bootstrap.js +12 -0
  734. package/src/shared/services/initializeApp.js +268 -0
  735. package/src/shared/utils/api.js +93 -0
  736. package/src/shared/utils/apiKey.js +98 -0
  737. package/src/shared/utils/clineAuth.js +37 -0
  738. package/src/shared/utils/cn.js +11 -0
  739. package/src/shared/utils/connectionStatus.js +162 -0
  740. package/src/shared/utils/index.js +40 -0
  741. package/src/shared/utils/machine.js +6 -0
  742. package/src/shared/utils/machineId.js +66 -0
  743. package/src/shared/utils/providerModelsFetcher.js +30 -0
  744. package/src/sse/handlers/chat.js +261 -0
  745. package/src/sse/handlers/embeddings.js +141 -0
  746. package/src/sse/handlers/fetch.js +213 -0
  747. package/src/sse/handlers/imageGeneration.js +142 -0
  748. package/src/sse/handlers/search.js +206 -0
  749. package/src/sse/handlers/stt.js +88 -0
  750. package/src/sse/handlers/tts.js +114 -0
  751. package/src/sse/services/auth.js +346 -0
  752. package/src/sse/services/codexGateway.js +215 -0
  753. package/src/sse/services/model.js +99 -0
  754. package/src/sse/services/tokenRefresh.js +319 -0
  755. package/src/sse/utils/logger.js +75 -0
  756. package/src/store/headerSearchStore.js +19 -0
  757. package/src/store/index.js +6 -0
  758. package/src/store/notificationStore.js +45 -0
  759. package/src/store/providerStore.js +55 -0
  760. package/src/store/settingsStore.js +51 -0
  761. package/src/store/themeStore.js +54 -0
  762. package/src/store/userStore.js +20 -0
  763. package/start.sh +4 -0
@@ -0,0 +1,1092 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useMemo, useState, useRef } from "react";
4
+ import { Badge, Button, Card, CardSkeleton, Input, Modal, Toggle, ConfirmModal, Pagination } from "@/shared/components";
5
+ import { useNotificationStore } from "@/store/notificationStore";
6
+
7
+ function getStatusVariant(status) {
8
+ if (status === "active") return "success";
9
+ if (status === "error") return "error";
10
+ return "default";
11
+ }
12
+
13
+ function formatDateTime(value) {
14
+ if (!value) return "Never";
15
+ const date = new Date(value);
16
+ if (Number.isNaN(date.getTime())) return "Never";
17
+ return date.toLocaleString();
18
+ }
19
+
20
+ function normalizeFormData(data = {}) {
21
+ return {
22
+ name: data.name || "",
23
+ proxyUrl: data.proxyUrl || "",
24
+ noProxy: data.noProxy || "",
25
+ isActive: data.isActive !== false,
26
+ strictProxy: data.strictProxy === true,
27
+ };
28
+ }
29
+
30
+ const PROXY_POOL_DEFAULT_PAGE_SIZE = 20;
31
+
32
+ export default function ProxyPoolsPage() {
33
+ const [proxyPools, setProxyPools] = useState([]);
34
+ const [loading, setLoading] = useState(true);
35
+ const [showFormModal, setShowFormModal] = useState(false);
36
+ const [showBatchImportModal, setShowBatchImportModal] = useState(false);
37
+ const [showVercelModal, setShowVercelModal] = useState(false);
38
+ const [showCloudflareModal, setShowCloudflareModal] = useState(false);
39
+ const [showDenoModal, setShowDenoModal] = useState(false);
40
+ const [showRelayMenu, setShowRelayMenu] = useState(false);
41
+ const [editingProxyPool, setEditingProxyPool] = useState(null);
42
+ const [formData, setFormData] = useState(normalizeFormData());
43
+ const [batchImportText, setBatchImportText] = useState("");
44
+ const [vercelForm, setVercelForm] = useState({ vercelToken: "", projectName: "vercel-relay" });
45
+ const [cloudflareForm, setCloudflareForm] = useState({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
46
+ const [denoForm, setDenoForm] = useState({ denoToken: "", orgDomain: "", projectName: "" });
47
+ const [saving, setSaving] = useState(false);
48
+ const [importing, setImporting] = useState(false);
49
+ const [deploying, setDeploying] = useState(false);
50
+ const [testingId, setTestingId] = useState(null);
51
+ const [selectedIds, setSelectedIds] = useState([]);
52
+ const [healthChecking, setHealthChecking] = useState(false);
53
+ const [healthProgress, setHealthProgress] = useState({ current: 0, total: 0 });
54
+ const [bulkBusy, setBulkBusy] = useState(false);
55
+ const [confirmState, setConfirmState] = useState(null);
56
+ const [page, setPage] = useState(1);
57
+ const [pageSize, setPageSize] = useState(PROXY_POOL_DEFAULT_PAGE_SIZE);
58
+ const relayMenuRef = useRef(null);
59
+ const notify = useNotificationStore();
60
+
61
+ useEffect(() => {
62
+ const handleClickOutside = (e) => {
63
+ if (relayMenuRef.current && !relayMenuRef.current.contains(e.target)) {
64
+ setShowRelayMenu(false);
65
+ }
66
+ };
67
+ if (showRelayMenu) {
68
+ document.addEventListener("mousedown", handleClickOutside);
69
+ }
70
+ return () => document.removeEventListener("mousedown", handleClickOutside);
71
+ }, [showRelayMenu]);
72
+
73
+ const fetchProxyPools = useCallback(async () => {
74
+ try {
75
+ const res = await fetch("/api/proxy-pools?includeUsage=true", { cache: "no-store" });
76
+ const data = await res.json();
77
+ if (res.ok) {
78
+ setProxyPools(data.proxyPools || []);
79
+ }
80
+ } catch (error) {
81
+ console.log("Error fetching proxy pools:", error);
82
+ } finally {
83
+ setLoading(false);
84
+ }
85
+ }, []);
86
+
87
+ useEffect(() => {
88
+ fetchProxyPools();
89
+ }, [fetchProxyPools]);
90
+
91
+ const resetForm = () => {
92
+ setEditingProxyPool(null);
93
+ setFormData(normalizeFormData());
94
+ };
95
+
96
+ const openCreateModal = () => {
97
+ resetForm();
98
+ setShowFormModal(true);
99
+ };
100
+
101
+ const openEditModal = (proxyPool) => {
102
+ setEditingProxyPool(proxyPool);
103
+ setFormData(normalizeFormData(proxyPool));
104
+ setShowFormModal(true);
105
+ };
106
+
107
+ const closeFormModal = () => {
108
+ setShowFormModal(false);
109
+ resetForm();
110
+ };
111
+
112
+ const handleSave = async () => {
113
+ const payload = {
114
+ name: formData.name.trim(),
115
+ proxyUrl: formData.proxyUrl.trim(),
116
+ noProxy: formData.noProxy.trim(),
117
+ isActive: formData.isActive === true,
118
+ strictProxy: formData.strictProxy === true,
119
+ };
120
+
121
+ if (!payload.name || !payload.proxyUrl) return;
122
+
123
+ setSaving(true);
124
+ try {
125
+ const isEdit = !!editingProxyPool;
126
+ const res = await fetch(isEdit ? `/api/proxy-pools/${editingProxyPool.id}` : "/api/proxy-pools", {
127
+ method: isEdit ? "PUT" : "POST",
128
+ headers: { "Content-Type": "application/json" },
129
+ body: JSON.stringify(payload),
130
+ });
131
+
132
+ if (res.ok) {
133
+ await fetchProxyPools();
134
+ closeFormModal();
135
+ notify.success(editingProxyPool ? "Proxy pool updated" : "Proxy pool created");
136
+ } else {
137
+ const data = await res.json();
138
+ notify.error(data.error || "Failed to save proxy pool");
139
+ }
140
+ } catch (error) {
141
+ console.log("Error saving proxy pool:", error);
142
+ } finally {
143
+ setSaving(false);
144
+ }
145
+ };
146
+
147
+ const handleDelete = async (proxyPool) => {
148
+ setConfirmState({
149
+ title: "Delete Proxy Pool",
150
+ message: `Delete proxy pool "${proxyPool.name}"?`,
151
+ onConfirm: async () => {
152
+ setConfirmState(null);
153
+ try {
154
+ const res = await fetch(`/api/proxy-pools/${proxyPool.id}`, { method: "DELETE" });
155
+ if (res.ok) {
156
+ setProxyPools((prev) => prev.filter((item) => item.id !== proxyPool.id));
157
+ notify.success("Proxy pool deleted");
158
+ return;
159
+ }
160
+
161
+ const data = await res.json();
162
+ if (res.status === 409) {
163
+ notify.warning(`Cannot delete: ${data.boundConnectionCount || 0} connection(s) are still using this pool.`);
164
+ } else {
165
+ notify.error(data.error || "Failed to delete proxy pool");
166
+ }
167
+ } catch (error) {
168
+ console.log("Error deleting proxy pool:", error);
169
+ notify.error("Failed to delete proxy pool");
170
+ }
171
+ }
172
+ });
173
+ };
174
+
175
+ const handleTest = async (proxyPoolId) => {
176
+ setTestingId(proxyPoolId);
177
+ try {
178
+ const res = await fetch(`/api/proxy-pools/${proxyPoolId}/test`, { method: "POST" });
179
+ const data = await res.json();
180
+
181
+ if (!res.ok) {
182
+ notify.error(data.error || "Failed to test proxy");
183
+ return;
184
+ }
185
+
186
+ await fetchProxyPools();
187
+ notify.success(data.ok ? "Proxy test passed" : "Proxy test failed");
188
+ } catch (error) {
189
+ console.log("Error testing proxy pool:", error);
190
+ notify.error("Failed to test proxy");
191
+ } finally {
192
+ setTestingId(null);
193
+ }
194
+ };
195
+
196
+ const handleToggleActive = async (pool) => {
197
+ const next = !pool.isActive;
198
+ setProxyPools((prev) => prev.map((p) => p.id === pool.id ? { ...p, isActive: next } : p));
199
+ try {
200
+ const res = await fetch(`/api/proxy-pools/${pool.id}`, {
201
+ method: "PUT",
202
+ headers: { "Content-Type": "application/json" },
203
+ body: JSON.stringify({ isActive: next }),
204
+ });
205
+ if (!res.ok) {
206
+ setProxyPools((prev) => prev.map((p) => p.id === pool.id ? { ...p, isActive: pool.isActive } : p));
207
+ notify.error("Failed to update active state");
208
+ }
209
+ } catch (error) {
210
+ console.log("Error toggling active:", error);
211
+ setProxyPools((prev) => prev.map((p) => p.id === pool.id ? { ...p, isActive: pool.isActive } : p));
212
+ }
213
+ };
214
+
215
+ const totalPages = Math.max(1, Math.ceil(proxyPools.length / pageSize));
216
+ const activePage = Math.min(page, totalPages);
217
+ const paginatedProxyPools = proxyPools.slice(
218
+ (activePage - 1) * pageSize,
219
+ activePage * pageSize
220
+ );
221
+ const allSelected = paginatedProxyPools.length > 0 && paginatedProxyPools.every((pool) => selectedIds.includes(pool.id));
222
+ const toggleSelect = (id) => setSelectedIds((prev) => prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]);
223
+ const toggleSelectAll = () => setSelectedIds((prev) => {
224
+ if (allSelected) return prev.filter((id) => !paginatedProxyPools.some((pool) => pool.id === id));
225
+ return Array.from(new Set([...prev, ...paginatedProxyPools.map((pool) => pool.id)]));
226
+ });
227
+ const clearSelection = () => setSelectedIds([]);
228
+
229
+ const bulkSetActive = async (isActive) => {
230
+ const targets = selectedIds.length > 0 ? selectedIds : proxyPools.map((p) => p.id);
231
+ if (targets.length === 0) return;
232
+ setBulkBusy(true);
233
+ try {
234
+ let ok = 0; let failed = 0;
235
+ for (const id of targets) {
236
+ try {
237
+ const res = await fetch(`/api/proxy-pools/${id}`, {
238
+ method: "PUT",
239
+ headers: { "Content-Type": "application/json" },
240
+ body: JSON.stringify({ isActive }),
241
+ });
242
+ if (res.ok) ok += 1; else failed += 1;
243
+ } catch { failed += 1; }
244
+ }
245
+ await fetchProxyPools();
246
+ notify.success(`${isActive ? "Activated" : "Deactivated"} ${ok}${failed ? `, failed ${failed}` : ""}`);
247
+ } finally {
248
+ setBulkBusy(false);
249
+ }
250
+ };
251
+
252
+ const bulkDelete = async () => {
253
+ if (selectedIds.length === 0) return;
254
+ setConfirmState({
255
+ title: "Delete Proxy Pools",
256
+ message: `Delete ${selectedIds.length} proxy pool(s)?`,
257
+ onConfirm: async () => {
258
+ setConfirmState(null);
259
+ setBulkBusy(true);
260
+ try {
261
+ let ok = 0; let blocked = 0; let failed = 0;
262
+ for (const id of selectedIds) {
263
+ try {
264
+ const res = await fetch(`/api/proxy-pools/${id}`, { method: "DELETE" });
265
+ if (res.ok) ok += 1;
266
+ else if (res.status === 409) blocked += 1;
267
+ else failed += 1;
268
+ } catch { failed += 1; }
269
+ }
270
+ await fetchProxyPools();
271
+ clearSelection();
272
+ notify.success(`Deleted ${ok}${blocked ? `, ${blocked} bound` : ""}${failed ? `, ${failed} failed` : ""}`);
273
+ } finally {
274
+ setBulkBusy(false);
275
+ }
276
+ }
277
+ });
278
+ };
279
+
280
+ const handleHealthCheck = async () => {
281
+ const targets = selectedIds.length > 0
282
+ ? proxyPools.filter((p) => selectedIds.includes(p.id))
283
+ : proxyPools;
284
+ if (targets.length === 0) return;
285
+ setHealthChecking(true);
286
+ setHealthProgress({ current: 0, total: targets.length });
287
+ let alive = 0; const deadIds = [];
288
+ let done = 0;
289
+ const CONCURRENCY = 10;
290
+ const queue = [...targets];
291
+
292
+ const worker = async () => {
293
+ while (queue.length > 0) {
294
+ const pool = queue.shift();
295
+ if (!pool) break;
296
+ try {
297
+ const res = await fetch(`/api/proxy-pools/${pool.id}/test`, { method: "POST" });
298
+ const data = await res.json();
299
+ if (res.ok && data.ok) alive += 1; else deadIds.push(pool.id);
300
+ } catch {
301
+ deadIds.push(pool.id);
302
+ } finally {
303
+ done += 1;
304
+ setHealthProgress({ current: done, total: targets.length });
305
+ }
306
+ }
307
+ };
308
+
309
+ await Promise.all(Array.from({ length: Math.min(CONCURRENCY, targets.length) }, worker));
310
+ await fetchProxyPools();
311
+ setHealthChecking(false);
312
+ setHealthProgress({ current: 0, total: 0 });
313
+
314
+ if (deadIds.length > 0) {
315
+ setConfirmState({
316
+ title: "Disable Dead Proxies",
317
+ message: `Alive: ${alive}, Dead: ${deadIds.length}.\n\nDisable ${deadIds.length} dead proxies?`,
318
+ onConfirm: async () => {
319
+ setConfirmState(null);
320
+ setBulkBusy(true);
321
+ try {
322
+ for (const id of deadIds) {
323
+ try {
324
+ await fetch(`/api/proxy-pools/${id}`, {
325
+ method: "PUT",
326
+ headers: { "Content-Type": "application/json" },
327
+ body: JSON.stringify({ isActive: false }),
328
+ });
329
+ } catch {}
330
+ }
331
+ await fetchProxyPools();
332
+ notify.success(`Disabled ${deadIds.length} dead proxies`);
333
+ } finally {
334
+ setBulkBusy(false);
335
+ }
336
+ }
337
+ });
338
+ } else {
339
+ notify.success(`Health check done. Alive: ${alive}, Dead: ${deadIds.length}`);
340
+ }
341
+ };
342
+
343
+ // Cleanup selectedIds when pools change
344
+ useEffect(() => {
345
+ setSelectedIds((prev) => prev.filter((id) => proxyPools.some((p) => p.id === id)));
346
+ }, [proxyPools]);
347
+
348
+ useEffect(() => {
349
+ setPage((current) => Math.min(current, Math.max(1, Math.ceil(proxyPools.length / pageSize))));
350
+ }, [proxyPools.length, pageSize]);
351
+
352
+ const openBatchImportModal = () => {
353
+ setBatchImportText("");
354
+ setShowBatchImportModal(true);
355
+ };
356
+
357
+ const closeBatchImportModal = () => {
358
+ if (importing) return;
359
+ setShowBatchImportModal(false);
360
+ };
361
+
362
+ const openVercelModal = () => {
363
+ setVercelForm({ vercelToken: "", projectName: "vercel-relay" });
364
+ setShowVercelModal(true);
365
+ };
366
+
367
+ const closeVercelModal = () => {
368
+ if (deploying) return;
369
+ setShowVercelModal(false);
370
+ };
371
+
372
+ const openCloudflareModal = () => {
373
+ setCloudflareForm({ accountId: "", apiToken: "", projectName: "cloudflare-relay" });
374
+ setShowCloudflareModal(true);
375
+ };
376
+
377
+ const closeCloudflareModal = () => {
378
+ if (deploying) return;
379
+ setShowCloudflareModal(false);
380
+ };
381
+
382
+ const openDenoModal = () => {
383
+ setDenoForm({ denoToken: "", orgDomain: "", projectName: "" });
384
+ setShowDenoModal(true);
385
+ };
386
+
387
+ const closeDenoModal = () => {
388
+ if (deploying) return;
389
+ setShowDenoModal(false);
390
+ };
391
+
392
+ const handleVercelDeploy = async () => {
393
+ if (!vercelForm.vercelToken.trim()) return;
394
+ setDeploying(true);
395
+ try {
396
+ const res = await fetch("/api/proxy-pools/vercel-deploy", {
397
+ method: "POST",
398
+ headers: { "Content-Type": "application/json" },
399
+ body: JSON.stringify(vercelForm),
400
+ });
401
+ const data = await res.json();
402
+ if (res.ok) {
403
+ await fetchProxyPools();
404
+ closeVercelModal();
405
+ notify.success(`Deployed: ${data.deployUrl}`);
406
+ } else {
407
+ notify.error(data.error || "Deploy failed");
408
+ }
409
+ } catch (error) {
410
+ console.log("Error deploying Vercel relay:", error);
411
+ notify.error("Deploy failed");
412
+ } finally {
413
+ setDeploying(false);
414
+ }
415
+ };
416
+
417
+ const handleCloudflareDeploy = async () => {
418
+ if (!cloudflareForm.accountId.trim() || !cloudflareForm.apiToken.trim()) return;
419
+ setDeploying(true);
420
+ try {
421
+ const res = await fetch("/api/proxy-pools/cloudflare-deploy", {
422
+ method: "POST",
423
+ headers: { "Content-Type": "application/json" },
424
+ body: JSON.stringify(cloudflareForm),
425
+ });
426
+ const data = await res.json();
427
+ if (res.ok) {
428
+ await fetchProxyPools();
429
+ closeCloudflareModal();
430
+ notify.success(`Deployed: ${data.deployUrl}`);
431
+ } else {
432
+ notify.error(data.error || "Deploy failed");
433
+ }
434
+ } catch (error) {
435
+ console.log("Error deploying Cloudflare relay:", error);
436
+ notify.error("Deploy failed");
437
+ } finally {
438
+ setDeploying(false);
439
+ }
440
+ };
441
+
442
+ const handleDenoDeploy = async () => {
443
+ if (!denoForm.denoToken.trim()) return;
444
+ setDeploying(true);
445
+ try {
446
+ const res = await fetch("/api/proxy-pools/deno-deploy", {
447
+ method: "POST",
448
+ headers: { "Content-Type": "application/json" },
449
+ body: JSON.stringify(denoForm),
450
+ });
451
+ const data = await res.json();
452
+ if (res.ok) {
453
+ await fetchProxyPools();
454
+ closeDenoModal();
455
+ notify.success(`Deployed: ${data.deployUrl}`);
456
+ } else {
457
+ notify.error(data.error || "Deploy failed");
458
+ }
459
+ } catch (error) {
460
+ console.log("Error deploying Deno relay:", error);
461
+ notify.error("Deploy failed");
462
+ } finally {
463
+ setDeploying(false);
464
+ }
465
+ };
466
+
467
+ const parseProxyLine = (line) => {
468
+ const trimmed = line.trim();
469
+ if (!trimmed) return null;
470
+
471
+ if (trimmed.includes("://")) {
472
+ const parsed = new URL(trimmed);
473
+ const hostLabel = parsed.port ? `${parsed.hostname}:${parsed.port}` : parsed.hostname;
474
+ return {
475
+ proxyUrl: parsed.toString(),
476
+ name: `Imported ${hostLabel}`,
477
+ };
478
+ }
479
+
480
+ const parts = trimmed.split(":");
481
+ if (parts.length === 4) {
482
+ const [host, port, username, password] = parts;
483
+ if (!host || !port || !username || !password) {
484
+ throw new Error("Invalid host:port:user:pass format");
485
+ }
486
+
487
+ const proxyUrl = `http://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}:${port}`;
488
+ const parsed = new URL(proxyUrl);
489
+ return {
490
+ proxyUrl: parsed.toString(),
491
+ name: `Imported ${host}:${port}`,
492
+ };
493
+ }
494
+
495
+ throw new Error("Unsupported format");
496
+ };
497
+
498
+ const handleBatchImport = async () => {
499
+ const lines = batchImportText
500
+ .split(/\r?\n/)
501
+ .map((line) => line.trim())
502
+ .filter(Boolean);
503
+
504
+ if (lines.length === 0) {
505
+ notify.warning("Please paste at least one proxy line.");
506
+ return;
507
+ }
508
+
509
+ const parsedEntries = [];
510
+ const invalidLines = [];
511
+
512
+ lines.forEach((line, index) => {
513
+ try {
514
+ const parsed = parseProxyLine(line);
515
+ if (parsed) {
516
+ parsedEntries.push({
517
+ ...parsed,
518
+ lineNumber: index + 1,
519
+ });
520
+ }
521
+ } catch (error) {
522
+ invalidLines.push(`Line ${index + 1}: ${error.message}`);
523
+ }
524
+ });
525
+
526
+ if (invalidLines.length > 0) {
527
+ notify.error(`Invalid proxy format:\n${invalidLines.join("\n")}`);
528
+ return;
529
+ }
530
+
531
+ setImporting(true);
532
+ try {
533
+ const existingKeys = new Set(
534
+ proxyPools.map((pool) => `${(pool.proxyUrl || "").trim()}|||${(pool.noProxy || "").trim()}`)
535
+ );
536
+
537
+ let created = 0;
538
+ let skipped = 0;
539
+ let failed = 0;
540
+
541
+ for (const entry of parsedEntries) {
542
+ const dedupeKey = `${entry.proxyUrl}|||`;
543
+ if (existingKeys.has(dedupeKey)) {
544
+ skipped += 1;
545
+ continue;
546
+ }
547
+
548
+ const res = await fetch("/api/proxy-pools", {
549
+ method: "POST",
550
+ headers: { "Content-Type": "application/json" },
551
+ body: JSON.stringify({
552
+ name: entry.name,
553
+ proxyUrl: entry.proxyUrl,
554
+ noProxy: "",
555
+ isActive: true,
556
+ }),
557
+ });
558
+
559
+ if (res.ok) {
560
+ created += 1;
561
+ existingKeys.add(dedupeKey);
562
+ } else {
563
+ failed += 1;
564
+ }
565
+ }
566
+
567
+ await fetchProxyPools();
568
+ setShowBatchImportModal(false);
569
+ notify.success(`Batch import completed: Created ${created}, Skipped ${skipped}, Failed ${failed}`);
570
+ } catch (error) {
571
+ console.log("Error batch importing proxies:", error);
572
+ notify.error("Batch import failed");
573
+ } finally {
574
+ setImporting(false);
575
+ }
576
+ };
577
+
578
+ const activeCount = useMemo(
579
+ () => proxyPools.filter((pool) => pool.isActive === true).length,
580
+ [proxyPools]
581
+ );
582
+
583
+ if (loading) {
584
+ return (
585
+ <div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-1 sm:gap-6 sm:px-0">
586
+ <CardSkeleton />
587
+ <CardSkeleton />
588
+ </div>
589
+ );
590
+ }
591
+
592
+ return (
593
+ <div className="mx-auto flex w-full max-w-5xl flex-col gap-4 px-1 sm:gap-6 sm:px-0">
594
+ <div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
595
+ <div className="min-w-0">
596
+ <h1 className="text-xl font-semibold sm:text-2xl">Proxy Pools</h1>
597
+ </div>
598
+
599
+ <div className="grid grid-cols-1 gap-2 sm:flex sm:items-center">
600
+ <div className="relative" ref={relayMenuRef}>
601
+ <Button
602
+ size="sm"
603
+ variant="secondary"
604
+ icon="rocket_launch"
605
+ onClick={() => setShowRelayMenu(!showRelayMenu)}
606
+ >
607
+ Deploy Relay
608
+ <span className="material-symbols-outlined ml-1 text-[18px]">
609
+ {showRelayMenu ? "expand_less" : "expand_more"}
610
+ </span>
611
+ </Button>
612
+
613
+ {showRelayMenu && (
614
+ <div className="absolute left-0 top-full z-50 mt-1 w-48 rounded-xl border border-black/10 bg-white p-1 shadow-xl dark:border-white/10 dark:bg-zinc-900 sm:left-auto sm:right-0">
615
+ <button
616
+ onClick={() => {
617
+ openCloudflareModal();
618
+ setShowRelayMenu(false);
619
+ }}
620
+ className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
621
+ >
622
+ <span className="material-symbols-outlined text-[20px] text-orange-500">cloud</span>
623
+ Cloudflare Relay
624
+ </button>
625
+ <button
626
+ onClick={() => {
627
+ openVercelModal();
628
+ setShowRelayMenu(false);
629
+ }}
630
+ className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
631
+ >
632
+ <span className="material-symbols-outlined text-[20px] text-blue-500">cloud_upload</span>
633
+ Vercel Relay
634
+ </button>
635
+ <button
636
+ onClick={() => {
637
+ openDenoModal();
638
+ setShowRelayMenu(false);
639
+ }}
640
+ className="flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm text-text-main transition-colors hover:bg-black/5 dark:hover:bg-white/5"
641
+ >
642
+ <span className="material-symbols-outlined text-[20px] text-green-500">terminal</span>
643
+ Deno Relay
644
+ </button>
645
+ </div>
646
+ )}
647
+ </div>
648
+
649
+ <Button size="sm" variant="secondary" icon="upload" onClick={openBatchImportModal}>
650
+ Batch Import
651
+ </Button>
652
+ <Button size="sm" icon="add" onClick={openCreateModal}>Add Proxy Pool</Button>
653
+ </div>
654
+ </div>
655
+
656
+ <Card>
657
+ <div className="mb-4 flex flex-wrap items-center gap-2">
658
+ {proxyPools.length > 0 && (
659
+ <label className="flex items-center gap-1.5 text-xs text-text-muted cursor-pointer">
660
+ <input
661
+ type="checkbox"
662
+ checked={allSelected}
663
+ onChange={toggleSelectAll}
664
+ className="size-4 rounded border-black/20 dark:border-white/20"
665
+ />
666
+ {allSelected ? "Unselect visible" : "Select visible"}
667
+ </label>
668
+ )}
669
+ <Badge variant="default">Total: {proxyPools.length}</Badge>
670
+ <Badge variant="success">Active: {activeCount}</Badge>
671
+ </div>
672
+
673
+ {(selectedIds.length > 0 || healthChecking) && (
674
+ <div className="mb-4 flex flex-wrap items-center gap-2 rounded-lg border border-primary/30 bg-primary/5 px-3 py-2">
675
+ <span className="material-symbols-outlined text-[18px] text-primary">checklist</span>
676
+ <span className="text-xs font-medium text-primary">
677
+ {selectedIds.length > 0 ? `${selectedIds.length} selected` : "All pools"}
678
+ </span>
679
+ <div className="ml-auto flex flex-wrap items-center gap-2">
680
+ <Button
681
+ size="sm"
682
+ icon={healthChecking ? "progress_activity" : "health_and_safety"}
683
+ onClick={handleHealthCheck}
684
+ disabled={healthChecking || bulkBusy || proxyPools.length === 0}
685
+ >
686
+ {healthChecking ? `Checking ${healthProgress.current}/${healthProgress.total}` : "Health Check"}
687
+ </Button>
688
+ {selectedIds.length > 0 && (
689
+ <>
690
+ <Button size="sm" variant="secondary" icon="toggle_on" onClick={() => bulkSetActive(true)} disabled={bulkBusy || healthChecking}>
691
+ Activate
692
+ </Button>
693
+ <Button size="sm" variant="secondary" icon="toggle_off" onClick={() => bulkSetActive(false)} disabled={bulkBusy || healthChecking}>
694
+ Deactivate
695
+ </Button>
696
+ <Button size="sm" variant="secondary" icon="delete" onClick={bulkDelete} disabled={bulkBusy || healthChecking}>
697
+ Delete
698
+ </Button>
699
+ <Button size="sm" variant="ghost" onClick={clearSelection} disabled={bulkBusy || healthChecking}>
700
+ Clear
701
+ </Button>
702
+ </>
703
+ )}
704
+ </div>
705
+ </div>
706
+ )}
707
+
708
+ {proxyPools.length === 0 ? (
709
+ <div className="text-center py-10">
710
+ <p className="text-text-main font-medium mb-1">No proxy pool entries yet</p>
711
+ <p className="text-sm text-text-muted mb-4">
712
+ Create a proxy pool entry, then assign it to connections.
713
+ </p>
714
+ <Button icon="add" onClick={openCreateModal}>Add Proxy Pool</Button>
715
+ </div>
716
+ ) : (
717
+ <div className="flex flex-col divide-y divide-black/[0.04] dark:divide-white/[0.05]">
718
+ {paginatedProxyPools.map((pool) => (
719
+ <div key={pool.id} className="flex flex-col gap-3 py-3 sm:flex-row sm:items-center sm:justify-between">
720
+ <div className="flex items-start gap-3 min-w-0 flex-1">
721
+ <input
722
+ type="checkbox"
723
+ checked={selectedIds.includes(pool.id)}
724
+ onChange={() => toggleSelect(pool.id)}
725
+ className="mt-1 size-4 shrink-0 rounded border-black/20 dark:border-white/20"
726
+ />
727
+ <div className="min-w-0 flex-1">
728
+ <div className="flex items-center gap-2 flex-wrap">
729
+ <p className="min-w-0 max-w-full truncate text-sm font-medium sm:max-w-[18rem]">{pool.name}</p>
730
+ <Badge variant={getStatusVariant(pool.testStatus)} size="sm" dot>
731
+ {pool.testStatus || "unknown"}
732
+ </Badge>
733
+ <Badge variant={pool.isActive ? "success" : "default"} size="sm">
734
+ {pool.isActive ? "active" : "inactive"}
735
+ </Badge>
736
+ {pool.type === "vercel" && (
737
+ <Badge variant="default" size="sm">vercel relay</Badge>
738
+ )}
739
+ {pool.type === "cloudflare" && (
740
+ <Badge variant="default" size="sm">cloudflare relay</Badge>
741
+ )}
742
+ <Badge variant="default" size="sm">
743
+ {pool.boundConnectionCount || 0} bound
744
+ </Badge>
745
+ </div>
746
+ <p className="text-xs text-text-muted truncate mt-1">{pool.proxyUrl}</p>
747
+ {pool.noProxy ? (
748
+ <p className="text-xs text-text-muted truncate">No proxy: {pool.noProxy}</p>
749
+ ) : null}
750
+ <p className="text-[11px] text-text-muted mt-1">
751
+ Last tested: {formatDateTime(pool.lastTestedAt)}
752
+ {pool.lastError ? ` · ${pool.lastError}` : ""}
753
+ </p>
754
+ </div>
755
+ </div>
756
+
757
+ <div className="flex items-center justify-end gap-1">
758
+ <Toggle
759
+ size="sm"
760
+ checked={pool.isActive === true}
761
+ onChange={() => handleToggleActive(pool)}
762
+ title={pool.isActive ? "Disable" : "Enable"}
763
+ />
764
+ <button
765
+ onClick={() => handleTest(pool.id)}
766
+ className="p-2 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-primary"
767
+ title="Test proxy"
768
+ disabled={testingId === pool.id}
769
+ >
770
+ <span
771
+ className="material-symbols-outlined text-[18px]"
772
+ style={testingId === pool.id ? { animation: "spin 1s linear infinite" } : undefined}
773
+ >
774
+ {testingId === pool.id ? "progress_activity" : "science"}
775
+ </span>
776
+ </button>
777
+ <button
778
+ onClick={() => openEditModal(pool)}
779
+ className="p-2 rounded hover:bg-black/5 dark:hover:bg-white/5 text-text-muted hover:text-primary"
780
+ title="Edit"
781
+ >
782
+ <span className="material-symbols-outlined text-[18px]">edit</span>
783
+ </button>
784
+ <button
785
+ onClick={() => handleDelete(pool)}
786
+ className="p-2 rounded hover:bg-red-500/10 text-red-500"
787
+ title="Delete"
788
+ >
789
+ <span className="material-symbols-outlined text-[18px]">delete</span>
790
+ </button>
791
+ </div>
792
+ </div>
793
+ ))}
794
+ {proxyPools.length > 0 && (
795
+ <Pagination
796
+ currentPage={activePage}
797
+ pageSize={pageSize}
798
+ totalItems={proxyPools.length}
799
+ onPageChange={setPage}
800
+ onPageSizeChange={(size) => {
801
+ setPageSize(size);
802
+ setPage(1);
803
+ }}
804
+ />
805
+ )}
806
+ </div>
807
+ )}
808
+ </Card>
809
+
810
+ <Modal
811
+ isOpen={showBatchImportModal}
812
+ title="Batch Import Proxies"
813
+ onClose={closeBatchImportModal}
814
+ >
815
+ <div className="flex flex-col gap-4">
816
+ <div>
817
+ <label className="text-sm font-medium text-text-main mb-1 block">Paste Proxy List (One per line)</label>
818
+ <textarea
819
+ value={batchImportText}
820
+ onChange={(e) => setBatchImportText(e.target.value)}
821
+ placeholder={"http://user:pass@127.0.0.1:7897\n127.0.0.1:7897:user:pass"}
822
+ className="w-full min-h-[180px] py-2 px-3 text-sm text-text-main bg-white dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-md focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none transition-all"
823
+ />
824
+ <p className="text-xs text-text-muted mt-1">
825
+ Supported formats: protocol://user:pass@host:port, host:port:user:pass
826
+ </p>
827
+ </div>
828
+
829
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
830
+ <Button fullWidth onClick={handleBatchImport} disabled={!batchImportText.trim() || importing}>
831
+ {importing ? "Importing..." : "Import"}
832
+ </Button>
833
+ <Button fullWidth variant="ghost" onClick={closeBatchImportModal} disabled={importing}>
834
+ Cancel
835
+ </Button>
836
+ </div>
837
+ </div>
838
+ </Modal>
839
+
840
+ <Modal
841
+ isOpen={showVercelModal}
842
+ title="Deploy Vercel Relay"
843
+ onClose={closeVercelModal}
844
+ >
845
+ <div className="flex flex-col gap-4">
846
+ <div className="rounded-lg bg-blue-500/5 border border-blue-500/10 p-3 flex flex-col gap-1.5">
847
+ <p className="text-sm text-text-main font-medium">What is Vercel Relay?</p>
848
+ <p className="text-xs text-text-muted">
849
+ Deploys an edge relay function to Vercel. All AI provider requests will be forwarded through Vercel&apos;s edge network, masking your real IP from providers.
850
+ </p>
851
+ <ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
852
+ <li>Your IP is replaced by Vercel&apos;s dynamic edge IPs (hundreds of IPs across 20+ global regions)</li>
853
+ <li>Vercel serves millions of apps — providers can&apos;t block Vercel IPs without affecting legitimate traffic</li>
854
+ <li>Free tier: 100GB bandwidth/month, 500K edge invocations</li>
855
+ <li>Deploy multiple relays on different accounts for more IP diversity</li>
856
+ </ul>
857
+ </div>
858
+ <Input
859
+ label="Vercel API Token"
860
+ value={vercelForm.vercelToken}
861
+ onChange={(e) => setVercelForm((prev) => ({ ...prev, vercelToken: e.target.value }))}
862
+ placeholder="your-vercel-api-token"
863
+ hint={<>Token is used once for deployment and not stored. <a href="https://vercel.com/account/tokens" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Get token →</a></>}
864
+ type="password"
865
+ />
866
+ <Input
867
+ label="Project Name"
868
+ value={vercelForm.projectName}
869
+ onChange={(e) => setVercelForm((prev) => ({ ...prev, projectName: e.target.value }))}
870
+ placeholder="my-relay"
871
+ hint="Unique name for your Vercel project. Leave empty for auto-generated name."
872
+ />
873
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
874
+ <Button
875
+ fullWidth
876
+ onClick={handleVercelDeploy}
877
+ disabled={!vercelForm.vercelToken.trim() || deploying}
878
+ >
879
+ {deploying ? "Deploying... (may take ~1 min)" : "Deploy"}
880
+ </Button>
881
+ <Button fullWidth variant="ghost" onClick={closeVercelModal} disabled={deploying}>
882
+ Cancel
883
+ </Button>
884
+ </div>
885
+ </div>
886
+ </Modal>
887
+
888
+ <Modal
889
+ isOpen={showCloudflareModal}
890
+ title="Deploy Cloudflare Relay"
891
+ onClose={closeCloudflareModal}
892
+ >
893
+ <div className="flex flex-col gap-4">
894
+ <div className="rounded-lg bg-orange-500/5 border border-orange-500/10 p-3 flex flex-col gap-1.5">
895
+ <p className="text-sm text-text-main font-medium">What is Cloudflare Relay?</p>
896
+ <p className="text-xs text-text-muted">
897
+ Deploys a Cloudflare Worker as a proxy relay. All AI provider requests will be forwarded through Cloudflare&apos;s global edge network.
898
+ </p>
899
+ <ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
900
+ <li>High performance global routing and IP masking via Cloudflare Workers</li>
901
+ <li>Free tier: 100,000 requests per day</li>
902
+ <li>Requires Cloudflare Account ID and a Workers API Token (Edit Workers permission)</li>
903
+ </ul>
904
+ <div className="mt-2 pt-2 border-t border-orange-500/10 text-xs text-text-muted">
905
+ <p className="font-medium text-text-main mb-1">How to generate your API Token:</p>
906
+ <ol className="list-decimal pl-4 space-y-0.5">
907
+ <li>Go to <b>My Profile</b> → <b>API Tokens</b> → <b>Create Token</b></li>
908
+ <li>Scroll down to <b>Custom Token</b> and click <b>Get started</b></li>
909
+ <li>Under <b>Permissions</b>: Account | Workers Scripts | Edit</li>
910
+ <li>Under <b>Account Resources</b>: Include | Account | <i>Your Account Name</i></li>
911
+ <li>Click <b>Continue to summary</b> → <b>Create Token</b></li>
912
+ </ol>
913
+ </div>
914
+ </div>
915
+ <Input
916
+ label="Account ID"
917
+ value={cloudflareForm.accountId}
918
+ onChange={(e) => setCloudflareForm((prev) => ({ ...prev, accountId: e.target.value }))}
919
+ placeholder="your-cloudflare-account-id"
920
+ hint={<>Found on the right side of the Cloudflare dashboard overview page.</>}
921
+ />
922
+ <Input
923
+ label="API Token"
924
+ value={cloudflareForm.apiToken}
925
+ onChange={(e) => setCloudflareForm((prev) => ({ ...prev, apiToken: e.target.value }))}
926
+ placeholder="your-cloudflare-api-token"
927
+ hint={<>Requires "Workers Scripts: Edit" permission. <a href="https://dash.cloudflare.com/profile/api-tokens" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Get token →</a></>}
928
+ type="password"
929
+ />
930
+ <Input
931
+ label="Worker Name"
932
+ value={cloudflareForm.projectName}
933
+ onChange={(e) => setCloudflareForm((prev) => ({ ...prev, projectName: e.target.value }))}
934
+ placeholder="my-relay"
935
+ hint="Unique name for your Cloudflare Worker. Leave empty for auto-generated name."
936
+ />
937
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
938
+ <Button
939
+ fullWidth
940
+ onClick={handleCloudflareDeploy}
941
+ disabled={!cloudflareForm.accountId.trim() || !cloudflareForm.apiToken.trim() || deploying}
942
+ >
943
+ {deploying ? "Deploying..." : "Deploy Worker"}
944
+ </Button>
945
+ <Button fullWidth variant="ghost" onClick={closeCloudflareModal} disabled={deploying}>
946
+ Cancel
947
+ </Button>
948
+ </div>
949
+ </div>
950
+ </Modal>
951
+
952
+ <Modal
953
+ isOpen={showDenoModal}
954
+ title="Deploy Deno Relay"
955
+ onClose={closeDenoModal}
956
+ >
957
+ <div className="flex flex-col gap-4">
958
+ <div className="rounded-lg bg-black/5 dark:bg-white/5 border border-black/10 dark:border-white/10 p-3 flex flex-col gap-1.5">
959
+ <p className="text-sm text-text-main font-medium">What is Deno Relay?</p>
960
+ <p className="text-xs text-text-muted">
961
+ Deploys a relay worker to Deno Deploy&apos;s global edge network. All AI provider requests are forwarded through Deno&apos;s edge, masking your real IP.
962
+ </p>
963
+ <ul className="text-xs text-text-muted list-disc pl-4 space-y-0.5">
964
+ <li>Deno Deploy v2 runs on a high-performance global edge network</li>
965
+ <li>Free tier: 1M requests & 100GiB outbound traffic per month</li>
966
+ <li>No per-request CPU time limits (unlike Vercel/Cloudflare)</li>
967
+ <li>Support up to 20 active apps & 50 custom domains</li>
968
+ <li>Deploy multiple relays for maximum IP diversity</li>
969
+ </ul>
970
+ <div className="mt-2 pt-2 border-t border-black/10 dark:border-white/10 text-xs text-text-muted">
971
+ <p className="font-medium text-text-main mb-1">How to generate API token:</p>
972
+ <ol className="list-decimal pl-4 space-y-0.5">
973
+ <li>Go to <b>console.deno.com</b></li>
974
+ <li>Select your <b>Organization</b> → <b>Settings</b> → <b>Organization Tokens</b></li>
975
+ <li>Create a <b>Organization Token</b> (prefix <b>ddo_</b>)</li>
976
+ </ol>
977
+ </div>
978
+ </div>
979
+ <Input
980
+ label="Deno Deploy API Token"
981
+ value={denoForm.denoToken}
982
+ onChange={(e) => setDenoForm((prev) => ({ ...prev, denoToken: e.target.value }))}
983
+ placeholder="ddo_xxxxxxxxxxxxxxxx"
984
+ hint={<>Token is used once for deployment, not stored. Found in Organization Settings.</>}
985
+ type="password"
986
+ />
987
+ <Input
988
+ label="Organization Domain"
989
+ value={denoForm.orgDomain}
990
+ onChange={(e) => setDenoForm((prev) => ({ ...prev, orgDomain: e.target.value }))}
991
+ placeholder="your-org.deno.net"
992
+ hint="Organization's default domain. Your relay URL will be in the format: https://my-relay.your-org.deno.net"
993
+ />
994
+ <Input
995
+ label="App Name"
996
+ value={denoForm.projectName}
997
+ onChange={(e) => setDenoForm((prev) => ({ ...prev, projectName: e.target.value }))}
998
+ placeholder="deno-relay"
999
+ hint="Unique app name. Leave empty for auto-generated name."
1000
+ />
1001
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
1002
+ <Button
1003
+ fullWidth
1004
+ onClick={handleDenoDeploy}
1005
+ disabled={!denoForm.denoToken.trim() || !denoForm.orgDomain.trim() || deploying}
1006
+ >
1007
+ {deploying ? "Deploying..." : "Deploy Relay"}
1008
+ </Button>
1009
+ <Button fullWidth variant="ghost" onClick={closeDenoModal} disabled={deploying}>
1010
+ Cancel
1011
+ </Button>
1012
+ </div>
1013
+ </div>
1014
+ </Modal>
1015
+
1016
+ <Modal
1017
+ isOpen={showFormModal}
1018
+ title={editingProxyPool ? "Edit Proxy Pool" : "Add Proxy Pool"}
1019
+ onClose={closeFormModal}
1020
+ >
1021
+ <div className="flex flex-col gap-4">
1022
+ <Input
1023
+ label="Name"
1024
+ value={formData.name}
1025
+ onChange={(e) => setFormData((prev) => ({ ...prev, name: e.target.value }))}
1026
+ placeholder="Office Proxy"
1027
+ />
1028
+ <Input
1029
+ label="Proxy URL"
1030
+ value={formData.proxyUrl}
1031
+ onChange={(e) => setFormData((prev) => ({ ...prev, proxyUrl: e.target.value }))}
1032
+ placeholder="http://127.0.0.1:7897"
1033
+ />
1034
+ <Input
1035
+ label="No Proxy"
1036
+ value={formData.noProxy}
1037
+ onChange={(e) => setFormData((prev) => ({ ...prev, noProxy: e.target.value }))}
1038
+ placeholder="localhost,127.0.0.1,.internal"
1039
+ hint="Comma-separated hosts/domains to bypass proxy"
1040
+ />
1041
+
1042
+ <div className="flex flex-col gap-3 rounded-lg border border-border/50 p-3 sm:flex-row sm:items-center sm:justify-between">
1043
+ <div>
1044
+ <p className="font-medium text-sm">Active</p>
1045
+ <p className="text-xs text-text-muted">Inactive pools are ignored by runtime resolution.</p>
1046
+ </div>
1047
+ <Toggle
1048
+ checked={formData.isActive === true}
1049
+ onChange={() => setFormData((prev) => ({ ...prev, isActive: !prev.isActive }))}
1050
+ disabled={saving}
1051
+ />
1052
+ </div>
1053
+
1054
+ <div className="flex flex-col gap-3 rounded-lg border border-border/50 p-3 sm:flex-row sm:items-center sm:justify-between">
1055
+ <div>
1056
+ <p className="font-medium text-sm">Strict Proxy</p>
1057
+ <p className="text-xs text-text-muted">Fail request if proxy is unreachable instead of falling back to direct.</p>
1058
+ </div>
1059
+ <Toggle
1060
+ checked={formData.strictProxy === true}
1061
+ onChange={() => setFormData((prev) => ({ ...prev, strictProxy: !prev.strictProxy }))}
1062
+ disabled={saving}
1063
+ />
1064
+ </div>
1065
+
1066
+ <div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
1067
+ <Button
1068
+ fullWidth
1069
+ onClick={handleSave}
1070
+ disabled={!formData.name.trim() || !formData.proxyUrl.trim() || saving}
1071
+ >
1072
+ {saving ? "Saving..." : "Save"}
1073
+ </Button>
1074
+ <Button fullWidth variant="ghost" onClick={closeFormModal} disabled={saving}>
1075
+ Cancel
1076
+ </Button>
1077
+ </div>
1078
+ </div>
1079
+ </Modal>
1080
+
1081
+ {/* Confirm Modal */}
1082
+ <ConfirmModal
1083
+ isOpen={!!confirmState}
1084
+ onClose={() => setConfirmState(null)}
1085
+ onConfirm={confirmState?.onConfirm}
1086
+ title={confirmState?.title || "Confirm"}
1087
+ message={confirmState?.message}
1088
+ variant="danger"
1089
+ />
1090
+ </div>
1091
+ );
1092
+ }