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,537 @@
1
+ "use client";
2
+
3
+ import { useState, useMemo, useEffect } from "react";
4
+ import PropTypes from "prop-types";
5
+ import Modal from "./Modal";
6
+ import ProviderIcon from "./ProviderIcon";
7
+ import { getModelsByProviderId } from "@/shared/constants/models";
8
+ import { OAUTH_PROVIDERS, APIKEY_PROVIDERS, FREE_PROVIDERS, FREE_TIER_PROVIDERS, AI_PROVIDERS, isOpenAICompatibleProvider, isAnthropicCompatibleProvider, getProviderAlias } from "@/shared/constants/providers";
9
+
10
+ // Provider order: OAuth first, then Free Tier, then API Key (matches dashboard/providers)
11
+ const PROVIDER_ORDER = [
12
+ ...Object.keys(OAUTH_PROVIDERS),
13
+ ...Object.keys(FREE_PROVIDERS),
14
+ ...Object.keys(FREE_TIER_PROVIDERS),
15
+ ...Object.keys(APIKEY_PROVIDERS),
16
+ ];
17
+
18
+ // Providers that need no auth — always show in model selector
19
+ const NO_AUTH_PROVIDER_IDS = Object.keys(FREE_PROVIDERS).filter(id => FREE_PROVIDERS[id].noAuth);
20
+
21
+ export default function ModelSelectModal({
22
+ isOpen,
23
+ onClose,
24
+ onSelect,
25
+ onDeselect,
26
+ selectedModel,
27
+ activeProviders = [],
28
+ title = "Select Model",
29
+ modelAliases = {},
30
+ kindFilter = null,
31
+ addedModelValues = [],
32
+ closeOnSelect = true,
33
+ }) {
34
+ // Filter activeProviders by serviceKinds when kindFilter set (e.g. "webSearch", "webFetch")
35
+ const filteredActiveProviders = useMemo(() => {
36
+ if (!kindFilter) return activeProviders;
37
+ return activeProviders.filter((p) => {
38
+ const info = AI_PROVIDERS[p.provider];
39
+ const kinds = info?.serviceKinds || ["llm"];
40
+ return kinds.includes(kindFilter);
41
+ });
42
+ }, [activeProviders, kindFilter]);
43
+ const [searchQuery, setSearchQuery] = useState("");
44
+ const [combos, setCombos] = useState([]);
45
+ const [providerNodes, setProviderNodes] = useState([]);
46
+ const [customModels, setCustomModels] = useState([]);
47
+ const [disabledModels, setDisabledModels] = useState({});
48
+
49
+ const fetchCombos = async () => {
50
+ try {
51
+ const res = await fetch("/api/combos");
52
+ if (!res.ok) throw new Error(`Failed to fetch combos: ${res.status}`);
53
+ const data = await res.json();
54
+ setCombos(data.combos || []);
55
+ } catch (error) {
56
+ console.error("Error fetching combos:", error);
57
+ setCombos([]);
58
+ }
59
+ };
60
+
61
+ useEffect(() => {
62
+ if (isOpen) fetchCombos();
63
+ }, [isOpen]);
64
+
65
+ const fetchProviderNodes = async () => {
66
+ try {
67
+ const res = await fetch("/api/provider-nodes");
68
+ if (!res.ok) throw new Error(`Failed to fetch provider nodes: ${res.status}`);
69
+ const data = await res.json();
70
+ setProviderNodes(data.nodes || []);
71
+ } catch (error) {
72
+ console.error("Error fetching provider nodes:", error);
73
+ setProviderNodes([]);
74
+ }
75
+ };
76
+
77
+ useEffect(() => {
78
+ if (isOpen) fetchProviderNodes();
79
+ }, [isOpen]);
80
+
81
+ const fetchCustomModels = async () => {
82
+ try {
83
+ const res = await fetch("/api/models/custom");
84
+ if (!res.ok) throw new Error(`Failed to fetch custom models: ${res.status}`);
85
+ const data = await res.json();
86
+ setCustomModels(data.models || []);
87
+ } catch (error) {
88
+ console.error("Error fetching custom models:", error);
89
+ setCustomModels([]);
90
+ }
91
+ };
92
+
93
+ useEffect(() => {
94
+ if (isOpen) fetchCustomModels();
95
+ }, [isOpen]);
96
+
97
+ const fetchDisabledModels = async () => {
98
+ try {
99
+ const res = await fetch("/api/models/disabled");
100
+ if (!res.ok) throw new Error(`Failed to fetch disabled models: ${res.status}`);
101
+ const data = await res.json();
102
+ setDisabledModels(data.disabled || {});
103
+ } catch (error) {
104
+ console.error("Error fetching disabled models:", error);
105
+ setDisabledModels({});
106
+ }
107
+ };
108
+
109
+ useEffect(() => {
110
+ if (isOpen) fetchDisabledModels();
111
+ }, [isOpen]);
112
+
113
+ const allProviders = useMemo(() => ({ ...OAUTH_PROVIDERS, ...FREE_PROVIDERS, ...FREE_TIER_PROVIDERS, ...APIKEY_PROVIDERS }), []);
114
+
115
+ // Group models by provider with priority order
116
+ const groupedModels = useMemo(() => {
117
+ const groups = {};
118
+
119
+ // Kinds where the provider IS the model (no per-model selection needed)
120
+ const PROVIDER_AS_MODEL_KINDS = new Set(["webSearch", "webFetch"]);
121
+ // Kinds that map directly to model.type field
122
+ const TYPED_KINDS = new Set(["image", "tts", "stt", "embedding", "imageToText"]);
123
+ // For these kinds, providers without hardcoded models can still be picked (provider-as-model fallback)
124
+ const ALLOW_PROVIDER_FALLBACK_KINDS = new Set(["tts", "image", "webFetch"]);
125
+
126
+ // Filter a models[] array by kindFilter (keep only matching m.type)
127
+ const filterByKind = (models) => {
128
+ // No kindFilter → LLM context: keep only LLM models (no type or type === "llm")
129
+ if (!kindFilter) return models.filter((m) => m.isPlaceholder || !m.type || m.type === "llm");
130
+ if (!TYPED_KINDS.has(kindFilter)) return models;
131
+ return models.filter((m) => m.isPlaceholder || m.type === kindFilter);
132
+ };
133
+
134
+ // Get all active provider IDs from connections (filtered by kindFilter if set)
135
+ const activeConnectionIds = filteredActiveProviders.map(p => p.provider);
136
+
137
+ // No-auth providers: filter by kindFilter as well
138
+ const noAuthIds = kindFilter
139
+ ? NO_AUTH_PROVIDER_IDS.filter((id) => (AI_PROVIDERS[id]?.serviceKinds || ["llm"]).includes(kindFilter))
140
+ : NO_AUTH_PROVIDER_IDS;
141
+
142
+ // Only show connected providers (including both standard and custom)
143
+ const providerIdsToShow = new Set([
144
+ ...activeConnectionIds, // Only connected providers
145
+ ...noAuthIds, // No-auth providers (kind-filtered)
146
+ ]);
147
+
148
+ // Sort by PROVIDER_ORDER
149
+ const sortedProviderIds = [...providerIdsToShow].sort((a, b) => {
150
+ const indexA = PROVIDER_ORDER.indexOf(a);
151
+ const indexB = PROVIDER_ORDER.indexOf(b);
152
+ return (indexA === -1 ? 999 : indexA) - (indexB === -1 ? 999 : indexB);
153
+ });
154
+
155
+ sortedProviderIds.forEach((providerId) => {
156
+ const alias = getProviderAlias(providerId);
157
+ const providerInfo = allProviders[providerId] || { name: providerId, color: "#666" };
158
+ const isCustomProvider = isOpenAICompatibleProvider(providerId) || isAnthropicCompatibleProvider(providerId);
159
+
160
+ // For provider-as-model kinds (webSearch/webFetch): emit a single entry where value === providerId
161
+ if (kindFilter && PROVIDER_AS_MODEL_KINDS.has(kindFilter)) {
162
+ groups[providerId] = {
163
+ name: providerInfo.name,
164
+ alias,
165
+ color: providerInfo.color,
166
+ models: [{ id: providerId, name: providerInfo.name, value: providerId }],
167
+ };
168
+ return;
169
+ }
170
+
171
+ if (providerInfo.passthroughModels) {
172
+ const aliasModels = Object.entries(modelAliases)
173
+ .filter(([, fullModel]) => fullModel.startsWith(`${alias}/`))
174
+ .map(([aliasName, fullModel]) => ({
175
+ id: fullModel.replace(`${alias}/`, ""),
176
+ name: aliasName,
177
+ value: fullModel,
178
+ }));
179
+
180
+ // For typed kinds, only include hardcoded typed models (aliases are typically LLM-only and lack type info)
181
+ let combined = aliasModels;
182
+ if (kindFilter && TYPED_KINDS.has(kindFilter)) {
183
+ combined = getModelsByProviderId(providerId)
184
+ .filter((m) => m.type === kindFilter)
185
+ .map((m) => ({ id: m.id, name: m.name, value: `${alias}/${m.id}`, type: m.type }));
186
+ // Fallback: provider-as-model when no hardcoded models match (tts/image/webFetch only)
187
+ if (combined.length === 0 && ALLOW_PROVIDER_FALLBACK_KINDS.has(kindFilter)) {
188
+ const supports = (providerInfo.serviceKinds || ["llm"]).includes(kindFilter);
189
+ if (supports) combined = [{ id: providerId, name: providerInfo.name, value: alias }];
190
+ }
191
+ }
192
+
193
+ if (combined.length > 0) {
194
+ // Check for custom name from providerNodes (for compatible providers)
195
+ const matchedNode = providerNodes.find(node => node.id === providerId);
196
+ const displayName = matchedNode?.name || providerInfo.name;
197
+
198
+ groups[providerId] = {
199
+ name: displayName,
200
+ alias: alias,
201
+ color: providerInfo.color,
202
+ models: combined,
203
+ };
204
+ }
205
+ } else if (isCustomProvider) {
206
+ // Custom (openai/anthropic-compatible) providers are LLM-only — skip for typed media kinds
207
+ if (kindFilter && TYPED_KINDS.has(kindFilter)) return;
208
+ // Find connection object to get prefix synchronously without waiting for providerNodes fetch
209
+ const connection = activeProviders.find(p => p.provider === providerId);
210
+ const matchedNode = providerNodes.find(node => node.id === providerId);
211
+ const displayName = connection?.name || matchedNode?.name || providerInfo.name;
212
+ const nodePrefix = connection?.providerSpecificData?.prefix || matchedNode?.prefix || providerId;
213
+
214
+ // Aliases are stored using the raw providerId as key (e.g. "openai-compatible-chat-<uuid>/glm-4.7"),
215
+ // so we must filter by providerId, not by the display prefix.
216
+ const nodeModels = Object.entries(modelAliases)
217
+ .filter(([, fullModel]) => fullModel.startsWith(`${providerId}/`))
218
+ .map(([aliasName, fullModel]) => ({
219
+ id: fullModel.replace(`${providerId}/`, ""),
220
+ name: aliasName,
221
+ value: `${nodePrefix}/${fullModel.replace(`${providerId}/`, "")}`,
222
+ }));
223
+
224
+ // Always show compatible providers that are connected, even with no aliases.
225
+ // When no aliases exist, show a placeholder so users know it's available.
226
+ const modelsToShow = nodeModels.length > 0 ? nodeModels : [{
227
+ id: `__placeholder__${providerId}`,
228
+ name: `${nodePrefix}/model-id`,
229
+ value: `${nodePrefix}/model-id`,
230
+ isPlaceholder: true,
231
+ }];
232
+
233
+ groups[providerId] = {
234
+ name: displayName,
235
+ alias: nodePrefix,
236
+ color: providerInfo.color,
237
+ models: modelsToShow,
238
+ isCustom: true,
239
+ hasModels: nodeModels.length > 0,
240
+ };
241
+ } else {
242
+ const hardcodedModels = getModelsByProviderId(providerId);
243
+ const hardcodedIds = new Set(hardcodedModels.map((m) => m.id));
244
+
245
+ // Custom models: if no hardcoded models (e.g. openrouter), show all aliases for this provider
246
+ // Otherwise only show aliases where aliasName === modelId ("Add Model" button pattern)
247
+ const hasHardcoded = hardcodedModels.length > 0;
248
+ const customAliasModels = Object.entries(modelAliases)
249
+ .filter(([aliasName, fullModel]) =>
250
+ fullModel.startsWith(`${alias}/`) &&
251
+ (hasHardcoded ? aliasName === fullModel.replace(`${alias}/`, "") : true) &&
252
+ !hardcodedIds.has(fullModel.replace(`${alias}/`, ""))
253
+ )
254
+ .map(([aliasName, fullModel]) => {
255
+ const modelId = fullModel.replace(`${alias}/`, "");
256
+ return { id: modelId, name: aliasName, value: fullModel, isCustom: true };
257
+ });
258
+
259
+ // Custom models registered via /api/models/custom (provider "Add Model" button)
260
+ const customAliasIds = new Set(customAliasModels.map((m) => m.id));
261
+ const customRegisteredModels = customModels
262
+ .filter((m) => m.providerAlias === alias && !hardcodedIds.has(m.id) && !customAliasIds.has(m.id))
263
+ .map((m) => ({ id: m.id, name: m.name || m.id, value: `${alias}/${m.id}`, isCustom: true }));
264
+
265
+ const merged = [
266
+ ...hardcodedModels.map((m) => ({ id: m.id, name: m.name, value: `${alias}/${m.id}`, type: m.type })),
267
+ ...customAliasModels,
268
+ ...customRegisteredModels,
269
+ ];
270
+ // Dedupe by value (alias may equal hardcoded id, causing React key collision)
271
+ const seen = new Set();
272
+ let allModels = filterByKind(merged.filter((m) => {
273
+ if (seen.has(m.value)) return false;
274
+ seen.add(m.value);
275
+ return true;
276
+ }));
277
+
278
+ // Provider-as-model fallback: providers that support the kind but have no hardcoded models
279
+ // can still be picked (value = providerAlias). Skips embedding (always needs model).
280
+ if (allModels.length === 0 && kindFilter && ALLOW_PROVIDER_FALLBACK_KINDS.has(kindFilter)) {
281
+ const supports = (providerInfo.serviceKinds || ["llm"]).includes(kindFilter);
282
+ if (supports) {
283
+ allModels = [{ id: providerId, name: providerInfo.name, value: alias }];
284
+ }
285
+ }
286
+
287
+ if (allModels.length > 0) {
288
+ groups[providerId] = {
289
+ name: providerInfo.name,
290
+ alias: alias,
291
+ color: providerInfo.color,
292
+ models: allModels,
293
+ };
294
+ }
295
+ }
296
+ });
297
+
298
+ // Filter out disabled models per provider (disabled keyed by storage alias OR providerId)
299
+ Object.entries(groups).forEach(([providerId, group]) => {
300
+ const aliasKey = getProviderAlias(providerId);
301
+ const disabledIds = new Set([
302
+ ...(disabledModels[aliasKey] || []),
303
+ ...(disabledModels[providerId] || []),
304
+ ]);
305
+ if (disabledIds.size === 0) return;
306
+ group.models = group.models.filter((m) => !disabledIds.has(m.id));
307
+ if (group.models.length === 0) delete groups[providerId];
308
+ });
309
+
310
+ return groups;
311
+ }, [filteredActiveProviders, modelAliases, allProviders, providerNodes, customModels, disabledModels, kindFilter, activeProviders]);
312
+
313
+ // Filter combos by search query (and hide combos when kindFilter is set — combos are LLM-only by design)
314
+ const filteredCombos = useMemo(() => {
315
+ if (kindFilter) return [];
316
+ if (!searchQuery.trim()) return combos;
317
+ const query = searchQuery.toLowerCase();
318
+ return combos.filter(c => c.name.toLowerCase().includes(query));
319
+ }, [combos, searchQuery, kindFilter]);
320
+
321
+ // Sort models alphabetically, with added models floated to top
322
+ const sortModels = (models) => {
323
+ const added = models.filter(m => addedModelValues.includes(m.value)).sort((a, b) => a.name.localeCompare(b.name));
324
+ const rest = models.filter(m => !addedModelValues.includes(m.value)).sort((a, b) => a.name.localeCompare(b.name));
325
+ return [...added, ...rest];
326
+ };
327
+
328
+ // Filter models by search query
329
+ const filteredGroups = useMemo(() => {
330
+ const query = searchQuery.trim().toLowerCase();
331
+
332
+ const filtered = {};
333
+ Object.entries(groupedModels).forEach(([providerId, group]) => {
334
+ let models = group.models;
335
+ if (query) {
336
+ const providerNameMatches = group.name.toLowerCase().includes(query);
337
+ models = models.filter(
338
+ (m) =>
339
+ m.name.toLowerCase().includes(query) ||
340
+ m.id.toLowerCase().includes(query)
341
+ );
342
+ if (models.length === 0 && !providerNameMatches) return;
343
+ }
344
+ filtered[providerId] = {
345
+ ...group,
346
+ models: sortModels(models),
347
+ };
348
+ });
349
+
350
+ return filtered;
351
+ }, [groupedModels, searchQuery, addedModelValues]);
352
+
353
+ const handleSelect = (model) => {
354
+ const value = model?.value || model?.name || model;
355
+ const isAdded = addedModelValues.includes(value);
356
+
357
+ if (isAdded && onDeselect) {
358
+ onDeselect(model);
359
+ } else {
360
+ onSelect(model);
361
+ }
362
+
363
+ if (closeOnSelect) {
364
+ onClose();
365
+ setSearchQuery("");
366
+ }
367
+ };
368
+
369
+ return (
370
+ <Modal
371
+ isOpen={isOpen}
372
+ onClose={() => {
373
+ onClose();
374
+ setSearchQuery("");
375
+ }}
376
+ title={title}
377
+ size="md"
378
+ className="p-4!"
379
+ footer={null}
380
+ >
381
+ {/* Info bar */}
382
+ <div className="flex items-center gap-2 mb-3 px-2.5 py-2 bg-primary/8 border border-primary/20 rounded-lg text-xs text-text-muted">
383
+ <span className="material-symbols-outlined text-primary shrink-0" style={{ fontSize: "14px" }}>info</span>
384
+ <span>Click to add, click again to remove. Changes are saved automatically.</span>
385
+ </div>
386
+
387
+ {/* Search - compact */}
388
+ <div className="mb-3">
389
+ <div className="relative">
390
+ <span className="material-symbols-outlined absolute left-2.5 top-1/2 -translate-y-1/2 text-text-muted text-[16px]">
391
+ search
392
+ </span>
393
+ <input
394
+ type="text"
395
+ placeholder="Search..."
396
+ value={searchQuery}
397
+ onChange={(e) => setSearchQuery(e.target.value)}
398
+ className="w-full pl-8 pr-3 py-1.5 bg-surface border border-border rounded text-xs focus:outline-none focus:ring-1 focus:ring-primary/50"
399
+ />
400
+ </div>
401
+ </div>
402
+
403
+ {/* Models grouped by provider - compact */}
404
+ <div className="max-h-[400px] overflow-y-auto space-y-3">
405
+ {/* Combos section - always first */}
406
+ {filteredCombos.length > 0 && (
407
+ <div>
408
+ <div className="flex items-center gap-1.5 mb-1.5 sticky top-0 bg-surface py-0.5">
409
+ <span className="material-symbols-outlined text-primary text-[14px]">layers</span>
410
+ <span className="text-xs font-medium text-primary">Combos</span>
411
+ <span className="text-[10px] text-text-muted">({filteredCombos.length})</span>
412
+ </div>
413
+ <div className="flex flex-wrap gap-1.5">
414
+ {filteredCombos.map((combo) => {
415
+ const isSelected = selectedModel === combo.name;
416
+ return (
417
+ <button
418
+ key={combo.id}
419
+ onClick={() => handleSelect({ id: combo.name, name: combo.name, value: combo.name })}
420
+ className={`
421
+ px-2 py-1 rounded-xl text-xs font-medium transition-all border hover:cursor-pointer flex items-center gap-1
422
+ ${isSelected
423
+ ? "bg-primary text-white border-primary"
424
+ : addedModelValues.includes(combo.name)
425
+ ? "bg-primary border-primary text-white hover:bg-primary-hover"
426
+ : "bg-surface border-border text-text-main hover:border-primary/50 hover:bg-primary/5"
427
+ }
428
+ `}
429
+ >
430
+ {addedModelValues.includes(combo.name) && (
431
+ <span className="material-symbols-outlined leading-none" style={{ fontSize: "10px" }}>check</span>
432
+ )}
433
+ {combo.name}
434
+ </button>
435
+ );
436
+ })}
437
+ </div>
438
+ </div>
439
+ )}
440
+
441
+ {/* Provider models */}
442
+ {Object.entries(filteredGroups).map(([providerId, group]) => (
443
+ <div key={providerId}>
444
+ {/* Provider header */}
445
+ <div className="flex items-center gap-1.5 mb-1.5 sticky top-0 bg-surface py-0.5">
446
+ <ProviderIcon
447
+ src={`/providers/${providerId}.png`}
448
+ alt={group.name}
449
+ size={14}
450
+ fallbackText={(group.name || providerId).slice(0, 2).toUpperCase()}
451
+ fallbackColor={group.color}
452
+ />
453
+ <span className="text-xs font-medium text-primary">
454
+ {group.name}
455
+ </span>
456
+ <span className="text-[10px] text-text-muted">
457
+ ({group.models.length})
458
+ </span>
459
+ </div>
460
+
461
+ <div className="flex flex-wrap gap-1.5">
462
+ {group.models.map((model) => {
463
+ const isSelected = selectedModel === model.value;
464
+ const isPlaceholder = model.isPlaceholder;
465
+ return (
466
+ <button
467
+ key={model.value}
468
+ onClick={() => handleSelect(model)}
469
+ title={isPlaceholder ? "Select to pre-fill, then edit model ID in the input" : undefined}
470
+ className={`
471
+ px-2 py-1 rounded-xl text-xs font-medium transition-all border hover:cursor-pointer
472
+ ${isPlaceholder
473
+ ? "border-dashed border-border text-text-muted hover:border-primary/50 hover:text-primary bg-surface italic"
474
+ : isSelected
475
+ ? "bg-primary text-white border-primary"
476
+ : addedModelValues.includes(model.value)
477
+ ? "bg-primary border-primary text-white hover:bg-primary-hover"
478
+ : "bg-surface border-border text-text-main hover:border-primary/50 hover:bg-primary/5"
479
+ }
480
+ `}
481
+ >
482
+ <span className="flex items-center gap-1">
483
+ {addedModelValues.includes(model.value) && !isPlaceholder && (
484
+ <span className="material-symbols-outlined leading-none" style={{ fontSize: "10px" }}>check</span>
485
+ )}
486
+ {isPlaceholder ? (
487
+ <>
488
+ <span className="material-symbols-outlined text-[11px]">edit</span>
489
+ {model.name}
490
+ </>
491
+ ) : model.isCustom ? (
492
+ <>
493
+ {model.name}
494
+ <span className="text-[9px] opacity-60 font-normal">custom</span>
495
+ </>
496
+ ) : (
497
+ model.name
498
+ )}
499
+ </span>
500
+ </button>
501
+ );
502
+ })}
503
+ </div>
504
+ </div>
505
+ ))}
506
+
507
+ {Object.keys(filteredGroups).length === 0 && filteredCombos.length === 0 && (
508
+ <div className="text-center py-4 text-text-muted">
509
+ <span className="material-symbols-outlined text-2xl mb-1 block">
510
+ search_off
511
+ </span>
512
+ <p className="text-xs">No models found</p>
513
+ </div>
514
+ )}
515
+ </div>
516
+ </Modal>
517
+ );
518
+ }
519
+
520
+ ModelSelectModal.propTypes = {
521
+ isOpen: PropTypes.bool.isRequired,
522
+ onClose: PropTypes.func.isRequired,
523
+ onSelect: PropTypes.func.isRequired,
524
+ onDeselect: PropTypes.func,
525
+ selectedModel: PropTypes.string,
526
+ activeProviders: PropTypes.arrayOf(
527
+ PropTypes.shape({
528
+ provider: PropTypes.string.isRequired,
529
+ })
530
+ ),
531
+ title: PropTypes.string,
532
+ modelAliases: PropTypes.object,
533
+ kindFilter: PropTypes.string,
534
+ addedModelValues: PropTypes.arrayOf(PropTypes.string),
535
+ closeOnSelect: PropTypes.bool,
536
+ };
537
+
@@ -0,0 +1,23 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+ import NineRemotePromoModal from "./NineRemotePromoModal";
5
+
6
+ export default function NineRemoteButton() {
7
+ const [isOpen, setIsOpen] = useState(false);
8
+
9
+ return (
10
+ <>
11
+ <button
12
+ onClick={() => setIsOpen(true)}
13
+ className="relative flex items-center gap-1.5 px-2.5 py-1.5 rounded-lg transition-all text-text-muted hover:text-text-main hover:bg-black/5 dark:hover:bg-white/5"
14
+ title="9Remote"
15
+ >
16
+ <span className="material-symbols-outlined text-[18px]">computer</span>
17
+ <span className="text-xs font-medium">Remote</span>
18
+ </button>
19
+
20
+ <NineRemotePromoModal isOpen={isOpen} onClose={() => setIsOpen(false)} />
21
+ </>
22
+ );
23
+ }
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ import { useEffect } from "react";
4
+ import { createPortal } from "react-dom";
5
+
6
+ const FEATURES = [
7
+ { icon: "terminal", label: "Terminal", desc: "Full shell access" },
8
+ { icon: "cast", label: "Desktop", desc: "Screen sharing" },
9
+ { icon: "folder_open", label: "Files", desc: "Browse & edit files" },
10
+ ];
11
+
12
+ const BULLETS = [
13
+ { icon: "qr_code_scanner", text: "Scan QR to connect instantly" },
14
+ { icon: "wifi_off", text: "No port forwarding needed" },
15
+ { icon: "devices", text: "Works on any device" },
16
+ ];
17
+
18
+ const NINE_REMOTE_URL = "https://9remote.cc";
19
+
20
+ export default function NineRemotePromoModal({ isOpen, onClose }) {
21
+ useEffect(() => {
22
+ if (!isOpen) return;
23
+ document.body.style.overflow = "hidden";
24
+ const onEsc = (e) => { if (e.key === "Escape") onClose(); };
25
+ document.addEventListener("keydown", onEsc);
26
+ return () => { document.body.style.overflow = ""; document.removeEventListener("keydown", onEsc); };
27
+ }, [isOpen, onClose]);
28
+
29
+ if (!isOpen) return null;
30
+
31
+ return createPortal(
32
+ <div className="fixed inset-0 z-[9999] flex items-center justify-center p-4">
33
+ <div className="absolute inset-0 bg-black/50 backdrop-blur-[2px] fade-in" onClick={onClose} />
34
+
35
+ <div className="relative w-full max-w-sm rounded-[14px] overflow-hidden shadow-[var(--shadow-elev)] fade-in flex flex-col bg-surface border border-border-subtle">
36
+ {/* Header */}
37
+ <div className="flex items-center justify-between px-5 py-3 border-b border-border-subtle">
38
+ <div className="flex items-center gap-3">
39
+ <div className="w-7 h-7 rounded-[8px] flex items-center justify-center bg-primary">
40
+ <span className="material-symbols-outlined text-white text-base">terminal</span>
41
+ </div>
42
+ <span className="text-xs font-bold uppercase tracking-wider text-primary font-mono">9Remote</span>
43
+ </div>
44
+ <button
45
+ onClick={onClose}
46
+ className="p-1.5 rounded-[10px] text-text-muted hover:bg-surface-2 hover:text-text-main transition-colors"
47
+ >
48
+ <span className="material-symbols-outlined text-base">close</span>
49
+ </button>
50
+ </div>
51
+
52
+ {/* Body */}
53
+ <div className="px-7 py-7 pb-9 flex flex-col gap-6">
54
+ {/* Hero */}
55
+ <div className="flex flex-col items-center gap-2 text-center mt-2">
56
+ <div className="w-14 h-14 rounded-[14px] flex items-center justify-center mb-1 bg-primary shadow-[var(--shadow-warm)]">
57
+ <span className="material-symbols-outlined text-white text-[30px]">terminal</span>
58
+ </div>
59
+ <h1 className="text-lg font-bold text-text-main tracking-tight">9Remote</h1>
60
+ <p className="text-xs text-text-muted leading-5 max-w-[220px]">
61
+ Access your terminal, desktop &amp; files from anywhere
62
+ </p>
63
+ </div>
64
+
65
+ {/* Feature cards */}
66
+ <div className="flex gap-2 w-full">
67
+ {FEATURES.map(({ icon, label, desc }) => (
68
+ <div key={label} className="flex-1 flex flex-col items-center gap-1.5 py-4 px-1 rounded-[10px] border border-border-subtle bg-surface-2">
69
+ <span className="material-symbols-outlined text-primary text-[22px]">{icon}</span>
70
+ <p className="text-xs font-semibold text-text-main">{label}</p>
71
+ <p className="text-[10px] text-text-muted text-center leading-4">{desc}</p>
72
+ </div>
73
+ ))}
74
+ </div>
75
+
76
+ {/* Bullets */}
77
+ <div className="flex flex-col gap-3 w-full">
78
+ {BULLETS.map(({ icon, text }) => (
79
+ <div key={icon} className="flex items-center gap-2.5">
80
+ <span className="material-symbols-outlined flex-shrink-0 text-primary text-[16px]">{icon}</span>
81
+ <span className="text-xs text-text-muted">{text}</span>
82
+ </div>
83
+ ))}
84
+ </div>
85
+
86
+ {/* CTA */}
87
+ <button
88
+ onClick={() => window.open(NINE_REMOTE_URL, "_blank")}
89
+ className="w-full py-3 flex items-center justify-center gap-2 text-sm font-semibold text-white rounded-[10px] bg-primary hover:bg-primary-hover shadow-[var(--shadow-warm)] active:scale-[0.98] transition-all"
90
+ >
91
+ <span className="material-symbols-outlined text-base">open_in_new</span>
92
+ Get 9Remote
93
+ </button>
94
+ </div>
95
+ </div>
96
+ </div>,
97
+ document.body
98
+ );
99
+ }