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.
- package/CHANGELOG.md +222 -0
- package/LICENSE +21 -0
- package/README.md +96 -0
- package/README.zh-CN.md +1311 -0
- package/assets/pixel-router-2d.png +0 -0
- package/cli/LICENSE +42 -0
- package/cli/README.md +125 -0
- package/cli/app/package.json +58 -0
- package/cli/cli.js +806 -0
- package/cli/package.json +48 -0
- package/i18n/README.ja-JP.md +1209 -0
- package/i18n/README.ru.md +1311 -0
- package/i18n/README.vi.md +1310 -0
- package/i18n/README.zh-CN.md +1306 -0
- package/images/9router.png +0 -0
- package/jsconfig.json +12 -0
- package/next.config.mjs +71 -0
- package/open-sse/config/appConstants.js +203 -0
- package/open-sse/config/codexInstructions.js +119 -0
- package/open-sse/config/constants.js +4 -0
- package/open-sse/config/defaultThinkingSignature.js +12 -0
- package/open-sse/config/errorConfig.js +85 -0
- package/open-sse/config/googleTtsLanguages.js +62 -0
- package/open-sse/config/kiroConstants.js +322 -0
- package/open-sse/config/models.js +13 -0
- package/open-sse/config/ollamaModels.js +19 -0
- package/open-sse/config/providerModels.js +944 -0
- package/open-sse/config/providers.js +458 -0
- package/open-sse/config/runtimeConfig.js +72 -0
- package/open-sse/config/ttsModels.js +129 -0
- package/open-sse/executors/antigravity.js +504 -0
- package/open-sse/executors/azure.js +57 -0
- package/open-sse/executors/base.js +185 -0
- package/open-sse/executors/codex.js +469 -0
- package/open-sse/executors/commandcode.js +88 -0
- package/open-sse/executors/cursor.js +795 -0
- package/open-sse/executors/default.js +497 -0
- package/open-sse/executors/gemini-cli.js +89 -0
- package/open-sse/executors/github.js +379 -0
- package/open-sse/executors/grok-web.js +345 -0
- package/open-sse/executors/iflow.js +108 -0
- package/open-sse/executors/index.js +75 -0
- package/open-sse/executors/kiro.js +508 -0
- package/open-sse/executors/ollama-local.js +14 -0
- package/open-sse/executors/opencode-go.js +41 -0
- package/open-sse/executors/opencode.js +32 -0
- package/open-sse/executors/perplexity-web.js +507 -0
- package/open-sse/executors/qoder.js +450 -0
- package/open-sse/executors/qwen.js +129 -0
- package/open-sse/executors/vertex.js +131 -0
- package/open-sse/executors/xiaomi-tokenplan.js +19 -0
- package/open-sse/handlers/chatCore/nonStreamingHandler.js +230 -0
- package/open-sse/handlers/chatCore/requestDetail.js +102 -0
- package/open-sse/handlers/chatCore/sseToJsonHandler.js +231 -0
- package/open-sse/handlers/chatCore/streamingHandler.js +103 -0
- package/open-sse/handlers/chatCore.js +287 -0
- package/open-sse/handlers/embeddingProviders/_base.js +4 -0
- package/open-sse/handlers/embeddingProviders/gemini.js +54 -0
- package/open-sse/handlers/embeddingProviders/index.js +23 -0
- package/open-sse/handlers/embeddingProviders/openai.js +39 -0
- package/open-sse/handlers/embeddingProviders/openaiCompatNode.js +13 -0
- package/open-sse/handlers/embeddingsCore.js +126 -0
- package/open-sse/handlers/fetch/index.js +237 -0
- package/open-sse/handlers/imageGenerationCore.js +189 -0
- package/open-sse/handlers/imageProviders/_base.js +31 -0
- package/open-sse/handlers/imageProviders/blackForestLabs.js +43 -0
- package/open-sse/handlers/imageProviders/cloudflareAi.js +178 -0
- package/open-sse/handlers/imageProviders/codex.js +198 -0
- package/open-sse/handlers/imageProviders/comfyui.js +8 -0
- package/open-sse/handlers/imageProviders/falAi.js +41 -0
- package/open-sse/handlers/imageProviders/gemini.js +25 -0
- package/open-sse/handlers/imageProviders/huggingface.js +22 -0
- package/open-sse/handlers/imageProviders/index.js +40 -0
- package/open-sse/handlers/imageProviders/nanobanana.js +58 -0
- package/open-sse/handlers/imageProviders/openai.js +40 -0
- package/open-sse/handlers/imageProviders/runwayml.js +47 -0
- package/open-sse/handlers/imageProviders/sdwebui.js +17 -0
- package/open-sse/handlers/imageProviders/stabilityAi.js +34 -0
- package/open-sse/handlers/responsesHandler.js +103 -0
- package/open-sse/handlers/search/callers.js +371 -0
- package/open-sse/handlers/search/chatSearch.js +409 -0
- package/open-sse/handlers/search/index.js +201 -0
- package/open-sse/handlers/search/normalizers.js +223 -0
- package/open-sse/handlers/sttCore.js +194 -0
- package/open-sse/handlers/ttsCore.js +74 -0
- package/open-sse/handlers/ttsProviders/_base.js +39 -0
- package/open-sse/handlers/ttsProviders/edgeTts.js +89 -0
- package/open-sse/handlers/ttsProviders/elevenlabs.js +48 -0
- package/open-sse/handlers/ttsProviders/gemini.js +117 -0
- package/open-sse/handlers/ttsProviders/genericFormats.js +169 -0
- package/open-sse/handlers/ttsProviders/googleTts.js +54 -0
- package/open-sse/handlers/ttsProviders/index.js +50 -0
- package/open-sse/handlers/ttsProviders/localDevice.js +87 -0
- package/open-sse/handlers/ttsProviders/minimax.js +59 -0
- package/open-sse/handlers/ttsProviders/openai.js +30 -0
- package/open-sse/handlers/ttsProviders/openrouter.js +70 -0
- package/open-sse/index.js +82 -0
- package/open-sse/rtk/applyFilter.js +15 -0
- package/open-sse/rtk/autodetect.js +111 -0
- package/open-sse/rtk/caveman.js +100 -0
- package/open-sse/rtk/cavemanPrompts.js +78 -0
- package/open-sse/rtk/constants.js +55 -0
- package/open-sse/rtk/filters/buildOutput.js +127 -0
- package/open-sse/rtk/filters/dedupLog.js +44 -0
- package/open-sse/rtk/filters/find.js +49 -0
- package/open-sse/rtk/filters/gitDiff.js +92 -0
- package/open-sse/rtk/filters/gitStatus.js +117 -0
- package/open-sse/rtk/filters/grep.js +48 -0
- package/open-sse/rtk/filters/ls.js +79 -0
- package/open-sse/rtk/filters/readNumbered.js +27 -0
- package/open-sse/rtk/filters/searchList.js +52 -0
- package/open-sse/rtk/filters/smartTruncate.js +15 -0
- package/open-sse/rtk/filters/tree.js +32 -0
- package/open-sse/rtk/index.js +155 -0
- package/open-sse/rtk/registry.js +38 -0
- package/open-sse/services/accountFallback.js +238 -0
- package/open-sse/services/combo.js +198 -0
- package/open-sse/services/compact.js +71 -0
- package/open-sse/services/kiroModels.js +332 -0
- package/open-sse/services/model.js +261 -0
- package/open-sse/services/oauthCredentialManager.js +151 -0
- package/open-sse/services/projectId.js +306 -0
- package/open-sse/services/provider.js +356 -0
- package/open-sse/services/qoderModels.js +214 -0
- package/open-sse/services/tokenRefresh.js +939 -0
- package/open-sse/services/usage.js +1496 -0
- package/open-sse/transformer/responsesTransformer.js +439 -0
- package/open-sse/transformer/streamToJsonConverter.js +103 -0
- package/open-sse/translator/formats.js +36 -0
- package/open-sse/translator/helpers/claudeHelper.js +216 -0
- package/open-sse/translator/helpers/geminiHelper.js +372 -0
- package/open-sse/translator/helpers/imageHelper.js +34 -0
- package/open-sse/translator/helpers/maxTokensHelper.js +27 -0
- package/open-sse/translator/helpers/openaiHelper.js +130 -0
- package/open-sse/translator/helpers/responsesApiHelper.js +139 -0
- package/open-sse/translator/helpers/toolCallHelper.js +148 -0
- package/open-sse/translator/index.js +251 -0
- package/open-sse/translator/request/antigravity-to-openai.js +229 -0
- package/open-sse/translator/request/claude-to-openai.js +232 -0
- package/open-sse/translator/request/gemini-to-openai.js +147 -0
- package/open-sse/translator/request/openai-responses.js +318 -0
- package/open-sse/translator/request/openai-to-claude.js +401 -0
- package/open-sse/translator/request/openai-to-commandcode.js +170 -0
- package/open-sse/translator/request/openai-to-cursor.js +183 -0
- package/open-sse/translator/request/openai-to-gemini.js +470 -0
- package/open-sse/translator/request/openai-to-kiro.js +629 -0
- package/open-sse/translator/request/openai-to-kiro.old.js +278 -0
- package/open-sse/translator/request/openai-to-ollama.js +192 -0
- package/open-sse/translator/request/openai-to-vertex.js +42 -0
- package/open-sse/translator/response/claude-to-openai.js +206 -0
- package/open-sse/translator/response/commandcode-to-openai.js +197 -0
- package/open-sse/translator/response/cursor-to-openai.js +30 -0
- package/open-sse/translator/response/gemini-to-openai.js +245 -0
- package/open-sse/translator/response/kiro-to-openai.js +195 -0
- package/open-sse/translator/response/ollama-to-openai.js +152 -0
- package/open-sse/translator/response/openai-responses.js +590 -0
- package/open-sse/translator/response/openai-to-antigravity.js +122 -0
- package/open-sse/translator/response/openai-to-claude.js +266 -0
- package/open-sse/utils/bypassHandler.js +298 -0
- package/open-sse/utils/claudeCloaking.js +155 -0
- package/open-sse/utils/claudeHeaderCache.js +70 -0
- package/open-sse/utils/clientDetector.js +63 -0
- package/open-sse/utils/cursorChecksum.js +149 -0
- package/open-sse/utils/cursorProtobuf.js +904 -0
- package/open-sse/utils/debugLog.js +14 -0
- package/open-sse/utils/error.js +147 -0
- package/open-sse/utils/ollamaTransform.js +85 -0
- package/open-sse/utils/proxyFetch.js +368 -0
- package/open-sse/utils/reasoningContentInjector.js +79 -0
- package/open-sse/utils/requestLogger.js +260 -0
- package/open-sse/utils/responsesStreamHelpers.js +49 -0
- package/open-sse/utils/sessionManager.js +82 -0
- package/open-sse/utils/stream.js +462 -0
- package/open-sse/utils/streamHandler.js +250 -0
- package/open-sse/utils/streamHelpers.js +122 -0
- package/open-sse/utils/toolDeduper.js +49 -0
- package/open-sse/utils/usageTracking.js +347 -0
- package/package.json +100 -0
- package/postcss.config.mjs +12 -0
- package/public/favicon.svg +11 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/i18n/literals/ar.json +195 -0
- package/public/i18n/literals/bn.json +195 -0
- package/public/i18n/literals/cs.json +195 -0
- package/public/i18n/literals/da.json +195 -0
- package/public/i18n/literals/de.json +195 -0
- package/public/i18n/literals/el.json +195 -0
- package/public/i18n/literals/es.json +195 -0
- package/public/i18n/literals/fi.json +195 -0
- package/public/i18n/literals/fr.json +195 -0
- package/public/i18n/literals/he.json +195 -0
- package/public/i18n/literals/hi.json +195 -0
- package/public/i18n/literals/hu.json +195 -0
- package/public/i18n/literals/id.json +195 -0
- package/public/i18n/literals/it.json +195 -0
- package/public/i18n/literals/ja.json +195 -0
- package/public/i18n/literals/ko.json +195 -0
- package/public/i18n/literals/nl.json +195 -0
- package/public/i18n/literals/no.json +195 -0
- package/public/i18n/literals/pl.json +195 -0
- package/public/i18n/literals/pt-BR.json +195 -0
- package/public/i18n/literals/pt-PT.json +195 -0
- package/public/i18n/literals/ro.json +195 -0
- package/public/i18n/literals/ru.json +195 -0
- package/public/i18n/literals/sv.json +195 -0
- package/public/i18n/literals/th.json +195 -0
- package/public/i18n/literals/tl.json +195 -0
- package/public/i18n/literals/tr.json +195 -0
- package/public/i18n/literals/uk.json +195 -0
- package/public/i18n/literals/ur.json +195 -0
- package/public/i18n/literals/vi.json +195 -0
- package/public/i18n/literals/zh-CN.json +772 -0
- package/public/i18n/literals/zh-TW.json +195 -0
- package/public/icons/discord.svg +4 -0
- package/public/icons/icon-192.svg +4 -0
- package/public/icons/icon-512.svg +4 -0
- package/public/next.svg +1 -0
- package/public/providers/alicode-intl.png +0 -0
- package/public/providers/alicode.png +0 -0
- package/public/providers/amp.png +0 -0
- package/public/providers/anthropic-m.png +0 -0
- package/public/providers/anthropic.png +0 -0
- package/public/providers/antigravity.png +0 -0
- package/public/providers/assemblyai.png +0 -0
- package/public/providers/aws-polly.png +0 -0
- package/public/providers/azure.png +0 -0
- package/public/providers/black-forest-labs.png +0 -0
- package/public/providers/blackbox.png +0 -0
- package/public/providers/brave-search.png +0 -0
- package/public/providers/byteplus.png +0 -0
- package/public/providers/cartesia.png +0 -0
- package/public/providers/cerebras.png +0 -0
- package/public/providers/chutes.png +0 -0
- package/public/providers/claude.png +0 -0
- package/public/providers/cline.png +0 -0
- package/public/providers/cloudflare-ai.png +0 -0
- package/public/providers/codebuddy.svg +37 -0
- package/public/providers/codex.png +0 -0
- package/public/providers/cohere.png +0 -0
- package/public/providers/comfyui.png +0 -0
- package/public/providers/commandcode.png +0 -0
- package/public/providers/continue.png +0 -0
- package/public/providers/copilot.png +0 -0
- package/public/providers/coqui.png +0 -0
- package/public/providers/cursor.png +0 -0
- package/public/providers/deepgram.png +0 -0
- package/public/providers/deepseek-tui.png +0 -0
- package/public/providers/deepseek.png +0 -0
- package/public/providers/droid.png +0 -0
- package/public/providers/edge-tts.png +0 -0
- package/public/providers/elevenlabs.png +0 -0
- package/public/providers/exa.png +0 -0
- package/public/providers/fal-ai.png +0 -0
- package/public/providers/firecrawl.png +0 -0
- package/public/providers/fireworks.png +0 -0
- package/public/providers/gemini-cli.png +0 -0
- package/public/providers/gemini.png +0 -0
- package/public/providers/github.png +0 -0
- package/public/providers/glm-cn.png +0 -0
- package/public/providers/glm.png +0 -0
- package/public/providers/google-pse.png +0 -0
- package/public/providers/google-tts.png +0 -0
- package/public/providers/grok-web.png +0 -0
- package/public/providers/groq.png +0 -0
- package/public/providers/hermes.png +0 -0
- package/public/providers/huggingface.png +0 -0
- package/public/providers/hyperbolic.png +0 -0
- package/public/providers/iflow.png +0 -0
- package/public/providers/inworld.png +0 -0
- package/public/providers/jcode.png +0 -0
- package/public/providers/jina-ai.png +0 -0
- package/public/providers/jina-reader.png +0 -0
- package/public/providers/kilocode.png +0 -0
- package/public/providers/kimi-coding.png +0 -0
- package/public/providers/kimi.png +0 -0
- package/public/providers/kiro.png +0 -0
- package/public/providers/linkup.png +0 -0
- package/public/providers/local-device.png +0 -0
- package/public/providers/minimax-cn.png +0 -0
- package/public/providers/minimax.png +0 -0
- package/public/providers/mistral.png +0 -0
- package/public/providers/nanobanana.png +0 -0
- package/public/providers/nebius.png +0 -0
- package/public/providers/nvidia.png +0 -0
- package/public/providers/oai-cc.png +0 -0
- package/public/providers/oai-r.png +0 -0
- package/public/providers/ollama-local.png +0 -0
- package/public/providers/ollama.png +0 -0
- package/public/providers/openai.png +0 -0
- package/public/providers/openclaw.png +0 -0
- package/public/providers/opencode-go.png +0 -0
- package/public/providers/opencode.png +0 -0
- package/public/providers/openrouter.png +0 -0
- package/public/providers/perplexity-web.png +0 -0
- package/public/providers/perplexity.png +0 -0
- package/public/providers/playht.png +0 -0
- package/public/providers/qoder.png +0 -0
- package/public/providers/qwen.png +0 -0
- package/public/providers/recraft.png +0 -0
- package/public/providers/roo.png +0 -0
- package/public/providers/runwayml.png +0 -0
- package/public/providers/sdwebui.png +0 -0
- package/public/providers/searchapi.png +0 -0
- package/public/providers/searxng.png +0 -0
- package/public/providers/serper.png +0 -0
- package/public/providers/siliconflow.png +0 -0
- package/public/providers/stability-ai.png +0 -0
- package/public/providers/tavily.png +0 -0
- package/public/providers/together.png +0 -0
- package/public/providers/topaz.png +0 -0
- package/public/providers/tortoise.png +0 -0
- package/public/providers/vertex-partner.png +0 -0
- package/public/providers/vertex.png +0 -0
- package/public/providers/volcengine-ark.png +0 -0
- package/public/providers/voyage-ai.png +0 -0
- package/public/providers/xai.png +0 -0
- package/public/providers/xiaomi-mimo.png +0 -0
- package/public/providers/xiaomi-tokenplan.png +0 -0
- package/public/providers/youcom.png +0 -0
- package/public/sw.js +22 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/compact-request-details.mjs +71 -0
- package/scripts/import-codex-gptjson.mjs +342 -0
- package/scripts/start-standalone.mjs +25 -0
- package/scripts/translate-readme.js +201 -0
- package/src/app/(dashboard)/dashboard/automation/page.js +294 -0
- package/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.js +967 -0
- package/src/app/(dashboard)/dashboard/basic-chat/page.js +5 -0
- package/src/app/(dashboard)/dashboard/cli-tools/CLIToolsPageClient.js +66 -0
- package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/ToolDetailClient.js +173 -0
- package/src/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +11 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/AntigravityToolCard.js +481 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ApiKeySelect.js +66 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/BaseUrlSelect.js +174 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ClaudeToolCard.js +390 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ClineToolCard.js +301 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CodexToolCard.js +458 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CopilotToolCard.js +323 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/CoworkToolCard.js +640 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DeepSeekTuiToolCard.js +338 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DefaultToolCard.js +271 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/DroidToolCard.js +410 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/EndpointPresetControl.js +128 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/HermesToolCard.js +317 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/JcodeToolCard.js +380 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/KiloToolCard.js +275 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmLinkCard.js +40 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmServerCard.js +329 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/MitmToolCard.js +318 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/OpenClawToolCard.js +388 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/OpenCodeToolCard.js +500 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/ToolSummaryCard.js +39 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/cliEndpointMatch.js +13 -0
- package/src/app/(dashboard)/dashboard/cli-tools/components/index.js +19 -0
- package/src/app/(dashboard)/dashboard/cli-tools/page.js +7 -0
- package/src/app/(dashboard)/dashboard/combos/page.js +612 -0
- package/src/app/(dashboard)/dashboard/console-log/ConsoleLogClient.js +91 -0
- package/src/app/(dashboard)/dashboard/console-log/page.js +8 -0
- package/src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.js +1555 -0
- package/src/app/(dashboard)/dashboard/endpoint/page.js +7 -0
- package/src/app/(dashboard)/dashboard/media-providers/[kind]/[id]/page.js +1903 -0
- package/src/app/(dashboard)/dashboard/media-providers/[kind]/page.js +289 -0
- package/src/app/(dashboard)/dashboard/media-providers/combo/[id]/page.js +410 -0
- package/src/app/(dashboard)/dashboard/media-providers/web/page.js +209 -0
- package/src/app/(dashboard)/dashboard/mitm/MitmPageClient.js +117 -0
- package/src/app/(dashboard)/dashboard/mitm/page.js +5 -0
- package/src/app/(dashboard)/dashboard/page.js +7 -0
- package/src/app/(dashboard)/dashboard/profile/page.js +1140 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +389 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/AddCustomModelModal.js +125 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/CompatibleModelsSection.js +250 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/ConnectionRow.js +299 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/CooldownTimer.js +42 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/EditCompatibleNodeModal.js +161 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/ModelRow.js +95 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/PassthroughModelsSection.js +183 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/page.js +2053 -0
- package/src/app/(dashboard)/dashboard/providers/[id]/page.new.js +1724 -0
- package/src/app/(dashboard)/dashboard/providers/components/ConnectionsCard.js +497 -0
- package/src/app/(dashboard)/dashboard/providers/components/ModelAvailabilityBadge.js +185 -0
- package/src/app/(dashboard)/dashboard/providers/components/ModelsCard.js +294 -0
- package/src/app/(dashboard)/dashboard/providers/new/page.js +220 -0
- package/src/app/(dashboard)/dashboard/providers/page.js +1365 -0
- package/src/app/(dashboard)/dashboard/proxy-pools/page.js +1092 -0
- package/src/app/(dashboard)/dashboard/quota/page.js +11 -0
- package/src/app/(dashboard)/dashboard/skills/page.js +112 -0
- package/src/app/(dashboard)/dashboard/translator/page.js +303 -0
- package/src/app/(dashboard)/dashboard/usage/components/OverviewCards.js +35 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/ProviderLimitCard.js +185 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaProgressBar.js +127 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/QuotaTable.js +259 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/index.js +1394 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderLimits/utils.js +244 -0
- package/src/app/(dashboard)/dashboard/usage/components/ProviderTopology.js +327 -0
- package/src/app/(dashboard)/dashboard/usage/components/RequestDetailsTab.js +433 -0
- package/src/app/(dashboard)/dashboard/usage/components/UsageChart.js +141 -0
- package/src/app/(dashboard)/dashboard/usage/components/UsageTable.js +247 -0
- package/src/app/(dashboard)/dashboard/usage/page.js +75 -0
- package/src/app/(dashboard)/layout.js +6 -0
- package/src/app/api/auth/login/route.js +76 -0
- package/src/app/api/auth/logout/route.js +12 -0
- package/src/app/api/auth/oidc/callback/route.js +87 -0
- package/src/app/api/auth/oidc/start/route.js +52 -0
- package/src/app/api/auth/oidc/test/route.js +84 -0
- package/src/app/api/auth/status/route.js +45 -0
- package/src/app/api/cli-tools/all-statuses/route.js +46 -0
- package/src/app/api/cli-tools/antigravity-mitm/alias/route.js +53 -0
- package/src/app/api/cli-tools/antigravity-mitm/route.js +202 -0
- package/src/app/api/cli-tools/claude-settings/route.js +203 -0
- package/src/app/api/cli-tools/cline-settings/route.js +133 -0
- package/src/app/api/cli-tools/codex-gateway/accounts/route.js +16 -0
- package/src/app/api/cli-tools/codex-settings/route.js +239 -0
- package/src/app/api/cli-tools/copilot-settings/route.js +148 -0
- package/src/app/api/cli-tools/cowork-mcp-registry/route.js +77 -0
- package/src/app/api/cli-tools/cowork-mcp-tools/route.js +95 -0
- package/src/app/api/cli-tools/cowork-settings/route.js +412 -0
- package/src/app/api/cli-tools/deepseek-tui-settings/route.js +164 -0
- package/src/app/api/cli-tools/droid-settings/route.js +213 -0
- package/src/app/api/cli-tools/hermes-settings/route.js +175 -0
- package/src/app/api/cli-tools/jcode-settings/route.js +216 -0
- package/src/app/api/cli-tools/kilo-settings/route.js +131 -0
- package/src/app/api/cli-tools/openclaw-settings/route.js +292 -0
- package/src/app/api/cli-tools/opencode-settings/route.js +259 -0
- package/src/app/api/combos/[id]/route.js +81 -0
- package/src/app/api/combos/route.js +48 -0
- package/src/app/api/health/route.js +15 -0
- package/src/app/api/init/route.js +4 -0
- package/src/app/api/keys/[id]/route.js +58 -0
- package/src/app/api/keys/route.js +42 -0
- package/src/app/api/locale/route.js +30 -0
- package/src/app/api/mcp/[plugin]/message/route.js +21 -0
- package/src/app/api/mcp/[plugin]/sse/route.js +37 -0
- package/src/app/api/media-providers/tts/deepgram/voices/route.js +65 -0
- package/src/app/api/media-providers/tts/elevenlabs/voices/route.js +71 -0
- package/src/app/api/media-providers/tts/inworld/voices/route.js +61 -0
- package/src/app/api/media-providers/tts/minimax/voices/route.js +113 -0
- package/src/app/api/media-providers/tts/voices/route.js +99 -0
- package/src/app/api/models/alias/route.js +53 -0
- package/src/app/api/models/availability/route.js +103 -0
- package/src/app/api/models/custom/route.js +48 -0
- package/src/app/api/models/disabled/route.js +50 -0
- package/src/app/api/models/route.js +64 -0
- package/src/app/api/models/test/ping.js +191 -0
- package/src/app/api/models/test/route.js +14 -0
- package/src/app/api/oauth/[provider]/[action]/route.js +343 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/cancel/route.js +19 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/manual/[workerId]/route.js +30 -0
- package/src/app/api/oauth/codebuddy/bulk-import/[jobId]/route.js +23 -0
- package/src/app/api/oauth/codebuddy/bulk-import/latest/route.js +25 -0
- package/src/app/api/oauth/codebuddy/bulk-import/route.js +49 -0
- package/src/app/api/oauth/codebuddy/quota-cookie/route.js +133 -0
- package/src/app/api/oauth/codex/import-token/route.js +96 -0
- package/src/app/api/oauth/cursor/auto-import/route.js +258 -0
- package/src/app/api/oauth/cursor/import/route.js +100 -0
- package/src/app/api/oauth/gitlab/pat/route.js +62 -0
- package/src/app/api/oauth/iflow/cookie/route.js +137 -0
- package/src/app/api/oauth/kiro/auto-import/route.js +85 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/cancel/route.js +18 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/manual/[workerId]/route.js +29 -0
- package/src/app/api/oauth/kiro/bulk-import/[jobId]/route.js +22 -0
- package/src/app/api/oauth/kiro/bulk-import/latest/route.js +25 -0
- package/src/app/api/oauth/kiro/bulk-import/route.js +49 -0
- package/src/app/api/oauth/kiro/import/route.js +110 -0
- package/src/app/api/oauth/kiro/social-authorize/route.js +27 -0
- package/src/app/api/oauth/kiro/social-exchange/route.js +41 -0
- package/src/app/api/pricing/route.js +134 -0
- package/src/app/api/provider-nodes/[id]/route.js +101 -0
- package/src/app/api/provider-nodes/route.js +104 -0
- package/src/app/api/provider-nodes/validate/route.js +201 -0
- package/src/app/api/providers/[id]/models/route.js +526 -0
- package/src/app/api/providers/[id]/route.js +189 -0
- package/src/app/api/providers/[id]/test/route.js +23 -0
- package/src/app/api/providers/[id]/test/testUtils.js +714 -0
- package/src/app/api/providers/[id]/test-models/route.js +66 -0
- package/src/app/api/providers/client/route.js +126 -0
- package/src/app/api/providers/kilo/free-models/route.js +55 -0
- package/src/app/api/providers/route.js +206 -0
- package/src/app/api/providers/suggested-models/filters.js +20 -0
- package/src/app/api/providers/suggested-models/route.js +32 -0
- package/src/app/api/providers/test-batch/route.js +131 -0
- package/src/app/api/providers/validate/route.js +637 -0
- package/src/app/api/proxy-pools/[id]/route.js +123 -0
- package/src/app/api/proxy-pools/[id]/test/route.js +70 -0
- package/src/app/api/proxy-pools/cloudflare-deploy/route.js +145 -0
- package/src/app/api/proxy-pools/deno-deploy/route.js +175 -0
- package/src/app/api/proxy-pools/route.js +93 -0
- package/src/app/api/proxy-pools/vercel-deploy/route.js +142 -0
- package/src/app/api/settings/database/route.js +36 -0
- package/src/app/api/settings/proxy-test/route.js +23 -0
- package/src/app/api/settings/require-login/route.js +15 -0
- package/src/app/api/settings/route.js +100 -0
- package/src/app/api/shutdown/route.js +24 -0
- package/src/app/api/tags/route.js +18 -0
- package/src/app/api/translator/console-logs/route.js +24 -0
- package/src/app/api/translator/console-logs/stream/route.js +79 -0
- package/src/app/api/translator/load/route.js +45 -0
- package/src/app/api/translator/save/route.js +44 -0
- package/src/app/api/translator/send/route.js +94 -0
- package/src/app/api/translator/translate/route.js +90 -0
- package/src/app/api/tunnel/disable/route.js +12 -0
- package/src/app/api/tunnel/enable/route.js +16 -0
- package/src/app/api/tunnel/status/route.js +13 -0
- package/src/app/api/tunnel/tailscale-check/route.js +50 -0
- package/src/app/api/tunnel/tailscale-disable/route.js +12 -0
- package/src/app/api/tunnel/tailscale-enable/route.js +12 -0
- package/src/app/api/tunnel/tailscale-install/route.js +72 -0
- package/src/app/api/usage/[connectionId]/route.js +188 -0
- package/src/app/api/usage/chart/route.js +21 -0
- package/src/app/api/usage/history/route.js +12 -0
- package/src/app/api/usage/logs/route.js +12 -0
- package/src/app/api/usage/providers/route.js +42 -0
- package/src/app/api/usage/request-details/route.js +57 -0
- package/src/app/api/usage/request-logs/route.js +13 -0
- package/src/app/api/usage/stats/route.js +23 -0
- package/src/app/api/usage/stream/route.js +79 -0
- package/src/app/api/v1/api/chat/route.js +37 -0
- package/src/app/api/v1/audio/speech/route.js +16 -0
- package/src/app/api/v1/audio/transcriptions/route.js +19 -0
- package/src/app/api/v1/audio/voices/route.js +68 -0
- package/src/app/api/v1/chat/completions/route.js +35 -0
- package/src/app/api/v1/embeddings/route.js +21 -0
- package/src/app/api/v1/images/generations/route.js +16 -0
- package/src/app/api/v1/messages/count_tokens/route.js +52 -0
- package/src/app/api/v1/messages/route.js +36 -0
- package/src/app/api/v1/models/[kind]/route.js +55 -0
- package/src/app/api/v1/models/info/route.js +110 -0
- package/src/app/api/v1/models/route.js +451 -0
- package/src/app/api/v1/responses/compact/route.js +37 -0
- package/src/app/api/v1/responses/route.js +30 -0
- package/src/app/api/v1/route.js +1 -0
- package/src/app/api/v1/search/route.js +21 -0
- package/src/app/api/v1/web/fetch/route.js +21 -0
- package/src/app/api/v1beta/models/[...path]/route.js +328 -0
- package/src/app/api/v1beta/models/route.js +44 -0
- package/src/app/api/version/route.js +45 -0
- package/src/app/api/version/shutdown/route.js +15 -0
- package/src/app/api/version/update/route.js +21 -0
- package/src/app/callback/page.js +148 -0
- package/src/app/dashboard/settings/pricing/page.js +173 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +496 -0
- package/src/app/landing/components/AnimatedBackground.js +57 -0
- package/src/app/landing/components/Features.js +133 -0
- package/src/app/landing/components/FlowAnimation.js +175 -0
- package/src/app/landing/components/Footer.js +61 -0
- package/src/app/landing/components/GetStarted.js +97 -0
- package/src/app/landing/components/HeroSection.js +47 -0
- package/src/app/landing/components/HowItWorks.js +66 -0
- package/src/app/landing/components/Navigation.js +72 -0
- package/src/app/landing/page.js +106 -0
- package/src/app/layout.js +49 -0
- package/src/app/login/page.js +197 -0
- package/src/app/manifest.js +30 -0
- package/src/app/page.js +5 -0
- package/src/dashboardGuard.js +242 -0
- package/src/i18n/RuntimeI18nProvider.js +27 -0
- package/src/i18n/config.js +146 -0
- package/src/i18n/runtime.js +162 -0
- package/src/lib/appUpdater.js +200 -0
- package/src/lib/auth/dashboardSession.js +68 -0
- package/src/lib/auth/loginLimiter.js +52 -0
- package/src/lib/auth/oidc.js +234 -0
- package/src/lib/consoleLogBuffer.js +79 -0
- package/src/lib/dataDir.js +29 -0
- package/src/lib/db/adapters/betterSqliteAdapter.js +55 -0
- package/src/lib/db/adapters/bunSqliteAdapter.js +63 -0
- package/src/lib/db/adapters/nodeSqliteAdapter.js +84 -0
- package/src/lib/db/adapters/sqljsAdapter.js +115 -0
- package/src/lib/db/backup.js +35 -0
- package/src/lib/db/driver.js +85 -0
- package/src/lib/db/helpers/jsonCol.js +9 -0
- package/src/lib/db/helpers/kvStore.js +39 -0
- package/src/lib/db/helpers/metaStore.js +22 -0
- package/src/lib/db/index.js +171 -0
- package/src/lib/db/migrate.js +286 -0
- package/src/lib/db/migrations/001-initial.js +14 -0
- package/src/lib/db/migrations/index.js +10 -0
- package/src/lib/db/paths.js +18 -0
- package/src/lib/db/repos/aliasRepo.js +62 -0
- package/src/lib/db/repos/apiKeysRepo.js +75 -0
- package/src/lib/db/repos/combosRepo.js +73 -0
- package/src/lib/db/repos/connectionsRepo.js +226 -0
- package/src/lib/db/repos/disabledModelsRepo.js +56 -0
- package/src/lib/db/repos/nodesRepo.js +95 -0
- package/src/lib/db/repos/pricingRepo.js +108 -0
- package/src/lib/db/repos/proxyPoolsRepo.js +103 -0
- package/src/lib/db/repos/requestDetailsRepo.js +259 -0
- package/src/lib/db/repos/settingsRepo.js +104 -0
- package/src/lib/db/repos/usageRepo.js +731 -0
- package/src/lib/db/schema.js +157 -0
- package/src/lib/db/version.js +21 -0
- package/src/lib/disabledModelsDb.js +4 -0
- package/src/lib/localDb.js +21 -0
- package/src/lib/mcp/stdioSseBridge.js +198 -0
- package/src/lib/mitmAliasCache.js +46 -0
- package/src/lib/network/connectionProxy.js +160 -0
- package/src/lib/network/initOutboundProxy.js +25 -0
- package/src/lib/network/outboundProxy.js +68 -0
- package/src/lib/network/proxyTest.js +91 -0
- package/src/lib/oauth/constants/oauth.js +284 -0
- package/src/lib/oauth/constants/xai.js +61 -0
- package/src/lib/oauth/providers.js +1506 -0
- package/src/lib/oauth/services/antigravity.js +321 -0
- package/src/lib/oauth/services/claude.js +136 -0
- package/src/lib/oauth/services/codebuddyBulkImportManager.js +454 -0
- package/src/lib/oauth/services/codex.js +144 -0
- package/src/lib/oauth/services/cursor.js +179 -0
- package/src/lib/oauth/services/gemini.js +240 -0
- package/src/lib/oauth/services/github.js +225 -0
- package/src/lib/oauth/services/iflow.js +202 -0
- package/src/lib/oauth/services/index.js +17 -0
- package/src/lib/oauth/services/kiro.js +334 -0
- package/src/lib/oauth/services/kiroBulkImportManager.js +778 -0
- package/src/lib/oauth/services/kiroConnections.js +93 -0
- package/src/lib/oauth/services/kiroGoogleAutomation.js +1136 -0
- package/src/lib/oauth/services/oauth.js +157 -0
- package/src/lib/oauth/services/openai.js +123 -0
- package/src/lib/oauth/services/qoder.js +216 -0
- package/src/lib/oauth/services/qwen.js +170 -0
- package/src/lib/oauth/services/xai.js +238 -0
- package/src/lib/oauth/utils/banner.js +63 -0
- package/src/lib/oauth/utils/pkce.js +39 -0
- package/src/lib/oauth/utils/server.js +415 -0
- package/src/lib/oauth/utils/ui.js +48 -0
- package/src/lib/providerNormalization.js +45 -0
- package/src/lib/qoder/constants.js +64 -0
- package/src/lib/qoder/cosy.js +175 -0
- package/src/lib/qoder/encoding.js +55 -0
- package/src/lib/requestDetailsDb.js +4 -0
- package/src/lib/tunnel/cloudflare/cloudflared.js +449 -0
- package/src/lib/tunnel/cloudflare/config.js +9 -0
- package/src/lib/tunnel/cloudflare/healthCheck.js +29 -0
- package/src/lib/tunnel/cloudflare/manager.js +151 -0
- package/src/lib/tunnel/cloudflare/pid.js +23 -0
- package/src/lib/tunnel/index.js +48 -0
- package/src/lib/tunnel/shared/dnsResolver.js +17 -0
- package/src/lib/tunnel/shared/internetCheck.js +26 -0
- package/src/lib/tunnel/shared/state.js +41 -0
- package/src/lib/tunnel/shared/watchdogConfig.js +8 -0
- package/src/lib/tunnel/tailscale/config.js +7 -0
- package/src/lib/tunnel/tailscale/healthCheck.js +29 -0
- package/src/lib/tunnel/tailscale/manager.js +129 -0
- package/src/lib/tunnel/tailscale/tailscale.js +790 -0
- package/src/lib/updater/updater.js +235 -0
- package/src/lib/usage/fetcher.js +208 -0
- package/src/lib/usageDb.js +7 -0
- package/src/mitm/antigravityIdeVersion.js +50 -0
- package/src/mitm/cert/generate.js +32 -0
- package/src/mitm/cert/install.js +269 -0
- package/src/mitm/cert/rootCA.js +173 -0
- package/src/mitm/config.js +87 -0
- package/src/mitm/dbReader.js +22 -0
- package/src/mitm/dns/dnsConfig.js +266 -0
- package/src/mitm/handlers/antigravity.js +33 -0
- package/src/mitm/handlers/base.js +226 -0
- package/src/mitm/handlers/copilot.js +35 -0
- package/src/mitm/handlers/cursor.js +15 -0
- package/src/mitm/handlers/kiro.js +526 -0
- package/src/mitm/logger.js +106 -0
- package/src/mitm/manager.js +851 -0
- package/src/mitm/paths.js +32 -0
- package/src/mitm/server.js +435 -0
- package/src/mitm/winElevated.js +81 -0
- package/src/models/index.js +38 -0
- package/src/proxy.js +5 -0
- package/src/shared/components/AddCustomEmbeddingModal.js +183 -0
- package/src/shared/components/Avatar.js +88 -0
- package/src/shared/components/Badge.js +54 -0
- package/src/shared/components/BulkAccountAutomationModal.js +508 -0
- package/src/shared/components/Button.js +56 -0
- package/src/shared/components/Card.js +116 -0
- package/src/shared/components/ChangelogModal.js +97 -0
- package/src/shared/components/CodeBuddyQuotaCookieModal.js +109 -0
- package/src/shared/components/ComboFormModal.js +176 -0
- package/src/shared/components/CursorAuthModal.js +212 -0
- package/src/shared/components/DonateModal.js +136 -0
- package/src/shared/components/Drawer.js +82 -0
- package/src/shared/components/EditConnectionModal.js +286 -0
- package/src/shared/components/Footer.js +132 -0
- package/src/shared/components/GitLabAuthModal.js +194 -0
- package/src/shared/components/Header.js +380 -0
- package/src/shared/components/HeaderLanguage.js +46 -0
- package/src/shared/components/HeaderMenu.js +126 -0
- package/src/shared/components/IFlowCookieModal.js +132 -0
- package/src/shared/components/Input.js +65 -0
- package/src/shared/components/KiroAuthModal.js +1171 -0
- package/src/shared/components/KiroOAuthWrapper.js +149 -0
- package/src/shared/components/KiroSocialOAuthModal.js +205 -0
- package/src/shared/components/LanguageSwitcher.js +190 -0
- package/src/shared/components/Loading.js +75 -0
- package/src/shared/components/ManualConfigModal.js +44 -0
- package/src/shared/components/McpMarketplaceModal.js +255 -0
- package/src/shared/components/Modal.js +146 -0
- package/src/shared/components/ModelSelectModal.js +537 -0
- package/src/shared/components/NineRemoteButton.js +23 -0
- package/src/shared/components/NineRemotePromoModal.js +99 -0
- package/src/shared/components/NoAuthProxyCard.js +86 -0
- package/src/shared/components/OAuthModal.js +682 -0
- package/src/shared/components/Pagination.js +150 -0
- package/src/shared/components/PricingModal.js +208 -0
- package/src/shared/components/ProviderIcon.js +63 -0
- package/src/shared/components/ProviderInfoCard.js +82 -0
- package/src/shared/components/RequestLogger.js +121 -0
- package/src/shared/components/SegmentedControl.js +48 -0
- package/src/shared/components/Select.js +67 -0
- package/src/shared/components/Sidebar.js +441 -0
- package/src/shared/components/ThemeProvider.js +15 -0
- package/src/shared/components/ThemeToggle.js +42 -0
- package/src/shared/components/Toggle.js +69 -0
- package/src/shared/components/Tooltip.js +25 -0
- package/src/shared/components/UsageStats.js +505 -0
- package/src/shared/components/index.js +46 -0
- package/src/shared/components/layouts/AuthLayout.js +29 -0
- package/src/shared/components/layouts/DashboardLayout.js +104 -0
- package/src/shared/components/layouts/index.js +4 -0
- package/src/shared/constants/cliTools.js +397 -0
- package/src/shared/constants/colors.js +77 -0
- package/src/shared/constants/config.js +99 -0
- package/src/shared/constants/coworkPlugins.js +75 -0
- package/src/shared/constants/index.js +4 -0
- package/src/shared/constants/locales.js +36 -0
- package/src/shared/constants/mitmToolHosts.js +12 -0
- package/src/shared/constants/models.js +38 -0
- package/src/shared/constants/pricing.js +303 -0
- package/src/shared/constants/providers.js +289 -0
- package/src/shared/constants/skills.js +78 -0
- package/src/shared/constants/ttsProviders.js +138 -0
- package/src/shared/hooks/index.js +2 -0
- package/src/shared/hooks/useCopyToClipboard.js +43 -0
- package/src/shared/hooks/useTheme.js +60 -0
- package/src/shared/services/bootstrap.js +12 -0
- package/src/shared/services/initializeApp.js +268 -0
- package/src/shared/utils/api.js +93 -0
- package/src/shared/utils/apiKey.js +98 -0
- package/src/shared/utils/clineAuth.js +37 -0
- package/src/shared/utils/cn.js +11 -0
- package/src/shared/utils/connectionStatus.js +162 -0
- package/src/shared/utils/index.js +40 -0
- package/src/shared/utils/machine.js +6 -0
- package/src/shared/utils/machineId.js +66 -0
- package/src/shared/utils/providerModelsFetcher.js +30 -0
- package/src/sse/handlers/chat.js +261 -0
- package/src/sse/handlers/embeddings.js +141 -0
- package/src/sse/handlers/fetch.js +213 -0
- package/src/sse/handlers/imageGeneration.js +142 -0
- package/src/sse/handlers/search.js +206 -0
- package/src/sse/handlers/stt.js +88 -0
- package/src/sse/handlers/tts.js +114 -0
- package/src/sse/services/auth.js +346 -0
- package/src/sse/services/codexGateway.js +215 -0
- package/src/sse/services/model.js +99 -0
- package/src/sse/services/tokenRefresh.js +319 -0
- package/src/sse/utils/logger.js +75 -0
- package/src/store/headerSearchStore.js +19 -0
- package/src/store/index.js +6 -0
- package/src/store/notificationStore.js +45 -0
- package/src/store/providerStore.js +55 -0
- package/src/store/settingsStore.js +51 -0
- package/src/store/themeStore.js +54 -0
- package/src/store/userStore.js +20 -0
- 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 & 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
|
+
}
|