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,1140 @@
1
+ "use client";
2
+
3
+ import { useState, useEffect, useRef } from "react";
4
+ import { useRouter } from "next/navigation";
5
+ import { Card, Button, Toggle, Input } from "@/shared/components";
6
+ import { ConfirmModal } from "@/shared/components/Modal";
7
+ import LanguageSwitcher from "@/shared/components/LanguageSwitcher";
8
+ import { useTheme } from "@/shared/hooks/useTheme";
9
+ import { cn } from "@/shared/utils/cn";
10
+ import { APP_CONFIG } from "@/shared/constants/config";
11
+ import { LOCALE_COOKIE, normalizeLocale } from "@/i18n/config";
12
+ import { LOCALE_FLAGS } from "@/shared/constants/locales";
13
+
14
+ function getLocaleFromCookie() {
15
+ if (typeof document === "undefined") return "en";
16
+ const cookie = document.cookie
17
+ .split(";")
18
+ .find((c) => c.trim().startsWith(`${LOCALE_COOKIE}=`));
19
+ const value = cookie ? decodeURIComponent(cookie.split("=")[1]) : "en";
20
+ return normalizeLocale(value);
21
+ }
22
+
23
+ export default function ProfilePage() {
24
+ const router = useRouter();
25
+ const { theme, setTheme, isDark } = useTheme();
26
+ const [locale, setLocale] = useState("en");
27
+ const [langOpen, setLangOpen] = useState(false);
28
+ const [shutdownOpen, setShutdownOpen] = useState(false);
29
+ const [isShuttingDown, setIsShuttingDown] = useState(false);
30
+ const [settings, setSettings] = useState({ fallbackStrategy: "fill-first" });
31
+ const [loading, setLoading] = useState(true);
32
+ const [passwords, setPasswords] = useState({ current: "", new: "", confirm: "" });
33
+ const [passStatus, setPassStatus] = useState({ type: "", message: "" });
34
+ const [passLoading, setPassLoading] = useState(false);
35
+ const [dbLoading, setDbLoading] = useState(false);
36
+ const [dbStatus, setDbStatus] = useState({ type: "", message: "" });
37
+ const [oidcForm, setOidcForm] = useState({
38
+ authMode: "password",
39
+ oidcIssuerUrl: "",
40
+ oidcClientId: "",
41
+ oidcScopes: "openid profile email",
42
+ oidcLoginLabel: "Sign in with OIDC",
43
+ });
44
+ const [oidcClientSecret, setOidcClientSecret] = useState("");
45
+ const [oidcStatus, setOidcStatus] = useState({ type: "", message: "" });
46
+ const [oidcLoading, setOidcLoading] = useState(false);
47
+ const [oidcTestLoading, setOidcTestLoading] = useState(false);
48
+ const [oidcTestStatus, setOidcTestStatus] = useState({ type: "", message: "" });
49
+ const [oidcRedirectUri, setOidcRedirectUri] = useState("/api/auth/oidc/callback");
50
+ const [oidcExpanded, setOidcExpanded] = useState(false);
51
+ const importFileRef = useRef(null);
52
+ const [proxyForm, setProxyForm] = useState({
53
+ outboundProxyEnabled: false,
54
+ outboundProxyUrl: "",
55
+ outboundNoProxy: "",
56
+ });
57
+ const [proxyStatus, setProxyStatus] = useState({ type: "", message: "" });
58
+ const [proxyLoading, setProxyLoading] = useState(false);
59
+ const [proxyTestLoading, setProxyTestLoading] = useState(false);
60
+
61
+ useEffect(() => {
62
+ setLocale(getLocaleFromCookie());
63
+ }, [langOpen]);
64
+
65
+ useEffect(() => {
66
+ fetch("/api/settings")
67
+ .then((res) => res.json())
68
+ .then((data) => {
69
+ setSettings(data);
70
+ setOidcForm({
71
+ authMode: data?.authMode || "password",
72
+ oidcIssuerUrl: data?.oidcIssuerUrl || "",
73
+ oidcClientId: data?.oidcClientId || "",
74
+ oidcScopes: data?.oidcScopes || "openid profile email",
75
+ oidcLoginLabel: data?.oidcLoginLabel || "Sign in with OIDC",
76
+ });
77
+ setOidcClientSecret("");
78
+ if (data?.authMode === "oidc" || data?.authMode === "both") setOidcExpanded(true);
79
+ setProxyForm({
80
+ outboundProxyEnabled: data?.outboundProxyEnabled === true,
81
+ outboundProxyUrl: data?.outboundProxyUrl || "",
82
+ outboundNoProxy: data?.outboundNoProxy || "",
83
+ });
84
+ setLoading(false);
85
+ })
86
+ .catch((err) => {
87
+ console.error("Failed to fetch settings:", err);
88
+ setLoading(false);
89
+ });
90
+ }, []);
91
+
92
+ useEffect(() => {
93
+ if (typeof window !== "undefined") {
94
+ setOidcRedirectUri(`${window.location.origin}/api/auth/oidc/callback`);
95
+ }
96
+ }, []);
97
+
98
+ const updateOutboundProxy = async (e) => {
99
+ e.preventDefault();
100
+ if (settings.outboundProxyEnabled !== true) return;
101
+ setProxyLoading(true);
102
+ setProxyStatus({ type: "", message: "" });
103
+
104
+ try {
105
+ const res = await fetch("/api/settings", {
106
+ method: "PATCH",
107
+ headers: { "Content-Type": "application/json" },
108
+ body: JSON.stringify({
109
+ outboundProxyUrl: proxyForm.outboundProxyUrl,
110
+ outboundNoProxy: proxyForm.outboundNoProxy,
111
+ }),
112
+ });
113
+
114
+ const data = await res.json();
115
+ if (res.ok) {
116
+ setSettings((prev) => ({ ...prev, ...data }));
117
+ setProxyStatus({ type: "success", message: "Proxy settings applied" });
118
+ } else {
119
+ setProxyStatus({ type: "error", message: data.error || "Failed to update proxy settings" });
120
+ }
121
+ } catch (err) {
122
+ setProxyStatus({ type: "error", message: "An error occurred" });
123
+ } finally {
124
+ setProxyLoading(false);
125
+ }
126
+ };
127
+
128
+ const testOutboundProxy = async () => {
129
+ if (settings.outboundProxyEnabled !== true) return;
130
+
131
+ const proxyUrl = (proxyForm.outboundProxyUrl || "").trim();
132
+ if (!proxyUrl) {
133
+ setProxyStatus({ type: "error", message: "Please enter a Proxy URL to test" });
134
+ return;
135
+ }
136
+
137
+ setProxyTestLoading(true);
138
+ setProxyStatus({ type: "", message: "" });
139
+
140
+ try {
141
+ const res = await fetch("/api/settings/proxy-test", {
142
+ method: "POST",
143
+ headers: { "Content-Type": "application/json" },
144
+ body: JSON.stringify({ proxyUrl }),
145
+ });
146
+
147
+ const data = await res.json();
148
+ if (res.ok && data?.ok) {
149
+ setProxyStatus({
150
+ type: "success",
151
+ message: `Proxy test OK (${data.status}) in ${data.elapsedMs}ms`,
152
+ });
153
+ } else {
154
+ setProxyStatus({
155
+ type: "error",
156
+ message: data?.error || "Proxy test failed",
157
+ });
158
+ }
159
+ } catch (err) {
160
+ setProxyStatus({ type: "error", message: "An error occurred" });
161
+ } finally {
162
+ setProxyTestLoading(false);
163
+ }
164
+ };
165
+
166
+ const updateOutboundProxyEnabled = async (outboundProxyEnabled) => {
167
+ setProxyLoading(true);
168
+ setProxyStatus({ type: "", message: "" });
169
+
170
+ try {
171
+ const res = await fetch("/api/settings", {
172
+ method: "PATCH",
173
+ headers: { "Content-Type": "application/json" },
174
+ body: JSON.stringify({ outboundProxyEnabled }),
175
+ });
176
+
177
+ const data = await res.json();
178
+ if (res.ok) {
179
+ setSettings((prev) => ({ ...prev, ...data }));
180
+ setProxyForm((prev) => ({ ...prev, outboundProxyEnabled: data?.outboundProxyEnabled === true }));
181
+ setProxyStatus({
182
+ type: "success",
183
+ message: outboundProxyEnabled ? "Proxy enabled" : "Proxy disabled",
184
+ });
185
+ } else {
186
+ setProxyStatus({ type: "error", message: data.error || "Failed to update proxy settings" });
187
+ }
188
+ } catch (err) {
189
+ setProxyStatus({ type: "error", message: "An error occurred" });
190
+ } finally {
191
+ setProxyLoading(false);
192
+ }
193
+ };
194
+
195
+ const handlePasswordChange = async (e) => {
196
+ e.preventDefault();
197
+ if (passwords.new !== passwords.confirm) {
198
+ setPassStatus({ type: "error", message: "Passwords do not match" });
199
+ return;
200
+ }
201
+
202
+ setPassLoading(true);
203
+ setPassStatus({ type: "", message: "" });
204
+
205
+ try {
206
+ const res = await fetch("/api/settings", {
207
+ method: "PATCH",
208
+ headers: { "Content-Type": "application/json" },
209
+ body: JSON.stringify({
210
+ currentPassword: passwords.current,
211
+ newPassword: passwords.new,
212
+ }),
213
+ });
214
+
215
+ const data = await res.json();
216
+
217
+ if (res.ok) {
218
+ setPassStatus({ type: "success", message: "Password updated successfully" });
219
+ setPasswords({ current: "", new: "", confirm: "" });
220
+ } else {
221
+ setPassStatus({ type: "error", message: data.error || "Failed to update password" });
222
+ }
223
+ } catch (err) {
224
+ setPassStatus({ type: "error", message: "An error occurred" });
225
+ } finally {
226
+ setPassLoading(false);
227
+ }
228
+ };
229
+
230
+ const updateFallbackStrategy = async (strategy) => {
231
+ try {
232
+ const res = await fetch("/api/settings", {
233
+ method: "PATCH",
234
+ headers: { "Content-Type": "application/json" },
235
+ body: JSON.stringify({ fallbackStrategy: strategy }),
236
+ });
237
+ if (res.ok) {
238
+ setSettings(prev => ({ ...prev, fallbackStrategy: strategy }));
239
+ }
240
+ } catch (err) {
241
+ console.error("Failed to update settings:", err);
242
+ }
243
+ };
244
+
245
+ const updateComboStrategy = async (strategy) => {
246
+ try {
247
+ const res = await fetch("/api/settings", {
248
+ method: "PATCH",
249
+ headers: { "Content-Type": "application/json" },
250
+ body: JSON.stringify({ comboStrategy: strategy }),
251
+ });
252
+ if (res.ok) {
253
+ setSettings(prev => ({ ...prev, comboStrategy: strategy }));
254
+ }
255
+ } catch (err) {
256
+ console.error("Failed to update combo strategy:", err);
257
+ }
258
+ };
259
+
260
+ const updateStickyLimit = async (limit) => {
261
+ const numLimit = parseInt(limit);
262
+ if (isNaN(numLimit) || numLimit < 1) return;
263
+
264
+ try {
265
+ const res = await fetch("/api/settings", {
266
+ method: "PATCH",
267
+ headers: { "Content-Type": "application/json" },
268
+ body: JSON.stringify({ stickyRoundRobinLimit: numLimit }),
269
+ });
270
+ if (res.ok) {
271
+ setSettings(prev => ({ ...prev, stickyRoundRobinLimit: numLimit }));
272
+ }
273
+ } catch (err) {
274
+ console.error("Failed to update sticky limit:", err);
275
+ }
276
+ };
277
+
278
+ const updateComboStickyLimit = async (limit) => {
279
+ const numLimit = parseInt(limit);
280
+ if (isNaN(numLimit) || numLimit < 1) return;
281
+
282
+ try {
283
+ const res = await fetch("/api/settings", {
284
+ method: "PATCH",
285
+ headers: { "Content-Type": "application/json" },
286
+ body: JSON.stringify({ comboStickyRoundRobinLimit: numLimit }),
287
+ });
288
+ if (res.ok) {
289
+ setSettings(prev => ({ ...prev, comboStickyRoundRobinLimit: numLimit }));
290
+ }
291
+ } catch (err) {
292
+ console.error("Failed to update combo sticky limit:", err);
293
+ }
294
+ };
295
+
296
+ const updateRequireLogin = async (requireLogin) => {
297
+ try {
298
+ const res = await fetch("/api/settings", {
299
+ method: "PATCH",
300
+ headers: { "Content-Type": "application/json" },
301
+ body: JSON.stringify({ requireLogin }),
302
+ });
303
+ if (res.ok) {
304
+ setSettings(prev => ({ ...prev, requireLogin }));
305
+ }
306
+ } catch (err) {
307
+ console.error("Failed to update require login:", err);
308
+ }
309
+ };
310
+
311
+ const updateOidcForm = (field, value) => {
312
+ setOidcForm((prev) => ({ ...prev, [field]: value }));
313
+ };
314
+
315
+ const saveOidcSettings = async (authMode = oidcForm.authMode || "password") => {
316
+ const issuerUrl = oidcForm.oidcIssuerUrl.trim();
317
+ const clientId = oidcForm.oidcClientId.trim();
318
+ const scopes = oidcForm.oidcScopes.trim();
319
+ const loginLabel = oidcForm.oidcLoginLabel.trim();
320
+ const secret = oidcClientSecret.trim();
321
+
322
+ if (authMode !== "password" && (!issuerUrl || !clientId || !secret) && !settings.oidcConfigured) {
323
+ setOidcStatus({ type: "error", message: "Issuer URL, client ID, and client secret are required to enable OIDC." });
324
+ return;
325
+ }
326
+
327
+ setOidcLoading(true);
328
+ setOidcStatus({ type: "", message: "" });
329
+ setOidcTestStatus({ type: "", message: "" });
330
+
331
+ try {
332
+ const payload = {
333
+ authMode,
334
+ oidcIssuerUrl: issuerUrl,
335
+ oidcClientId: clientId,
336
+ oidcScopes: scopes || "openid profile email",
337
+ oidcLoginLabel: loginLabel || "Sign in with OIDC",
338
+ };
339
+ if (secret) {
340
+ payload.oidcClientSecret = secret;
341
+ }
342
+
343
+ const res = await fetch("/api/settings", {
344
+ method: "PATCH",
345
+ headers: { "Content-Type": "application/json" },
346
+ body: JSON.stringify(payload),
347
+ });
348
+
349
+ const data = await res.json();
350
+ if (res.ok) {
351
+ setSettings((prev) => ({ ...prev, ...data }));
352
+ setOidcForm({
353
+ authMode: data?.authMode || authMode,
354
+ oidcIssuerUrl: data?.oidcIssuerUrl || issuerUrl,
355
+ oidcClientId: data?.oidcClientId || clientId,
356
+ oidcScopes: data?.oidcScopes || scopes || "openid profile email",
357
+ oidcLoginLabel: data?.oidcLoginLabel || loginLabel || "Sign in with OIDC",
358
+ });
359
+ setOidcClientSecret("");
360
+ setOidcStatus({
361
+ type: "success",
362
+ message:
363
+ authMode === "oidc"
364
+ ? "OIDC login enabled"
365
+ : authMode === "both"
366
+ ? "Password and OIDC login enabled"
367
+ : "OIDC settings saved",
368
+ });
369
+ } else {
370
+ setOidcStatus({ type: "error", message: data.error || "Failed to save OIDC settings" });
371
+ }
372
+ } catch (err) {
373
+ setOidcStatus({ type: "error", message: "An error occurred" });
374
+ } finally {
375
+ setOidcLoading(false);
376
+ }
377
+ };
378
+
379
+ const testOidcConnection = async () => {
380
+ const issuerUrl = oidcForm.oidcIssuerUrl.trim();
381
+ const clientId = oidcForm.oidcClientId.trim();
382
+ const scopes = oidcForm.oidcScopes.trim();
383
+ const secret = oidcClientSecret.trim();
384
+
385
+ if (!issuerUrl || !clientId) {
386
+ setOidcTestStatus({ type: "error", message: "Issuer URL and client ID are required to test the connection." });
387
+ return;
388
+ }
389
+
390
+ setOidcTestLoading(true);
391
+ setOidcStatus({ type: "", message: "" });
392
+ setOidcTestStatus({ type: "", message: "" });
393
+
394
+ try {
395
+ const saveRes = await fetch("/api/settings", {
396
+ method: "PATCH",
397
+ headers: { "Content-Type": "application/json" },
398
+ body: JSON.stringify({
399
+ authMode: oidcForm.authMode || settings.authMode || "password",
400
+ oidcIssuerUrl: issuerUrl,
401
+ oidcClientId: clientId,
402
+ oidcScopes: scopes || "openid profile email",
403
+ oidcLoginLabel: oidcForm.oidcLoginLabel.trim() || "Sign in with OIDC",
404
+ ...(secret ? { oidcClientSecret: secret } : {}),
405
+ }),
406
+ });
407
+
408
+ const saved = await saveRes.json().catch(() => ({}));
409
+ if (!saveRes.ok) {
410
+ setOidcTestStatus({
411
+ type: "error",
412
+ message: saved.error || "Failed to save OIDC settings before testing",
413
+ });
414
+ return;
415
+ }
416
+
417
+ const res = await fetch("/api/auth/oidc/test", {
418
+ method: "POST",
419
+ headers: { "Content-Type": "application/json" },
420
+ body: JSON.stringify({
421
+ issuerUrl: saved.oidcIssuerUrl || issuerUrl,
422
+ clientId: saved.oidcClientId || clientId,
423
+ scopes: saved.oidcScopes || scopes || "openid profile email",
424
+ }),
425
+ });
426
+
427
+ const data = await res.json().catch(() => ({}));
428
+ if (res.ok && data?.ok) {
429
+ const statusMessage = data.clientSecretTested
430
+ ? data.clientSecretValid === true
431
+ ? `Connection OK. Discovery loaded from ${data.issuerUrl}. Client secret validated too.`
432
+ : `Connection OK. Discovery loaded from ${data.issuerUrl}. Client secret was not checked.`
433
+ : `Connection OK. Discovery loaded from ${data.issuerUrl}.`;
434
+ setOidcTestStatus({
435
+ type: "success",
436
+ message: statusMessage,
437
+ });
438
+ } else {
439
+ setOidcTestStatus({ type: "error", message: data.error || "OIDC connection test failed" });
440
+ }
441
+ } catch (err) {
442
+ setOidcTestStatus({ type: "error", message: "An error occurred" });
443
+ } finally {
444
+ setOidcTestLoading(false);
445
+ }
446
+ };
447
+
448
+ const updateObservabilityEnabled = async (enabled) => {
449
+ try {
450
+ const res = await fetch("/api/settings", {
451
+ method: "PATCH",
452
+ headers: { "Content-Type": "application/json" },
453
+ body: JSON.stringify({ enableObservability: enabled }),
454
+ });
455
+ if (res.ok) {
456
+ setSettings(prev => ({ ...prev, enableObservability: enabled }));
457
+ }
458
+ } catch (err) {
459
+ console.error("Failed to update enableObservability:", err);
460
+ }
461
+ };
462
+
463
+ const reloadSettings = async () => {
464
+ try {
465
+ const res = await fetch("/api/settings");
466
+ if (!res.ok) return;
467
+ const data = await res.json();
468
+ setSettings(data);
469
+ } catch (err) {
470
+ console.error("Failed to reload settings:", err);
471
+ }
472
+ };
473
+
474
+ const handleExportDatabase = async () => {
475
+ setDbLoading(true);
476
+ setDbStatus({ type: "", message: "" });
477
+ try {
478
+ const res = await fetch("/api/settings/database");
479
+ if (!res.ok) {
480
+ const data = await res.json().catch(() => ({}));
481
+ throw new Error(data.error || "Failed to export database");
482
+ }
483
+
484
+ const payload = await res.json();
485
+ const content = JSON.stringify(payload, null, 2);
486
+ const blob = new Blob([content], { type: "application/json" });
487
+ const url = URL.createObjectURL(blob);
488
+ const anchor = document.createElement("a");
489
+ const stamp = new Date().toISOString().replace(/[.:]/g, "-");
490
+ anchor.href = url;
491
+ anchor.download = `9router-backup-${stamp}.json`;
492
+ document.body.appendChild(anchor);
493
+ anchor.click();
494
+ document.body.removeChild(anchor);
495
+ URL.revokeObjectURL(url);
496
+
497
+ setDbStatus({ type: "success", message: "Database backup downloaded" });
498
+ } catch (err) {
499
+ setDbStatus({ type: "error", message: err.message || "Failed to export database" });
500
+ } finally {
501
+ setDbLoading(false);
502
+ }
503
+ };
504
+
505
+ const handleImportDatabase = async (event) => {
506
+ const file = event.target.files?.[0];
507
+ if (!file) return;
508
+
509
+ setDbLoading(true);
510
+ setDbStatus({ type: "", message: "" });
511
+
512
+ try {
513
+ const raw = await file.text();
514
+ const payload = JSON.parse(raw);
515
+
516
+ const res = await fetch("/api/settings/database", {
517
+ method: "POST",
518
+ headers: { "Content-Type": "application/json" },
519
+ body: JSON.stringify(payload),
520
+ });
521
+
522
+ const data = await res.json().catch(() => ({}));
523
+ if (!res.ok) {
524
+ throw new Error(data.error || "Failed to import database");
525
+ }
526
+
527
+ await reloadSettings();
528
+ setDbStatus({ type: "success", message: "Database imported successfully" });
529
+ } catch (err) {
530
+ setDbStatus({ type: "error", message: err.message || "Invalid backup file" });
531
+ } finally {
532
+ if (importFileRef.current) {
533
+ importFileRef.current.value = "";
534
+ }
535
+ setDbLoading(false);
536
+ }
537
+ };
538
+
539
+ const observabilityEnabled = settings.enableObservability === true;
540
+
541
+ const handleShutdown = async () => {
542
+ setIsShuttingDown(true);
543
+ try {
544
+ await fetch("/api/version/shutdown", { method: "POST" });
545
+ } catch (e) {
546
+ // Expected to fail as server shuts down; ignore error
547
+ }
548
+ setIsShuttingDown(false);
549
+ setShutdownOpen(false);
550
+ };
551
+
552
+ const handleLogout = async () => {
553
+ try {
554
+ const res = await fetch("/api/auth/logout", { method: "POST" });
555
+ if (res.ok) {
556
+ router.push("/login");
557
+ router.refresh();
558
+ }
559
+ } catch (err) {
560
+ console.error("Failed to logout:", err);
561
+ }
562
+ };
563
+
564
+ return (
565
+ <div className="max-w-2xl mx-auto px-4 sm:px-0">
566
+ <div className="flex flex-col gap-6">
567
+ {/* Local Mode Info */}
568
+ <Card>
569
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-4">
570
+ <div className="flex items-center gap-3 sm:gap-4">
571
+ <div className="size-10 sm:size-12 rounded-lg bg-green-500/10 text-green-500 flex items-center justify-center shrink-0">
572
+ <span className="material-symbols-outlined text-xl sm:text-2xl">computer</span>
573
+ </div>
574
+ <div>
575
+ <h2 className="text-lg sm:text-xl font-semibold">Local Mode</h2>
576
+ <p className="text-sm text-text-muted">Running on your machine</p>
577
+ </div>
578
+ </div>
579
+ <div className="inline-flex p-1 rounded-lg bg-black/5 dark:bg-white/5 w-full sm:w-auto">
580
+ {["light", "dark", "system"].map((option) => (
581
+ <button
582
+ key={option}
583
+ type="button"
584
+ onClick={() => setTheme(option)}
585
+ className={cn(
586
+ "flex items-center justify-center gap-1 sm:gap-1.5 px-2 sm:px-3 py-1.5 rounded-md font-medium transition-all flex-1 sm:flex-initial",
587
+ theme === option
588
+ ? "bg-white dark:bg-white/10 text-text-main shadow-sm"
589
+ : "text-text-muted hover:text-text-main"
590
+ )}
591
+ >
592
+ <span className="material-symbols-outlined text-[18px]">
593
+ {option === "light" ? "light_mode" : option === "dark" ? "dark_mode" : "contrast"}
594
+ </span>
595
+ <span className="capitalize text-xs sm:text-sm">{option}</span>
596
+ </button>
597
+ ))}
598
+ </div>
599
+ </div>
600
+ <div className="flex flex-col gap-3 pt-4 border-t border-border">
601
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between p-3 rounded-lg bg-bg border border-border gap-2">
602
+ <div>
603
+ <p className="font-medium text-sm sm:text-base">Database Location</p>
604
+ <p className="text-xs sm:text-sm text-text-muted font-mono break-all">~/.9router/db/data.sqlite</p>
605
+ </div>
606
+ </div>
607
+ <div className="flex flex-col sm:flex-row gap-2">
608
+ <Button
609
+ variant="secondary"
610
+ icon="download"
611
+ onClick={handleExportDatabase}
612
+ loading={dbLoading}
613
+ className="w-full sm:w-auto"
614
+ >
615
+ Download Backup
616
+ </Button>
617
+ <Button
618
+ variant="outline"
619
+ icon="upload"
620
+ onClick={() => importFileRef.current?.click()}
621
+ disabled={dbLoading}
622
+ className="w-full sm:w-auto"
623
+ >
624
+ Import Backup
625
+ </Button>
626
+ <input
627
+ ref={importFileRef}
628
+ type="file"
629
+ accept="application/json,.json"
630
+ className="hidden"
631
+ onChange={handleImportDatabase}
632
+ />
633
+ </div>
634
+ {dbStatus.message && (
635
+ <p className={`text-sm ${dbStatus.type === "error" ? "text-red-500" : "text-green-600 dark:text-green-400"}`}>
636
+ {dbStatus.message}
637
+ </p>
638
+ )}
639
+ </div>
640
+ </Card>
641
+
642
+ {/* Language */}
643
+ <Card>
644
+ <div className="flex items-center gap-3 mb-4">
645
+ <div className="size-10 rounded-lg bg-blue-500/10 text-blue-500 flex items-center justify-center shrink-0">
646
+ <span className="material-symbols-outlined text-[20px]">language</span>
647
+ </div>
648
+ <h3 className="text-base sm:text-lg font-semibold">Language</h3>
649
+ </div>
650
+ <button
651
+ onClick={() => setLangOpen(true)}
652
+ className="flex items-center justify-between w-full p-3 rounded-lg bg-bg border border-border hover:border-primary/50 transition-colors"
653
+ data-i18n-skip="true"
654
+ >
655
+ <span className="text-sm text-text-muted">Display language</span>
656
+ <span className="text-2xl">{LOCALE_FLAGS[locale] || "🌐"}</span>
657
+ </button>
658
+ </Card>
659
+
660
+ {/* Security */}
661
+ <Card>
662
+ <div className="flex items-center gap-3 mb-4">
663
+ <div className="p-2 rounded-lg bg-primary/10 text-primary shrink-0">
664
+ <span className="material-symbols-outlined text-[20px]">shield</span>
665
+ </div>
666
+ <h3 className="text-base sm:text-lg font-semibold">Security</h3>
667
+ </div>
668
+ <div className="flex flex-col gap-4">
669
+ <div className="flex items-start sm:items-center justify-between gap-4">
670
+ <div className="flex-1 min-w-0">
671
+ <p className="font-medium text-sm sm:text-base">Require login</p>
672
+ <p className="text-xs sm:text-sm text-text-muted">
673
+ When ON, dashboard requires password. When OFF, access without login.
674
+ </p>
675
+ </div>
676
+ <Toggle
677
+ checked={settings.requireLogin === true}
678
+ onChange={() => updateRequireLogin(!settings.requireLogin)}
679
+ disabled={loading}
680
+ />
681
+ </div>
682
+ {settings.requireLogin === true && (
683
+ <form onSubmit={handlePasswordChange} className="flex flex-col gap-4 pt-4 border-t border-border/50">
684
+ {settings.hasPassword && (
685
+ <div className="flex flex-col gap-2">
686
+ <label className="text-xs sm:text-sm font-medium">Current Password</label>
687
+ <Input
688
+ type="password"
689
+ placeholder="Enter current password"
690
+ value={passwords.current}
691
+ onChange={(e) => setPasswords({ ...passwords, current: e.target.value })}
692
+ required
693
+ />
694
+ </div>
695
+ )}
696
+ {/* {!settings.hasPassword && (
697
+ <div className="p-3 rounded-lg bg-blue-500/10 border border-blue-500/20">
698
+ <p className="text-sm text-blue-600 dark:text-blue-400">
699
+ Setting password for the first time. Leave current password empty or use default: <code className="bg-blue-500/20 px-1 rounded">123456</code>
700
+ </p>
701
+ </div>
702
+ )} */}
703
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
704
+ <div className="flex flex-col gap-2">
705
+ <label className="text-xs sm:text-sm font-medium">New Password</label>
706
+ <Input
707
+ type="password"
708
+ placeholder="Enter new password"
709
+ value={passwords.new}
710
+ onChange={(e) => setPasswords({ ...passwords, new: e.target.value })}
711
+ required
712
+ />
713
+ </div>
714
+ <div className="flex flex-col gap-2">
715
+ <label className="text-xs sm:text-sm font-medium">Confirm New Password</label>
716
+ <Input
717
+ type="password"
718
+ placeholder="Confirm new password"
719
+ value={passwords.confirm}
720
+ onChange={(e) => setPasswords({ ...passwords, confirm: e.target.value })}
721
+ required
722
+ />
723
+ </div>
724
+ </div>
725
+
726
+ {passStatus.message && (
727
+ <p className={`text-xs sm:text-sm ${passStatus.type === "error" ? "text-red-500" : "text-green-500"}`}>
728
+ {passStatus.message}
729
+ </p>
730
+ )}
731
+
732
+ <div className="pt-2">
733
+ <Button type="submit" variant="primary" loading={passLoading} className="w-full sm:w-auto">
734
+ {settings.hasPassword ? "Update Password" : "Set Password"}
735
+ </Button>
736
+ </div>
737
+ </form>
738
+ )}
739
+ </div>
740
+ </Card>
741
+
742
+ {/* OIDC */}
743
+ <Card>
744
+ <button
745
+ type="button"
746
+ onClick={() => setOidcExpanded((v) => !v)}
747
+ className="w-full flex items-center gap-3 text-left"
748
+ >
749
+ <div className="p-2 rounded-lg bg-indigo-500/10 text-indigo-500 shrink-0">
750
+ <span className="material-symbols-outlined text-[20px]">lock_open</span>
751
+ </div>
752
+ <div className="flex-1 min-w-0">
753
+ <h3 className="text-base sm:text-lg font-semibold">OIDC Dashboard Login</h3>
754
+ <p className="text-xs text-text-muted">
755
+ {settings.authMode === "oidc" ? "OIDC active" : settings.authMode === "both" ? "Password + OIDC active" : "Optional SSO via Authentik/Keycloak/Google"}
756
+ </p>
757
+ </div>
758
+ <span className="material-symbols-outlined text-text-muted shrink-0">
759
+ {oidcExpanded ? "expand_less" : "expand_more"}
760
+ </span>
761
+ </button>
762
+ {oidcExpanded && (
763
+ <div className="flex flex-col gap-4 mt-4">
764
+ <p className="text-xs sm:text-sm text-text-muted">
765
+ Use Authentik or any OIDC provider to sign in to the dashboard. You can enable password-only, OIDC-only, or both for the dashboard; model API access still uses API keys.
766
+ </p>
767
+
768
+ <div className="flex flex-col gap-2">
769
+ <label className="font-medium text-sm sm:text-base">Auth Mode</label>
770
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-2">
771
+ {[
772
+ {
773
+ value: "password",
774
+ title: "Password only",
775
+ desc: "Keep the legacy password login.",
776
+ },
777
+ {
778
+ value: "oidc",
779
+ title: "OIDC only",
780
+ desc: "Require OIDC for dashboard access.",
781
+ },
782
+ {
783
+ value: "both",
784
+ title: "Both",
785
+ desc: "Allow either password or OIDC.",
786
+ },
787
+ ].map((option) => {
788
+ const active = oidcForm.authMode === option.value;
789
+ return (
790
+ <button
791
+ key={option.value}
792
+ type="button"
793
+ onClick={() => updateOidcForm("authMode", option.value)}
794
+ className={cn(
795
+ "text-left rounded-lg border p-3 transition-colors",
796
+ active
797
+ ? "border-primary bg-primary/5"
798
+ : "border-border bg-bg hover:bg-black/5 dark:hover:bg-white/5"
799
+ )}
800
+ disabled={loading || oidcLoading}
801
+ >
802
+ <p className="font-medium text-sm sm:text-base">{option.title}</p>
803
+ <p className="text-xs sm:text-sm text-text-muted mt-1">{option.desc}</p>
804
+ </button>
805
+ );
806
+ })}
807
+ </div>
808
+ </div>
809
+
810
+ <div className="grid grid-cols-1 gap-4">
811
+ <div className="flex flex-col gap-2">
812
+ <label className="font-medium text-sm sm:text-base">Issuer URL</label>
813
+ <Input
814
+ placeholder="https://auth.example.com/application/o/9router/"
815
+ value={oidcForm.oidcIssuerUrl}
816
+ onChange={(e) => updateOidcForm("oidcIssuerUrl", e.target.value)}
817
+ disabled={loading || oidcLoading}
818
+ />
819
+ </div>
820
+
821
+ <div className="flex flex-col gap-2">
822
+ <label className="font-medium text-sm sm:text-base">Client ID</label>
823
+ <Input
824
+ placeholder="9router-dashboard"
825
+ value={oidcForm.oidcClientId}
826
+ onChange={(e) => updateOidcForm("oidcClientId", e.target.value)}
827
+ disabled={loading || oidcLoading}
828
+ />
829
+ </div>
830
+
831
+ <div className="flex flex-col gap-2">
832
+ <label className="font-medium text-sm sm:text-base">Client Secret</label>
833
+ <Input
834
+ type="password"
835
+ placeholder="Leave blank to keep existing secret"
836
+ value={oidcClientSecret}
837
+ onChange={(e) => setOidcClientSecret(e.target.value)}
838
+ disabled={loading || oidcLoading}
839
+ />
840
+ <p className="text-xs sm:text-sm text-text-muted">This value is write-only after saving.</p>
841
+ </div>
842
+
843
+ <div className="flex flex-col gap-2">
844
+ <label className="font-medium text-sm sm:text-base">Scopes</label>
845
+ <Input
846
+ placeholder="openid profile email"
847
+ value={oidcForm.oidcScopes}
848
+ onChange={(e) => updateOidcForm("oidcScopes", e.target.value)}
849
+ disabled={loading || oidcLoading}
850
+ />
851
+ </div>
852
+
853
+ <div className="flex flex-col gap-2">
854
+ <label className="font-medium text-sm sm:text-base">Login Button Label</label>
855
+ <Input
856
+ placeholder="Sign in with OIDC"
857
+ value={oidcForm.oidcLoginLabel}
858
+ onChange={(e) => updateOidcForm("oidcLoginLabel", e.target.value)}
859
+ disabled={loading || oidcLoading}
860
+ />
861
+ </div>
862
+ </div>
863
+
864
+ <div className="rounded-lg border border-border bg-bg p-3 text-xs sm:text-sm text-text-muted">
865
+ <p className="font-medium text-text-main mb-1">Redirect URI</p>
866
+ <code className="block break-all font-mono">{oidcRedirectUri}</code>
867
+ </div>
868
+
869
+ <div className="flex flex-col sm:flex-row gap-2 pt-2 border-t border-border/50">
870
+ <Button type="button" variant="primary" loading={oidcLoading} onClick={() => saveOidcSettings()} className="w-full sm:w-auto">
871
+ Save auth mode
872
+ </Button>
873
+ <Button type="button" variant="outline" loading={oidcTestLoading} onClick={testOidcConnection} className="w-full sm:w-auto">
874
+ Test connection
875
+ </Button>
876
+ </div>
877
+
878
+ {oidcTestStatus.message && (
879
+ <p className={`text-xs sm:text-sm ${oidcTestStatus.type === "error" ? "text-red-500" : "text-green-500"}`}>
880
+ {oidcTestStatus.message}
881
+ </p>
882
+ )}
883
+
884
+ {oidcStatus.message && (
885
+ <p className={`text-xs sm:text-sm ${oidcStatus.type === "error" ? "text-red-500" : "text-green-500"}`}>
886
+ {oidcStatus.message}
887
+ </p>
888
+ )}
889
+
890
+ {settings.authMode === "oidc" && (
891
+ <p className="text-xs sm:text-sm text-amber-600 dark:text-amber-400">
892
+ OIDC login is currently active. Password login is disabled until you switch back.
893
+ </p>
894
+ )}
895
+
896
+ {settings.authMode === "both" && (
897
+ <p className="text-xs sm:text-sm text-amber-600 dark:text-amber-400">
898
+ Password and OIDC login are both active.
899
+ </p>
900
+ )}
901
+ </div>
902
+ )}
903
+ </Card>
904
+
905
+ {/* Routing Preferences */}
906
+ <Card>
907
+ <div className="flex items-center gap-3 mb-4">
908
+ <div className="p-2 rounded-lg bg-blue-500/10 text-blue-500 shrink-0">
909
+ <span className="material-symbols-outlined text-[20px]">route</span>
910
+ </div>
911
+ <h3 className="text-base sm:text-lg font-semibold">Routing Strategy</h3>
912
+ </div>
913
+ <div className="flex flex-col gap-4">
914
+ <div className="flex items-start sm:items-center justify-between gap-4">
915
+ <div className="flex-1 min-w-0">
916
+ <p className="font-medium text-sm sm:text-base">Round Robin</p>
917
+ <p className="text-xs sm:text-sm text-text-muted">
918
+ Cycle through accounts to distribute load
919
+ </p>
920
+ </div>
921
+ <Toggle
922
+ checked={settings.fallbackStrategy === "round-robin"}
923
+ onChange={() => updateFallbackStrategy(settings.fallbackStrategy === "round-robin" ? "fill-first" : "round-robin")}
924
+ disabled={loading}
925
+ />
926
+ </div>
927
+
928
+ {/* Sticky Round Robin Limit */}
929
+ {settings.fallbackStrategy === "round-robin" && (
930
+ <div className="flex items-start sm:items-center justify-between gap-4 pt-2 border-t border-border/50">
931
+ <div className="flex-1 min-w-0">
932
+ <p className="font-medium text-sm sm:text-base">Sticky Limit</p>
933
+ <p className="text-xs sm:text-sm text-text-muted">
934
+ Calls per account before switching
935
+ </p>
936
+ </div>
937
+ <Input
938
+ type="number"
939
+ min="1"
940
+ max="10"
941
+ value={settings.stickyRoundRobinLimit || 3}
942
+ onChange={(e) => updateStickyLimit(e.target.value)}
943
+ disabled={loading}
944
+ className="w-16 sm:w-20 text-center shrink-0"
945
+ />
946
+ </div>
947
+ )}
948
+
949
+ {/* Combo Round Robin */}
950
+ <div className="flex items-start sm:items-center justify-between gap-4 pt-4 border-t border-border/50">
951
+ <div className="flex-1 min-w-0">
952
+ <p className="font-medium text-sm sm:text-base">Combo Round Robin</p>
953
+ <p className="text-xs sm:text-sm text-text-muted">
954
+ Cycle through providers in combos instead of always starting with first
955
+ </p>
956
+ </div>
957
+ <Toggle
958
+ checked={settings.comboStrategy === "round-robin"}
959
+ onChange={() => updateComboStrategy(settings.comboStrategy === "round-robin" ? "fallback" : "round-robin")}
960
+ disabled={loading}
961
+ />
962
+ </div>
963
+
964
+ {/* Combo Sticky Round Robin Limit */}
965
+ {settings.comboStrategy === "round-robin" && (
966
+ <div className="flex items-center justify-between pt-2 border-t border-border/50">
967
+ <div>
968
+ <p className="font-medium">Combo Sticky Limit</p>
969
+ <p className="text-sm text-text-muted">
970
+ Calls per combo model before switching
971
+ </p>
972
+ </div>
973
+ <Input
974
+ type="number"
975
+ min="1"
976
+ max="100"
977
+ value={settings.comboStickyRoundRobinLimit || 1}
978
+ onChange={(e) => updateComboStickyLimit(e.target.value)}
979
+ disabled={loading}
980
+ className="w-20 text-center"
981
+ />
982
+ </div>
983
+ )}
984
+
985
+ <p className="text-xs text-text-muted italic pt-2 border-t border-border/50">
986
+ {settings.fallbackStrategy === "round-robin"
987
+ ? `Currently distributing requests across all available accounts with ${settings.stickyRoundRobinLimit || 3} calls per account.`
988
+ : "Currently using accounts in priority order (Fill First)."}
989
+ {settings.comboStrategy === "round-robin"
990
+ ? ` Combos rotate after ${settings.comboStickyRoundRobinLimit || 1} call${(settings.comboStickyRoundRobinLimit || 1) === 1 ? "" : "s"} per model.`
991
+ : " Combos always start with their first model."}
992
+ </p>
993
+ </div>
994
+ </Card>
995
+
996
+ {/* Network */}
997
+ <Card>
998
+ <div className="flex items-center gap-3 mb-4">
999
+ <div className="p-2 rounded-lg bg-purple-500/10 text-purple-500 shrink-0">
1000
+ <span className="material-symbols-outlined text-[20px]">wifi</span>
1001
+ </div>
1002
+ <h3 className="text-base sm:text-lg font-semibold">Network</h3>
1003
+ </div>
1004
+
1005
+ <div className="flex flex-col gap-4">
1006
+ <div className="flex items-start sm:items-center justify-between gap-4">
1007
+ <div className="flex-1 min-w-0">
1008
+ <p className="font-medium text-sm sm:text-base">Outbound Proxy</p>
1009
+ <p className="text-xs sm:text-sm text-text-muted">Enable proxy for OAuth + provider outbound requests.</p>
1010
+ </div>
1011
+ <Toggle
1012
+ checked={settings.outboundProxyEnabled === true}
1013
+ onChange={() => updateOutboundProxyEnabled(!(settings.outboundProxyEnabled === true))}
1014
+ disabled={loading || proxyLoading}
1015
+ />
1016
+ </div>
1017
+
1018
+ {settings.outboundProxyEnabled === true && (
1019
+ <form onSubmit={updateOutboundProxy} className="flex flex-col gap-4 pt-2 border-t border-border/50">
1020
+ <div className="flex flex-col gap-2">
1021
+ <label className="font-medium text-sm sm:text-base">Proxy URL</label>
1022
+ <Input
1023
+ placeholder="http://127.0.0.1:7897"
1024
+ value={proxyForm.outboundProxyUrl}
1025
+ onChange={(e) => setProxyForm((prev) => ({ ...prev, outboundProxyUrl: e.target.value }))}
1026
+ disabled={loading || proxyLoading}
1027
+ />
1028
+ <p className="text-xs sm:text-sm text-text-muted">Leave empty to inherit existing env proxy (if any).</p>
1029
+ </div>
1030
+
1031
+ <div className="flex flex-col gap-2 pt-2 border-t border-border/50">
1032
+ <label className="font-medium text-sm sm:text-base">No Proxy</label>
1033
+ <Input
1034
+ placeholder="localhost,127.0.0.1"
1035
+ value={proxyForm.outboundNoProxy}
1036
+ onChange={(e) => setProxyForm((prev) => ({ ...prev, outboundNoProxy: e.target.value }))}
1037
+ disabled={loading || proxyLoading}
1038
+ />
1039
+ <p className="text-xs sm:text-sm text-text-muted">Comma-separated hostnames/domains to bypass the proxy.</p>
1040
+ </div>
1041
+
1042
+ <div className="pt-2 border-t border-border/50 flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
1043
+ <Button
1044
+ type="button"
1045
+ variant="secondary"
1046
+ loading={proxyTestLoading}
1047
+ disabled={loading || proxyLoading}
1048
+ onClick={testOutboundProxy}
1049
+ className="w-full sm:w-auto"
1050
+ >
1051
+ Test proxy URL
1052
+ </Button>
1053
+ <Button type="submit" variant="primary" loading={proxyLoading} className="w-full sm:w-auto">
1054
+ Apply
1055
+ </Button>
1056
+ </div>
1057
+ </form>
1058
+ )}
1059
+
1060
+ {proxyStatus.message && (
1061
+ <p className={`text-xs sm:text-sm ${proxyStatus.type === "error" ? "text-red-500" : "text-green-500"} pt-2 border-t border-border/50`}>
1062
+ {proxyStatus.message}
1063
+ </p>
1064
+ )}
1065
+ </div>
1066
+ </Card>
1067
+
1068
+ {/* Observability Settings */}
1069
+ <Card>
1070
+ <div className="flex items-center gap-3 mb-4">
1071
+ <div className="p-2 rounded-lg bg-orange-500/10 text-orange-500 shrink-0">
1072
+ <span className="material-symbols-outlined text-[20px]">monitoring</span>
1073
+ </div>
1074
+ <h3 className="text-base sm:text-lg font-semibold">Observability</h3>
1075
+ </div>
1076
+ <div className="flex items-start sm:items-center justify-between gap-4">
1077
+ <div className="flex-1 min-w-0">
1078
+ <p className="font-medium text-sm sm:text-base">Enable Observability</p>
1079
+ <p className="text-xs sm:text-sm text-text-muted">
1080
+ Record request details for inspection in the logs view
1081
+ </p>
1082
+ </div>
1083
+ <Toggle
1084
+ checked={observabilityEnabled}
1085
+ onChange={updateObservabilityEnabled}
1086
+ disabled={loading}
1087
+ />
1088
+ </div>
1089
+ </Card>
1090
+
1091
+ {/* Account actions */}
1092
+ <div className="flex flex-col sm:flex-row gap-2">
1093
+ <Button
1094
+ variant="outline"
1095
+ fullWidth
1096
+ icon="power_settings_new"
1097
+ onClick={() => setShutdownOpen(true)}
1098
+ className="text-red-500 border-red-200 hover:bg-red-50 hover:border-red-300"
1099
+ >
1100
+ Shutdown
1101
+ </Button>
1102
+ <Button
1103
+ variant="outline"
1104
+ fullWidth
1105
+ icon="logout"
1106
+ onClick={handleLogout}
1107
+ >
1108
+ Logout
1109
+ </Button>
1110
+ </div>
1111
+
1112
+ {/* App Info */}
1113
+ <div className="text-center text-xs sm:text-sm text-text-muted py-4">
1114
+ <p>{APP_CONFIG.name} v{APP_CONFIG.version}</p>
1115
+ <p className="mt-1">Local Mode - All data stored on your machine</p>
1116
+ </div>
1117
+ </div>
1118
+
1119
+ <LanguageSwitcher
1120
+ hideTrigger
1121
+ isOpen={langOpen}
1122
+ onClose={(next) => {
1123
+ setLangOpen(false);
1124
+ setLocale(next);
1125
+ }}
1126
+ />
1127
+ <ConfirmModal
1128
+ isOpen={shutdownOpen}
1129
+ onClose={() => setShutdownOpen(false)}
1130
+ onConfirm={handleShutdown}
1131
+ title="Close Proxy"
1132
+ message="Are you sure you want to close the proxy server?"
1133
+ confirmText="Close"
1134
+ cancelText="Cancel"
1135
+ variant="danger"
1136
+ loading={isShuttingDown}
1137
+ />
1138
+ </div>
1139
+ );
1140
+ }