github-to-mcp-monorepo 1.0.0
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/.env.example +8 -0
- package/.github/CODEOWNERS +6 -0
- package/.husky/pre-commit +1 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +7 -0
- package/.vscode/settings.json +4 -0
- package/ARCHITECTURE.md +1429 -0
- package/CHANGELOG.md +167 -0
- package/CONTRIBUTING.md +327 -0
- package/LICENSE +201 -0
- package/README.md +1028 -0
- package/SECURITY.md +248 -0
- package/VISUAL_GUIDE.md +437 -0
- package/apps/vscode/IMPLEMENTATION.md +480 -0
- package/apps/vscode/README.md +248 -0
- package/apps/vscode/package.json +381 -0
- package/apps/vscode/resources/icon.png +0 -0
- package/apps/vscode/resources/icon.svg +5 -0
- package/apps/vscode/src/commands/browseRegistry.ts +211 -0
- package/apps/vscode/src/commands/configureClaudeDesktop.ts +332 -0
- package/apps/vscode/src/commands/convert.ts +82 -0
- package/apps/vscode/src/commands/convertCurrentRepo.ts +109 -0
- package/apps/vscode/src/commands/convertFromUrl.ts +138 -0
- package/apps/vscode/src/commands/index.ts +121 -0
- package/apps/vscode/src/commands/validate.ts +197 -0
- package/apps/vscode/src/extension.ts +464 -0
- package/apps/vscode/src/global.d.ts +36 -0
- package/apps/vscode/src/test/extension.test.ts +73 -0
- package/apps/vscode/src/utils/file-generator.ts +529 -0
- package/apps/vscode/src/utils/github-api.ts +335 -0
- package/apps/vscode/src/utils/index.ts +29 -0
- package/apps/vscode/src/utils/mcp-config.ts +334 -0
- package/apps/vscode/src/utils/storage.ts +87 -0
- package/apps/vscode/src/views/McpServersTreeView.ts +160 -0
- package/apps/vscode/src/views/OutputChannelView.ts +195 -0
- package/apps/vscode/src/views/StatusBarItem.ts +251 -0
- package/apps/vscode/src/views/ToolsExplorerView.ts +314 -0
- package/apps/vscode/src/views/historyProvider.ts +75 -0
- package/apps/vscode/src/views/index.ts +12 -0
- package/apps/vscode/src/views/resultsPanel.ts +330 -0
- package/apps/vscode/src/webviews/ConversionPanel.ts +350 -0
- package/apps/vscode/src/webviews/ToolDetailsPanel.ts +448 -0
- package/apps/vscode/src/webviews/index.ts +9 -0
- package/apps/vscode/src/webviews/webview-ui/styles.ts +492 -0
- package/apps/vscode/tsconfig.json +20 -0
- package/apps/web/PLAYGROUND_GUIDE.md +499 -0
- package/apps/web/README.md +505 -0
- package/apps/web/app/api/convert/route.ts +100 -0
- package/apps/web/app/api/convert/stream/route.ts +198 -0
- package/apps/web/app/api/deploy/route.ts +157 -0
- package/apps/web/app/api/edge/route.ts +308 -0
- package/apps/web/app/api/export-docker/route.ts +284 -0
- package/apps/web/app/api/generate-openapi/route.ts +119 -0
- package/apps/web/app/api/mcp/[serverId]/route.ts +263 -0
- package/apps/web/app/api/playground/connect/route.ts +143 -0
- package/apps/web/app/api/playground/disconnect/route.ts +78 -0
- package/apps/web/app/api/playground/execute/route.ts +135 -0
- package/apps/web/app/api/playground/sessions/route.ts +103 -0
- package/apps/web/app/api/playground/tools/route.ts +117 -0
- package/apps/web/app/api/playground/v2/connect/route.ts +96 -0
- package/apps/web/app/api/playground/v2/disconnect/route.ts +88 -0
- package/apps/web/app/api/playground/v2/health/route.ts +80 -0
- package/apps/web/app/api/playground/v2/prompts/route.ts +160 -0
- package/apps/web/app/api/playground/v2/resources/route.ts +159 -0
- package/apps/web/app/api/playground/v2/sessions/route.ts +184 -0
- package/apps/web/app/api/playground/v2/tools/route.ts +167 -0
- package/apps/web/app/api/stream/route.ts +232 -0
- package/apps/web/app/batch/BatchConvertClient.tsx +190 -0
- package/apps/web/app/batch/page.tsx +37 -0
- package/apps/web/app/convert/page.tsx +269 -0
- package/apps/web/app/dashboard/page.tsx +380 -0
- package/apps/web/app/globals.css +622 -0
- package/apps/web/app/layout.tsx +120 -0
- package/apps/web/app/manifest.ts +31 -0
- package/apps/web/app/opengraph-image.tsx +112 -0
- package/apps/web/app/page.old.tsx +924 -0
- package/apps/web/app/page.tsx +77 -0
- package/apps/web/app/playground/page.tsx +306 -0
- package/apps/web/app/playground/v2/error.tsx +163 -0
- package/apps/web/app/playground/v2/layout.tsx +58 -0
- package/apps/web/app/playground/v2/loading.tsx +152 -0
- package/apps/web/app/playground/v2/page.tsx +644 -0
- package/apps/web/app/playground/v2/providers.tsx +214 -0
- package/apps/web/app/playground/v2/use-shortcuts.ts +209 -0
- package/apps/web/app/playground/v2/use-url-state.ts +296 -0
- package/apps/web/app/providers.tsx +22 -0
- package/apps/web/app/sitemap.ts +32 -0
- package/apps/web/app/twitter-image.tsx +112 -0
- package/apps/web/components/BranchSelector.tsx +401 -0
- package/apps/web/components/ClaudeConfigExport.tsx +226 -0
- package/apps/web/components/Features.tsx +84 -0
- package/apps/web/components/Footer.tsx +119 -0
- package/apps/web/components/GenerationProgress.tsx +248 -0
- package/apps/web/components/GithubUrlInput.tsx +483 -0
- package/apps/web/components/Header.tsx +175 -0
- package/apps/web/components/Hero.tsx +117 -0
- package/apps/web/components/HowItWorks.tsx +119 -0
- package/apps/web/components/InstallBanner.tsx +158 -0
- package/apps/web/components/Logo.tsx +116 -0
- package/apps/web/components/ParticleBackground.tsx +105 -0
- package/apps/web/components/Playground.tsx +472 -0
- package/apps/web/components/PlaygroundToolTester.tsx +410 -0
- package/apps/web/components/ProductCards.tsx +179 -0
- package/apps/web/components/SplitView.tsx +194 -0
- package/apps/web/components/ToolFilter.tsx +260 -0
- package/apps/web/components/ToolList.tsx +325 -0
- package/apps/web/components/batch/BatchConvert.tsx +785 -0
- package/apps/web/components/batch/index.ts +7 -0
- package/apps/web/components/convert/ConfigTabs.tsx +230 -0
- package/apps/web/components/convert/ConversionResult.tsx +482 -0
- package/apps/web/components/convert/InlinePlayground.tsx +259 -0
- package/apps/web/components/convert/LoadingSteps.tsx +311 -0
- package/apps/web/components/convert/OneClickInstall.tsx +224 -0
- package/apps/web/components/convert/ToolCard.tsx +189 -0
- package/apps/web/components/convert/TryInPlayground.tsx +242 -0
- package/apps/web/components/convert/index.ts +12 -0
- package/apps/web/components/deploy/DeployButton.tsx +369 -0
- package/apps/web/components/deploy/index.ts +7 -0
- package/apps/web/components/docker/DockerExport.tsx +690 -0
- package/apps/web/components/docker/index.ts +7 -0
- package/apps/web/components/install/OneClickInstall.tsx +676 -0
- package/apps/web/components/install/index.ts +7 -0
- package/apps/web/components/playground/CapabilityTabs.tsx +150 -0
- package/apps/web/components/playground/ConnectionStatusV2.tsx +322 -0
- package/apps/web/components/playground/EmptyStates.tsx +305 -0
- package/apps/web/components/playground/ExecutionLog.tsx +260 -0
- package/apps/web/components/playground/ExecutionLogV2.tsx +378 -0
- package/apps/web/components/playground/JsonViewer.tsx +388 -0
- package/apps/web/components/playground/PlaygroundLayout.tsx +244 -0
- package/apps/web/components/playground/PromptsPanel.tsx +385 -0
- package/apps/web/components/playground/ResourcesPanel.tsx +378 -0
- package/apps/web/components/playground/SchemaForm.tsx +477 -0
- package/apps/web/components/playground/ServerStatus.tsx +151 -0
- package/apps/web/components/playground/ShareButton.tsx +239 -0
- package/apps/web/components/playground/ToolsPanel.tsx +309 -0
- package/apps/web/components/playground/TransportConfigurator.tsx +563 -0
- package/apps/web/components/playground/index.ts +74 -0
- package/apps/web/components/playground/types.ts +202 -0
- package/apps/web/components/streaming/StreamingProgress.tsx +441 -0
- package/apps/web/components/streaming/index.ts +7 -0
- package/apps/web/components/ui/badge.tsx +42 -0
- package/apps/web/components/ui/button.tsx +88 -0
- package/apps/web/components/ui/card.tsx +75 -0
- package/apps/web/components/ui/code-block.tsx +122 -0
- package/apps/web/components/ui/index.ts +12 -0
- package/apps/web/components/ui/input.tsx +55 -0
- package/apps/web/components/ui/tabs.tsx +61 -0
- package/apps/web/hooks/index.ts +85 -0
- package/apps/web/hooks/types.ts +1173 -0
- package/apps/web/hooks/use-conversion.ts +133 -0
- package/apps/web/hooks/use-execution-history.ts +376 -0
- package/apps/web/hooks/use-generation-progress.ts +147 -0
- package/apps/web/hooks/use-local-storage.ts +88 -0
- package/apps/web/hooks/use-mcp-client.ts +623 -0
- package/apps/web/hooks/use-mcp-connection.ts +500 -0
- package/apps/web/hooks/use-mcp-execution.ts +282 -0
- package/apps/web/hooks/use-mcp-prompts.ts +441 -0
- package/apps/web/hooks/use-mcp-resources.ts +430 -0
- package/apps/web/hooks/use-mcp-tools.ts +540 -0
- package/apps/web/hooks/use-playground-store.ts +299 -0
- package/apps/web/hooks/use-playground.ts +184 -0
- package/apps/web/hooks/use-streaming-conversion.ts +227 -0
- package/apps/web/hooks/useBatchConversion.ts +271 -0
- package/apps/web/hooks/useDockerConfig.ts +161 -0
- package/apps/web/hooks/usePlatformDetection.ts +80 -0
- package/apps/web/hooks/useStreaming.ts +199 -0
- package/apps/web/lib/api/errors.ts +386 -0
- package/apps/web/lib/api/index.ts +137 -0
- package/apps/web/lib/api/logger.ts +187 -0
- package/apps/web/lib/api/middleware.ts +364 -0
- package/apps/web/lib/api/openapi.ts +977 -0
- package/apps/web/lib/api/session-manager.ts +594 -0
- package/apps/web/lib/api/types.ts +433 -0
- package/apps/web/lib/api/validation.ts +523 -0
- package/apps/web/lib/constants.ts +114 -0
- package/apps/web/lib/mcp/client.ts +1137 -0
- package/apps/web/lib/mcp/events.ts +651 -0
- package/apps/web/lib/mcp/index.ts +347 -0
- package/apps/web/lib/mcp/logger.ts +428 -0
- package/apps/web/lib/mcp/metrics.ts +703 -0
- package/apps/web/lib/mcp/retry.ts +616 -0
- package/apps/web/lib/mcp/session-manager.ts +779 -0
- package/apps/web/lib/mcp/transports.ts +988 -0
- package/apps/web/lib/mcp/types.ts +594 -0
- package/apps/web/lib/mcp-client-enhanced.ts +871 -0
- package/apps/web/lib/mcp-client.ts +778 -0
- package/apps/web/lib/mcp-errors.ts +489 -0
- package/apps/web/lib/mcp-sandbox.ts +593 -0
- package/apps/web/lib/mcp-testing.ts +428 -0
- package/apps/web/lib/mcp-types.ts +448 -0
- package/apps/web/lib/playground-store.tsx +1147 -0
- package/apps/web/lib/utils.ts +439 -0
- package/apps/web/next-env.d.ts +5 -0
- package/apps/web/next.config.js +23 -0
- package/apps/web/package.json +55 -0
- package/apps/web/postcss.config.js +6 -0
- package/apps/web/public/.well-known/ai-plugin.json +17 -0
- package/apps/web/public/logo.svg +6 -0
- package/apps/web/public/robots.txt +22 -0
- package/apps/web/public/schema.json +27 -0
- package/apps/web/tailwind.config.js +26 -0
- package/apps/web/tailwind.config.ts +123 -0
- package/apps/web/tsconfig.json +20 -0
- package/apps/web/types/deploy.ts +139 -0
- package/apps/web/types/index.ts +247 -0
- package/apps/web/vercel.json +39 -0
- package/eslint.config.mjs +23 -0
- package/llms.txt +102 -0
- package/mkdocs/docs/api/core.md +318 -0
- package/mkdocs/docs/api/index.md +128 -0
- package/mkdocs/docs/api/mcp-server.md +301 -0
- package/mkdocs/docs/api/openapi-parser.md +254 -0
- package/mkdocs/docs/assets/logo.svg +7 -0
- package/mkdocs/docs/changelog.md +118 -0
- package/mkdocs/docs/cli/generate.md +148 -0
- package/mkdocs/docs/cli/index.md +52 -0
- package/mkdocs/docs/cli/inspect.md +164 -0
- package/mkdocs/docs/cli/serve.md +136 -0
- package/mkdocs/docs/concepts/classification.md +254 -0
- package/mkdocs/docs/concepts/how-it-works.md +299 -0
- package/mkdocs/docs/concepts/index.md +77 -0
- package/mkdocs/docs/concepts/mcp-protocol.md +362 -0
- package/mkdocs/docs/concepts/tool-types.md +382 -0
- package/mkdocs/docs/contributing/architecture.md +262 -0
- package/mkdocs/docs/contributing/development.md +245 -0
- package/mkdocs/docs/contributing/index.md +73 -0
- package/mkdocs/docs/contributing/testing.md +320 -0
- package/mkdocs/docs/getting-started/configuration.md +235 -0
- package/mkdocs/docs/getting-started/index.md +54 -0
- package/mkdocs/docs/getting-started/installation.md +145 -0
- package/mkdocs/docs/getting-started/quickstart.md +160 -0
- package/mkdocs/docs/guides/batch.md +375 -0
- package/mkdocs/docs/guides/claude-desktop.md +227 -0
- package/mkdocs/docs/guides/cursor.md +188 -0
- package/mkdocs/docs/guides/custom-tools.md +367 -0
- package/mkdocs/docs/guides/index.md +78 -0
- package/mkdocs/docs/guides/private-repos.md +221 -0
- package/mkdocs/docs/guides/vscode.md +247 -0
- package/mkdocs/docs/index.md +175 -0
- package/mkdocs/docs/reference/config.md +223 -0
- package/mkdocs/docs/reference/env.md +192 -0
- package/mkdocs/docs/reference/index.md +102 -0
- package/mkdocs/docs/reference/tools.md +309 -0
- package/mkdocs/docs/stylesheets/extra.css +231 -0
- package/mkdocs/mkdocs.yml +204 -0
- package/mkdocs/overrides/.gitkeep +1 -0
- package/mkdocs/overrides/main.html +7 -0
- package/mkdocs/python-deps.txt +7 -0
- package/mkdocs/vercel.json +11 -0
- package/package.json +63 -0
- package/packages/core/package.json +61 -0
- package/packages/core/src/__tests__/bitbucket-client.test.ts +366 -0
- package/packages/core/src/__tests__/cli.test.ts +235 -0
- package/packages/core/src/__tests__/code-extractor.test.ts +378 -0
- package/packages/core/src/__tests__/docker-generator.test.ts +255 -0
- package/packages/core/src/__tests__/github-client.test.ts +390 -0
- package/packages/core/src/__tests__/gitlab-client.test.ts +319 -0
- package/packages/core/src/__tests__/go-extractor.test.ts +351 -0
- package/packages/core/src/__tests__/graphql-extractor.test.ts +330 -0
- package/packages/core/src/__tests__/java-extractor.test.ts +497 -0
- package/packages/core/src/__tests__/plugins.test.ts +467 -0
- package/packages/core/src/__tests__/readme-extractor.test.ts +258 -0
- package/packages/core/src/__tests__/redis-cache.test.ts +307 -0
- package/packages/core/src/__tests__/rust-extractor.test.ts +252 -0
- package/packages/core/src/__tests__/streaming.test.ts +251 -0
- package/packages/core/src/additional-extractors.ts +333 -0
- package/packages/core/src/cache/cache-interface.ts +179 -0
- package/packages/core/src/cache/index.ts +210 -0
- package/packages/core/src/cache/redis-cache.ts +291 -0
- package/packages/core/src/cache/upstash-cache.ts +379 -0
- package/packages/core/src/cache.ts +251 -0
- package/packages/core/src/cli.ts +822 -0
- package/packages/core/src/code-extractor.ts +696 -0
- package/packages/core/src/docker-generator.ts +470 -0
- package/packages/core/src/edge-compatible.ts +491 -0
- package/packages/core/src/extractors/go-extractor.ts +791 -0
- package/packages/core/src/extractors/index.ts +9 -0
- package/packages/core/src/extractors/java-extractor.ts +937 -0
- package/packages/core/src/extractors/rust-extractor.ts +744 -0
- package/packages/core/src/github-client.ts +319 -0
- package/packages/core/src/go-generator.ts +356 -0
- package/packages/core/src/graphql-extractor.ts +358 -0
- package/packages/core/src/index.ts +797 -0
- package/packages/core/src/langchain-exporter.ts +617 -0
- package/packages/core/src/language-parsers.ts +1114 -0
- package/packages/core/src/mcp-introspector.ts +279 -0
- package/packages/core/src/monorepo-detector.ts +378 -0
- package/packages/core/src/plugins/index.ts +370 -0
- package/packages/core/src/plugins/registry.ts +404 -0
- package/packages/core/src/plugins/types.ts +215 -0
- package/packages/core/src/providers/base-provider.ts +246 -0
- package/packages/core/src/providers/bitbucket-client.ts +464 -0
- package/packages/core/src/providers/gitlab-client.ts +388 -0
- package/packages/core/src/providers/index.ts +176 -0
- package/packages/core/src/python-generator.ts +260 -0
- package/packages/core/src/queue/index.ts +100 -0
- package/packages/core/src/queue/memory-queue.ts +445 -0
- package/packages/core/src/queue/redis-queue.ts +578 -0
- package/packages/core/src/queue/types.ts +251 -0
- package/packages/core/src/readme-extractor.ts +409 -0
- package/packages/core/src/schema-generator.ts +638 -0
- package/packages/core/src/streaming.ts +999 -0
- package/packages/core/src/types.ts +289 -0
- package/packages/core/tsconfig.json +9 -0
- package/packages/core/tsup.config.ts +25 -0
- package/packages/mcp-server/README.md +297 -0
- package/packages/mcp-server/package.json +55 -0
- package/packages/mcp-server/src/__tests__/mcp-server.test.ts +177 -0
- package/packages/mcp-server/src/__tests__/tools.test.ts +217 -0
- package/packages/mcp-server/src/index.ts +1206 -0
- package/packages/mcp-server/src/prompts/index.ts +601 -0
- package/packages/mcp-server/src/tools/export-docker.ts +362 -0
- package/packages/mcp-server/src/tools/generate-openapi.ts +162 -0
- package/packages/mcp-server/src/tools/monitor-mcp-server.ts +448 -0
- package/packages/mcp-server/src/tools/stream-convert.ts +398 -0
- package/packages/mcp-server/src/tools/test-mcp-tool.ts +531 -0
- package/packages/mcp-server/tsconfig.json +12 -0
- package/packages/mcp-server/tsup.config.ts +14 -0
- package/packages/openapi-parser/package-lock.json +3028 -0
- package/packages/openapi-parser/package.json +41 -0
- package/packages/openapi-parser/src/analyzer.ts +700 -0
- package/packages/openapi-parser/src/asyncapi-parser.ts +475 -0
- package/packages/openapi-parser/src/cli.ts +302 -0
- package/packages/openapi-parser/src/generator.ts +570 -0
- package/packages/openapi-parser/src/generators/express-analyzer.ts +649 -0
- package/packages/openapi-parser/src/generators/fastapi-analyzer.ts +960 -0
- package/packages/openapi-parser/src/generators/index.ts +200 -0
- package/packages/openapi-parser/src/generators/nextjs-analyzer.ts +768 -0
- package/packages/openapi-parser/src/generators/openapi-builder.ts +527 -0
- package/packages/openapi-parser/src/generators/types.ts +298 -0
- package/packages/openapi-parser/src/graphql-parser.ts +462 -0
- package/packages/openapi-parser/src/grpc-parser.ts +649 -0
- package/packages/openapi-parser/src/har-parser.ts +723 -0
- package/packages/openapi-parser/src/index.ts +635 -0
- package/packages/openapi-parser/src/insomnia-parser.ts +614 -0
- package/packages/openapi-parser/src/parser.ts +231 -0
- package/packages/openapi-parser/src/postman-parser.ts +611 -0
- package/packages/openapi-parser/src/ref-resolver.ts +313 -0
- package/packages/openapi-parser/src/transformer.ts +459 -0
- package/packages/openapi-parser/tests/generators/express.test.ts +209 -0
- package/packages/openapi-parser/tests/generators/fastapi.test.ts +236 -0
- package/packages/openapi-parser/tests/generators/nextjs.test.ts +273 -0
- package/packages/openapi-parser/tests/parsers.test.ts +847 -0
- package/packages/openapi-parser/tsconfig.json +9 -0
- package/packages/openapi-parser/tsup.config.ts +11 -0
- package/packages/registry/package.json +59 -0
- package/packages/registry/src/cli.ts +456 -0
- package/packages/registry/src/index.ts +44 -0
- package/packages/registry/src/popular/github.json +47 -0
- package/packages/registry/src/popular/index.ts +55 -0
- package/packages/registry/src/popular/linear.json +42 -0
- package/packages/registry/src/popular/notion.json +42 -0
- package/packages/registry/src/popular/openai.json +40 -0
- package/packages/registry/src/popular/resend.json +38 -0
- package/packages/registry/src/popular/slack.json +42 -0
- package/packages/registry/src/popular/stripe.json +163 -0
- package/packages/registry/src/popular/supabase.json +42 -0
- package/packages/registry/src/popular/twilio.json +40 -0
- package/packages/registry/src/popular/vercel.json +40 -0
- package/packages/registry/src/registry.ts +492 -0
- package/packages/registry/src/storage.ts +334 -0
- package/packages/registry/src/types.ts +275 -0
- package/packages/registry/src/updater.ts +208 -0
- package/packages/registry/tsconfig.json +10 -0
- package/packages/registry/tsup.config.ts +11 -0
- package/pnpm-workspace.yaml +3 -0
- package/scripts/build-docs.sh +16 -0
- package/server.json +9 -0
- package/templates/Dockerfile.python.template +60 -0
- package/templates/Dockerfile.typescript.template +60 -0
- package/templates/docker-compose.template.yml +68 -0
- package/tests/fixtures/express-app/index.js +34 -0
- package/tests/fixtures/express-app/routes/posts.js +43 -0
- package/tests/fixtures/express-app/routes/users.js +58 -0
- package/tests/fixtures/fastapi-app/main.py +125 -0
- package/tests/fixtures/fastapi-app/routes/admin.py +42 -0
- package/tests/fixtures/graphql/simple-schema.graphql +65 -0
- package/tests/fixtures/mocks/github-api-responses.json +63 -0
- package/tests/fixtures/nextjs-app/app/api/posts/route.ts +55 -0
- package/tests/fixtures/nextjs-app/app/api/users/[id]/route.ts +63 -0
- package/tests/fixtures/nextjs-app/app/api/users/route.ts +44 -0
- package/tests/fixtures/nextjs-app/pages/api/health.ts +28 -0
- package/tests/fixtures/openapi/petstore.yaml +179 -0
- package/tests/integration/langchain-export.test.ts +405 -0
- package/tests/integration/openapi-conversion.test.ts +221 -0
- package/tsconfig.json +18 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch Convert Component - Convert multiple GitHub repos to MCP servers
|
|
3
|
+
* @copyright 2024-2026 nirholas
|
|
4
|
+
* @license MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use client';
|
|
8
|
+
|
|
9
|
+
import { useState, useCallback, useRef, useMemo } from 'react';
|
|
10
|
+
import { motion, AnimatePresence, Reorder } from 'framer-motion';
|
|
11
|
+
import {
|
|
12
|
+
Plus,
|
|
13
|
+
Trash2,
|
|
14
|
+
Play,
|
|
15
|
+
Pause,
|
|
16
|
+
X,
|
|
17
|
+
Check,
|
|
18
|
+
Loader2,
|
|
19
|
+
AlertCircle,
|
|
20
|
+
Download,
|
|
21
|
+
Github,
|
|
22
|
+
Package,
|
|
23
|
+
GripVertical,
|
|
24
|
+
Upload,
|
|
25
|
+
FileText,
|
|
26
|
+
Archive,
|
|
27
|
+
ExternalLink,
|
|
28
|
+
RefreshCw,
|
|
29
|
+
ChevronDown,
|
|
30
|
+
ChevronUp,
|
|
31
|
+
Copy,
|
|
32
|
+
Zap,
|
|
33
|
+
Clock,
|
|
34
|
+
} from 'lucide-react';
|
|
35
|
+
import type { BatchConversionItem, BatchConversionState, ConversionResult, ApiError } from '@/types';
|
|
36
|
+
|
|
37
|
+
interface BatchConvertProps {
|
|
38
|
+
onBatchComplete?: (results: Array<{ url: string; result?: ConversionResult; error?: string }>) => void;
|
|
39
|
+
maxConcurrent?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const EXAMPLE_REPOS = [
|
|
43
|
+
'https://github.com/langchain-ai/langchain',
|
|
44
|
+
'https://github.com/anthropics/anthropic-sdk-python',
|
|
45
|
+
'https://github.com/openai/openai-python',
|
|
46
|
+
'https://github.com/vercel/ai',
|
|
47
|
+
'https://github.com/microsoft/autogen',
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const isValidGithubUrl = (url: string): boolean => {
|
|
51
|
+
const pattern = /^https?:\/\/(www\.)?github\.com\/[\w-]+\/[\w.-]+\/?$/i;
|
|
52
|
+
return pattern.test(url.trim());
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const extractRepoInfo = (url: string): { owner: string; repo: string } | null => {
|
|
56
|
+
const match = url.match(/github\.com\/([^\/]+)\/([^\/]+)/i);
|
|
57
|
+
if (match) {
|
|
58
|
+
return { owner: match[1], repo: match[2].replace(/\.git$/, '') };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default function BatchConvert({
|
|
64
|
+
onBatchComplete,
|
|
65
|
+
maxConcurrent = 3,
|
|
66
|
+
}: BatchConvertProps) {
|
|
67
|
+
const [items, setItems] = useState<BatchConversionItem[]>([]);
|
|
68
|
+
const [inputValue, setInputValue] = useState('');
|
|
69
|
+
const [state, setState] = useState<BatchConversionState>('idle');
|
|
70
|
+
const [showImport, setShowImport] = useState(false);
|
|
71
|
+
const [expandedItem, setExpandedItem] = useState<string | null>(null);
|
|
72
|
+
const [copiedUrl, setCopiedUrl] = useState<string | null>(null);
|
|
73
|
+
|
|
74
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
75
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
76
|
+
|
|
77
|
+
// Computed stats
|
|
78
|
+
const stats = useMemo(() => {
|
|
79
|
+
const pending = items.filter(i => i.status === 'pending').length;
|
|
80
|
+
const converting = items.filter(i => i.status === 'converting').length;
|
|
81
|
+
const success = items.filter(i => i.status === 'success').length;
|
|
82
|
+
const error = items.filter(i => i.status === 'error').length;
|
|
83
|
+
const total = items.length;
|
|
84
|
+
const progress = total > 0 ? ((success + error) / total) * 100 : 0;
|
|
85
|
+
const totalTools = items.reduce((sum, item) => sum + (item.result?.tools.length || 0), 0);
|
|
86
|
+
|
|
87
|
+
return { pending, converting, success, error, total, progress, totalTools };
|
|
88
|
+
}, [items]);
|
|
89
|
+
|
|
90
|
+
// Add single URL
|
|
91
|
+
const addUrl = useCallback((url: string) => {
|
|
92
|
+
const trimmedUrl = url.trim();
|
|
93
|
+
if (!trimmedUrl || !isValidGithubUrl(trimmedUrl)) return false;
|
|
94
|
+
|
|
95
|
+
// Check for duplicates
|
|
96
|
+
if (items.some(item => item.url.toLowerCase() === trimmedUrl.toLowerCase())) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const newItem: BatchConversionItem = {
|
|
101
|
+
id: `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
|
|
102
|
+
url: trimmedUrl,
|
|
103
|
+
status: 'pending',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
setItems(prev => [...prev, newItem]);
|
|
107
|
+
return true;
|
|
108
|
+
}, [items]);
|
|
109
|
+
|
|
110
|
+
// Handle input submission
|
|
111
|
+
const handleInputSubmit = useCallback((e: React.FormEvent) => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
if (addUrl(inputValue)) {
|
|
114
|
+
setInputValue('');
|
|
115
|
+
}
|
|
116
|
+
}, [inputValue, addUrl]);
|
|
117
|
+
|
|
118
|
+
// Add multiple URLs from text
|
|
119
|
+
const importUrls = useCallback((text: string) => {
|
|
120
|
+
const urls = text
|
|
121
|
+
.split(/[\n,;]+/)
|
|
122
|
+
.map(u => u.trim())
|
|
123
|
+
.filter(u => isValidGithubUrl(u));
|
|
124
|
+
|
|
125
|
+
let added = 0;
|
|
126
|
+
urls.forEach(url => {
|
|
127
|
+
if (addUrl(url)) added++;
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return added;
|
|
131
|
+
}, [addUrl]);
|
|
132
|
+
|
|
133
|
+
// Handle file import
|
|
134
|
+
const handleFileImport = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
135
|
+
const file = e.target.files?.[0];
|
|
136
|
+
if (!file) return;
|
|
137
|
+
|
|
138
|
+
const reader = new FileReader();
|
|
139
|
+
reader.onload = (event) => {
|
|
140
|
+
const text = event.target?.result as string;
|
|
141
|
+
importUrls(text);
|
|
142
|
+
};
|
|
143
|
+
reader.readAsText(file);
|
|
144
|
+
|
|
145
|
+
// Reset input
|
|
146
|
+
if (fileInputRef.current) {
|
|
147
|
+
fileInputRef.current.value = '';
|
|
148
|
+
}
|
|
149
|
+
}, [importUrls]);
|
|
150
|
+
|
|
151
|
+
// Remove item
|
|
152
|
+
const removeItem = useCallback((id: string) => {
|
|
153
|
+
setItems(prev => prev.filter(item => item.id !== id));
|
|
154
|
+
}, []);
|
|
155
|
+
|
|
156
|
+
// Clear all
|
|
157
|
+
const clearAll = useCallback(() => {
|
|
158
|
+
setItems([]);
|
|
159
|
+
setState('idle');
|
|
160
|
+
}, []);
|
|
161
|
+
|
|
162
|
+
// Clear completed
|
|
163
|
+
const clearCompleted = useCallback(() => {
|
|
164
|
+
setItems(prev => prev.filter(item => item.status !== 'success'));
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
// Retry failed
|
|
168
|
+
const retryFailed = useCallback(() => {
|
|
169
|
+
setItems(prev => prev.map(item =>
|
|
170
|
+
item.status === 'error'
|
|
171
|
+
? { ...item, status: 'pending', error: undefined, progress: 0 }
|
|
172
|
+
: item
|
|
173
|
+
));
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
// Convert single item
|
|
177
|
+
const convertItem = useCallback(async (item: BatchConversionItem, signal: AbortSignal) => {
|
|
178
|
+
// Update to converting status
|
|
179
|
+
setItems(prev => prev.map(i =>
|
|
180
|
+
i.id === item.id ? { ...i, status: 'converting', progress: 10 } : i
|
|
181
|
+
));
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
// Simulate progress updates (in real app, would use SSE)
|
|
185
|
+
const progressInterval = setInterval(() => {
|
|
186
|
+
setItems(prev => prev.map(i =>
|
|
187
|
+
i.id === item.id && i.status === 'converting'
|
|
188
|
+
? { ...i, progress: Math.min((i.progress || 10) + Math.random() * 20, 90) }
|
|
189
|
+
: i
|
|
190
|
+
));
|
|
191
|
+
}, 500);
|
|
192
|
+
|
|
193
|
+
// Make API call
|
|
194
|
+
const response = await fetch('/api/convert', {
|
|
195
|
+
method: 'POST',
|
|
196
|
+
headers: { 'Content-Type': 'application/json' },
|
|
197
|
+
body: JSON.stringify({ url: item.url }),
|
|
198
|
+
signal,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
clearInterval(progressInterval);
|
|
202
|
+
|
|
203
|
+
if (!response.ok) {
|
|
204
|
+
const errorData = await response.json() as ApiError;
|
|
205
|
+
throw new Error(errorData.error || 'Conversion failed');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const result = await response.json() as ConversionResult;
|
|
209
|
+
|
|
210
|
+
setItems(prev => prev.map(i =>
|
|
211
|
+
i.id === item.id
|
|
212
|
+
? { ...i, status: 'success', result, progress: 100 }
|
|
213
|
+
: i
|
|
214
|
+
));
|
|
215
|
+
|
|
216
|
+
return result;
|
|
217
|
+
} catch (err: any) {
|
|
218
|
+
if (err.name === 'AbortError') {
|
|
219
|
+
setItems(prev => prev.map(i =>
|
|
220
|
+
i.id === item.id
|
|
221
|
+
? { ...i, status: 'pending', progress: 0 }
|
|
222
|
+
: i
|
|
223
|
+
));
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setItems(prev => prev.map(i =>
|
|
228
|
+
i.id === item.id
|
|
229
|
+
? { ...i, status: 'error' as const, error: { error: err.message, code: 'CONVERSION_FAILED' }, progress: 0 }
|
|
230
|
+
: i
|
|
231
|
+
));
|
|
232
|
+
return null;
|
|
233
|
+
}
|
|
234
|
+
}, []);
|
|
235
|
+
|
|
236
|
+
// Start batch conversion
|
|
237
|
+
const startBatch = useCallback(async () => {
|
|
238
|
+
if (state === 'running') return;
|
|
239
|
+
|
|
240
|
+
setState('running');
|
|
241
|
+
abortControllerRef.current = new AbortController();
|
|
242
|
+
const signal = abortControllerRef.current.signal;
|
|
243
|
+
|
|
244
|
+
const pendingItems = items.filter(i => i.status === 'pending');
|
|
245
|
+
const queue = [...pendingItems];
|
|
246
|
+
const running: Promise<any>[] = [];
|
|
247
|
+
|
|
248
|
+
while ((queue.length > 0 || running.length > 0) && !signal.aborted) {
|
|
249
|
+
// Fill up to maxConcurrent
|
|
250
|
+
while (queue.length > 0 && running.length < maxConcurrent) {
|
|
251
|
+
const item = queue.shift()!;
|
|
252
|
+
const promise = convertItem(item, signal).finally(() => {
|
|
253
|
+
running.splice(running.indexOf(promise), 1);
|
|
254
|
+
});
|
|
255
|
+
running.push(promise);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Wait for at least one to complete
|
|
259
|
+
if (running.length > 0) {
|
|
260
|
+
await Promise.race(running);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Wait for remaining
|
|
265
|
+
await Promise.all(running);
|
|
266
|
+
|
|
267
|
+
if (!signal.aborted) {
|
|
268
|
+
setState('complete');
|
|
269
|
+
onBatchComplete?.(items.map(i => ({
|
|
270
|
+
url: i.url,
|
|
271
|
+
result: i.result,
|
|
272
|
+
error: i.error?.error,
|
|
273
|
+
})));
|
|
274
|
+
}
|
|
275
|
+
}, [items, state, maxConcurrent, convertItem, onBatchComplete]);
|
|
276
|
+
|
|
277
|
+
// Pause batch
|
|
278
|
+
const pauseBatch = useCallback(() => {
|
|
279
|
+
abortControllerRef.current?.abort();
|
|
280
|
+
setState('paused');
|
|
281
|
+
}, []);
|
|
282
|
+
|
|
283
|
+
// Resume batch
|
|
284
|
+
const resumeBatch = useCallback(() => {
|
|
285
|
+
startBatch();
|
|
286
|
+
}, [startBatch]);
|
|
287
|
+
|
|
288
|
+
// Download all results
|
|
289
|
+
const downloadAllResults = useCallback(() => {
|
|
290
|
+
const successItems = items.filter(i => i.status === 'success' && i.result);
|
|
291
|
+
|
|
292
|
+
const combined = {
|
|
293
|
+
timestamp: new Date().toISOString(),
|
|
294
|
+
totalRepos: successItems.length,
|
|
295
|
+
totalTools: stats.totalTools,
|
|
296
|
+
results: successItems.map(i => ({
|
|
297
|
+
url: i.url,
|
|
298
|
+
repository: i.result!.repository,
|
|
299
|
+
tools: i.result!.tools,
|
|
300
|
+
})),
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const blob = new Blob([JSON.stringify(combined, null, 2)], { type: 'application/json' });
|
|
304
|
+
const url = URL.createObjectURL(blob);
|
|
305
|
+
const a = document.createElement('a');
|
|
306
|
+
a.href = url;
|
|
307
|
+
a.download = `batch-conversion-${Date.now()}.json`;
|
|
308
|
+
document.body.appendChild(a);
|
|
309
|
+
a.click();
|
|
310
|
+
document.body.removeChild(a);
|
|
311
|
+
URL.revokeObjectURL(url);
|
|
312
|
+
}, [items, stats.totalTools]);
|
|
313
|
+
|
|
314
|
+
const copyUrl = useCallback(async (url: string) => {
|
|
315
|
+
await navigator.clipboard.writeText(url);
|
|
316
|
+
setCopiedUrl(url);
|
|
317
|
+
setTimeout(() => setCopiedUrl(null), 2000);
|
|
318
|
+
}, []);
|
|
319
|
+
|
|
320
|
+
const getStatusColor = (status: BatchConversionItem['status']) => {
|
|
321
|
+
switch (status) {
|
|
322
|
+
case 'pending': return 'text-neutral-400';
|
|
323
|
+
case 'converting': return 'text-blue-400';
|
|
324
|
+
case 'success': return 'text-green-400';
|
|
325
|
+
case 'error': return 'text-red-400';
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const getStatusIcon = (status: BatchConversionItem['status']) => {
|
|
330
|
+
switch (status) {
|
|
331
|
+
case 'pending': return <Clock className="w-4 h-4" />;
|
|
332
|
+
case 'converting': return <Loader2 className="w-4 h-4 animate-spin" />;
|
|
333
|
+
case 'success': return <Check className="w-4 h-4" />;
|
|
334
|
+
case 'error': return <AlertCircle className="w-4 h-4" />;
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
return (
|
|
339
|
+
<div className="space-y-6">
|
|
340
|
+
{/* Header */}
|
|
341
|
+
<motion.div
|
|
342
|
+
initial={{ opacity: 0, y: 20 }}
|
|
343
|
+
animate={{ opacity: 1, y: 0 }}
|
|
344
|
+
className="flex items-center justify-between"
|
|
345
|
+
>
|
|
346
|
+
<div className="flex items-center gap-3">
|
|
347
|
+
<div className="w-12 h-12 rounded-xl bg-purple-500/10 border border-purple-500/20 flex items-center justify-center">
|
|
348
|
+
<Archive className="w-6 h-6 text-purple-400" />
|
|
349
|
+
</div>
|
|
350
|
+
<div>
|
|
351
|
+
<h2 className="text-xl font-semibold text-white">Batch Convert</h2>
|
|
352
|
+
<p className="text-sm text-neutral-400">
|
|
353
|
+
Convert multiple repositories in parallel
|
|
354
|
+
</p>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
|
|
358
|
+
{stats.total > 0 && (
|
|
359
|
+
<div className="flex items-center gap-4 text-sm">
|
|
360
|
+
<div className="flex items-center gap-1 text-neutral-400">
|
|
361
|
+
<Package className="w-4 h-4" />
|
|
362
|
+
<span>{stats.totalTools} tools</span>
|
|
363
|
+
</div>
|
|
364
|
+
<div className="flex items-center gap-1 text-green-400">
|
|
365
|
+
<Check className="w-4 h-4" />
|
|
366
|
+
<span>{stats.success}/{stats.total}</span>
|
|
367
|
+
</div>
|
|
368
|
+
{stats.error > 0 && (
|
|
369
|
+
<div className="flex items-center gap-1 text-red-400">
|
|
370
|
+
<AlertCircle className="w-4 h-4" />
|
|
371
|
+
<span>{stats.error} failed</span>
|
|
372
|
+
</div>
|
|
373
|
+
)}
|
|
374
|
+
</div>
|
|
375
|
+
)}
|
|
376
|
+
</motion.div>
|
|
377
|
+
|
|
378
|
+
{/* Input section */}
|
|
379
|
+
<motion.div
|
|
380
|
+
initial={{ opacity: 0, y: 20 }}
|
|
381
|
+
animate={{ opacity: 1, y: 0 }}
|
|
382
|
+
transition={{ delay: 0.1 }}
|
|
383
|
+
className="rounded-xl border border-neutral-800 bg-neutral-900/50 backdrop-blur-xl p-4"
|
|
384
|
+
>
|
|
385
|
+
<form onSubmit={handleInputSubmit} className="flex gap-3">
|
|
386
|
+
<div className="flex-1 relative">
|
|
387
|
+
<Github className="absolute left-4 top-1/2 transform -translate-y-1/2 w-5 h-5 text-neutral-500" />
|
|
388
|
+
<input
|
|
389
|
+
type="text"
|
|
390
|
+
value={inputValue}
|
|
391
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
392
|
+
placeholder="https://github.com/owner/repo"
|
|
393
|
+
className="w-full pl-12 pr-4 py-3 bg-black border border-neutral-700 rounded-xl text-white placeholder-neutral-500 focus:outline-none focus:border-white/50 transition-colors"
|
|
394
|
+
disabled={state === 'running'}
|
|
395
|
+
/>
|
|
396
|
+
</div>
|
|
397
|
+
<button
|
|
398
|
+
type="submit"
|
|
399
|
+
disabled={!isValidGithubUrl(inputValue) || state === 'running'}
|
|
400
|
+
className="px-6 py-3 bg-white text-black rounded-xl font-medium hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
|
|
401
|
+
>
|
|
402
|
+
<Plus className="w-5 h-5" />
|
|
403
|
+
Add
|
|
404
|
+
</button>
|
|
405
|
+
</form>
|
|
406
|
+
|
|
407
|
+
{/* Import options */}
|
|
408
|
+
<div className="mt-3 flex items-center gap-3">
|
|
409
|
+
<button
|
|
410
|
+
onClick={() => setShowImport(!showImport)}
|
|
411
|
+
className="text-sm text-neutral-400 hover:text-white transition-colors flex items-center gap-1"
|
|
412
|
+
>
|
|
413
|
+
<Upload className="w-4 h-4" />
|
|
414
|
+
Import URLs
|
|
415
|
+
{showImport ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
|
416
|
+
</button>
|
|
417
|
+
<span className="text-neutral-600">|</span>
|
|
418
|
+
<button
|
|
419
|
+
onClick={() => EXAMPLE_REPOS.forEach(url => addUrl(url))}
|
|
420
|
+
className="text-sm text-neutral-400 hover:text-white transition-colors"
|
|
421
|
+
>
|
|
422
|
+
Add example repos
|
|
423
|
+
</button>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
426
|
+
{/* Import panel */}
|
|
427
|
+
<AnimatePresence>
|
|
428
|
+
{showImport && (
|
|
429
|
+
<motion.div
|
|
430
|
+
initial={{ height: 0, opacity: 0 }}
|
|
431
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
432
|
+
exit={{ height: 0, opacity: 0 }}
|
|
433
|
+
className="mt-3 space-y-3 overflow-hidden"
|
|
434
|
+
>
|
|
435
|
+
<textarea
|
|
436
|
+
placeholder="Paste multiple GitHub URLs (one per line, comma, or semicolon separated)"
|
|
437
|
+
className="w-full h-32 p-3 bg-black border border-neutral-700 rounded-xl text-white placeholder-neutral-500 resize-none focus:outline-none focus:border-white/50"
|
|
438
|
+
onBlur={(e) => {
|
|
439
|
+
if (e.target.value.trim()) {
|
|
440
|
+
importUrls(e.target.value);
|
|
441
|
+
e.target.value = '';
|
|
442
|
+
}
|
|
443
|
+
}}
|
|
444
|
+
/>
|
|
445
|
+
<div className="flex items-center gap-3">
|
|
446
|
+
<input
|
|
447
|
+
ref={fileInputRef}
|
|
448
|
+
type="file"
|
|
449
|
+
accept=".txt,.csv"
|
|
450
|
+
onChange={handleFileImport}
|
|
451
|
+
className="hidden"
|
|
452
|
+
/>
|
|
453
|
+
<button
|
|
454
|
+
onClick={() => fileInputRef.current?.click()}
|
|
455
|
+
className="flex items-center gap-2 px-4 py-2 bg-white/10 text-white rounded-lg text-sm hover:bg-white/20 transition-colors"
|
|
456
|
+
>
|
|
457
|
+
<FileText className="w-4 h-4" />
|
|
458
|
+
Import from file
|
|
459
|
+
</button>
|
|
460
|
+
</div>
|
|
461
|
+
</motion.div>
|
|
462
|
+
)}
|
|
463
|
+
</AnimatePresence>
|
|
464
|
+
</motion.div>
|
|
465
|
+
|
|
466
|
+
{/* Progress bar (when running) */}
|
|
467
|
+
<AnimatePresence>
|
|
468
|
+
{state === 'running' && stats.total > 0 && (
|
|
469
|
+
<motion.div
|
|
470
|
+
initial={{ opacity: 0, height: 0 }}
|
|
471
|
+
animate={{ opacity: 1, height: 'auto' }}
|
|
472
|
+
exit={{ opacity: 0, height: 0 }}
|
|
473
|
+
className="rounded-xl border border-neutral-800 bg-neutral-900/50 p-4"
|
|
474
|
+
>
|
|
475
|
+
<div className="flex items-center justify-between mb-2">
|
|
476
|
+
<span className="text-sm text-neutral-400">Converting...</span>
|
|
477
|
+
<span className="text-sm text-white">{Math.round(stats.progress)}%</span>
|
|
478
|
+
</div>
|
|
479
|
+
<div className="h-2 bg-black/50 rounded-full overflow-hidden">
|
|
480
|
+
<motion.div
|
|
481
|
+
className="h-full bg-white rounded-full"
|
|
482
|
+
initial={{ width: 0 }}
|
|
483
|
+
animate={{ width: `${stats.progress}%` }}
|
|
484
|
+
transition={{ duration: 0.3 }}
|
|
485
|
+
/>
|
|
486
|
+
</div>
|
|
487
|
+
<div className="flex items-center gap-4 mt-2 text-xs text-neutral-500">
|
|
488
|
+
<span>{stats.converting} converting</span>
|
|
489
|
+
<span>{stats.pending} pending</span>
|
|
490
|
+
<span>{stats.success} complete</span>
|
|
491
|
+
</div>
|
|
492
|
+
</motion.div>
|
|
493
|
+
)}
|
|
494
|
+
</AnimatePresence>
|
|
495
|
+
|
|
496
|
+
{/* URL list */}
|
|
497
|
+
{items.length > 0 && (
|
|
498
|
+
<motion.div
|
|
499
|
+
initial={{ opacity: 0, y: 20 }}
|
|
500
|
+
animate={{ opacity: 1, y: 0 }}
|
|
501
|
+
transition={{ delay: 0.2 }}
|
|
502
|
+
className="rounded-xl border border-neutral-800 bg-neutral-900/50 backdrop-blur-xl overflow-hidden"
|
|
503
|
+
>
|
|
504
|
+
<div className="p-3 border-b border-neutral-800 flex items-center justify-between">
|
|
505
|
+
<span className="text-sm font-medium text-white">{stats.total} Repositories</span>
|
|
506
|
+
<div className="flex items-center gap-2">
|
|
507
|
+
{stats.error > 0 && state !== 'running' && (
|
|
508
|
+
<button
|
|
509
|
+
onClick={retryFailed}
|
|
510
|
+
className="text-xs text-red-400 hover:text-red-300 transition-colors flex items-center gap-1"
|
|
511
|
+
>
|
|
512
|
+
<RefreshCw className="w-3 h-3" />
|
|
513
|
+
Retry failed
|
|
514
|
+
</button>
|
|
515
|
+
)}
|
|
516
|
+
{stats.success > 0 && (
|
|
517
|
+
<button
|
|
518
|
+
onClick={clearCompleted}
|
|
519
|
+
className="text-xs text-neutral-400 hover:text-white transition-colors"
|
|
520
|
+
>
|
|
521
|
+
Clear completed
|
|
522
|
+
</button>
|
|
523
|
+
)}
|
|
524
|
+
<button
|
|
525
|
+
onClick={clearAll}
|
|
526
|
+
className="text-xs text-neutral-400 hover:text-red-400 transition-colors"
|
|
527
|
+
>
|
|
528
|
+
Clear all
|
|
529
|
+
</button>
|
|
530
|
+
</div>
|
|
531
|
+
</div>
|
|
532
|
+
|
|
533
|
+
<Reorder.Group
|
|
534
|
+
axis="y"
|
|
535
|
+
values={items}
|
|
536
|
+
onReorder={setItems}
|
|
537
|
+
className="divide-y divide-neutral-800"
|
|
538
|
+
>
|
|
539
|
+
<AnimatePresence mode="popLayout">
|
|
540
|
+
{items.map((item) => {
|
|
541
|
+
const repoInfo = extractRepoInfo(item.url);
|
|
542
|
+
const isExpanded = expandedItem === item.id;
|
|
543
|
+
|
|
544
|
+
return (
|
|
545
|
+
<Reorder.Item
|
|
546
|
+
key={item.id}
|
|
547
|
+
value={item}
|
|
548
|
+
initial={{ opacity: 0, height: 0 }}
|
|
549
|
+
animate={{ opacity: 1, height: 'auto' }}
|
|
550
|
+
exit={{ opacity: 0, height: 0 }}
|
|
551
|
+
className="bg-neutral-900/50"
|
|
552
|
+
>
|
|
553
|
+
<div className="p-3">
|
|
554
|
+
<div className="flex items-center gap-3">
|
|
555
|
+
<div className="cursor-grab active:cursor-grabbing text-neutral-600 hover:text-neutral-400">
|
|
556
|
+
<GripVertical className="w-4 h-4" />
|
|
557
|
+
</div>
|
|
558
|
+
|
|
559
|
+
<div className={`flex-shrink-0 ${getStatusColor(item.status)}`}>
|
|
560
|
+
{getStatusIcon(item.status)}
|
|
561
|
+
</div>
|
|
562
|
+
|
|
563
|
+
<div className="flex-1 min-w-0">
|
|
564
|
+
<div className="flex items-center gap-2">
|
|
565
|
+
<span className="font-medium text-white truncate">
|
|
566
|
+
{repoInfo ? `${repoInfo.owner}/${repoInfo.repo}` : item.url}
|
|
567
|
+
</span>
|
|
568
|
+
{item.result && (
|
|
569
|
+
<span className="text-xs px-2 py-0.5 rounded-full bg-green-500/10 text-green-400 border border-green-500/20">
|
|
570
|
+
{item.result.tools.length} tools
|
|
571
|
+
</span>
|
|
572
|
+
)}
|
|
573
|
+
</div>
|
|
574
|
+
{item.status === 'converting' && typeof item.progress === 'number' && (
|
|
575
|
+
<div className="mt-1 h-1 bg-neutral-800 rounded-full overflow-hidden">
|
|
576
|
+
<div
|
|
577
|
+
className="h-full bg-blue-500 rounded-full transition-all"
|
|
578
|
+
style={{ width: `${item.progress}%` }}
|
|
579
|
+
/>
|
|
580
|
+
</div>
|
|
581
|
+
)}
|
|
582
|
+
{item.error && (
|
|
583
|
+
<p className="text-xs text-red-400 mt-1 truncate">
|
|
584
|
+
{item.error.error}
|
|
585
|
+
</p>
|
|
586
|
+
)}
|
|
587
|
+
</div>
|
|
588
|
+
|
|
589
|
+
<div className="flex items-center gap-1">
|
|
590
|
+
{item.result && (
|
|
591
|
+
<button
|
|
592
|
+
onClick={() => setExpandedItem(isExpanded ? null : item.id)}
|
|
593
|
+
className="p-2 rounded-lg hover:bg-white/5 text-neutral-400 hover:text-white transition-colors"
|
|
594
|
+
title="View details"
|
|
595
|
+
>
|
|
596
|
+
{isExpanded ? <ChevronUp className="w-4 h-4" /> : <ChevronDown className="w-4 h-4" />}
|
|
597
|
+
</button>
|
|
598
|
+
)}
|
|
599
|
+
<button
|
|
600
|
+
onClick={() => copyUrl(item.url)}
|
|
601
|
+
className="p-2 rounded-lg hover:bg-white/5 text-neutral-400 hover:text-white transition-colors"
|
|
602
|
+
title="Copy URL"
|
|
603
|
+
>
|
|
604
|
+
{copiedUrl === item.url ? (
|
|
605
|
+
<Check className="w-4 h-4 text-green-400" />
|
|
606
|
+
) : (
|
|
607
|
+
<Copy className="w-4 h-4" />
|
|
608
|
+
)}
|
|
609
|
+
</button>
|
|
610
|
+
<a
|
|
611
|
+
href={item.url}
|
|
612
|
+
target="_blank"
|
|
613
|
+
rel="noopener noreferrer"
|
|
614
|
+
className="p-2 rounded-lg hover:bg-white/5 text-neutral-400 hover:text-white transition-colors"
|
|
615
|
+
title="Open on GitHub"
|
|
616
|
+
>
|
|
617
|
+
<ExternalLink className="w-4 h-4" />
|
|
618
|
+
</a>
|
|
619
|
+
{state !== 'running' && (
|
|
620
|
+
<button
|
|
621
|
+
onClick={() => removeItem(item.id)}
|
|
622
|
+
className="p-2 rounded-lg hover:bg-red-500/10 text-neutral-400 hover:text-red-400 transition-colors"
|
|
623
|
+
title="Remove"
|
|
624
|
+
>
|
|
625
|
+
<Trash2 className="w-4 h-4" />
|
|
626
|
+
</button>
|
|
627
|
+
)}
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
630
|
+
|
|
631
|
+
{/* Expanded details */}
|
|
632
|
+
<AnimatePresence>
|
|
633
|
+
{isExpanded && item.result && (
|
|
634
|
+
<motion.div
|
|
635
|
+
initial={{ height: 0, opacity: 0 }}
|
|
636
|
+
animate={{ height: 'auto', opacity: 1 }}
|
|
637
|
+
exit={{ height: 0, opacity: 0 }}
|
|
638
|
+
className="mt-3 pt-3 border-t border-neutral-800 overflow-hidden"
|
|
639
|
+
>
|
|
640
|
+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
|
641
|
+
<div>
|
|
642
|
+
<div className="text-neutral-500">Tools</div>
|
|
643
|
+
<div className="text-white font-medium">{item.result.tools.length}</div>
|
|
644
|
+
</div>
|
|
645
|
+
<div>
|
|
646
|
+
<div className="text-neutral-500">Language</div>
|
|
647
|
+
<div className="text-white font-medium">{item.result.repository.language || 'Unknown'}</div>
|
|
648
|
+
</div>
|
|
649
|
+
<div>
|
|
650
|
+
<div className="text-neutral-500">Stars</div>
|
|
651
|
+
<div className="text-white font-medium">{item.result.repository.stars?.toLocaleString() || '-'}</div>
|
|
652
|
+
</div>
|
|
653
|
+
<div>
|
|
654
|
+
<div className="text-neutral-500">Last Updated</div>
|
|
655
|
+
<div className="text-white font-medium">
|
|
656
|
+
{item.result.repository.lastUpdated
|
|
657
|
+
? new Date(item.result.repository.lastUpdated).toLocaleDateString()
|
|
658
|
+
: '-'
|
|
659
|
+
}
|
|
660
|
+
</div>
|
|
661
|
+
</div>
|
|
662
|
+
</div>
|
|
663
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
664
|
+
{item.result.tools.slice(0, 6).map((tool, idx) => (
|
|
665
|
+
<span
|
|
666
|
+
key={idx}
|
|
667
|
+
className="text-xs px-2 py-1 rounded bg-white/5 border border-neutral-700 text-neutral-300 font-mono"
|
|
668
|
+
>
|
|
669
|
+
{tool.name}
|
|
670
|
+
</span>
|
|
671
|
+
))}
|
|
672
|
+
{item.result.tools.length > 6 && (
|
|
673
|
+
<span className="text-xs px-2 py-1 text-neutral-500">
|
|
674
|
+
+{item.result.tools.length - 6} more
|
|
675
|
+
</span>
|
|
676
|
+
)}
|
|
677
|
+
</div>
|
|
678
|
+
</motion.div>
|
|
679
|
+
)}
|
|
680
|
+
</AnimatePresence>
|
|
681
|
+
</div>
|
|
682
|
+
</Reorder.Item>
|
|
683
|
+
);
|
|
684
|
+
})}
|
|
685
|
+
</AnimatePresence>
|
|
686
|
+
</Reorder.Group>
|
|
687
|
+
</motion.div>
|
|
688
|
+
)}
|
|
689
|
+
|
|
690
|
+
{/* Empty state */}
|
|
691
|
+
{items.length === 0 && (
|
|
692
|
+
<motion.div
|
|
693
|
+
initial={{ opacity: 0 }}
|
|
694
|
+
animate={{ opacity: 1 }}
|
|
695
|
+
className="rounded-xl border border-dashed border-neutral-700 p-8 text-center"
|
|
696
|
+
>
|
|
697
|
+
<Archive className="w-12 h-12 text-neutral-600 mx-auto mb-4" />
|
|
698
|
+
<h3 className="text-lg font-medium text-white mb-2">No repositories added</h3>
|
|
699
|
+
<p className="text-neutral-400 text-sm mb-4">
|
|
700
|
+
Add GitHub repository URLs to convert them to MCP servers in batch
|
|
701
|
+
</p>
|
|
702
|
+
<button
|
|
703
|
+
onClick={() => EXAMPLE_REPOS.forEach(url => addUrl(url))}
|
|
704
|
+
className="text-sm text-white underline hover:no-underline"
|
|
705
|
+
>
|
|
706
|
+
Add example repositories
|
|
707
|
+
</button>
|
|
708
|
+
</motion.div>
|
|
709
|
+
)}
|
|
710
|
+
|
|
711
|
+
{/* Action buttons */}
|
|
712
|
+
{items.length > 0 && (
|
|
713
|
+
<motion.div
|
|
714
|
+
initial={{ opacity: 0, y: 20 }}
|
|
715
|
+
animate={{ opacity: 1, y: 0 }}
|
|
716
|
+
transition={{ delay: 0.3 }}
|
|
717
|
+
className="flex items-center justify-between"
|
|
718
|
+
>
|
|
719
|
+
<div className="flex items-center gap-3">
|
|
720
|
+
{state === 'idle' || state === 'complete' || state === 'paused' ? (
|
|
721
|
+
<button
|
|
722
|
+
onClick={startBatch}
|
|
723
|
+
disabled={stats.pending === 0}
|
|
724
|
+
className="flex items-center gap-2 px-6 py-3 bg-white text-black rounded-xl font-medium hover:bg-neutral-200 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
|
|
725
|
+
>
|
|
726
|
+
<Zap className="w-5 h-5" />
|
|
727
|
+
{state === 'paused' ? 'Resume' : stats.pending < stats.total ? 'Continue' : 'Start Batch'}
|
|
728
|
+
</button>
|
|
729
|
+
) : (
|
|
730
|
+
<button
|
|
731
|
+
onClick={pauseBatch}
|
|
732
|
+
className="flex items-center gap-2 px-6 py-3 bg-white/10 text-white rounded-xl font-medium hover:bg-white/20 transition-colors"
|
|
733
|
+
>
|
|
734
|
+
<Pause className="w-5 h-5" />
|
|
735
|
+
Pause
|
|
736
|
+
</button>
|
|
737
|
+
)}
|
|
738
|
+
|
|
739
|
+
{stats.success > 0 && (
|
|
740
|
+
<button
|
|
741
|
+
onClick={downloadAllResults}
|
|
742
|
+
className="flex items-center gap-2 px-4 py-3 bg-white/10 text-white rounded-xl font-medium hover:bg-white/20 transition-colors"
|
|
743
|
+
>
|
|
744
|
+
<Download className="w-5 h-5" />
|
|
745
|
+
Download All ({stats.success})
|
|
746
|
+
</button>
|
|
747
|
+
)}
|
|
748
|
+
</div>
|
|
749
|
+
|
|
750
|
+
<div className="text-sm text-neutral-500">
|
|
751
|
+
Max {maxConcurrent} concurrent conversions
|
|
752
|
+
</div>
|
|
753
|
+
</motion.div>
|
|
754
|
+
)}
|
|
755
|
+
|
|
756
|
+
{/* Completion message */}
|
|
757
|
+
<AnimatePresence>
|
|
758
|
+
{state === 'complete' && stats.success > 0 && (
|
|
759
|
+
<motion.div
|
|
760
|
+
initial={{ opacity: 0, scale: 0.95 }}
|
|
761
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
762
|
+
exit={{ opacity: 0, scale: 0.95 }}
|
|
763
|
+
className="rounded-xl border border-green-500/30 bg-green-500/10 p-6 text-center"
|
|
764
|
+
>
|
|
765
|
+
<div className="w-16 h-16 rounded-full bg-green-500/20 flex items-center justify-center mx-auto mb-4">
|
|
766
|
+
<Check className="w-8 h-8 text-green-400" />
|
|
767
|
+
</div>
|
|
768
|
+
<h3 className="text-xl font-semibold text-white mb-2">Batch Conversion Complete!</h3>
|
|
769
|
+
<p className="text-neutral-400 mb-4">
|
|
770
|
+
Successfully converted {stats.success} repositories with {stats.totalTools} total tools.
|
|
771
|
+
{stats.error > 0 && ` (${stats.error} failed)`}
|
|
772
|
+
</p>
|
|
773
|
+
<button
|
|
774
|
+
onClick={downloadAllResults}
|
|
775
|
+
className="flex items-center gap-2 px-6 py-3 bg-white text-black rounded-xl font-medium hover:bg-neutral-200 transition-colors mx-auto"
|
|
776
|
+
>
|
|
777
|
+
<Download className="w-5 h-5" />
|
|
778
|
+
Download All Results
|
|
779
|
+
</button>
|
|
780
|
+
</motion.div>
|
|
781
|
+
)}
|
|
782
|
+
</AnimatePresence>
|
|
783
|
+
</div>
|
|
784
|
+
);
|
|
785
|
+
}
|