sloth-d2c-mcp 1.0.4-beta71 → 1.0.4-beta73
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/dist/build/index.js +2 -0
- package/dist/build/server.js +354 -57
- package/dist/build/utils/file-manager.js +73 -3
- package/dist/interceptor-web/dist/build-report.json +7 -7
- package/dist/interceptor-web/dist/detail.html +1 -1
- package/dist/interceptor-web/dist/index.html +1 -1
- package/package.json +3 -3
package/dist/build/index.js
CHANGED
|
@@ -203,6 +203,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
203
203
|
const savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
|
|
204
204
|
const convertConfig = config.fileConfigs?.[fileKey] || defaultConfigData;
|
|
205
205
|
// 获取提示词,优先使用用户保存的提示词,否则使用默认提示词
|
|
206
|
+
const frameworkPrompt = savedPromptSetting?.frameworkGuidePrompt;
|
|
206
207
|
const chunkPrompt = savedPromptSetting?.chunkOptimizePrompt || chunkOptimizeCodePrompt;
|
|
207
208
|
const aggregationPrompt = savedPromptSetting?.aggregationOptimizePrompt || aggregationOptimizeCodePrompt;
|
|
208
209
|
const finalPrompt = savedPromptSetting?.finalOptimizePrompt || finalOptimizeCodePrompt;
|
|
@@ -218,6 +219,7 @@ mcpServer.tool('d2c_figma', 'Convert Figma Design File to Code', {
|
|
|
218
219
|
imageMap,
|
|
219
220
|
convertConfig,
|
|
220
221
|
chunkPrompt,
|
|
222
|
+
frameworkPrompt,
|
|
221
223
|
mcpServer,
|
|
222
224
|
componentMappings,
|
|
223
225
|
codeSnippets,
|
package/dist/build/server.js
CHANGED
|
@@ -17,6 +17,8 @@ import { ComponentMappingSystem } from './component-mapping/index.js';
|
|
|
17
17
|
import multer from 'multer';
|
|
18
18
|
import { ImageMatcher } from './utils/image-matcher.js';
|
|
19
19
|
import { initOpenCV } from './utils/opencv-loader.js';
|
|
20
|
+
import { extractJson } from './utils/extract.js';
|
|
21
|
+
import { componentAnalysisPrompt, componentAnalysisPromptVue } from 'sloth-d2c-node/convert';
|
|
20
22
|
// 配置 multer 用于文件上传
|
|
21
23
|
const storage = multer.memoryStorage();
|
|
22
24
|
const upload = multer({
|
|
@@ -91,6 +93,7 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
91
93
|
chunkOptimizePrompt: chunkOptimizeCodePrompt,
|
|
92
94
|
aggregationOptimizePrompt: aggregationOptimizeCodePrompt,
|
|
93
95
|
finalOptimizePrompt: finalOptimizeCodePrompt,
|
|
96
|
+
componentAnalysisPrompt: componentAnalysisPrompt,
|
|
94
97
|
});
|
|
95
98
|
const reactConfigPath = configManager.getFrameworkConfigPath('react');
|
|
96
99
|
Logger.log(`已创建默认 react 配置文件: ${reactConfigPath}`);
|
|
@@ -102,10 +105,22 @@ export async function loadConfig(mcpServer, configManagerInstance) {
|
|
|
102
105
|
chunkOptimizePrompt: chunkOptimizeCodePromptVue,
|
|
103
106
|
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
104
107
|
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
108
|
+
componentAnalysisPrompt: componentAnalysisPromptVue,
|
|
105
109
|
});
|
|
106
110
|
const vueConfigPath = configManager.getFrameworkConfigPath('vue');
|
|
107
111
|
Logger.log(`已创建默认 vue 配置文件: ${vueConfigPath}`);
|
|
108
112
|
}
|
|
113
|
+
else {
|
|
114
|
+
const vueConfig = await configManager.loadFrameworkConfig('vue');
|
|
115
|
+
if (!vueConfig || Object.keys(vueConfig).length < 3) {
|
|
116
|
+
await configManager.saveFrameworkConfig('vue', {
|
|
117
|
+
chunkOptimizePrompt: chunkOptimizeCodePromptVue,
|
|
118
|
+
aggregationOptimizePrompt: aggregationOptimizeCodePromptVue,
|
|
119
|
+
finalOptimizePrompt: finalOptimizeCodePromptVue,
|
|
120
|
+
...(vueConfig || {}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
109
124
|
}
|
|
110
125
|
catch (error) {
|
|
111
126
|
Logger.error(`获取配置信息时出错: ${error}`);
|
|
@@ -159,6 +174,8 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
159
174
|
// 为认证端点添加中间件 - 需要同时支持 JSON 和表单数据
|
|
160
175
|
app.use(express.urlencoded({ extended: true }));
|
|
161
176
|
app.use('/saveGlobalConfig', express.json());
|
|
177
|
+
app.use('/analyzeComponents', express.json());
|
|
178
|
+
app.use('/saveComponents', express.json());
|
|
162
179
|
// 设置跨域
|
|
163
180
|
app.use((req, res, next) => {
|
|
164
181
|
res.header('Access-Control-Allow-Origin', '*');
|
|
@@ -666,6 +683,337 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
666
683
|
return './';
|
|
667
684
|
}
|
|
668
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* 获取项目文件树
|
|
688
|
+
*/
|
|
689
|
+
app.get('/getProjectFiles', async (req, res) => {
|
|
690
|
+
try {
|
|
691
|
+
const { directory = '' } = req.query;
|
|
692
|
+
const projectPath = getProjectRoot();
|
|
693
|
+
const targetPath = path.join(projectPath, directory);
|
|
694
|
+
Logger.log(`获取项目文件树: ${targetPath}`);
|
|
695
|
+
// 排除的文件扩展名(静态资源和配置文件)
|
|
696
|
+
const excludeExtensions = [
|
|
697
|
+
// 图片
|
|
698
|
+
'.png',
|
|
699
|
+
'.jpg',
|
|
700
|
+
'.jpeg',
|
|
701
|
+
'.gif',
|
|
702
|
+
'.svg',
|
|
703
|
+
'.ico',
|
|
704
|
+
'.webp',
|
|
705
|
+
'.bmp',
|
|
706
|
+
// 视频/音频
|
|
707
|
+
'.mp4',
|
|
708
|
+
'.avi',
|
|
709
|
+
'.mov',
|
|
710
|
+
'.mp3',
|
|
711
|
+
'.wav',
|
|
712
|
+
// 字体
|
|
713
|
+
'.ttf',
|
|
714
|
+
'.woff',
|
|
715
|
+
'.woff2',
|
|
716
|
+
'.eot',
|
|
717
|
+
// 配置文件
|
|
718
|
+
'.json',
|
|
719
|
+
'.lock',
|
|
720
|
+
'.yaml',
|
|
721
|
+
'.yml',
|
|
722
|
+
'.toml',
|
|
723
|
+
// 文档
|
|
724
|
+
'.md',
|
|
725
|
+
'.txt',
|
|
726
|
+
'.pdf',
|
|
727
|
+
'.doc',
|
|
728
|
+
'.docx',
|
|
729
|
+
// 其他
|
|
730
|
+
'.zip',
|
|
731
|
+
'.tar',
|
|
732
|
+
'.gz',
|
|
733
|
+
'.rar',
|
|
734
|
+
'.map',
|
|
735
|
+
'.d.ts.map',
|
|
736
|
+
];
|
|
737
|
+
// 递归读取文件树
|
|
738
|
+
const buildFileTree = async (dirPath, relativePath = '') => {
|
|
739
|
+
const entries = await fs.promises.readdir(dirPath, { withFileTypes: true });
|
|
740
|
+
const files = [];
|
|
741
|
+
for (const entry of entries) {
|
|
742
|
+
// 排除特殊目录
|
|
743
|
+
const excludeDirs = ['node_modules', 'dist', 'build', '.git', '.next', 'coverage', '.sloth', 'public', 'static', 'assets'];
|
|
744
|
+
if (excludeDirs.includes(entry.name) || entry.name.startsWith('.')) {
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
const entryRelativePath = path.join(relativePath, entry.name);
|
|
748
|
+
const entryFullPath = path.join(dirPath, entry.name);
|
|
749
|
+
if (entry.isDirectory()) {
|
|
750
|
+
const children = await buildFileTree(entryFullPath, entryRelativePath);
|
|
751
|
+
if (children.length > 0) {
|
|
752
|
+
files.push({
|
|
753
|
+
title: entry.name,
|
|
754
|
+
key: entryRelativePath,
|
|
755
|
+
path: entryRelativePath,
|
|
756
|
+
isLeaf: false,
|
|
757
|
+
selectable: false,
|
|
758
|
+
children,
|
|
759
|
+
});
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
// 排除静态资源文件
|
|
764
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
765
|
+
if (!excludeExtensions.includes(ext)) {
|
|
766
|
+
files.push({
|
|
767
|
+
title: entry.name,
|
|
768
|
+
key: entryRelativePath,
|
|
769
|
+
path: entryRelativePath,
|
|
770
|
+
isLeaf: true,
|
|
771
|
+
selectable: true,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return files;
|
|
777
|
+
};
|
|
778
|
+
const fileTree = await buildFileTree(targetPath);
|
|
779
|
+
res.json({
|
|
780
|
+
success: true,
|
|
781
|
+
data: {
|
|
782
|
+
projectPath,
|
|
783
|
+
files: fileTree,
|
|
784
|
+
},
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
catch (error) {
|
|
788
|
+
Logger.error('获取项目文件树失败:', error);
|
|
789
|
+
res.status(500).json({
|
|
790
|
+
success: false,
|
|
791
|
+
message: '获取项目文件树失败',
|
|
792
|
+
error: error instanceof Error ? error.message : String(error),
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
});
|
|
796
|
+
/**
|
|
797
|
+
* 分析项目中的组件
|
|
798
|
+
*/
|
|
799
|
+
app.post('/analyzeComponents', async (req, res) => {
|
|
800
|
+
try {
|
|
801
|
+
const { filePaths, fileKey, nodeId } = req.body;
|
|
802
|
+
if (!filePaths || !Array.isArray(filePaths)) {
|
|
803
|
+
res.status(400).json({
|
|
804
|
+
success: false,
|
|
805
|
+
message: '缺少必要参数: filePaths',
|
|
806
|
+
});
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
Logger.log(`分析 ${filePaths.length} 个项目文件`);
|
|
810
|
+
const projectPath = getProjectRoot();
|
|
811
|
+
const results = [];
|
|
812
|
+
// 尝试加载保存的提示词设置(如果提供了 fileKey 和 nodeId)
|
|
813
|
+
let savedPromptSetting = null;
|
|
814
|
+
if (fileKey && nodeId) {
|
|
815
|
+
try {
|
|
816
|
+
savedPromptSetting = await fileManager.loadPromptSetting(fileKey, nodeId);
|
|
817
|
+
Logger.log(`已加载提示词设置: fileKey=${fileKey}, nodeId=${nodeId}`);
|
|
818
|
+
}
|
|
819
|
+
catch (error) {
|
|
820
|
+
Logger.warn(`加载提示词设置失败: ${error}`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const frameworkGuidePrompt = savedPromptSetting?.frameworkGuidePrompt;
|
|
824
|
+
const _componentAnalysisPrompt = savedPromptSetting?.componentAnalysisPrompt || componentAnalysisPrompt;
|
|
825
|
+
// 读取并分析每个文件
|
|
826
|
+
for (const filePath of filePaths) {
|
|
827
|
+
try {
|
|
828
|
+
const fullPath = path.join(projectPath, filePath);
|
|
829
|
+
// 安全检查:确保文件在项目目录内
|
|
830
|
+
if (!fullPath.startsWith(projectPath)) {
|
|
831
|
+
results.push({
|
|
832
|
+
success: false,
|
|
833
|
+
filename: filePath,
|
|
834
|
+
error: '无权访问该文件',
|
|
835
|
+
});
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
// 读取文件内容
|
|
839
|
+
const content = await fs.promises.readFile(fullPath, 'utf-8');
|
|
840
|
+
const filename = path.basename(filePath);
|
|
841
|
+
// 使用 MCP Sampling 进行实际分析
|
|
842
|
+
if (mcpServer) {
|
|
843
|
+
try {
|
|
844
|
+
// 构建提示词,替换占位符
|
|
845
|
+
const prompt = _componentAnalysisPrompt.replace(/{filename}/g, filename).replace(/{fileContent}/g, content);
|
|
846
|
+
// 调用 MCP Sampling API
|
|
847
|
+
const { content: { text }, } = await mcpServer.server.createMessage({
|
|
848
|
+
messages: [
|
|
849
|
+
frameworkGuidePrompt && {
|
|
850
|
+
role: 'user',
|
|
851
|
+
content: {
|
|
852
|
+
type: 'text',
|
|
853
|
+
text: frameworkGuidePrompt,
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
{
|
|
857
|
+
role: 'user',
|
|
858
|
+
content: {
|
|
859
|
+
type: 'text',
|
|
860
|
+
text: prompt,
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
].filter(Boolean),
|
|
864
|
+
maxTokens: 8000,
|
|
865
|
+
}, { timeout: 2 * 60 * 1000 });
|
|
866
|
+
// 解析 AI 返回的 JSON
|
|
867
|
+
const extractResult = extractJson(text);
|
|
868
|
+
if (extractResult.state === 'successful-parse' && extractResult.value) {
|
|
869
|
+
const analysisResult = extractResult.value;
|
|
870
|
+
// 构建组件信息
|
|
871
|
+
const component = {
|
|
872
|
+
id: `comp-${Date.now()}-${Math.random()}`,
|
|
873
|
+
name: analysisResult.componentName || path.basename(filePath, path.extname(filePath)),
|
|
874
|
+
type: 'custom',
|
|
875
|
+
path: filePath,
|
|
876
|
+
import: analysisResult.import,
|
|
877
|
+
props: analysisResult.props || [],
|
|
878
|
+
description: analysisResult.description || '组件描述',
|
|
879
|
+
};
|
|
880
|
+
results.push({
|
|
881
|
+
success: true,
|
|
882
|
+
component,
|
|
883
|
+
filename: filePath,
|
|
884
|
+
});
|
|
885
|
+
Logger.log(`成功分析组件: ${filePath}, 组件名: ${component.name}`);
|
|
886
|
+
}
|
|
887
|
+
else {
|
|
888
|
+
// JSON 解析失败,使用默认值
|
|
889
|
+
Logger.warn(`解析组件分析结果失败: ${extractResult.error}, 文件: ${filePath}`);
|
|
890
|
+
results.push({
|
|
891
|
+
success: true,
|
|
892
|
+
component: {
|
|
893
|
+
id: `comp-${Date.now()}-${Math.random()}`,
|
|
894
|
+
name: path.basename(filePath, path.extname(filePath)),
|
|
895
|
+
type: 'custom',
|
|
896
|
+
path: filePath,
|
|
897
|
+
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
898
|
+
props: [],
|
|
899
|
+
description: '组件描述',
|
|
900
|
+
},
|
|
901
|
+
filename: filePath,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
catch (samplingError) {
|
|
906
|
+
Logger.error(`调用采样 API 失败: ${filePath}`, samplingError);
|
|
907
|
+
// 采样失败时使用默认值
|
|
908
|
+
results.push({
|
|
909
|
+
success: true,
|
|
910
|
+
component: {
|
|
911
|
+
id: `comp-${Date.now()}-${Math.random()}`,
|
|
912
|
+
name: path.basename(filePath, path.extname(filePath)),
|
|
913
|
+
type: 'custom',
|
|
914
|
+
path: filePath,
|
|
915
|
+
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
916
|
+
props: [],
|
|
917
|
+
description: '组件描述',
|
|
918
|
+
},
|
|
919
|
+
filename: filePath,
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
else {
|
|
924
|
+
// MCP 服务器未初始化,使用默认值
|
|
925
|
+
Logger.warn('MCP 服务器未初始化,使用默认组件信息');
|
|
926
|
+
results.push({
|
|
927
|
+
success: true,
|
|
928
|
+
component: {
|
|
929
|
+
id: `comp-${Date.now()}-${Math.random()}`,
|
|
930
|
+
name: path.basename(filePath, path.extname(filePath)),
|
|
931
|
+
type: 'custom',
|
|
932
|
+
path: filePath,
|
|
933
|
+
import: `import { ${path.basename(filePath, path.extname(filePath))} } from './${filePath}'`,
|
|
934
|
+
props: [],
|
|
935
|
+
description: '组件描述',
|
|
936
|
+
},
|
|
937
|
+
filename: filePath,
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
catch (error) {
|
|
942
|
+
Logger.error(`读取文件失败: ${filePath}`, error);
|
|
943
|
+
results.push({
|
|
944
|
+
success: false,
|
|
945
|
+
filename: filePath,
|
|
946
|
+
error: error instanceof Error ? error.message : String(error),
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const succeeded = results.filter((r) => r.success).length;
|
|
951
|
+
const failed = results.filter((r) => !r.success).length;
|
|
952
|
+
res.json({
|
|
953
|
+
success: true,
|
|
954
|
+
data: {
|
|
955
|
+
total: filePaths.length,
|
|
956
|
+
succeeded,
|
|
957
|
+
failed,
|
|
958
|
+
components: results.filter((r) => r.success).map((r) => r.component),
|
|
959
|
+
errors: results
|
|
960
|
+
.filter((r) => !r.success)
|
|
961
|
+
.map((r) => ({
|
|
962
|
+
filename: r.filename,
|
|
963
|
+
error: r.error,
|
|
964
|
+
})),
|
|
965
|
+
},
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
catch (error) {
|
|
969
|
+
Logger.error('分析项目组件失败:', error);
|
|
970
|
+
res.status(500).json({
|
|
971
|
+
success: false,
|
|
972
|
+
message: '分析项目组件失败',
|
|
973
|
+
error: error instanceof Error ? error.message : String(error),
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
/**
|
|
978
|
+
* 保存项目中的组件到components.json
|
|
979
|
+
*/
|
|
980
|
+
app.post('/saveComponents', async (req, res) => {
|
|
981
|
+
try {
|
|
982
|
+
const { components } = req.body;
|
|
983
|
+
if (!components || !Array.isArray(components)) {
|
|
984
|
+
res.status(400).json({
|
|
985
|
+
success: false,
|
|
986
|
+
message: '无效的组件数据',
|
|
987
|
+
});
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
Logger.log(`准备保存 ${components.length} 个组件`);
|
|
991
|
+
// 读取现有组件
|
|
992
|
+
const existingComponents = await fileManager.loadComponentsDatabase();
|
|
993
|
+
// 直接追加(暂不处理冲突)
|
|
994
|
+
const allComponents = [...components, ...existingComponents];
|
|
995
|
+
// 保存到文件(带备份)
|
|
996
|
+
await fileManager.saveComponentsDatabase(allComponents);
|
|
997
|
+
Logger.log(`✅ 成功保存,当前共 ${allComponents.length} 个组件`);
|
|
998
|
+
res.json({
|
|
999
|
+
success: true,
|
|
1000
|
+
data: {
|
|
1001
|
+
added: components.length,
|
|
1002
|
+
total: allComponents.length,
|
|
1003
|
+
components: allComponents,
|
|
1004
|
+
},
|
|
1005
|
+
message: '组件导入成功',
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
catch (error) {
|
|
1009
|
+
Logger.error('保存组件失败:', error);
|
|
1010
|
+
res.status(500).json({
|
|
1011
|
+
success: false,
|
|
1012
|
+
message: '保存组件失败',
|
|
1013
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
669
1017
|
/**
|
|
670
1018
|
* 获取或创建映射系统实例
|
|
671
1019
|
*/
|
|
@@ -689,56 +1037,10 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
689
1037
|
});
|
|
690
1038
|
return;
|
|
691
1039
|
}
|
|
692
|
-
Logger.log('扫描项目组件:', { platform, includePaths, excludePaths });
|
|
693
|
-
const system = await getMappingSystem();
|
|
694
|
-
const adapter = system.adapterManager.getAdapter(platform);
|
|
695
|
-
if (!adapter) {
|
|
696
|
-
res.status(400).json({
|
|
697
|
-
success: false,
|
|
698
|
-
message: `不支持的平台: ${platform}`,
|
|
699
|
-
});
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
1040
|
const projectPath = getProjectRoot();
|
|
703
|
-
Logger.log('
|
|
704
|
-
// 扫描项目组件
|
|
705
|
-
const components = await adapter.scanComponents(projectPath, {
|
|
706
|
-
includePaths,
|
|
707
|
-
excludePaths,
|
|
708
|
-
});
|
|
709
|
-
Logger.log(`扫描完成,发现 ${components.length} 个项目组件`);
|
|
710
|
-
// 将绝对路径转换为相对路径
|
|
711
|
-
const normalizePath = (filePath) => {
|
|
712
|
-
if (!filePath || !projectPath)
|
|
713
|
-
return filePath;
|
|
714
|
-
try {
|
|
715
|
-
const path = require('path');
|
|
716
|
-
const relativePath = path.relative(projectPath, filePath);
|
|
717
|
-
// 统一使用正斜杠
|
|
718
|
-
return relativePath.replace(/\\/g, '/');
|
|
719
|
-
}
|
|
720
|
-
catch {
|
|
721
|
-
return filePath;
|
|
722
|
-
}
|
|
723
|
-
};
|
|
724
|
-
const projectComponents = components.map((c) => ({
|
|
725
|
-
id: c.id,
|
|
726
|
-
name: c.name,
|
|
727
|
-
type: c.type,
|
|
728
|
-
path: normalizePath(c.path),
|
|
729
|
-
props: c.props.map((p) => ({
|
|
730
|
-
name: p.name,
|
|
731
|
-
type: p.type,
|
|
732
|
-
required: p.required,
|
|
733
|
-
})),
|
|
734
|
-
framework: c.metadata.framework,
|
|
735
|
-
description: c.metadata.description,
|
|
736
|
-
source: 'project', // 标记为项目组件
|
|
737
|
-
importType: c.metadata.importType || 'default', // 导入类型
|
|
738
|
-
signature: c.metadata.signature,
|
|
739
|
-
}));
|
|
1041
|
+
Logger.log('扫描项目组件:', { platform, includePaths, excludePaths });
|
|
740
1042
|
// 读取外部依赖组件(从 .sloth/components.json)
|
|
741
|
-
let
|
|
1043
|
+
let projectComponents = [];
|
|
742
1044
|
const slothPath = path.join(projectPath, '.sloth', 'components.json');
|
|
743
1045
|
const fs = await import('fs/promises');
|
|
744
1046
|
try {
|
|
@@ -749,7 +1051,7 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
749
1051
|
// 验证数据结构
|
|
750
1052
|
if (Array.isArray(componentsData)) {
|
|
751
1053
|
// 验证每个组件的基本字段
|
|
752
|
-
|
|
1054
|
+
projectComponents = componentsData
|
|
753
1055
|
.filter((comp) => comp && comp.id && comp.name && comp.path)
|
|
754
1056
|
.map((c) => ({
|
|
755
1057
|
id: c.id,
|
|
@@ -761,13 +1063,10 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
761
1063
|
type: p.type || 'any',
|
|
762
1064
|
required: p.required || false,
|
|
763
1065
|
})),
|
|
764
|
-
framework: c.framework || c.metadata?.framework || 'React',
|
|
765
1066
|
description: c.description || c.metadata?.description,
|
|
766
|
-
|
|
767
|
-
lib: c.lib, // 业务库名称
|
|
768
|
-
importType: c.importType || c.metadata?.importType || 'named', // 导入类型,外部组件默认为具名导入
|
|
1067
|
+
import: c.import,
|
|
769
1068
|
}));
|
|
770
|
-
Logger.log(`从 .sloth/components.json 加载了 ${
|
|
1069
|
+
Logger.log(`从 .sloth/components.json 加载了 ${projectComponents.length} 个项目组件`);
|
|
771
1070
|
}
|
|
772
1071
|
else {
|
|
773
1072
|
Logger.warn('.sloth/components.json 格式错误:应为组件数组');
|
|
@@ -782,11 +1081,9 @@ export async function startHttpServer(port = PORT, mcpServer, configManagerInsta
|
|
|
782
1081
|
res.json({
|
|
783
1082
|
success: true,
|
|
784
1083
|
data: {
|
|
785
|
-
total: projectComponents.length
|
|
1084
|
+
total: projectComponents.length,
|
|
786
1085
|
platform,
|
|
787
|
-
projectPath,
|
|
788
1086
|
projectComponents, // 项目组件
|
|
789
|
-
externalComponents, // 外部依赖组件
|
|
790
1087
|
},
|
|
791
1088
|
});
|
|
792
1089
|
}
|
|
@@ -333,9 +333,7 @@ export class FileManager {
|
|
|
333
333
|
}
|
|
334
334
|
const slothDir = path.join(this.workspaceRoot, '.sloth');
|
|
335
335
|
const entries = await fs.readdir(slothDir, { withFileTypes: true });
|
|
336
|
-
const fileKeys = entries
|
|
337
|
-
.filter(entry => entry.isDirectory())
|
|
338
|
-
.map(entry => entry.name);
|
|
336
|
+
const fileKeys = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
339
337
|
Logger.log(`找到 ${fileKeys.length} 个 fileKey:`, fileKeys);
|
|
340
338
|
return fileKeys;
|
|
341
339
|
}
|
|
@@ -528,5 +526,77 @@ export class FileManager {
|
|
|
528
526
|
Logger.error(`清理旧截图失败: ${error}`);
|
|
529
527
|
}
|
|
530
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* 读取组件数据库
|
|
531
|
+
* @returns Promise<StoredComponent[]> - 组件列表
|
|
532
|
+
*/
|
|
533
|
+
async loadComponentsDatabase() {
|
|
534
|
+
const workspaceRoot = this.getWorkspaceRoot();
|
|
535
|
+
if (!workspaceRoot) {
|
|
536
|
+
Logger.warn('工作目录根路径未设置,无法加载组件数据库');
|
|
537
|
+
return [];
|
|
538
|
+
}
|
|
539
|
+
const componentsPath = path.join(workspaceRoot, '.sloth', 'components.json');
|
|
540
|
+
try {
|
|
541
|
+
const content = await fs.readFile(componentsPath, 'utf-8');
|
|
542
|
+
// 验证文件内容不为空
|
|
543
|
+
if (!content || !content.trim()) {
|
|
544
|
+
Logger.warn('components.json 文件为空,返回空数组');
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
const components = JSON.parse(content);
|
|
548
|
+
// 验证数据格式
|
|
549
|
+
if (!Array.isArray(components)) {
|
|
550
|
+
Logger.warn('components.json 格式错误(不是数组),返回空数组');
|
|
551
|
+
return [];
|
|
552
|
+
}
|
|
553
|
+
Logger.log(`成功加载 ${components.length} 个组件`);
|
|
554
|
+
return components;
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
const err = error;
|
|
558
|
+
if (err.code === 'ENOENT') {
|
|
559
|
+
// 文件不存在,返回空数组
|
|
560
|
+
Logger.log('components.json 不存在,将创建新文件');
|
|
561
|
+
return [];
|
|
562
|
+
}
|
|
563
|
+
if (err instanceof SyntaxError) {
|
|
564
|
+
Logger.error('components.json JSON 解析失败:', err.message);
|
|
565
|
+
return [];
|
|
566
|
+
}
|
|
567
|
+
Logger.error('读取组件数据库失败:', err);
|
|
568
|
+
throw error;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* 保存组件数据库
|
|
573
|
+
* @param components - 要保存的组件列表
|
|
574
|
+
*/
|
|
575
|
+
async saveComponentsDatabase(components) {
|
|
576
|
+
const workspaceRoot = this.getWorkspaceRoot();
|
|
577
|
+
if (!workspaceRoot) {
|
|
578
|
+
throw new Error('工作目录根路径未设置,无法保存组件数据库');
|
|
579
|
+
}
|
|
580
|
+
const slothDir = path.join(workspaceRoot, '.sloth');
|
|
581
|
+
const componentsPath = path.join(slothDir, 'components.json');
|
|
582
|
+
// 确保目录存在
|
|
583
|
+
try {
|
|
584
|
+
await fs.mkdir(slothDir, { recursive: true });
|
|
585
|
+
}
|
|
586
|
+
catch (error) {
|
|
587
|
+
Logger.error('创建 .sloth 目录失败:', error);
|
|
588
|
+
throw new Error(`无法创建目录: ${slothDir}`);
|
|
589
|
+
}
|
|
590
|
+
// 写入数据
|
|
591
|
+
try {
|
|
592
|
+
const jsonContent = JSON.stringify(components, null, 2);
|
|
593
|
+
await fs.writeFile(componentsPath, jsonContent, 'utf-8');
|
|
594
|
+
Logger.log(`✅ 已保存 ${components.length} 个组件到 components.json`);
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
Logger.error('写入组件数据库失败:', error);
|
|
598
|
+
throw new Error(`无法写入文件: ${componentsPath}`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
531
601
|
}
|
|
532
602
|
export default FileManager;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
-
"buildTime": "2025-12-
|
|
2
|
+
"buildTime": "2025-12-02T07:57:55.137Z",
|
|
3
3
|
"mode": "build",
|
|
4
4
|
"pages": {
|
|
5
5
|
"main": {
|
|
6
6
|
"file": "index.html",
|
|
7
|
-
"size":
|
|
8
|
-
"sizeFormatted": "1.
|
|
7
|
+
"size": 1494888,
|
|
8
|
+
"sizeFormatted": "1.43 MB"
|
|
9
9
|
},
|
|
10
10
|
"detail": {
|
|
11
11
|
"file": "detail.html",
|
|
12
|
-
"size":
|
|
13
|
-
"sizeFormatted": "
|
|
12
|
+
"size": 280311,
|
|
13
|
+
"sizeFormatted": "273.74 KB"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
|
-
"totalSize":
|
|
17
|
-
"totalSizeFormatted": "1.
|
|
16
|
+
"totalSize": 1775199,
|
|
17
|
+
"totalSizeFormatted": "1.69 MB"
|
|
18
18
|
}
|