Compare commits

..

2 Commits

Author SHA1 Message Date
Liujian 7b54e12950 fix bug 2025-10-24 17:18:29 +08:00
Liujian ea32fb1cd6 MCP transports supports Streamable HTTP 2025-10-21 17:52:50 +08:00
576 changed files with 4040 additions and 5652 deletions
+1 -54
View File
@@ -156,10 +156,6 @@ curl -sSO https://download.apipark.com/install/quick-start.sh ; bash quick-sta
<br>
<br>
# 🚀 Use Cases
## Simplify AI Integration Costs
- Connect to 100+ major models from all mainstream AI vendors, with standardized API calls requiring no additional adaptation work.
@@ -203,20 +199,11 @@ To achieve this goal, we plan to add new features to APIPark, including:
# 📕 Documentation
Visit [APIPark Documentation](https://docs.apipark.com/docs/deploy) for detailed installation guides, API references, and usage instructions.
# 🧑‍🤝‍🧑Friendly Links
<a href="https://xroute.ai/">
<img width="1248" height="158" alt="新建 PPTX 演示文稿 (2)_03" src="https://github.com/user-attachments/assets/0ebd694c-410a-4e3f-a793-90f1140d15df" />
</a>
<br>
<br>
# 🧾 License
APIPark uses the Apache 2.0 License. For more details, please refer to the LICENSE file.
<br>
# 💌 Contact Us
@@ -225,46 +212,6 @@ For enterprise-level features and professional technical support, contact our pr
- Website: https://apipark.com
- Email: contact@apipark.com
🙏 A big thanks to everyone who helped shape APIPark. We are thrilled to hear the communitys thoughts! Lets make the world of APIs and AI stronger and more fun together. 🎉
<br>
# 🤝 Partner
- [Cursor](https://www.cursor.com/): Cursor is an AI-powered code editor that integrates artificial intelligence directly into the coding workflow, offering features like intelligent next edit suggestions, deep codebase understanding for relevant answers, and natural language editing to streamline development tasks and boost developer productivity.
- [Dify](https://dify.ai/): Dify is a leading Agentic AI Development Platform that provides a comprehensive suite of tools for building and extending AI applications, offering everything needed for agentic workflows, RAG pipelines, integrations, and observability, while allowing users to amplify their applications with various global Large Language Models (LLMs) and versatile plugins.
- [Trae](https://www.trae.ai/): Trae is an AI-native Integrated Development Environment (IDE) product that aims to embody the concept of “The Real AI Engineer” through intelligent productivity, seamlessly integrating into the development process to enhance quality and efficiency, featuring a chat-based interaction interface and supporting code generation and assistance.
- [Windsurf](https://windsurf.com/): Windsurf is an AI code editor designed to provide a seamless and limitless flow for developers, introducing a new purpose-built IDE that leverages AI to enhance coding with features like "Cascade" for deep codebase understanding, "Windsurf Tab" for intelligent autocompletion, and "Memories" for remembering important aspects of the codebase.
- [Coze](https://www.coze.com/): Coze is a next-generation AI application and chatbot development platform by ByteDance, empowering users to easily create and deploy powerful AI chatbots across various platforms with a no-code bot builder, integrated workflow logic, access to proprietary data, and simplified creation through pre-built plugins, knowledge bases, and workflows.
- [Claude Code](https://www.anthropic.com/claude-code): Claude Code is a command-line AI tool by Anthropic that embeds the Claude Opus 4 model directly into the users terminal, providing deep codebase awareness, the ability to edit files and execute commands, and making coordinated changes across multiple files, all while integrating seamlessly with popular IDEs and leveraging existing test suites.
- [Flowith](https://flowith.io/): Flowith is an AI creation workspace designed to revolutionize productivity and deep work by transforming knowledge and streamlining tasks through a multi-thread interface powered by advanced AI agents, offering an intuitive canvas-based user experience unlike traditional chat-based AI tools, and including a 24/7 operational version for complex tasks.
- [OpenManus](https://github.com/FoundationAgents/OpenManus): OpenManus is an open-source framework dedicated to building general AI agents, aiming to provide a platform where users can create and deploy their own agents without an invite code, supporting multi-agent capabilities, and requiring configuration for Large Language Model (LLM) APIs while integrating with browser automation tools.
- [Fellou](https://fellou.ai/): Fellou is an innovative Agentic Browser designed to transcend traditional web browsing by actively performing actions on behalf of the user, automating the entire process of information gathering and insight delivery, and excelling in in-depth research with seamless integrations with popular tools like Notion and LinkedIn.
- [Genspark](https://www.genspark.ai/): Genspark is an ultimate all-in-one AI companion offering a comprehensive suite of tools like AI Slides, AI Sheets, and AI Chat, designed to enhance various aspects of productivity and content creation, with personalized tools and AI Pods for generating content from diverse sources.
- [TEN](https://github.com/TEN-framework/ten-framework): TEN (The Embodied Narrator) is an open-source framework for building real-time, multimodal conversational voice AI agents, including components like TEN Framework, TEN Turn Detection, TEN Agent, TMAN Designer, and TEN Portal, offering features like Real-time Avatar, seamless MCP integration, real-time hardware communication, and vision/screenshare detection.
- [ChatGPT](https://chatgpt.com/): ChatGPT is an AI chatbot developed by OpenAI, built upon large language models like GPT-3.5 and GPT-4, designed to generate human-like conversational dialogue, understand context, answer follow-up questions, and integrate with various platforms for enhanced productivity through advanced language understanding, generation, and multilingual capabilities.
- [LangChain](https://www.langchain.com/): LangChain is a robust platform engineered for the development of reliable agents and Large Language Model (LLM) applications, offering a comprehensive product suite that seamlessly integrates various tools across the entire application development lifecycle, including LangGraph, LangSmith, and the LangGraph Platform, with functionalities for code generation, automation, and AI Search.
- [LEMON AI](https://lemonai.cc/): Lemon AI is the first Full-stack, Open-source, Agentic AI framework, offering a fully local alternative to platforms like Manus & Genspark AI. It features an integrated Code Interpreter VM sandbox for safe execution.
- [LobeChat](https://lobehub.com/): LobeHub offers LobeChat, a personal LLM productivity tool designed to elevate the user experience beyond traditional chatbots by empowering individuals to build personal AI agents and professional teams, supporting a wide array of LLMs, offering a simple chat interface, visual recognition, voice interaction, a rich plugin ecosystem, and knowledge base functionalities.
- [VS Code](https://code.visualstudio.com/): Visual Studio Code (VS Code) is a widely popular, free, and open-source code editor by Microsoft, renowned for its extensibility and customization, supporting vast programming languages, and integrating AI capabilities like intelligent next edit suggestions and an advanced “agent mode” for complex tasks, with broad compatibility with various AI models.
- [XRoute](https://xroute.ai): The Unified Interface For LLMs, provides better prices, better throughput, and no subscription.
- [XPack MCP Marketplace](https://github.com/xpack-ai/XPack-MCP-Marketplace): The world's first open-source MCP monetization platform, transform any OpenAPI into a monetizable MCP server and build your own API marketplace in just 10 minutes. Everything is open-source and ready for commercial use.
- [MemU](https://github.com/NevaMind-AI/memU): MemU is an open-source memory framework for AI companions
🙏 A big thanks to everyone who helped shape APIPark. We are thrilled to hear the communitys thoughts! Lets make the world of APIs and AI stronger and more fun together. 🎉
+5 -1
View File
@@ -113,9 +113,13 @@ func (i *imlAPIController) Edit(ctx *gin.Context, serviceId string, apiId string
if input.AiModel.Type != "local" {
provider = input.AiModel.Provider
}
modelName := input.AiModel.Name
if modelName == "" {
modelName = input.AiModel.Id
}
proxy.Plugins["ai_formatter"] = api.PluginSetting{
Config: plugin_model.ConfigType{
"model": input.AiModel.Name,
"model": modelName,
"provider": provider,
"config": input.AiModel.Config,
},
+71 -31
View File
@@ -21,13 +21,14 @@ import (
var _ IMcpController = (*imlMcpController)(nil)
type imlMcpController struct {
settingModule system.ISettingModule `autowired:""`
authorizationModule application_authorization.IAuthorizationModule `autowired:""`
appModule service.IAppModule `autowired:""`
mcpModule mcp.IMcpModule `autowired:""`
sessionKeys sync.Map
server map[string]http.Handler
openServer http.Handler
settingModule system.ISettingModule `autowired:""`
authorizationModule application_authorization.IAuthorizationModule `autowired:""`
appModule service.IAppModule `autowired:""`
mcpModule mcp.IMcpModule `autowired:""`
sessionKeys sync.Map
sseServers map[string]http.Handler
openSseServer http.Handler
openStreamableServer http.Handler
}
func (i *imlMcpController) AppMCPHandle(ctx *gin.Context) {
@@ -42,12 +43,12 @@ func (i *imlMcpController) AppMCPHandle(ctx *gin.Context) {
paths := strings.Split(req.URL.Path, "/")
req.URL.Path = fmt.Sprintf("/api/v1/%s/%s", mcp_server.GlobalBasePath, paths[len(paths)-1])
locale := utils.I18n(ctx)
if v, ok := i.server[locale]; ok {
if v, ok := i.sseServers[locale]; ok {
v.ServeHTTP(ctx.Writer, req)
return
}
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
i.sseServers[languageEnUs].ServeHTTP(ctx.Writer, req)
}
func (i *imlMcpController) AppHandleSSE(ctx *gin.Context) {
@@ -68,7 +69,7 @@ func (i *imlMcpController) AppHandleSSE(ctx *gin.Context) {
}
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/sse", mcp_server.GlobalBasePath)
i.handleSSE(ctx, i.openServer, SessionInfo{
i.handleSSE(ctx, i.openSseServer, SessionInfo{
Apikey: apikey,
App: appId,
})
@@ -81,8 +82,29 @@ func (i *imlMcpController) AppHandleMessage(ctx *gin.Context) {
return
}
ctx.Request.URL.Path = fmt.Sprintf("/openapi/v1/%s/message", mcp_server.GlobalBasePath)
ctx.Request = ctx.Request.WithContext(utils.SetLabel(ctx.Request.Context(), "app", appId))
i.handleMessage(ctx, i.openServer)
//ctx.Request = ctx.Request.WithContext(utils.SetLabel(ctx.Request.Context(), "app", appId))
i.handleMessage(ctx, i.openSseServer)
}
func (i *imlMcpController) AppHandleStreamHTTP(ctx *gin.Context) {
apikey := ctx.Request.Header.Get("Authorization")
apikey = strings.TrimPrefix(apikey, "Bearer ")
if apikey == "" {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid apikey", "success": "fail"})
return
}
appId := ctx.Request.Header.Get("X-Application-Id")
if appId == "" {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid app id", "success": "fail"})
return
}
cfg := i.settingModule.Get(ctx)
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", apikey))
req = req.WithContext(utils.SetLabel(req.Context(), "app", appId))
req.URL.Path = mcp_server.OpenGlobalMCPPath
i.openStreamableServer.ServeHTTP(ctx.Writer, req)
}
func (i *imlMcpController) AppMCPConfig(ctx *gin.Context, appId string) (string, error) {
@@ -94,36 +116,44 @@ func (i *imlMcpController) AppMCPConfig(ctx *gin.Context, appId string) (string,
if err != nil {
return "", fmt.Errorf("get app info error: %v", err)
}
return fmt.Sprintf(mcpDefaultConfig, appInfo.Name, fmt.Sprintf("%s/openapi/v1/mcp/app/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), appId)), nil
}
var mcpDefaultConfig = `{
"mcpServers": {
"%s": {
"url": "%s"
}
}
return mcp_server.NewMCPConfig(
mcp_server.TransportTypeStreamableHTTP,
fmt.Sprintf("%s%s", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.OpenAppMCPPath),
map[string]string{
"Authorization": "Bearer {your_api_key}",
"X-Application-Id": appId,
},
nil,
).ToString(appInfo.Name), nil
}
`
func (i *imlMcpController) GlobalMCPConfig(ctx *gin.Context) (string, error) {
cfg := i.settingModule.Get(ctx)
if cfg.SitePrefix == "" {
return "", fmt.Errorf("site prefix is empty")
}
return fmt.Sprintf(mcpDefaultConfig, "APIPark-MCP-Server", fmt.Sprintf("%s/openapi/v1/%s/sse?apikey={your_api_key}", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.GlobalBasePath)), nil
return mcp_server.NewMCPConfig(
mcp_server.TransportTypeStreamableHTTP,
fmt.Sprintf("%s%s", strings.TrimSuffix(cfg.SitePrefix, "/"), mcp_server.OpenGlobalMCPPath),
map[string]string{
"Authorization": "Bearer {your_api_key}",
},
nil,
).ToString("APIPark-MCP-Server"), nil
}
func (i *imlMcpController) OnComplete() {
i.server = make(map[string]http.Handler)
i.sseServers = make(map[string]http.Handler)
for language, tools := range mcpToolsByLanguage {
s := server.NewMCPServer("APIPark MCP Server", "1.0.0", server.WithLogging())
s.AddTool(tools[ToolServiceList], i.mcpModule.Services)
s.AddTool(tools[ToolOpenAPIDocument], i.mcpModule.APIs)
s.AddTool(tools[ToolInvokeAPI], i.mcpModule.Invoke)
i.server[language] = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
i.sseServers[language] = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/api/v1/%s", mcp_server.GlobalBasePath)))
if language == languageEnUs {
i.openServer = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
i.openSseServer = server.NewSSEServer(s, server.WithStaticBasePath(fmt.Sprintf("/openapi/v1/%s", strings.Trim(mcp_server.GlobalBasePath, "/"))))
i.openStreamableServer = server.NewStreamableHTTPServer(s, server.WithEndpointPath(mcp_server.OpenGlobalMCPPath))
}
}
}
@@ -132,16 +162,16 @@ func (i *imlMcpController) GlobalMCPHandle(ctx *gin.Context) {
cfg := i.settingModule.Get(ctx)
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
locale := utils.I18n(ctx)
if v, ok := i.server[locale]; ok {
if v, ok := i.sseServers[locale]; ok {
v.ServeHTTP(ctx.Writer, req)
return
}
i.server[languageEnUs].ServeHTTP(ctx.Writer, req)
i.sseServers[languageEnUs].ServeHTTP(ctx.Writer, req)
}
func (i *imlMcpController) GlobalHandleSSE(ctx *gin.Context) {
apikey := ctx.Request.URL.Query().Get("apikey")
i.handleSSE(ctx, i.openServer, SessionInfo{
i.handleSSE(ctx, i.openSseServer, SessionInfo{
Apikey: apikey,
})
}
@@ -167,7 +197,16 @@ func (i *imlMcpController) handleSSE(ctx *gin.Context, server http.Handler, sIn
}
func (i *imlMcpController) GlobalHandleMessage(ctx *gin.Context) {
i.handleMessage(ctx, i.openServer)
i.handleMessage(ctx, i.openSseServer)
}
func (i *imlMcpController) GlobalHandleStreamHTTP(ctx *gin.Context) {
apikey := ctx.Request.Header.Get("Authorization")
apikey = strings.TrimPrefix(apikey, "Bearer ")
cfg := i.settingModule.Get(ctx)
req := ctx.Request.WithContext(utils.SetGatewayInvoke(ctx.Request.Context(), cfg.InvokeAddress))
req = req.WithContext(utils.SetLabel(req.Context(), "apikey", apikey))
i.openStreamableServer.ServeHTTP(ctx.Writer, req)
}
func (i *imlMcpController) MCPHandle(ctx *gin.Context) {
@@ -204,12 +243,13 @@ func (i *imlMcpController) ServiceHandleMessage(ctx *gin.Context) {
}
func (i *imlMcpController) ServiceHandleStreamHTTP(ctx *gin.Context) {
apikey := ctx.Request.URL.Query().Get("apikey")
serviceId := ctx.Param("serviceId")
apikey := ctx.Request.Header.Get("Authorization")
serviceId := ctx.Request.Header.Get("X-Service-Id")
if serviceId == "" {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": "invalid service id", "success": "fail"})
return
}
apikey = strings.TrimPrefix(apikey, "Bearer ")
ok, err := i.authorizationModule.CheckAPIKeyAuthorizationByService(ctx, serviceId, apikey)
if err != nil {
ctx.AbortWithStatusJSON(403, gin.H{"code": -1, "msg": err.Error(), "success": "fail"})
+2
View File
@@ -13,11 +13,13 @@ type IMcpController interface {
GlobalMCPHandle(ctx *gin.Context)
GlobalHandleSSE(ctx *gin.Context)
GlobalHandleMessage(ctx *gin.Context)
GlobalHandleStreamHTTP(ctx *gin.Context)
GlobalMCPConfig(ctx *gin.Context) (string, error)
AppMCPHandle(ctx *gin.Context)
AppHandleSSE(ctx *gin.Context)
AppHandleMessage(ctx *gin.Context)
AppHandleStreamHTTP(ctx *gin.Context)
AppMCPConfig(ctx *gin.Context, appId string) (string, error)
ServiceHandleSSE(ctx *gin.Context)
+16 -10
View File
@@ -520,16 +520,21 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
modelId := ""
modelCfg := ""
modelType := "online"
if input.Model != nil {
modelId = *input.Model
}
if *input.Provider == ai_provider_local.ProviderLocal {
modelType = "local"
list, err := i.aiLocalModel.SimpleList(ctx)
if err != nil {
return nil, err
if modelId == "" {
list, err := i.aiLocalModel.SimpleList(ctx)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, fmt.Errorf("no local model")
}
modelId = list[0].Id
}
if len(list) == 0 {
return nil, fmt.Errorf("no local model")
}
modelId = list[0].Id
modelCfg = ai_provider_local.LocalConfig
} else {
pv, err := i.providerModule.Provider(ctx, *input.Provider)
@@ -540,14 +545,15 @@ func (i *imlServiceController) createAIService(ctx *gin.Context, teamID string,
if !has {
return nil, fmt.Errorf("provider not found")
}
m, has := p.GetModel(pv.DefaultLLM)
if modelId == "" {
modelId = pv.DefaultLLM
}
m, has := p.GetModel(modelId)
if !has {
return nil, fmt.Errorf("model %s not found", pv.DefaultLLM)
}
//modelId = m.ID()
modelId = m.Name()
modelCfg = m.DefaultConfig()
}
var info *service_dto.Service
+1 -1
View File
@@ -21,7 +21,7 @@
"plugins": ["react", "@typescript-eslint", "prettier", "unused-imports"],
"rules": {
"react/react-in-jsx-scope": "off",
"prettier/prettier": "off",
"prettier/prettier": "error",
"@typescript-eslint/no-explicit-any": "warn",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
+1 -1
View File
@@ -6,7 +6,7 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
.next/
node_modules
dist
market_dist
-42
View File
@@ -1,42 +0,0 @@
const fs = require('fs');
const path = require('path');
const srcDir = path.join(__dirname, 'src');
const filesToDelete = [
'package.json',
'tsconfig.json',
'tsconfig.node.json',
'vite.config.ts',
'postcss.config.js',
'tailwind.config.js',
'.eslintrc.cjs',
'index.html',
'start-vite.js'
];
function walkDirAndClean(dir) {
if (!fs.existsSync(dir)) return;
fs.readdirSync(dir).forEach(f => {
let dirPath = path.join(dir, f);
try {
let stat = fs.statSync(dirPath);
if (stat.isDirectory() && f !== 'node_modules') {
// If it's a top-level module directory like src/core, src/common
if (dir === srcDir) {
filesToDelete.forEach(file => {
const fileToDelete = path.join(dirPath, file);
if (fs.existsSync(fileToDelete)) {
fs.unlinkSync(fileToDelete);
console.log(`Deleted: ${fileToDelete}`);
}
});
}
}
} catch(e) {}
});
}
walkDirAndClean(srcDir);
console.log('Cleanup completed!');
-50
View File
@@ -1,50 +0,0 @@
const fs = require('fs');
const path = require('path');
const srcDir = path.join(__dirname, 'src');
function walkDir(dir, callback) {
if (dir.includes('node_modules')) return;
fs.readdirSync(dir).forEach(f => {
let dirPath = path.join(dir, f);
try {
let isDirectory = fs.statSync(dirPath).isDirectory();
isDirectory ? walkDir(dirPath, callback) : callback(path.join(dir, f));
} catch(e) {}
});
}
const filesToRename = [];
const filesToUpdate = [];
walkDir(srcDir, (filePath) => {
if (filePath.endsWith('.module.css')) {
filesToRename.push(filePath);
} else if (filePath.endsWith('.tsx') || filePath.endsWith('.ts')) {
filesToUpdate.push(filePath);
}
});
filesToRename.forEach(oldPath => {
const newPath = oldPath.replace(/\.module\.css$/, '.css');
// Read and remove :global wrappers entirely
let content = fs.readFileSync(oldPath, 'utf8');
content = content.replace(/:global\(([^)]+)\)/g, '$1'); // replace :global(.foo) with .foo
content = content.replace(/:global\s+/g, ''); // replace :global .foo with .foo
fs.writeFileSync(oldPath, content);
fs.renameSync(oldPath, newPath);
console.log(`Renamed and cleaned: ${path.basename(oldPath)} -> ${path.basename(newPath)}`);
});
filesToUpdate.forEach(filePath => {
let content = fs.readFileSync(filePath, 'utf8');
if (content.includes('.module.css')) {
content = content.replace(/\.module\.css/g, '.css');
fs.writeFileSync(filePath, content);
console.log(`Updated imports in: ${path.basename(filePath)}`);
}
});
console.log('Done!');
+7
View File
@@ -0,0 +1,7 @@
{
"packages": [
"packages/*"
],
"version": "independent"
}
-144
View File
@@ -1,144 +0,0 @@
const fs = require('fs');
const path = require('path');
const rootDir = __dirname;
const packagesDir = path.join(rootDir, 'packages');
const srcDir = path.join(rootDir, 'src');
const appDir = path.join(srcDir, 'app');
console.log('🚀 开始拆除 Lerna 并迁移至 Next.js...');
// 1. 创建基础目录
if (!fs.existsSync(srcDir)) fs.mkdirSync(srcDir);
if (!fs.existsSync(appDir)) fs.mkdirSync(appDir);
// 2. 读取并合并 package.json
const rootPkgPath = path.join(rootDir, 'package.json');
const rootPkg = JSON.parse(fs.readFileSync(rootPkgPath, 'utf8'));
const mergedDeps = { ...rootPkg.dependencies };
const mergedDevDeps = { ...rootPkg.devDependencies };
if (fs.existsSync(packagesDir)) {
const packages = fs.readdirSync(packagesDir);
for (const pkg of packages) {
const pkgPath = path.join(packagesDir, pkg);
if (fs.statSync(pkgPath).isDirectory()) {
// 合并依赖
const childPkgPath = path.join(pkgPath, 'package.json');
if (fs.existsSync(childPkgPath)) {
const childPkg = JSON.parse(fs.readFileSync(childPkgPath, 'utf8'));
Object.assign(mergedDeps, childPkg.dependencies || {});
Object.assign(mergedDevDeps, childPkg.devDependencies || {});
}
// 移动目录到 src 下
const destPath = path.join(srcDir, pkg);
if (!fs.existsSync(destPath)) {
fs.renameSync(pkgPath, destPath);
console.log(`📦 已迁移模块: packages/${pkg} -> src/${pkg}`);
}
}
}
// 删除空的 packages 文件夹
try { fs.rmdirSync(packagesDir); } catch (e) { console.error('Failed to remove packages dir, skipping', e) }
}
// 3. 清理并更新根 package.json
delete rootPkg.workspaces; // 移除 lerna workspaces
rootPkg.scripts = {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
};
// 移除 Vite 和 Lerna 相关依赖
const removeDeps = ['lerna', 'vite', '@originjs/vite-plugin-federation', '@vitejs/plugin-react', 'vite-tsconfig-paths'];
removeDeps.forEach(dep => {
delete mergedDeps[dep];
delete mergedDevDeps[dep];
});
// 添加 Next.js 和 React 最新核心依赖 (与 xroute-ui 对齐)
mergedDeps['next'] = "15.4.5";
mergedDeps['react'] = "19.1.0";
mergedDeps['react-dom'] = "19.1.0";
mergedDevDeps['@types/react'] = "^19";
mergedDevDeps['@types/react-dom'] = "^19";
rootPkg.dependencies = mergedDeps;
rootPkg.devDependencies = mergedDevDeps;
fs.writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, 2));
console.log('✅ package.json 依赖已合并并重写');
// 4. 生成 tsconfig.json (配置路径别名)
const tsconfigPath = path.join(rootDir, 'tsconfig.json');
const tsconfig = {
compilerOptions: {
target: "es5",
lib: ["dom", "dom.iterable", "esnext"],
allowJs: true,
skipLibCheck: true,
strict: false,
noEmit: true,
esModuleInterop: true,
module: "esnext",
moduleResolution: "bundler",
resolveJsonModule: true,
isolatedModules: true,
jsx: "preserve",
incremental: true,
plugins: [{ name: "next" }],
baseUrl: ".",
paths: {
"@/*": ["src/*"],
// 欺骗原有代码,使其能找到拍平后的新路径
"@apipark/common/*": ["src/common/src/*"],
"@apipark/core/*": ["src/core/src/*"],
"@apipark/dashboard/*": ["src/dashboard/src/*"],
"@apipark/market/*": ["src/market/src/*"],
"@apipark/openApi/*": ["src/openApi/src/*"],
"@apipark/systemRunning/*": ["src/systemRunning/src/*"]
}
},
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
exclude: ["node_modules"]
};
fs.writeFileSync(tsconfigPath, JSON.stringify(tsconfig, null, 2));
console.log('✅ tsconfig.json 别名映射已配置');
// 5. 创建 Next.js App Router 挂载点
const layoutCode = `export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}`;
fs.writeFileSync(path.join(appDir, 'layout.tsx'), layoutCode);
const slugDir = path.join(appDir, '[[...slug]]');
if (!fs.existsSync(slugDir)) fs.mkdirSync(slugDir, { recursive: true });
const pageCode = `"use client";
import dynamic from 'next/dynamic';
import { useEffect, useState } from 'react';
// 动态导入原有的 Vite SPA 根组件,禁用 SSR 避免 window 报错
const ApiParkApp = dynamic(() => import('@/core/src/App'), {
ssr: false,
loading: () => <div style={{ padding: 50 }}>Loading APIPark...</div>
});
export default function Page() {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return null;
return <ApiParkApp />;
}`;
fs.writeFileSync(path.join(slugDir, 'page.tsx'), pageCode);
console.log('✅ Next.js 路由挂载点创建完毕!');
console.log('🎉 迁移完成!请执行 pnpm install 重新安装依赖。');
-4
View File
@@ -1,4 +0,0 @@
/// <reference types="next" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
-42
View File
@@ -1,42 +0,0 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
disableStaticImages: true,
},
experimental: {
optimizePackageImports: ["@heroui/react"],
},
transpilePackages: ['@heroui/react', '@heroui/theme', '@ant-design', 'antd', 'rc-util', 'rc-pagination', 'rc-picker', 'rc-tree', 'rc-table'],
async rewrites() {
return [
{
source: '/api/v1/:path*',
destination: 'http://172.18.166.219:8288/api/v1/:path*', // Proxy to backend
},
{
source: '/api2/v1/:path*',
destination: 'http://172.18.166.219:8288/api2/v1/:path*', // Proxy to backend 2
}
];
},
webpack: (config) => {
config.module.rules.push({
test: /\.(svg|png|jpe?g|gif|webp)$/i,
type: 'asset/resource',
generator: {
filename: 'static/media/[name].[hash][ext]'
}
});
// 解决一些 Node.js polyfill 在浏览器端缺失的问题
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
os: false,
};
return config;
},
};
module.exports = nextConfig;
+28 -57
View File
@@ -2,12 +2,20 @@
"name": "frontend",
"version": "1.0.0",
"private": true,
"workspaces": [
"packages/*"
],
"description": "",
"scripts": {
"dev": "next dev -p 5000",
"build": "next build",
"start": "next start -p 5000",
"lint": "next lint"
"test": "jest",
"build": "set NODE_OPTIONS=--max-old-space-size=4096 && lerna run build --scope=core --stream --verbose ",
"serve": "lerna run preview --parallel",
"serve:remotes": "lerna run serve --scope=remote --parallel",
"dev": "lerna run dev --scope=core --stream",
"stop": "kill-port --port 5000",
"scan": "i18next-scanner --config i18next-scanner.config.js",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix && prettier --write ."
},
"keywords": [],
"author": "",
@@ -15,88 +23,51 @@
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@ant-design/pro-components": "2.7.19",
"@emotion/react": "^11.14.0",
"@floating-ui/react": "^0.26.24",
"@formkit/auto-animate": "^0.8.1",
"@heroui/react": "^3.0.3",
"@heroui/styles": "^3.0.3",
"@heroui/theme": "^2.4.20",
"@lexical/code": "^0.17.1",
"@lexical/react": "^0.17.1",
"@lexical/selection": "^0.17.1",
"@lexical/text": "^0.17.1",
"@lexical/utils": "^0.17.1",
"@modelcontextprotocol/sdk": "^1.9.0",
"@mui/icons-material": "^5.15.6",
"@mui/lab": "5.0.0-alpha.150",
"@mui/material": "5.14.14",
"@mui/x-data-grid-pro": "6.18.1",
"@originjs/vite-plugin-federation": "^1.3.3",
"@rollup/plugin-dynamic-import-vars": "^2.1.2",
"@tinymce/tinymce-react": "^4.3.2",
"@types/dompurify": "^3.0.5",
"@types/lodash-es": "^4.17.12",
"@types/uuid": "^9.0.7",
"@xyflow/react": "^12.3.6",
"ahooks": "^3.8.1",
"allotment": "^1.20.0",
"@vitejs/plugin-react": "^4.2.0",
"autoprefixer": "^10.4.16",
"copy-to-clipboard": "^3.3.3",
"crc": "^4.3.2",
"dayjs": "^1.11.10",
"dompurify": "^3.1.6",
"echarts": "^5.5.0",
"echarts-for-react": "^3.0.2",
"framer-motion": "^10.16.4",
"fs-extra": "^11.2.0",
"highlight.js": "^11.9.0",
"i18next": "^23.12.2",
"i18next-browser-languagedetector": "^8.0.0",
"js-base64": "^3.7.5",
"lexical": "^0.17.1",
"mockjs": "^1.1.0",
"next": "15.4.5",
"postcss": "^8.4.31",
"postcss-import": "^16.1.0",
"postcss-nesting": "^12.1.5",
"rc-picker": "^4.1.1",
"react": "19.1.0",
"react": "^18.2.0",
"react-ace": "^10.1.0",
"react-dom": "19.1.0",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.49.3",
"react-dom": "^18.2.0",
"react-i18next": "^15.0.1",
"react-joyride": "^2.8.2",
"react-json-view": "^1.21.3",
"react-router-dom": "6.20.0",
"react-virtuoso": "^4.7.11",
"swagger-ui-react": "^5.17.14",
"tailwindcss": "^4.2.1",
"tinymce": "^6.8.1",
"use-context-selector": "^2.0.0",
"tailwindcss": "^3.3.5",
"uuid": "^9.0.1",
"zod": "^3.23.8"
"vite-tsconfig-paths": "^4.3.2",
"react-json-view": "^1.21.3",
"zod": "^3.23.8",
"@modelcontextprotocol/sdk": "^1.9.0",
"echarts-for-react": "^3.0.2"
},
"devDependencies": {
"@ant-design/cssinjs": "^1.18.2",
"@antv/g6": "^4.8.24",
"@formily/antd-v5": "^1.2.1",
"@formily/core": "^2.2.13",
"@formily/react": "^2.2.13",
"@formily/reactive": "^2.2.13",
"@iconify/react": "^5.0.2",
"@monaco-editor/react": "^4.6.0",
"@tailwindcss/postcss": "^4.2.1",
"lightningcss": "^1.32.0",
"@testing-library/jest-dom": "^6.4.5",
"@testing-library/react": "^15.0.7",
"@testing-library/react-hooks": "^8.0.1",
"@types/file-saver": "^2.0.7",
"@types/jest": "^29.5.12",
"@types/node": "^20.10.5",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@vitejs/plugin-react": "^4.2.0",
"antd": "^5.19.4",
"babel-jest": "^29.7.0",
"eslint": "^8.53.0",
@@ -106,22 +77,22 @@
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-unused-imports": "^4.1.4",
"exceljs": "^4.4.0",
"file-saver": "^2.0.5",
"i18next-scanner": "^4.5.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
"jsdom": "^24.0.0",
"lerna": "^8.1.3",
"less": "^4.2.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"monaco-editor": "^0.45.0",
"postcss-nested": "^6.0.1",
"prettier": "^3.1.1",
"react-test-renderer": "^18.3.1",
"ts-jest": "^29.1.2",
"typescript": "^5.2.2",
"vite": "^5.0.0",
"vite-jest": "^0.1.4"
}
}
}
+41
View File
@@ -0,0 +1,41 @@
{
"name": "common",
"version": "1.0.0",
"description": "Common library for AO Platform",
"scripts": {
"dev": "vite",
"build": "vite build",
"test": "node ./__tests__/common.test.js"
},
"dependencies": {
"@floating-ui/react": "^0.26.24",
"@formkit/auto-animate": "^0.8.1",
"@lexical/code": "^0.17.1",
"@lexical/react": "^0.17.1",
"@lexical/selection": "^0.17.1",
"@lexical/text": "^0.17.1",
"@lexical/utils": "^0.17.1",
"@mui/icons-material": "^5.15.6",
"@mui/lab": "5.0.0-alpha.150",
"@mui/material": "5.14.14",
"@mui/x-data-grid-pro": "6.18.1",
"ahooks": "^3.8.1",
"allotment": "^1.20.0",
"echarts": "^5.5.0",
"lexical": "^0.17.1",
"mockjs": "^1.1.0",
"rc-picker": "^4.1.1",
"react-dropzone": "^14.2.3",
"react-hook-form": "^7.49.3",
"use-context-selector": "^2.0.0"
},
"devDependencies": {
"@formily/antd-v5": "^1.2.1",
"@formily/core": "^2.2.13",
"@formily/react": "^2.2.13",
"@formily/reactive": "^2.2.13",
"@monaco-editor/react": "^4.6.0",
"exceljs": "^4.4.0",
"monaco-editor": "^0.45.0"
}
}
@@ -0,0 +1,10 @@
export default {
plugins: {
'postcss-import': {},
'tailwindcss/nesting': {},
tailwindcss: {},
autoprefixer: {}
},
}

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -15,7 +15,7 @@ import { useEffect, useMemo, useState } from 'react'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import LanguageSetting from './LanguageSetting'
const APP_MODE = process.env.NEXT_PUBLIC_APP_MODE || 'production'
const APP_MODE = import.meta.env.VITE_APP_MODE
export type MenuItem = Required<MenuProps>['items'][number]
const themeToken = {
@@ -132,7 +132,7 @@ const EditableTableWithModal = <T extends { _id?: string }>({
}
])
],
[state.language, disabled, configFields, configurations]
[state.language, disabled, configFields]
)
const formItems = useMemo(() => {
@@ -1,9 +1,9 @@
.eo_page_list .ant-pro-card .ant-pro-card-body{
:global .eo_page_list .ant-pro-card .ant-pro-card-body{
padding:0 !important;
}
.eo_page_list .ant-pro-table-list-toolbar-container{
:global .eo_page_list .ant-pro-table-list-toolbar-container{
.ant-pro-table-list-toolbar-right{
justify-content: flex-start;
@@ -14,7 +14,7 @@
display:none;
}
}
.eo_page_list .ant-table-wrapper {
:global .eo_page_list .ant-table-wrapper {
.ant-table-pagination.ant-pagination {
margin: 1px 10px 0 !important;
padding: 10px 0;
@@ -18,7 +18,7 @@ import {
useState
} from 'react'
import { useGlobalContext } from '../../contexts/GlobalStateContext'
import './PageList.css'
import './PageList.module.css'
export type PageProColumns<T = any, ValueType = 'text'> = ProColumns<T, ValueType> & { btnNums?: number }

Some files were not shown because too many files have changed in this diff Show More