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,1171 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import Image from "next/image";
|
|
5
|
+
import PropTypes from "prop-types";
|
|
6
|
+
import { Modal, Button, Input } from "@/shared/components";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_CONCURRENCY = 4;
|
|
9
|
+
const BULK_JOB_STORAGE_KEY = "kiro-bulk-import-active-job";
|
|
10
|
+
const JOB_SESSION_EXPIRED_MESSAGE = "Bulk import progress could not be restored. The previous job session was cleared.";
|
|
11
|
+
|
|
12
|
+
function isJobTerminal(status) {
|
|
13
|
+
return ["completed", "cancelled", "failed"].includes(status);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isJobActive(status) {
|
|
17
|
+
return ["queued", "running", "needs_manual"].includes(status);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatStepLabel(step) {
|
|
21
|
+
if (!step) return "waiting";
|
|
22
|
+
return step.replaceAll("_", " ");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function formatClock(value) {
|
|
26
|
+
if (!value) return "--:--:--";
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
return new Date(value).toLocaleTimeString("id-ID", {
|
|
30
|
+
hour: "2-digit",
|
|
31
|
+
minute: "2-digit",
|
|
32
|
+
second: "2-digit",
|
|
33
|
+
});
|
|
34
|
+
} catch {
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function groupAccountsByStatus(accounts = []) {
|
|
40
|
+
const order = ["running", "queued", "needs_manual", "success", "failed", "failed_invalid_credentials", "failed_exchange", "failed_timeout", "cancelled"];
|
|
41
|
+
const grouped = new Map();
|
|
42
|
+
|
|
43
|
+
accounts.forEach((account) => {
|
|
44
|
+
const key = account.status || "queued";
|
|
45
|
+
if (!grouped.has(key)) grouped.set(key, []);
|
|
46
|
+
grouped.get(key).push(account);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return order
|
|
50
|
+
.filter((status) => grouped.has(status))
|
|
51
|
+
.map((status) => ({ status, accounts: grouped.get(status) }));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getStatusPanelClasses(status) {
|
|
55
|
+
const styles = {
|
|
56
|
+
queued: "border-slate-200 bg-slate-50/80 dark:border-slate-800 dark:bg-slate-900/20",
|
|
57
|
+
running: "border-blue-200 bg-blue-50/80 dark:border-blue-900/50 dark:bg-blue-950/20",
|
|
58
|
+
success: "border-green-200 bg-green-50/80 dark:border-green-900/50 dark:bg-green-950/20",
|
|
59
|
+
failed: "border-red-200 bg-red-50/80 dark:border-red-900/50 dark:bg-red-950/20",
|
|
60
|
+
failed_invalid_credentials: "border-red-200 bg-red-50/80 dark:border-red-900/50 dark:bg-red-950/20",
|
|
61
|
+
failed_exchange: "border-red-200 bg-red-50/80 dark:border-red-900/50 dark:bg-red-950/20",
|
|
62
|
+
failed_timeout: "border-red-200 bg-red-50/80 dark:border-red-900/50 dark:bg-red-950/20",
|
|
63
|
+
needs_manual: "border-amber-200 bg-amber-50/80 dark:border-amber-900/50 dark:bg-amber-950/20",
|
|
64
|
+
cancelled: "border-slate-200 bg-slate-50/80 dark:border-slate-800 dark:bg-slate-900/20",
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
return styles[status] || styles.queued;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function fetchBulkJobById(jobId) {
|
|
71
|
+
if (!jobId) return null;
|
|
72
|
+
const res = await fetch(`/api/oauth/kiro/bulk-import/${jobId}`, { cache: "no-store" });
|
|
73
|
+
const data = await res.json();
|
|
74
|
+
return { res, data };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function fetchLatestBulkJob(scope = "active") {
|
|
78
|
+
const res = await fetch(`/api/oauth/kiro/bulk-import/latest?scope=${encodeURIComponent(scope)}`, { cache: "no-store" });
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
return { res, data };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function AccountStatusBadge({ status }) {
|
|
84
|
+
const palette = {
|
|
85
|
+
queued: "bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-200",
|
|
86
|
+
running: "bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300",
|
|
87
|
+
success: "bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300",
|
|
88
|
+
failed: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300",
|
|
89
|
+
failed_invalid_credentials: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300",
|
|
90
|
+
failed_exchange: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300",
|
|
91
|
+
failed_timeout: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300",
|
|
92
|
+
needs_manual: "bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-300",
|
|
93
|
+
cancelled: "bg-slate-100 text-slate-700 dark:bg-slate-800 dark:text-slate-200",
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<span className={`rounded-full px-2 py-1 text-[11px] font-medium ${palette[status] || palette.queued}`}>
|
|
98
|
+
{status.replaceAll("_", " ")}
|
|
99
|
+
</span>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
AccountStatusBadge.propTypes = {
|
|
104
|
+
status: PropTypes.string.isRequired,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export default function KiroAuthModal({
|
|
108
|
+
isOpen,
|
|
109
|
+
onMethodSelect,
|
|
110
|
+
onImportSuccess,
|
|
111
|
+
onClose,
|
|
112
|
+
initialJobId,
|
|
113
|
+
initialSelectedMethod,
|
|
114
|
+
initialImportMode,
|
|
115
|
+
initialFlowKey,
|
|
116
|
+
onBulkJobChange,
|
|
117
|
+
}) {
|
|
118
|
+
const [selectedMethod, setSelectedMethod] = useState(null);
|
|
119
|
+
const [idcStartUrl, setIdcStartUrl] = useState("");
|
|
120
|
+
const [idcRegion, setIdcRegion] = useState("us-east-1");
|
|
121
|
+
const [refreshToken, setRefreshToken] = useState("");
|
|
122
|
+
const [importMode, setImportMode] = useState("single-token");
|
|
123
|
+
const [bulkText, setBulkText] = useState("");
|
|
124
|
+
const [concurrency, setConcurrency] = useState(String(DEFAULT_CONCURRENCY));
|
|
125
|
+
const [bulkResult, setBulkResult] = useState(null);
|
|
126
|
+
const [activeJob, setActiveJob] = useState(null);
|
|
127
|
+
const [error, setError] = useState(null);
|
|
128
|
+
const [jobRestoreNotice, setJobRestoreNotice] = useState(null);
|
|
129
|
+
const [importing, setImporting] = useState(false);
|
|
130
|
+
const [autoDetecting, setAutoDetecting] = useState(false);
|
|
131
|
+
const [autoDetected, setAutoDetected] = useState(false);
|
|
132
|
+
const completedRefreshJobsRef = useRef(new Set());
|
|
133
|
+
const previousSuccessCountRef = useRef(0);
|
|
134
|
+
const resumeJobId = isOpen
|
|
135
|
+
? (initialJobId || (typeof window !== "undefined"
|
|
136
|
+
? window.localStorage.getItem(BULK_JOB_STORAGE_KEY)
|
|
137
|
+
: null))
|
|
138
|
+
: null;
|
|
139
|
+
const effectiveSelectedMethod = selectedMethod || (resumeJobId ? "import" : null);
|
|
140
|
+
const effectiveImportMode = effectiveSelectedMethod === "import" && resumeJobId && !selectedMethod
|
|
141
|
+
? "bulk-account"
|
|
142
|
+
: importMode;
|
|
143
|
+
|
|
144
|
+
useEffect(() => {
|
|
145
|
+
if (!isOpen) return;
|
|
146
|
+
if (!initialSelectedMethod) return;
|
|
147
|
+
setSelectedMethod(initialSelectedMethod);
|
|
148
|
+
setImportMode(initialImportMode || "single-token");
|
|
149
|
+
setBulkResult(null);
|
|
150
|
+
setError(null);
|
|
151
|
+
}, [initialFlowKey, initialImportMode, initialSelectedMethod, isOpen]);
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
if (effectiveSelectedMethod !== "import" || effectiveImportMode !== "single-token" || !isOpen) return;
|
|
155
|
+
|
|
156
|
+
const autoDetect = async () => {
|
|
157
|
+
setAutoDetecting(true);
|
|
158
|
+
setError(null);
|
|
159
|
+
setAutoDetected(false);
|
|
160
|
+
|
|
161
|
+
try {
|
|
162
|
+
const res = await fetch("/api/oauth/kiro/auto-import");
|
|
163
|
+
const data = await res.json();
|
|
164
|
+
|
|
165
|
+
if (data.found) {
|
|
166
|
+
setRefreshToken(data.refreshToken);
|
|
167
|
+
setAutoDetected(true);
|
|
168
|
+
} else {
|
|
169
|
+
setError(data.error || "Could not auto-detect token");
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
setError("Failed to auto-detect token");
|
|
173
|
+
} finally {
|
|
174
|
+
setAutoDetecting(false);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
autoDetect();
|
|
179
|
+
}, [effectiveSelectedMethod, effectiveImportMode, isOpen]);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
if (!isOpen || effectiveSelectedMethod !== "import" || effectiveImportMode !== "bulk-account") return undefined;
|
|
183
|
+
|
|
184
|
+
const restoreJob = async () => {
|
|
185
|
+
if (activeJob?.jobId) return;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
setJobRestoreNotice(null);
|
|
189
|
+
|
|
190
|
+
const preferredJobId = initialJobId || null;
|
|
191
|
+
if (preferredJobId) {
|
|
192
|
+
const preferred = await fetchBulkJobById(preferredJobId);
|
|
193
|
+
if (preferred.res.ok && preferred.data?.job) {
|
|
194
|
+
setActiveJob(preferred.data.job);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const latest = await fetchLatestBulkJob();
|
|
200
|
+
if (latest.res.ok && latest.data?.job) {
|
|
201
|
+
setActiveJob(latest.data.job);
|
|
202
|
+
if (typeof window !== "undefined") {
|
|
203
|
+
window.localStorage.setItem(BULK_JOB_STORAGE_KEY, latest.data.job.jobId);
|
|
204
|
+
}
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (resumeJobId) {
|
|
209
|
+
const stored = await fetchBulkJobById(resumeJobId);
|
|
210
|
+
if (stored.res.ok && stored.data?.job && isJobActive(stored.data.job.status)) {
|
|
211
|
+
setActiveJob(stored.data.job);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (typeof window !== "undefined") {
|
|
216
|
+
window.localStorage.removeItem(BULK_JOB_STORAGE_KEY);
|
|
217
|
+
}
|
|
218
|
+
onBulkJobChange?.(null);
|
|
219
|
+
setJobRestoreNotice(JOB_SESSION_EXPIRED_MESSAGE);
|
|
220
|
+
}
|
|
221
|
+
} catch {
|
|
222
|
+
setJobRestoreNotice(JOB_SESSION_EXPIRED_MESSAGE);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
void restoreJob();
|
|
227
|
+
}, [activeJob?.jobId, effectiveImportMode, effectiveSelectedMethod, initialJobId, isOpen, onBulkJobChange, resumeJobId]);
|
|
228
|
+
|
|
229
|
+
useEffect(() => {
|
|
230
|
+
if (!activeJob?.jobId) {
|
|
231
|
+
previousSuccessCountRef.current = 0;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const currentSuccessCount = activeJob.summary?.success || 0;
|
|
236
|
+
if (currentSuccessCount > previousSuccessCountRef.current) {
|
|
237
|
+
onImportSuccess?.();
|
|
238
|
+
}
|
|
239
|
+
previousSuccessCountRef.current = currentSuccessCount;
|
|
240
|
+
}, [activeJob, onImportSuccess]);
|
|
241
|
+
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
if (!activeJob?.jobId) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (typeof window !== "undefined") {
|
|
248
|
+
window.localStorage.setItem(BULK_JOB_STORAGE_KEY, activeJob.jobId);
|
|
249
|
+
}
|
|
250
|
+
onBulkJobChange?.(activeJob);
|
|
251
|
+
}, [activeJob, onBulkJobChange]);
|
|
252
|
+
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
if (!isOpen || !activeJob?.jobId || isJobTerminal(activeJob.status)) return undefined;
|
|
255
|
+
|
|
256
|
+
const interval = window.setInterval(async () => {
|
|
257
|
+
try {
|
|
258
|
+
const current = await fetchBulkJobById(activeJob.jobId);
|
|
259
|
+
if (current.res.ok && current.data?.job) {
|
|
260
|
+
setJobRestoreNotice(null);
|
|
261
|
+
setActiveJob(current.data.job);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (current.res.status === 404) {
|
|
266
|
+
const latest = await fetchLatestBulkJob();
|
|
267
|
+
if (latest.res.ok && latest.data?.job) {
|
|
268
|
+
setJobRestoreNotice(null);
|
|
269
|
+
setActiveJob(latest.data.job);
|
|
270
|
+
if (typeof window !== "undefined") {
|
|
271
|
+
window.localStorage.setItem(BULK_JOB_STORAGE_KEY, latest.data.job.jobId);
|
|
272
|
+
}
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (typeof window !== "undefined") {
|
|
277
|
+
window.localStorage.removeItem(BULK_JOB_STORAGE_KEY);
|
|
278
|
+
}
|
|
279
|
+
onBulkJobChange?.(null);
|
|
280
|
+
setActiveJob(null);
|
|
281
|
+
setJobRestoreNotice("Bulk import progress expired or was cleared.");
|
|
282
|
+
}
|
|
283
|
+
} catch {
|
|
284
|
+
// Keep last-known UI state and let user retry/cancel manually.
|
|
285
|
+
}
|
|
286
|
+
}, 1500);
|
|
287
|
+
|
|
288
|
+
return () => window.clearInterval(interval);
|
|
289
|
+
}, [activeJob?.jobId, activeJob?.status, isOpen, onBulkJobChange]);
|
|
290
|
+
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
if (!isOpen || effectiveImportMode !== "bulk-account" || !activeJob?.jobId || !isJobTerminal(activeJob.status)) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if ((activeJob.summary?.success || 0) <= 0) return;
|
|
297
|
+
if (completedRefreshJobsRef.current.has(activeJob.jobId)) return;
|
|
298
|
+
|
|
299
|
+
completedRefreshJobsRef.current.add(activeJob.jobId);
|
|
300
|
+
onImportSuccess?.();
|
|
301
|
+
}, [activeJob, effectiveImportMode, isOpen, onImportSuccess]);
|
|
302
|
+
|
|
303
|
+
const runningBulkAccountJob = effectiveImportMode === "bulk-account" && activeJob && !isJobTerminal(activeJob.status);
|
|
304
|
+
const finishedBulkAccountJob = effectiveImportMode === "bulk-account" && activeJob && isJobTerminal(activeJob.status);
|
|
305
|
+
|
|
306
|
+
const summaryItems = useMemo(() => {
|
|
307
|
+
if (!activeJob?.summary) return [];
|
|
308
|
+
return [
|
|
309
|
+
["Total", activeJob.summary.total],
|
|
310
|
+
["Queued", activeJob.summary.queued],
|
|
311
|
+
["Running", activeJob.summary.running],
|
|
312
|
+
["Success", activeJob.summary.success],
|
|
313
|
+
["Failed", activeJob.summary.failed],
|
|
314
|
+
["Manual", activeJob.summary.needs_manual],
|
|
315
|
+
];
|
|
316
|
+
}, [activeJob]);
|
|
317
|
+
|
|
318
|
+
const activityItems = useMemo(
|
|
319
|
+
() => (Array.isArray(activeJob?.activity) ? [...activeJob.activity].reverse() : []),
|
|
320
|
+
[activeJob]
|
|
321
|
+
);
|
|
322
|
+
const groupedAccounts = useMemo(
|
|
323
|
+
() => groupAccountsByStatus(activeJob?.accounts || []),
|
|
324
|
+
[activeJob]
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const spotlightAccount = useMemo(() => {
|
|
328
|
+
if (!activeJob?.accounts?.length) return null;
|
|
329
|
+
return activeJob.accounts.find((account) => account.status === "running")
|
|
330
|
+
|| activeJob.accounts.find((account) => account.status === "needs_manual")
|
|
331
|
+
|| activeJob.accounts.find((account) => account.status === "success")
|
|
332
|
+
|| activeJob.accounts[0];
|
|
333
|
+
}, [activeJob]);
|
|
334
|
+
|
|
335
|
+
const handleMethodSelect = (method) => {
|
|
336
|
+
setSelectedMethod(method);
|
|
337
|
+
setError(null);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const resetImportState = () => {
|
|
341
|
+
setRefreshToken("");
|
|
342
|
+
setBulkText("");
|
|
343
|
+
setBulkResult(null);
|
|
344
|
+
setConcurrency(String(DEFAULT_CONCURRENCY));
|
|
345
|
+
setError(null);
|
|
346
|
+
setJobRestoreNotice(null);
|
|
347
|
+
setAutoDetected(false);
|
|
348
|
+
setActiveJob(null);
|
|
349
|
+
previousSuccessCountRef.current = 0;
|
|
350
|
+
if (typeof window !== "undefined") {
|
|
351
|
+
window.localStorage.removeItem(BULK_JOB_STORAGE_KEY);
|
|
352
|
+
}
|
|
353
|
+
onBulkJobChange?.(null);
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const handleBack = () => {
|
|
357
|
+
if (runningBulkAccountJob) {
|
|
358
|
+
onClose?.();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
setSelectedMethod(null);
|
|
362
|
+
setImportMode("single-token");
|
|
363
|
+
resetImportState();
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const handleImportToken = async () => {
|
|
367
|
+
if (!refreshToken.trim()) {
|
|
368
|
+
setError("Please enter a refresh token");
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
setImporting(true);
|
|
373
|
+
setError(null);
|
|
374
|
+
|
|
375
|
+
try {
|
|
376
|
+
const res = await fetch("/api/oauth/kiro/import", {
|
|
377
|
+
method: "POST",
|
|
378
|
+
headers: { "Content-Type": "application/json" },
|
|
379
|
+
body: JSON.stringify({
|
|
380
|
+
mode: "token",
|
|
381
|
+
refreshToken: refreshToken.trim(),
|
|
382
|
+
}),
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
const data = await res.json();
|
|
386
|
+
if (!res.ok) {
|
|
387
|
+
throw new Error(data.error || "Import failed");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
onImportSuccess?.();
|
|
391
|
+
} catch (err) {
|
|
392
|
+
setError(err.message);
|
|
393
|
+
} finally {
|
|
394
|
+
setImporting(false);
|
|
395
|
+
}
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const handleBulkTokenImport = async (lines) => {
|
|
399
|
+
const res = await fetch("/api/oauth/kiro/import", {
|
|
400
|
+
method: "POST",
|
|
401
|
+
headers: { "Content-Type": "application/json" },
|
|
402
|
+
body: JSON.stringify({
|
|
403
|
+
mode: "token",
|
|
404
|
+
refreshTokens: lines,
|
|
405
|
+
}),
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
const data = await res.json();
|
|
409
|
+
if (!res.ok) {
|
|
410
|
+
throw new Error(data.error || "Bulk import failed");
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
setBulkResult({
|
|
414
|
+
success: data.imported || 0,
|
|
415
|
+
failed: data.failed || 0,
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
onImportSuccess?.();
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const handleBulkAccountImport = async (lines) => {
|
|
422
|
+
const res = await fetch("/api/oauth/kiro/bulk-import", {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: { "Content-Type": "application/json" },
|
|
425
|
+
body: JSON.stringify({
|
|
426
|
+
accounts: lines,
|
|
427
|
+
concurrency: Number.parseInt(concurrency, 10) || DEFAULT_CONCURRENCY,
|
|
428
|
+
}),
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const data = await res.json();
|
|
432
|
+
if (!res.ok) {
|
|
433
|
+
const invalidHint = Array.isArray(data.invalidLines) && data.invalidLines.length > 0
|
|
434
|
+
? ` Invalid lines: ${data.invalidLines.join(", ")}`
|
|
435
|
+
: "";
|
|
436
|
+
throw new Error((data.error || "Bulk account import failed") + invalidHint);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
setActiveJob(data.job || null);
|
|
440
|
+
setJobRestoreNotice(null);
|
|
441
|
+
if (data.job?.jobId) {
|
|
442
|
+
completedRefreshJobsRef.current.delete(data.job.jobId);
|
|
443
|
+
}
|
|
444
|
+
setBulkResult(null);
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const handleBulkImport = async () => {
|
|
448
|
+
const lines = bulkText
|
|
449
|
+
.split("\n")
|
|
450
|
+
.map((line) => line.trim())
|
|
451
|
+
.filter(Boolean);
|
|
452
|
+
|
|
453
|
+
if (!lines.length) {
|
|
454
|
+
setError(
|
|
455
|
+
effectiveImportMode === "bulk-account"
|
|
456
|
+
? "Please enter at least one gmail|password line"
|
|
457
|
+
: "Please enter at least one refresh token line"
|
|
458
|
+
);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
setImporting(true);
|
|
463
|
+
setError(null);
|
|
464
|
+
setBulkResult(null);
|
|
465
|
+
|
|
466
|
+
try {
|
|
467
|
+
if (effectiveImportMode === "bulk-account") {
|
|
468
|
+
await handleBulkAccountImport(lines);
|
|
469
|
+
} else {
|
|
470
|
+
await handleBulkTokenImport(lines);
|
|
471
|
+
}
|
|
472
|
+
} catch (err) {
|
|
473
|
+
setError(err.message);
|
|
474
|
+
} finally {
|
|
475
|
+
setImporting(false);
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const handleCancelJob = async () => {
|
|
480
|
+
if (!activeJob?.jobId) return;
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
const res = await fetch(`/api/oauth/kiro/bulk-import/${activeJob.jobId}/cancel`, {
|
|
484
|
+
method: "POST",
|
|
485
|
+
});
|
|
486
|
+
const data = await res.json();
|
|
487
|
+
if (!res.ok) {
|
|
488
|
+
throw new Error(data.error || "Failed to cancel job");
|
|
489
|
+
}
|
|
490
|
+
if (data.job) setActiveJob(data.job);
|
|
491
|
+
} catch (err) {
|
|
492
|
+
setError(err.message);
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
const handleDoneRefresh = () => {
|
|
497
|
+
resetImportState();
|
|
498
|
+
onImportSuccess?.();
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
const handleOpenManualSession = async (workerId) => {
|
|
502
|
+
if (!activeJob?.jobId || !workerId) return;
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
const res = await fetch(`/api/oauth/kiro/bulk-import/${activeJob.jobId}/manual/${workerId}`, {
|
|
506
|
+
method: "POST",
|
|
507
|
+
});
|
|
508
|
+
const data = await res.json();
|
|
509
|
+
if (!res.ok) {
|
|
510
|
+
throw new Error(data.error || "Failed to open manual session");
|
|
511
|
+
}
|
|
512
|
+
if (data.job) setActiveJob(data.job);
|
|
513
|
+
} catch (err) {
|
|
514
|
+
setError(err.message);
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
|
|
518
|
+
const handleIdcContinue = () => {
|
|
519
|
+
if (!idcStartUrl.trim()) {
|
|
520
|
+
setError("Please enter your IDC start URL");
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
onMethodSelect("idc", { startUrl: idcStartUrl.trim(), region: idcRegion });
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const handleSocialLogin = (provider) => {
|
|
528
|
+
onMethodSelect("social", { provider });
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
return (
|
|
532
|
+
<Modal
|
|
533
|
+
isOpen={isOpen}
|
|
534
|
+
title="Connect Kiro"
|
|
535
|
+
onClose={onClose}
|
|
536
|
+
size="full"
|
|
537
|
+
className="max-w-[min(96vw,1540px)]"
|
|
538
|
+
>
|
|
539
|
+
<div className="flex flex-col gap-4">
|
|
540
|
+
{!effectiveSelectedMethod && (
|
|
541
|
+
<div className="space-y-3">
|
|
542
|
+
<p className="mb-4 text-sm text-text-muted">
|
|
543
|
+
Choose your authentication method:
|
|
544
|
+
</p>
|
|
545
|
+
|
|
546
|
+
<button
|
|
547
|
+
onClick={() => onMethodSelect("builder-id")}
|
|
548
|
+
className="w-full rounded-lg border border-border p-4 text-left transition-colors hover:bg-sidebar"
|
|
549
|
+
>
|
|
550
|
+
<div className="flex items-start gap-3">
|
|
551
|
+
<span className="material-symbols-outlined mt-0.5 text-primary">shield</span>
|
|
552
|
+
<div className="flex-1">
|
|
553
|
+
<h3 className="mb-1 font-semibold">AWS Builder ID</h3>
|
|
554
|
+
<p className="text-sm text-text-muted">
|
|
555
|
+
Recommended for most users. Free AWS account required.
|
|
556
|
+
</p>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
</button>
|
|
560
|
+
|
|
561
|
+
<button
|
|
562
|
+
onClick={() => handleMethodSelect("idc")}
|
|
563
|
+
className="w-full rounded-lg border border-border p-4 text-left transition-colors hover:bg-sidebar"
|
|
564
|
+
>
|
|
565
|
+
<div className="flex items-start gap-3">
|
|
566
|
+
<span className="material-symbols-outlined mt-0.5 text-primary">business</span>
|
|
567
|
+
<div className="flex-1">
|
|
568
|
+
<h3 className="mb-1 font-semibold">AWS IAM Identity Center</h3>
|
|
569
|
+
<p className="text-sm text-text-muted">
|
|
570
|
+
For enterprise users with custom AWS IAM Identity Center.
|
|
571
|
+
</p>
|
|
572
|
+
</div>
|
|
573
|
+
</div>
|
|
574
|
+
</button>
|
|
575
|
+
|
|
576
|
+
<button
|
|
577
|
+
onClick={() => handleMethodSelect("social-google")}
|
|
578
|
+
className="hidden w-full rounded-lg border border-border p-4 text-left transition-colors hover:bg-sidebar"
|
|
579
|
+
>
|
|
580
|
+
<div className="flex items-start gap-3">
|
|
581
|
+
<span className="material-symbols-outlined mt-0.5 text-primary">account_circle</span>
|
|
582
|
+
<div className="flex-1">
|
|
583
|
+
<h3 className="mb-1 font-semibold">Google Account</h3>
|
|
584
|
+
<p className="text-sm text-text-muted">
|
|
585
|
+
Login with your Google account (manual callback).
|
|
586
|
+
</p>
|
|
587
|
+
</div>
|
|
588
|
+
</div>
|
|
589
|
+
</button>
|
|
590
|
+
|
|
591
|
+
<button
|
|
592
|
+
onClick={() => handleMethodSelect("social-github")}
|
|
593
|
+
className="hidden w-full rounded-lg border border-border p-4 text-left transition-colors hover:bg-sidebar"
|
|
594
|
+
>
|
|
595
|
+
<div className="flex items-start gap-3">
|
|
596
|
+
<span className="material-symbols-outlined mt-0.5 text-primary">code</span>
|
|
597
|
+
<div className="flex-1">
|
|
598
|
+
<h3 className="mb-1 font-semibold">GitHub Account</h3>
|
|
599
|
+
<p className="text-sm text-text-muted">
|
|
600
|
+
Login with your GitHub account (manual callback).
|
|
601
|
+
</p>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
</button>
|
|
605
|
+
|
|
606
|
+
<button
|
|
607
|
+
onClick={() => handleMethodSelect("import")}
|
|
608
|
+
className="w-full rounded-lg border border-border p-4 text-left transition-colors hover:bg-sidebar"
|
|
609
|
+
>
|
|
610
|
+
<div className="flex items-start gap-3">
|
|
611
|
+
<span className="material-symbols-outlined mt-0.5 text-primary">file_upload</span>
|
|
612
|
+
<div className="flex-1">
|
|
613
|
+
<h3 className="mb-1 font-semibold">Bulk Option</h3>
|
|
614
|
+
<p className="text-sm text-text-muted">
|
|
615
|
+
Import a single token, bulk tokens, or bulk Google accounts.
|
|
616
|
+
</p>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
</button>
|
|
620
|
+
</div>
|
|
621
|
+
)}
|
|
622
|
+
|
|
623
|
+
{effectiveSelectedMethod === "idc" && (
|
|
624
|
+
<div className="space-y-4">
|
|
625
|
+
<div>
|
|
626
|
+
<label className="mb-2 block text-sm font-medium">
|
|
627
|
+
IDC Start URL <span className="text-red-500">*</span>
|
|
628
|
+
</label>
|
|
629
|
+
<Input
|
|
630
|
+
value={idcStartUrl}
|
|
631
|
+
onChange={(e) => setIdcStartUrl(e.target.value)}
|
|
632
|
+
placeholder="https://your-org.awsapps.com/start"
|
|
633
|
+
className="font-mono text-sm"
|
|
634
|
+
/>
|
|
635
|
+
<p className="mt-1 text-xs text-text-muted">
|
|
636
|
+
Your organization's AWS IAM Identity Center URL
|
|
637
|
+
</p>
|
|
638
|
+
</div>
|
|
639
|
+
|
|
640
|
+
<div>
|
|
641
|
+
<label className="mb-2 block text-sm font-medium">
|
|
642
|
+
AWS Region
|
|
643
|
+
</label>
|
|
644
|
+
<Input
|
|
645
|
+
value={idcRegion}
|
|
646
|
+
onChange={(e) => setIdcRegion(e.target.value)}
|
|
647
|
+
placeholder="us-east-1"
|
|
648
|
+
className="font-mono text-sm"
|
|
649
|
+
/>
|
|
650
|
+
<p className="mt-1 text-xs text-text-muted">
|
|
651
|
+
AWS region for your Identity Center (default: us-east-1)
|
|
652
|
+
</p>
|
|
653
|
+
</div>
|
|
654
|
+
|
|
655
|
+
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
656
|
+
|
|
657
|
+
<div className="flex gap-2">
|
|
658
|
+
<Button onClick={handleIdcContinue} fullWidth>
|
|
659
|
+
Continue
|
|
660
|
+
</Button>
|
|
661
|
+
<Button onClick={handleBack} variant="ghost" fullWidth>
|
|
662
|
+
Back
|
|
663
|
+
</Button>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
)}
|
|
667
|
+
|
|
668
|
+
{effectiveSelectedMethod === "social-google" && (
|
|
669
|
+
<div className="space-y-4">
|
|
670
|
+
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20">
|
|
671
|
+
<div className="flex gap-2">
|
|
672
|
+
<span className="material-symbols-outlined text-amber-600 dark:text-amber-400">info</span>
|
|
673
|
+
<div className="flex-1 text-sm">
|
|
674
|
+
<p className="mb-1 font-medium text-amber-900 dark:text-amber-100">
|
|
675
|
+
Manual Callback Required
|
|
676
|
+
</p>
|
|
677
|
+
<p className="text-amber-800 dark:text-amber-200">
|
|
678
|
+
After login, you'll need to copy the callback URL from your browser and paste it back here.
|
|
679
|
+
</p>
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
</div>
|
|
683
|
+
|
|
684
|
+
<div className="flex gap-2">
|
|
685
|
+
<Button onClick={() => handleSocialLogin("google")} fullWidth>
|
|
686
|
+
Continue with Google
|
|
687
|
+
</Button>
|
|
688
|
+
<Button onClick={handleBack} variant="ghost" fullWidth>
|
|
689
|
+
Back
|
|
690
|
+
</Button>
|
|
691
|
+
</div>
|
|
692
|
+
</div>
|
|
693
|
+
)}
|
|
694
|
+
|
|
695
|
+
{effectiveSelectedMethod === "social-github" && (
|
|
696
|
+
<div className="space-y-4">
|
|
697
|
+
<div className="rounded-lg border border-amber-200 bg-amber-50 p-4 dark:border-amber-800 dark:bg-amber-900/20">
|
|
698
|
+
<div className="flex gap-2">
|
|
699
|
+
<span className="material-symbols-outlined text-amber-600 dark:text-amber-400">info</span>
|
|
700
|
+
<div className="flex-1 text-sm">
|
|
701
|
+
<p className="mb-1 font-medium text-amber-900 dark:text-amber-100">
|
|
702
|
+
Manual Callback Required
|
|
703
|
+
</p>
|
|
704
|
+
<p className="text-amber-800 dark:text-amber-200">
|
|
705
|
+
After login, you'll need to copy the callback URL from your browser and paste it back here.
|
|
706
|
+
</p>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
</div>
|
|
710
|
+
|
|
711
|
+
<div className="flex gap-2">
|
|
712
|
+
<Button onClick={() => handleSocialLogin("github")} fullWidth>
|
|
713
|
+
Continue with GitHub
|
|
714
|
+
</Button>
|
|
715
|
+
<Button onClick={handleBack} variant="ghost" fullWidth>
|
|
716
|
+
Back
|
|
717
|
+
</Button>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
)}
|
|
721
|
+
|
|
722
|
+
{effectiveSelectedMethod === "import" && (
|
|
723
|
+
<div className="space-y-4">
|
|
724
|
+
<div className="flex flex-wrap gap-2">
|
|
725
|
+
<Button
|
|
726
|
+
size="sm"
|
|
727
|
+
variant={effectiveImportMode === "single-token" ? "primary" : "ghost"}
|
|
728
|
+
onClick={() => {
|
|
729
|
+
setImportMode("single-token");
|
|
730
|
+
setBulkResult(null);
|
|
731
|
+
setError(null);
|
|
732
|
+
}}
|
|
733
|
+
disabled={runningBulkAccountJob}
|
|
734
|
+
>
|
|
735
|
+
Single Token
|
|
736
|
+
</Button>
|
|
737
|
+
<Button
|
|
738
|
+
size="sm"
|
|
739
|
+
variant={effectiveImportMode === "bulk-token" ? "primary" : "ghost"}
|
|
740
|
+
onClick={() => {
|
|
741
|
+
setImportMode("bulk-token");
|
|
742
|
+
setBulkResult(null);
|
|
743
|
+
setError(null);
|
|
744
|
+
}}
|
|
745
|
+
disabled={runningBulkAccountJob}
|
|
746
|
+
>
|
|
747
|
+
Bulk Token
|
|
748
|
+
</Button>
|
|
749
|
+
<Button
|
|
750
|
+
size="sm"
|
|
751
|
+
variant={effectiveImportMode === "bulk-account" ? "primary" : "ghost"}
|
|
752
|
+
onClick={() => {
|
|
753
|
+
setImportMode("bulk-account");
|
|
754
|
+
setBulkResult(null);
|
|
755
|
+
setError(null);
|
|
756
|
+
}}
|
|
757
|
+
>
|
|
758
|
+
Bulk Account
|
|
759
|
+
</Button>
|
|
760
|
+
</div>
|
|
761
|
+
|
|
762
|
+
{effectiveImportMode === "single-token" && (
|
|
763
|
+
<>
|
|
764
|
+
{autoDetecting && (
|
|
765
|
+
<div className="py-6 text-center">
|
|
766
|
+
<div className="mx-auto mb-4 flex size-16 items-center justify-center rounded-full bg-primary/10">
|
|
767
|
+
<span className="material-symbols-outlined animate-spin text-3xl text-primary">
|
|
768
|
+
progress_activity
|
|
769
|
+
</span>
|
|
770
|
+
</div>
|
|
771
|
+
<h3 className="mb-2 text-lg font-semibold">Auto-detecting token...</h3>
|
|
772
|
+
<p className="text-sm text-text-muted">Reading from AWS SSO cache</p>
|
|
773
|
+
</div>
|
|
774
|
+
)}
|
|
775
|
+
|
|
776
|
+
{!autoDetecting && (
|
|
777
|
+
<>
|
|
778
|
+
{autoDetected && (
|
|
779
|
+
<div className="rounded-lg border border-green-200 bg-green-50 p-3 dark:border-green-800 dark:bg-green-900/20">
|
|
780
|
+
<div className="flex gap-2">
|
|
781
|
+
<span className="material-symbols-outlined text-green-600 dark:text-green-400">check_circle</span>
|
|
782
|
+
<p className="text-sm text-green-800 dark:text-green-200">
|
|
783
|
+
Token auto-detected from Kiro IDE successfully!
|
|
784
|
+
</p>
|
|
785
|
+
</div>
|
|
786
|
+
</div>
|
|
787
|
+
)}
|
|
788
|
+
|
|
789
|
+
{!autoDetected && !error && (
|
|
790
|
+
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
|
|
791
|
+
<div className="flex gap-2">
|
|
792
|
+
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400">info</span>
|
|
793
|
+
<p className="text-sm text-blue-800 dark:text-blue-200">
|
|
794
|
+
Kiro IDE not detected. Please paste your refresh token manually.
|
|
795
|
+
</p>
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
)}
|
|
799
|
+
|
|
800
|
+
<div>
|
|
801
|
+
<label className="mb-2 block text-sm font-medium">
|
|
802
|
+
Refresh Token <span className="text-red-500">*</span>
|
|
803
|
+
</label>
|
|
804
|
+
<Input
|
|
805
|
+
value={refreshToken}
|
|
806
|
+
onChange={(e) => setRefreshToken(e.target.value)}
|
|
807
|
+
placeholder="Token will be auto-filled..."
|
|
808
|
+
className="font-mono text-sm"
|
|
809
|
+
/>
|
|
810
|
+
</div>
|
|
811
|
+
|
|
812
|
+
{error && (
|
|
813
|
+
<div className="rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/20">
|
|
814
|
+
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
815
|
+
</div>
|
|
816
|
+
)}
|
|
817
|
+
|
|
818
|
+
<div className="flex gap-2">
|
|
819
|
+
<Button onClick={handleImportToken} fullWidth disabled={importing || !refreshToken.trim()}>
|
|
820
|
+
{importing ? "Importing..." : "Import Token"}
|
|
821
|
+
</Button>
|
|
822
|
+
<Button onClick={handleBack} variant="ghost" fullWidth>
|
|
823
|
+
Back
|
|
824
|
+
</Button>
|
|
825
|
+
</div>
|
|
826
|
+
</>
|
|
827
|
+
)}
|
|
828
|
+
</>
|
|
829
|
+
)}
|
|
830
|
+
|
|
831
|
+
{effectiveImportMode === "bulk-token" && (
|
|
832
|
+
<>
|
|
833
|
+
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
|
|
834
|
+
<div className="flex gap-2">
|
|
835
|
+
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400">info</span>
|
|
836
|
+
<p className="text-sm text-blue-800 dark:text-blue-200">
|
|
837
|
+
Bulk token import accepts one refresh token per line.
|
|
838
|
+
</p>
|
|
839
|
+
</div>
|
|
840
|
+
</div>
|
|
841
|
+
|
|
842
|
+
<div>
|
|
843
|
+
<label className="mb-2 block text-sm font-medium">
|
|
844
|
+
Bulk Refresh Tokens <span className="text-red-500">*</span>
|
|
845
|
+
</label>
|
|
846
|
+
<textarea
|
|
847
|
+
value={bulkText}
|
|
848
|
+
onChange={(e) => setBulkText(e.target.value)}
|
|
849
|
+
placeholder={"aorAAAAAG...\naorAAAAAG..."}
|
|
850
|
+
className="min-h-[160px] w-full resize-y rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm focus:outline-none focus:ring-1 focus:ring-primary"
|
|
851
|
+
/>
|
|
852
|
+
<p className="mt-1 text-xs text-text-muted">
|
|
853
|
+
One refresh token per line.
|
|
854
|
+
</p>
|
|
855
|
+
</div>
|
|
856
|
+
|
|
857
|
+
{bulkResult && (
|
|
858
|
+
<div className={`text-sm font-medium ${bulkResult.failed > 0 ? "text-yellow-600 dark:text-yellow-400" : "text-green-600 dark:text-green-400"}`}>
|
|
859
|
+
{`Success: ${bulkResult.success} imported${bulkResult.failed > 0 ? `, ${bulkResult.failed} failed` : ""}`}
|
|
860
|
+
</div>
|
|
861
|
+
)}
|
|
862
|
+
|
|
863
|
+
{error && (
|
|
864
|
+
<div className="rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/20">
|
|
865
|
+
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
866
|
+
</div>
|
|
867
|
+
)}
|
|
868
|
+
|
|
869
|
+
{jobRestoreNotice && (
|
|
870
|
+
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 dark:border-amber-800 dark:bg-amber-900/20">
|
|
871
|
+
<p className="text-sm text-amber-700 dark:text-amber-300">{jobRestoreNotice}</p>
|
|
872
|
+
</div>
|
|
873
|
+
)}
|
|
874
|
+
|
|
875
|
+
<div className="flex gap-2">
|
|
876
|
+
<Button onClick={handleBulkImport} fullWidth disabled={importing || !bulkText.trim()}>
|
|
877
|
+
{importing ? "Importing..." : "Import Tokens"}
|
|
878
|
+
</Button>
|
|
879
|
+
<Button onClick={handleBack} variant="ghost" fullWidth>
|
|
880
|
+
Back
|
|
881
|
+
</Button>
|
|
882
|
+
</div>
|
|
883
|
+
</>
|
|
884
|
+
)}
|
|
885
|
+
|
|
886
|
+
{effectiveImportMode === "bulk-account" && (
|
|
887
|
+
<>
|
|
888
|
+
<div className="rounded-lg border border-blue-200 bg-blue-50 p-3 dark:border-blue-800 dark:bg-blue-900/20">
|
|
889
|
+
<div className="flex gap-2">
|
|
890
|
+
<span className="material-symbols-outlined text-blue-600 dark:text-blue-400">info</span>
|
|
891
|
+
<p className="text-sm text-blue-800 dark:text-blue-200">
|
|
892
|
+
Bulk account import runs Playwright in the background, keeps passwords in memory only, and only needs manual action when Google or Kiro blocks a worker. Recommended worker count for a 24 GB machine: 4.
|
|
893
|
+
</p>
|
|
894
|
+
</div>
|
|
895
|
+
</div>
|
|
896
|
+
|
|
897
|
+
{!activeJob && (
|
|
898
|
+
<>
|
|
899
|
+
<div>
|
|
900
|
+
<label className="mb-2 block text-sm font-medium">
|
|
901
|
+
Bulk Accounts <span className="text-red-500">*</span>
|
|
902
|
+
</label>
|
|
903
|
+
<textarea
|
|
904
|
+
value={bulkText}
|
|
905
|
+
onChange={(e) => setBulkText(e.target.value)}
|
|
906
|
+
placeholder={"gmail1@example.com|password1\ngmail2@example.com|password2"}
|
|
907
|
+
className="min-h-[180px] w-full resize-y rounded-lg border border-border bg-background px-3 py-2 font-mono text-sm focus:outline-none focus:ring-1 focus:ring-primary"
|
|
908
|
+
/>
|
|
909
|
+
<p className="mt-1 text-xs text-text-muted">
|
|
910
|
+
One account per line in the format gmail|password.
|
|
911
|
+
</p>
|
|
912
|
+
</div>
|
|
913
|
+
|
|
914
|
+
<div>
|
|
915
|
+
<label className="mb-2 block text-sm font-medium">
|
|
916
|
+
Concurrent Workers
|
|
917
|
+
</label>
|
|
918
|
+
<Input
|
|
919
|
+
type="number"
|
|
920
|
+
min="1"
|
|
921
|
+
max="8"
|
|
922
|
+
value={concurrency}
|
|
923
|
+
onChange={(e) => setConcurrency(e.target.value)}
|
|
924
|
+
placeholder="4"
|
|
925
|
+
/>
|
|
926
|
+
<p className="mt-1 text-xs text-text-muted">
|
|
927
|
+
Default 4. Allowed range: 1 to 8 workers.
|
|
928
|
+
</p>
|
|
929
|
+
</div>
|
|
930
|
+
</>
|
|
931
|
+
)}
|
|
932
|
+
|
|
933
|
+
{activeJob && (
|
|
934
|
+
<div className="space-y-4 rounded-xl border border-border p-4">
|
|
935
|
+
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
936
|
+
<div>
|
|
937
|
+
<h3 className="font-semibold">Bulk Import Job</h3>
|
|
938
|
+
<p className="text-xs text-text-muted">
|
|
939
|
+
Job ID: <span className="font-mono">{activeJob.jobId}</span>
|
|
940
|
+
</p>
|
|
941
|
+
<p className="text-xs text-text-muted">
|
|
942
|
+
Status: <span className="font-medium">{activeJob.status}</span> | Workers: {activeJob.concurrency}
|
|
943
|
+
</p>
|
|
944
|
+
<p className="text-xs text-text-muted">
|
|
945
|
+
Browser workers run in the background. Close this modal if needed and reopen it later from the provider page with Resume Bulk Progress.
|
|
946
|
+
</p>
|
|
947
|
+
</div>
|
|
948
|
+
<div className="flex gap-2">
|
|
949
|
+
{runningBulkAccountJob && (
|
|
950
|
+
<Button size="sm" variant="secondary" onClick={handleCancelJob}>
|
|
951
|
+
Cancel Job
|
|
952
|
+
</Button>
|
|
953
|
+
)}
|
|
954
|
+
{finishedBulkAccountJob && (
|
|
955
|
+
<Button size="sm" onClick={handleDoneRefresh}>
|
|
956
|
+
Done & Refresh
|
|
957
|
+
</Button>
|
|
958
|
+
)}
|
|
959
|
+
</div>
|
|
960
|
+
</div>
|
|
961
|
+
|
|
962
|
+
<div className="grid grid-cols-2 gap-2 sm:grid-cols-3">
|
|
963
|
+
{summaryItems.map(([label, value]) => (
|
|
964
|
+
<div key={label} className="rounded-lg bg-sidebar px-3 py-2">
|
|
965
|
+
<p className="text-[11px] uppercase tracking-wide text-text-muted">{label}</p>
|
|
966
|
+
<p className="text-lg font-semibold">{value}</p>
|
|
967
|
+
</div>
|
|
968
|
+
))}
|
|
969
|
+
</div>
|
|
970
|
+
|
|
971
|
+
{activeJob.error && (
|
|
972
|
+
<div className="rounded-lg border border-red-200 bg-red-50 p-3 text-sm text-red-700 dark:border-red-800 dark:bg-red-900/20 dark:text-red-300">
|
|
973
|
+
{activeJob.error}
|
|
974
|
+
</div>
|
|
975
|
+
)}
|
|
976
|
+
|
|
977
|
+
{activeJob.summary?.needs_manual > 0 && (
|
|
978
|
+
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 text-sm text-amber-800 dark:border-amber-800 dark:bg-amber-900/20 dark:text-amber-200">
|
|
979
|
+
Some accounts need manual assist. Open the blocked worker session below, finish the Google or Kiro prompts, and the job will continue updating automatically.
|
|
980
|
+
</div>
|
|
981
|
+
)}
|
|
982
|
+
|
|
983
|
+
<div className="grid gap-4 lg:grid-cols-[minmax(0,7fr)_minmax(320px,3fr)]">
|
|
984
|
+
<div className="space-y-4">
|
|
985
|
+
<div className="overflow-hidden rounded-xl border border-border bg-sidebar">
|
|
986
|
+
<div className="flex flex-col gap-2 border-b border-border px-4 py-3 sm:flex-row sm:items-center sm:justify-between">
|
|
987
|
+
<div>
|
|
988
|
+
<p className="text-sm font-semibold">Live Browser Preview</p>
|
|
989
|
+
<p className="text-xs text-text-muted">
|
|
990
|
+
{activeJob.preview?.email || spotlightAccount?.email || "Waiting for worker"}
|
|
991
|
+
{activeJob.preview?.workerId ? ` | Worker ${activeJob.preview.workerId}` : ""}
|
|
992
|
+
</p>
|
|
993
|
+
</div>
|
|
994
|
+
<div className="text-right text-xs text-text-muted">
|
|
995
|
+
<p>{formatStepLabel(activeJob.preview?.step || spotlightAccount?.currentStep)}</p>
|
|
996
|
+
<p>Updated {formatClock(activeJob.preview?.updatedAt || spotlightAccount?.updatedAt)}</p>
|
|
997
|
+
</div>
|
|
998
|
+
</div>
|
|
999
|
+
|
|
1000
|
+
<div className="relative bg-black/90">
|
|
1001
|
+
{activeJob.preview?.imageData ? (
|
|
1002
|
+
<Image
|
|
1003
|
+
src={activeJob.preview.imageData}
|
|
1004
|
+
alt={`Live worker preview for ${activeJob.preview.email || "Kiro import"}`}
|
|
1005
|
+
width={1440}
|
|
1006
|
+
height={900}
|
|
1007
|
+
unoptimized
|
|
1008
|
+
className="h-[420px] w-full object-contain"
|
|
1009
|
+
/>
|
|
1010
|
+
) : (
|
|
1011
|
+
<div className="flex h-[420px] flex-col items-center justify-center gap-3 px-6 text-center text-slate-200">
|
|
1012
|
+
<span className="material-symbols-outlined text-5xl text-primary/80">browser_updated</span>
|
|
1013
|
+
<div>
|
|
1014
|
+
<p className="text-base font-medium">Preview will appear when a worker opens Google or Kiro</p>
|
|
1015
|
+
<p className="mt-1 text-sm text-slate-400">
|
|
1016
|
+
The import keeps running in the background even if the screenshot is not available yet.
|
|
1017
|
+
</p>
|
|
1018
|
+
</div>
|
|
1019
|
+
</div>
|
|
1020
|
+
)}
|
|
1021
|
+
</div>
|
|
1022
|
+
</div>
|
|
1023
|
+
|
|
1024
|
+
<div className="space-y-4">
|
|
1025
|
+
{groupedAccounts.map((group) => (
|
|
1026
|
+
<div key={group.status} className={`rounded-xl border p-4 ${getStatusPanelClasses(group.status)}`}>
|
|
1027
|
+
<div className="mb-3 flex items-center justify-between gap-3">
|
|
1028
|
+
<div className="flex items-center gap-2">
|
|
1029
|
+
<AccountStatusBadge status={group.status} />
|
|
1030
|
+
<p className="text-sm font-semibold capitalize">{formatStepLabel(group.status)}</p>
|
|
1031
|
+
</div>
|
|
1032
|
+
<p className="text-xs text-text-muted">{group.accounts.length} account{group.accounts.length > 1 ? "s" : ""}</p>
|
|
1033
|
+
</div>
|
|
1034
|
+
|
|
1035
|
+
<div className="grid gap-3 xl:grid-cols-2">
|
|
1036
|
+
{group.accounts.map((account) => (
|
|
1037
|
+
<div key={`${account.email}-${account.line}`} className="rounded-xl border border-border bg-background/80 p-4">
|
|
1038
|
+
<div className="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between">
|
|
1039
|
+
<div className="min-w-0">
|
|
1040
|
+
<p className="truncate text-sm font-semibold">{account.email}</p>
|
|
1041
|
+
<p className="text-[11px] text-text-muted">
|
|
1042
|
+
Line {account.line}{account.workerId ? ` | Worker ${account.workerId}` : ""} | {formatClock(account.updatedAt)}
|
|
1043
|
+
</p>
|
|
1044
|
+
</div>
|
|
1045
|
+
<AccountStatusBadge status={account.status} />
|
|
1046
|
+
</div>
|
|
1047
|
+
|
|
1048
|
+
<div className="mt-3 rounded-lg border border-border/70 bg-sidebar/70 px-3 py-2">
|
|
1049
|
+
<p className="text-[11px] uppercase tracking-wide text-text-muted">Current Step</p>
|
|
1050
|
+
<p className="mt-1 text-sm font-medium capitalize">{formatStepLabel(account.currentStep)}</p>
|
|
1051
|
+
</div>
|
|
1052
|
+
|
|
1053
|
+
{account.logs?.length > 0 && (
|
|
1054
|
+
<div className="mt-3 space-y-2">
|
|
1055
|
+
{account.logs.slice(-3).reverse().map((entry) => (
|
|
1056
|
+
<div key={entry.id} className="rounded-lg bg-sidebar/60 px-3 py-2">
|
|
1057
|
+
<div className="flex items-center justify-between gap-3">
|
|
1058
|
+
<p className="text-xs font-medium capitalize">{formatStepLabel(entry.step)}</p>
|
|
1059
|
+
<span className="text-[11px] text-text-muted">{formatClock(entry.at)}</span>
|
|
1060
|
+
</div>
|
|
1061
|
+
<p className="mt-1 text-xs text-text-muted">{entry.message}</p>
|
|
1062
|
+
</div>
|
|
1063
|
+
))}
|
|
1064
|
+
</div>
|
|
1065
|
+
)}
|
|
1066
|
+
|
|
1067
|
+
{account.error && (
|
|
1068
|
+
<p className="mt-3 text-xs text-red-500">{account.error}</p>
|
|
1069
|
+
)}
|
|
1070
|
+
|
|
1071
|
+
{account.manualSessionAvailable && account.workerId ? (
|
|
1072
|
+
<div className="mt-3 flex items-center gap-2">
|
|
1073
|
+
<Button
|
|
1074
|
+
size="sm"
|
|
1075
|
+
variant={account.manualSessionOpened ? "secondary" : "primary"}
|
|
1076
|
+
onClick={() => handleOpenManualSession(account.workerId)}
|
|
1077
|
+
>
|
|
1078
|
+
{account.manualSessionOpened ? "Re-open Manual Session" : "Open Manual Session"}
|
|
1079
|
+
</Button>
|
|
1080
|
+
<p className="text-[11px] text-text-muted">
|
|
1081
|
+
Open only if this worker is blocked by CAPTCHA, 2FA, or recovery prompts.
|
|
1082
|
+
</p>
|
|
1083
|
+
</div>
|
|
1084
|
+
) : null}
|
|
1085
|
+
</div>
|
|
1086
|
+
))}
|
|
1087
|
+
</div>
|
|
1088
|
+
</div>
|
|
1089
|
+
))}
|
|
1090
|
+
</div>
|
|
1091
|
+
</div>
|
|
1092
|
+
|
|
1093
|
+
<div className="rounded-xl border border-border bg-sidebar/70">
|
|
1094
|
+
<div className="border-b border-border px-4 py-3">
|
|
1095
|
+
<p className="text-sm font-semibold">Live Activity Log</p>
|
|
1096
|
+
<p className="text-xs text-text-muted">
|
|
1097
|
+
Every worker step is recorded here in near real time.
|
|
1098
|
+
</p>
|
|
1099
|
+
</div>
|
|
1100
|
+
<div className="max-h-[720px] space-y-3 overflow-y-auto p-4">
|
|
1101
|
+
{activityItems.length === 0 && (
|
|
1102
|
+
<div className="rounded-lg bg-background/70 px-3 py-4 text-sm text-text-muted">
|
|
1103
|
+
Waiting for the first worker event...
|
|
1104
|
+
</div>
|
|
1105
|
+
)}
|
|
1106
|
+
{activityItems.map((entry) => (
|
|
1107
|
+
<div key={entry.id} className="rounded-lg border border-border/70 bg-background/80 px-3 py-3">
|
|
1108
|
+
<div className="flex items-start justify-between gap-3">
|
|
1109
|
+
<div className="min-w-0">
|
|
1110
|
+
<p className="truncate text-sm font-semibold">{entry.email}</p>
|
|
1111
|
+
<p className="text-[11px] text-text-muted">
|
|
1112
|
+
{entry.workerId ? `Worker ${entry.workerId}` : "Waiting"} | {formatStepLabel(entry.step)}
|
|
1113
|
+
</p>
|
|
1114
|
+
</div>
|
|
1115
|
+
<span className="shrink-0 text-[11px] text-text-muted">{formatClock(entry.at)}</span>
|
|
1116
|
+
</div>
|
|
1117
|
+
<p className="mt-2 text-xs text-text-muted">{entry.message}</p>
|
|
1118
|
+
</div>
|
|
1119
|
+
))}
|
|
1120
|
+
</div>
|
|
1121
|
+
</div>
|
|
1122
|
+
</div>
|
|
1123
|
+
</div>
|
|
1124
|
+
)}
|
|
1125
|
+
|
|
1126
|
+
{error && (
|
|
1127
|
+
<div className="rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-900/20">
|
|
1128
|
+
<p className="text-sm text-red-600 dark:text-red-400">{error}</p>
|
|
1129
|
+
</div>
|
|
1130
|
+
)}
|
|
1131
|
+
|
|
1132
|
+
<div className="flex gap-2">
|
|
1133
|
+
{!activeJob && (
|
|
1134
|
+
<Button onClick={handleBulkImport} fullWidth disabled={importing || !bulkText.trim()}>
|
|
1135
|
+
{importing ? "Starting..." : "Start Bulk Import"}
|
|
1136
|
+
</Button>
|
|
1137
|
+
)}
|
|
1138
|
+
{activeJob && !finishedBulkAccountJob && (
|
|
1139
|
+
<Button onClick={handleCancelJob} fullWidth variant="secondary" disabled={!runningBulkAccountJob}>
|
|
1140
|
+
{runningBulkAccountJob ? "Cancel Running Job" : "Job Stopped"}
|
|
1141
|
+
</Button>
|
|
1142
|
+
)}
|
|
1143
|
+
{finishedBulkAccountJob && (
|
|
1144
|
+
<Button onClick={handleDoneRefresh} fullWidth>
|
|
1145
|
+
Done & Refresh Connections
|
|
1146
|
+
</Button>
|
|
1147
|
+
)}
|
|
1148
|
+
<Button onClick={handleBack} variant="ghost" fullWidth>
|
|
1149
|
+
{activeJob ? "Back" : "Cancel"}
|
|
1150
|
+
</Button>
|
|
1151
|
+
</div>
|
|
1152
|
+
</>
|
|
1153
|
+
)}
|
|
1154
|
+
</div>
|
|
1155
|
+
)}
|
|
1156
|
+
</div>
|
|
1157
|
+
</Modal>
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
KiroAuthModal.propTypes = {
|
|
1162
|
+
isOpen: PropTypes.bool.isRequired,
|
|
1163
|
+
onMethodSelect: PropTypes.func.isRequired,
|
|
1164
|
+
onImportSuccess: PropTypes.func,
|
|
1165
|
+
initialJobId: PropTypes.string,
|
|
1166
|
+
initialSelectedMethod: PropTypes.string,
|
|
1167
|
+
initialImportMode: PropTypes.string,
|
|
1168
|
+
initialFlowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
1169
|
+
onBulkJobChange: PropTypes.func,
|
|
1170
|
+
onClose: PropTypes.func.isRequired,
|
|
1171
|
+
};
|